Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[sc-11385] Allow selection of label filtering logic #1921

Merged
merged 1 commit into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions libs/common/common.babel
Original file line number Diff line number Diff line change
Expand Up @@ -22312,6 +22312,29 @@
<folder_node>
<name>filter</name>
<children>
<concept_node>
<name>and</name>
<description/>
<comment/>
<translations>
<translation>
<language>ca-ES</language>
<approved>false</approved>
</translation>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-ES</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>apply</name>
<description/>
Expand Down Expand Up @@ -22404,6 +22427,29 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>or</name>
<description/>
<comment/>
<translations>
<translation>
<language>ca-ES</language>
<approved>false</approved>
</translation>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-ES</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>resource-type</name>
<description/>
Expand Down
2 changes: 2 additions & 0 deletions libs/common/src/assets/i18n/ca.json
Original file line number Diff line number Diff line change
Expand Up @@ -923,10 +923,12 @@
"resource.field-text": "Camp de Text",
"resource.field-text-body-label": "Cos del text",
"resource.field-text-format-label": "Format de text",
"resource.filter.and": "L'operador AND mostra recursos que coincideixen amb totes les etiquetes seleccionades",
"resource.filter.apply": "Aplicar",
"resource.filter.clear": "Esborra tot",
"resource.filter.date-added": "Data de creació",
"resource.filter.from": "Des de",
"resource.filter.or": "L'operador OR mostra recursos que coincideixen almenys amb una de les etiquetes seleccionades",
"resource.filter.resource-type": "Tipus de recurs",
"resource.filter.until": "Fins a",
"resource.filtered-by": "Filtrat per",
Expand Down
2 changes: 2 additions & 0 deletions libs/common/src/assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -923,10 +923,12 @@
"resource.field-text": "Text Field",
"resource.field-text-body-label": "Text body",
"resource.field-text-format-label": "Text format",
"resource.filter.and": "AND operator displays resources that match all of the selected labels",
"resource.filter.apply": "Apply",
"resource.filter.clear": "Clear all",
"resource.filter.date-added": "Date added",
"resource.filter.from": "From",
"resource.filter.or": "OR operator displays resources that match at least one of the selected labels",
"resource.filter.resource-type": "Resource type",
"resource.filter.until": "Until",
"resource.filtered-by": "Filtered by",
Expand Down
2 changes: 2 additions & 0 deletions libs/common/src/assets/i18n/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -923,10 +923,12 @@
"resource.field-text": "Campo de Texto",
"resource.field-text-body-label": "Cuerpo de texto",
"resource.field-text-format-label": "Formato de texto",
"resource.filter.and": "El operador AND muestra los recursos que coinciden con todas las etiquetas seleccionadas",
"resource.filter.apply": "Aplicar",
"resource.filter.clear": "Limpiar todo",
"resource.filter.date-added": "Fecha de creación",
"resource.filter.from": "De",
"resource.filter.or": "El operador OR muestra los recursos que coinciden con al menos una de las etiquetas seleccionadas",
"resource.filter.resource-type": "Tipo de recurso",
"resource.filter.until": "Hasta",
"resource.filtered-by": "Filtrado por",
Expand Down
2 changes: 2 additions & 0 deletions libs/common/src/assets/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -923,10 +923,12 @@
"resource.field-text": "Champ de texte",
"resource.field-text-body-label": "Corps du texte",
"resource.field-text-format-label": "Format texte",
"resource.filter.and": "L'opérateur AND affiche les ressources qui correspondent à toutes les étiquettes sélectionnées",
"resource.filter.apply": "Appliquer",
"resource.filter.clear": "Tout effacer",
"resource.filter.date-added": "Fecha de création",
"resource.filter.from": "Depuis",
"resource.filter.or": "L'opérateur OU affiche les ressources qui correspondent à au moins une des étiquettes sélectionnées",
"resource.filter.resource-type": "Type de ressource",
"resource.filter.until": "Jusqu’à",
"resource.filtered-by": "Filtre par",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
}
</pa-dropdown>
</div>
<div>
<div class="label-filters">
<app-label-dropdown
[labelSets]="displayedLabelSets"
[selection]="selectedClassifications"
Expand All @@ -53,6 +53,20 @@
{{ 'resource.classification-column' | translate }}
{{ selectedClassifications.length ? '(' + selectedClassifications.length + ')' : '' }}
</app-label-dropdown>
@if (selectedClassificationOptions.length > 1) {
<div class="labels-logic">
<pa-toggle
labelOnRight
[value]="andLogicForLabels"
(valueChange)="updateLabelsLogic($event)"
[paPopover]="labelsLogic">
{{ andLogicForLabels ? 'AND' : 'OR' }}
</pa-toggle>
<pa-popover #labelsLogic>
{{ (andLogicForLabels ? 'resource.filter.and' : 'resource.filter.or') | translate }}
</pa-popover>
</div>
}
</div>

<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@
gap: rhythm(1.5);
}

.label-filters {
align-items: center;
display: flex;
::ng-deep .pa-button {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.labels-logic {
border: 1px solid $color-neutral-light;
border-left: 0;
height: rhythm(5);
padding: rhythm(1) rhythm(1.5);
}
}
.filtered-by {
color: $color-neutral-regular;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnDestroy, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { FormControl, FormGroup } from '@angular/forms';
import { distinctUntilChanged, filter, forkJoin, merge, Observable, of, Subject, take } from 'rxjs';
import { map, switchMap, takeUntil } from 'rxjs/operators';
Expand Down Expand Up @@ -44,6 +44,7 @@ export class ResourceListComponent implements OnDestroy {
isFiltering = this.resourceListService.filters.pipe(map((filters) => filters.length > 0));
showClearButton = this.resourceListService.filters.pipe(map((filters) => filters.length > 2));
filterOptions: Filters = { classification: [], mainTypes: [], creation: {}, hidden: undefined };
andLogicForLabels: boolean = false;
displayedLabelSets: LabelSets = {};

dateForm = new FormGroup({
Expand Down Expand Up @@ -174,6 +175,11 @@ export class ResourceListComponent implements OnDestroy {
this.onToggleFilter();
}

updateLabelsLogic(value: boolean) {
this.andLogicForLabels = value;
this.onToggleFilter();
}

clearFilter(option: OptionModel) {
option.selected = !option.selected;
this.onToggleFilter();
Expand All @@ -182,8 +188,12 @@ export class ResourceListComponent implements OnDestroy {
onToggleFilter() {
const filters = this.selectedFilters;
if (filters.length > 0) {
this.router.navigate(['./'], { relativeTo: this.route, queryParams: { filters } });
this.resourceListService.filter(filters);
const queryParams: Params = { filters };
if (this.andLogicForLabels) {
queryParams['labelsLogic'] = 'AND';
}
this.router.navigate(['./'], { relativeTo: this.route, queryParams });
this.resourceListService.filter(filters, this.andLogicForLabels ? 'AND' : 'OR');
} else {
this.clearFilters();
}
Expand Down Expand Up @@ -235,13 +245,19 @@ export class ResourceListComponent implements OnDestroy {
this.labelSets.pipe(take(1)),
this.route.queryParamMap.pipe(take(1)),
this.resourceListService.prevFilters.pipe(take(1)),
this.resourceListService.prevLabelsLogic.pipe(take(1)),
]).pipe(
switchMap(([labelSets, queryParams, prevFilters]) => {
switchMap(([labelSets, queryParams, prevFilters, prevLabelsLogic]) => {
const faceted = MIME_FACETS.concat(Object.keys(labelSets).map((setId) => `/l/${setId}`));
return this.getFacets(faceted).pipe(map((facets) => ({ facets, labelSets, queryParams, prevFilters })));
return this.getFacets(faceted).pipe(
map((facets) => ({ facets, labelSets, queryParams, prevFilters, prevLabelsLogic })),
);
}),
map(({ facets, labelSets, queryParams, prevFilters }) => {
map(({ facets, labelSets, queryParams, prevFilters, prevLabelsLogic }) => {
const previousFilters = queryParams.get('preserveFilters') ? prevFilters : queryParams.getAll('filters');
this.andLogicForLabels = queryParams.get('preserveFilters')
? prevLabelsLogic === 'AND'
: queryParams.get('labelsLogic') === 'AND';
this.formatFiltersFromFacets(facets, labelSets, previousFilters);
if (previousFilters.length > 0) {
this.onToggleFilter();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ export const DEFAULT_PAGE_SIZE = 25;
export const PAGE_SIZES = [25, 50, 100];
export const DEFAULT_SORTING: SortOption = { field: SortField.created, order: 'desc' };

export type LabelsLogic = 'OR' | 'AND';
export const DEFAULT_LABELS_LOGIC = 'OR';

export const RESOURCE_LIST_PREFERENCES = 'NUCLIA_RESOURCE_LIST_PREFERENCES';

export interface BulkAction {
Expand All @@ -60,11 +63,13 @@ export interface ResourceListParams {
sort: SortOption;
query: string;
filters: string[];
labelsLogic?: LabelsLogic;
}
export function getSearchOptions(params: ResourceListParams): CatalogOptions {
const labelsOperator = params.labelsLogic === 'AND' ? FilterOperator.all : FilterOperator.any;
const filters: Filter[] = [
{ [FilterOperator.any]: params.filters.filter((filter) => filter.startsWith('/icon/')) },
{ [FilterOperator.any]: params.filters.filter((filter) => filter.startsWith('/classification.labels/')) },
{ [labelsOperator]: params.filters.filter((filter) => filter.startsWith('/classification.labels/')) },
{ [FilterOperator.any]: params.status ? [`/n/s/${params.status}`] : [] },
].filter((item) => (Object.values(item)[0] || []).length > 0);
const start = params.filters.find((filter) => filter.startsWith(CREATION_START_PREFIX));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import {
tap,
} from 'rxjs';
import {
DEFAULT_LABELS_LOGIC,
DEFAULT_PAGE_SIZE,
DEFAULT_SORTING,
getSearchOptions,
LabelsLogic,
ResourceListParams,
ResourceWithLabels,
searchResources,
Expand Down Expand Up @@ -71,6 +73,7 @@ export class ResourceListService {
query = this._query.asObservable();
private _headerHeight = new BehaviorSubject<number>(0);
headerHeight = this._headerHeight.asObservable();
private _labelsLogic = new BehaviorSubject<LabelsLogic>(DEFAULT_LABELS_LOGIC);

private _page = new BehaviorSubject<number>(0);
page = this._page.asObservable();
Expand Down Expand Up @@ -98,6 +101,12 @@ export class ResourceListService {
map(([prev]) => prev),
shareReplay(1),
);
prevLabelsLogic = this._labelsLogic.pipe(
startWith(DEFAULT_LABELS_LOGIC),
pairwise(),
map(([prev]) => prev),
shareReplay(1),
);

private _sort: SortOption = DEFAULT_SORTING;
private _cursor?: string;
Expand Down Expand Up @@ -127,15 +136,17 @@ export class ResourceListService {
this._sort = DEFAULT_SORTING;
this._query.next('');
this._filters.next([]);
this._labelsLogic.next(DEFAULT_LABELS_LOGIC);
}

get sort(): SortOption {
return this._sort;
}

filter(filters: string[]) {
filter(filters: string[], labelsLogic: LabelsLogic = DEFAULT_LABELS_LOGIC) {
this._page.next(0);
this._filters.next(filters);
this._labelsLogic.next(labelsLogic);
this._triggerResourceLoad.next({ replaceData: true, updateCount: false });
}

Expand Down Expand Up @@ -238,6 +249,7 @@ export class ResourceListService {
sort: this._sort,
query: this._query.value.trim().replace('.', '\\.'),
filters: this._filters.value,
labelsLogic: this._labelsLogic.value,
};
return forkJoin([
this.labelSets.pipe(take(1)),
Expand Down
Loading