diff --git a/docs/user-guide/search-configuration-guide.md b/docs/user-guide/search-configuration-guide.md index 362373e838..a0dd049dd8 100644 --- a/docs/user-guide/search-configuration-guide.md +++ b/docs/user-guide/search-configuration-guide.md @@ -444,7 +444,10 @@ Note: `settings` property used to control UI actions and interaction. i.e ], "settings" : { "allowUpdateOnChange": false, - "hideDefaultAction": true + "hideDefaultAction": true, + "facetOrder": 100, + "bucketSortBy": "LABEL", + "bucketSortDirection": "ASCENDING" } } } diff --git a/lib/content-services/src/lib/search/models/facet-field-bucket.interface.ts b/lib/content-services/src/lib/search/models/facet-field-bucket.interface.ts index 7cec26c1c5..8d18d6aeb1 100644 --- a/lib/content-services/src/lib/search/models/facet-field-bucket.interface.ts +++ b/lib/content-services/src/lib/search/models/facet-field-bucket.interface.ts @@ -23,5 +23,4 @@ export interface FacetFieldBucket { checked?: boolean; field?: string; - } diff --git a/lib/content-services/src/lib/search/models/facet-field.interface.ts b/lib/content-services/src/lib/search/models/facet-field.interface.ts index 54e5606fdb..17d418e4e2 100644 --- a/lib/content-services/src/lib/search/models/facet-field.interface.ts +++ b/lib/content-services/src/lib/search/models/facet-field.interface.ts @@ -40,4 +40,22 @@ export interface FacetFieldSettings { allowUpdateOnChange?: boolean; /* allow the user show/hide default search actions */ hideDefaultAction?: boolean; + /* a number to compare to other facets to determine the order in which they will appear */ + facetOrder?: number; + /* the field used to sort the buckets */ + bucketSortBy?: FacetBucketSortBy; + /* the direction in which the buckets are ordered */ + bucketSortDirection?: FacetBucketSortDirection; +} + +// eslint-disable-next-line no-shadow +export enum FacetBucketSortBy { + LABEL = 'LABEL', + COUNT = 'COUNT' +} + +// eslint-disable-next-line no-shadow +export enum FacetBucketSortDirection { + ASCENDING = 'ASCENDING', + DESCENDING = 'DESCENDING' } diff --git a/lib/content-services/src/lib/search/services/search-facet-filters.service.spec.ts b/lib/content-services/src/lib/search/services/search-facet-filters.service.spec.ts index 4a772522f3..02014d460f 100644 --- a/lib/content-services/src/lib/search/services/search-facet-filters.service.spec.ts +++ b/lib/content-services/src/lib/search/services/search-facet-filters.service.spec.ts @@ -20,6 +20,7 @@ import { TestBed } from '@angular/core/testing'; import { SearchFacetFiltersService } from './search-facet-filters.service'; import { ContentTestingModule } from '../../testing/content.testing.module'; import { SearchQueryBuilderService } from './search-query-builder.service'; +import { FacetBucketSortBy, FacetBucketSortDirection } from '@alfresco/adf-content-services'; describe('SearchFacetFiltersService', () => { let searchFacetFiltersService: SearchFacetFiltersService; @@ -416,4 +417,103 @@ describe('SearchFacetFiltersService', () => { expect(searchFacetFiltersService.responseFacets[0].buckets.length).toEqual(1); }); + it('should sort the facets based on the order set in the settings', () => { + searchFacetFiltersService.responseFacets = null; + queryBuilder.config = { + categories: [], + facetQueries: { + label: 'Query 1', + queries: [ + { label: 'q1', query: 'query1' }, + { label: 'q2', query: 'query2' } + ], + settings: { + facetOrder: 300 + } + }, + facetFields: { + fields: [ + { field: 'field1', label: 'Field 1', settings: { facetOrder: 200 }}, + { field: 'field2', label: 'Field 2', settings: { facetOrder: 400 }}, + { field: 'field3', label: 'Field 3', settings: { facetOrder: 500 }}, + { field: 'field4', label: 'Field 4', settings: { facetOrder: 100 }} + ] + } + }; + const queryBucketsMock = [{ label: 'q1', filterQuery: 'query1', metrics: [{value: {count: 1} }] }]; + const fieldBucketsMock = [{ label: 'b1', metrics: [{ value: { count: 10 } }] }]; + const data = { + list: { + context: { + facets: [ + { type: 'query', label: 'Query 1', buckets: queryBucketsMock }, + { type: 'field', label: 'Field 1', buckets: fieldBucketsMock }, + { type: 'field', label: 'Field 2', buckets: fieldBucketsMock }, + { type: 'field', label: 'Field 3', buckets: fieldBucketsMock }, + { type: 'field', label: 'Field 4', buckets: fieldBucketsMock } + ] + } + } + }; + + searchFacetFiltersService.onDataLoaded(data); + expect(searchFacetFiltersService.responseFacets.map(f => f.field)).toEqual(['field4', 'field1', 'Query 1', 'field2', 'field3']); + }); + + describe('Bucket sorting', () => { + let data; + + beforeEach(() => { + searchFacetFiltersService.responseFacets = null; + data = { + list: { + context: { + facets: [ + { + type: 'field', + label: 'Field', + buckets: [ + { label: 'foo', metrics: [{ value: { count: 8 } }] }, + { label: 'bar', metrics: [{ value: { count: 30 } }] }, + { label: 'xyzzy', metrics: [{ value: { count: 14 } }] }, + { label: 'qux', metrics: [{ value: { count: 28 } }] }, + { label: 'baz', metrics: [{ value: { count: 1 } }] } + ] + } + ] + } + } + }; + }); + + it('should sort the buckets by label', () => { + queryBuilder.config = { + categories: [], + facetQueries: { queries: [] }, + facetFields: { + fields: [ + { field: 'field', label: 'Field', settings: { bucketSortBy: FacetBucketSortBy.LABEL, bucketSortDirection: FacetBucketSortDirection.DESCENDING }} + ] + } + }; + searchFacetFiltersService.onDataLoaded(data); + + expect(searchFacetFiltersService.responseFacets[0].buckets.items.map(b => b.label)).toEqual(['xyzzy', 'qux', 'foo', 'baz', 'bar']); + }); + + it('should sort the buckets by count', () => { + queryBuilder.config = { + categories: [], + facetQueries: { queries: [] }, + facetFields: { + fields: [ + { field: 'field', label: 'Field', settings: { bucketSortBy: FacetBucketSortBy.COUNT, bucketSortDirection: FacetBucketSortDirection.ASCENDING }} + ] + } + }; + searchFacetFiltersService.onDataLoaded(data); + + expect(searchFacetFiltersService.responseFacets[0].buckets.items.map(b => b.label)).toEqual(['baz', 'foo', 'xyzzy', 'qux', 'bar']); + }); + }); }); diff --git a/lib/content-services/src/lib/search/services/search-facet-filters.service.ts b/lib/content-services/src/lib/search/services/search-facet-filters.service.ts index c5e8396b77..635351f9e4 100644 --- a/lib/content-services/src/lib/search/services/search-facet-filters.service.ts +++ b/lib/content-services/src/lib/search/services/search-facet-filters.service.ts @@ -16,7 +16,7 @@ */ import { Inject, Injectable, OnDestroy } from '@angular/core'; -import { FacetField } from '../models/facet-field.interface'; +import { FacetBucketSortBy, FacetBucketSortDirection, FacetField } from '../models/facet-field.interface'; import { Subject } from 'rxjs'; import { SEARCH_QUERY_SERVICE_TOKEN } from '../search-query-service.token'; import { SearchQueryBuilderService } from './search-query-builder.service'; @@ -91,6 +91,7 @@ export class SearchFacetFiltersService implements OnDestroy { this.parseFacetFields(context); this.parseFacetIntervals(context); this.parseFacetQueries(context); + this.sortFacets(); } private parseFacetItems(context: ResultSetContext, configFacetFields: FacetField[], itemType: string) { @@ -98,6 +99,7 @@ export class SearchFacetFiltersService implements OnDestroy { const responseField = this.findFacet(context, itemType, field.label); const responseBuckets = this.getResponseBuckets(responseField, field) .filter(this.getFilterByMinCount(field.mincount)); + this.sortFacetBuckets(responseBuckets, field.settings?.bucketSortBy, field.settings?.bucketSortDirection ?? FacetBucketSortDirection.ASCENDING); const alreadyExistingField = this.findResponseFacet(itemType, field.label); if (alreadyExistingField) { @@ -155,6 +157,7 @@ export class SearchFacetFiltersService implements OnDestroy { const responseField = this.findFacet(context, 'query', group); const responseBuckets = this.getResponseQueryBuckets(responseField, configGroups[group]) .filter(mincountFilter); + this.sortFacetBuckets(responseBuckets, facetQuerySetting?.bucketSortBy, facetQuerySetting.bucketSortDirection ?? FacetBucketSortDirection.ASCENDING); const alreadyExistingField = this.findResponseFacet('query', group); if (alreadyExistingField) { @@ -184,6 +187,10 @@ export class SearchFacetFiltersService implements OnDestroy { } + private sortFacets() { + this.responseFacets?.sort((facet1, facet2) => (facet1.settings?.facetOrder ?? 0) - (facet2.settings?.facetOrder ?? 0)); + } + private getResponseBuckets(responseField: GenericFacetResponse, configField: FacetField): FacetFieldBucket[] { return ((responseField && responseField.buckets) || []).map((respBucket) => { @@ -213,6 +220,23 @@ export class SearchFacetFiltersService implements OnDestroy { }); } + private sortFacetBuckets(buckets: FacetFieldBucket[], sortBy: FacetBucketSortBy, sortDirection: FacetBucketSortDirection) { + switch (sortBy) { + case FacetBucketSortBy.LABEL: + buckets.sort((bucket1, bucket2) => + sortDirection === FacetBucketSortDirection.ASCENDING ? bucket1.label.localeCompare(bucket2.label) : bucket2.label.localeCompare(bucket1.label) + ); + break; + case FacetBucketSortBy.COUNT: + buckets.sort((bucket1, bucket2) => + sortDirection === FacetBucketSortDirection.ASCENDING ? bucket1.count - bucket2.count : bucket2.count - bucket1.count + ); + break; + default: + return; + } + } + private getCountValue(bucket: GenericBucket): number { return (!!bucket && !!bucket.metrics && bucket.metrics[0]?.value?.count) || 0; }