Skip to content

Commit

Permalink
[sc-11385] Allow selection of label filtering logic
Browse files Browse the repository at this point in the history
  • Loading branch information
operramon committed Jan 21, 2025
1 parent f89146b commit b9beb08
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 9 deletions.
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

0 comments on commit b9beb08

Please sign in to comment.