From 61ba6f18be7a66a65163ff6a09c934aabd1b29e2 Mon Sep 17 00:00:00 2001 From: Anton Ramanovich Date: Thu, 20 Nov 2025 22:49:34 +0100 Subject: [PATCH 1/6] [MNT-25408]: fixes execution sequence --- .../src/lib/search/services/base-query-builder.service.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/content-services/src/lib/search/services/base-query-builder.service.ts b/lib/content-services/src/lib/search/services/base-query-builder.service.ts index 6d838d13891..e3e7fc4d311 100644 --- a/lib/content-services/src/lib/search/services/base-query-builder.service.ts +++ b/lib/content-services/src/lib/search/services/base-query-builder.service.ts @@ -146,10 +146,10 @@ export abstract class BaseQueryBuilderService { const currentConfig = this.loadConfiguration(); if (Array.isArray(currentConfig) && currentConfig[index] !== undefined) { this.selectedConfiguration = index; - this.configUpdated.next(currentConfig[index]); this.searchForms.next(this.getSearchFormDetails()); this.resetSearchOptions(); this.setUpSearchConfiguration(currentConfig[index]); + this.configUpdated.next(currentConfig[index]); this.update(); } } @@ -209,6 +209,7 @@ export abstract class BaseQueryBuilderService { * @param bucket Bucket to add */ addUserFacetBucket(field: string, bucket: FacetFieldBucket) { + // console.log('Adding user facet bucket', field, bucket); if (field && bucket) { const buckets = this.userFacetBuckets[field] || []; const existing = buckets.find((facetBucket) => facetBucket.label === bucket.label); @@ -236,6 +237,7 @@ export abstract class BaseQueryBuilderService { * @param bucket Bucket to remove */ removeUserFacetBucket(field: string, bucket: FacetFieldBucket) { + // console.log('Removing user facet bucket', field, bucket); if (field && bucket) { const buckets = this.userFacetBuckets[field] || []; this.userFacetBuckets[field] = buckets.filter((facetBucket) => facetBucket.label !== bucket.label); From 403b990b874e18da1cd719d50409bf183fd36a78 Mon Sep 17 00:00:00 2001 From: Anton Ramanovich Date: Thu, 20 Nov 2025 22:51:46 +0100 Subject: [PATCH 2/6] [MNT-25408]: adds getter --- .../search-facet-filters.service.spec.ts | 42 ++++++++++++++++++- .../services/search-facet-filters.service.ts | 4 ++ 2 files changed, 45 insertions(+), 1 deletion(-) 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 d2746da4929..a003db3cdc7 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 @@ -22,7 +22,9 @@ import { ContentTestingModule } from '../../testing/content.testing.module'; import { SearchQueryBuilderService } from './search-query-builder.service'; import { EMPTY, of } from 'rxjs'; import { CategoryService } from '../../category/services/category.service'; -import { FacetBucketSortBy, FacetBucketSortDirection } from '../models/facet-field.interface'; +import { FacetBucketSortBy, FacetBucketSortDirection, FacetField } from '../models/facet-field.interface'; +import { FacetFieldBucket } from '../models/facet-field-bucket.interface'; +import { TabbedFacetField } from '../models/tabbed-facet-field.interface'; describe('SearchFacetFiltersService', () => { let searchFacetFiltersService: SearchFacetFiltersService; @@ -681,4 +683,42 @@ describe('SearchFacetFiltersService', () => { expect(searchFacetFiltersService.responseFacets[0].buckets.items.map((b) => b.label)).toEqual(['baz', 'foo', 'xyzzy', 'qux', 'bar']); }); }); + + describe('hasActiveFilters getter', () => { + it('should return false when there are no facets, no tabbed facet and no selected buckets', () => { + searchFacetFiltersService.responseFacets = []; + searchFacetFiltersService.tabbedFacet = null; + searchFacetFiltersService.selectedBuckets = []; + + expect(searchFacetFiltersService.hasActiveFilters).toBeFalse(); + }); + + it('should return true when responseFacets has items', () => { + const facet = { field: 'f1', label: 'f1' } as FacetField; + searchFacetFiltersService.responseFacets = [facet]; + searchFacetFiltersService.tabbedFacet = null; + searchFacetFiltersService.selectedBuckets = []; + + expect(searchFacetFiltersService.hasActiveFilters).toBeTrue(); + }); + + it('should return true when tabbedFacet is present', () => { + searchFacetFiltersService.responseFacets = []; + const tab = { fields: ['creator'], label: 'label', facets: {} } as TabbedFacetField; + searchFacetFiltersService.tabbedFacet = tab; + searchFacetFiltersService.selectedBuckets = []; + + expect(searchFacetFiltersService.hasActiveFilters).toBeTrue(); + }); + + it('should return true when selectedBuckets has items', () => { + searchFacetFiltersService.responseFacets = []; + searchFacetFiltersService.tabbedFacet = null; + const facet = { field: 'f1', label: 'f1' } as FacetField; + const bucket = { label: 'b1', filterQuery: 'fq', count: 1 } as FacetFieldBucket; + searchFacetFiltersService.selectedBuckets = [{ field: facet, bucket }]; + + expect(searchFacetFiltersService.hasActiveFilters).toBeTrue(); + }); + }); }); 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 9e8a4741aee..38fe4e5ea82 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 @@ -76,6 +76,10 @@ export class SearchFacetFiltersService { }); } + get hasActiveFilters(): boolean { + return this.responseFacets?.length > 0 || this.tabbedFacet !== null || this.selectedBuckets?.length > 0; + } + onDataLoaded(data: any) { const context = data.list.context; From 7c68650c796eb0e0c485b893a2df8499b08b96e5 Mon Sep 17 00:00:00 2001 From: Anton Ramanovich Date: Wed, 26 Nov 2025 07:22:16 +0100 Subject: [PATCH 3/6] [MNT-25408]: reverts getter for property access proxy --- .../services/base-query-builder.service.ts | 33 ++++++++++++++- .../search-facet-filters.service.spec.ts | 42 +------------------ .../services/search-facet-filters.service.ts | 4 -- 3 files changed, 32 insertions(+), 47 deletions(-) diff --git a/lib/content-services/src/lib/search/services/base-query-builder.service.ts b/lib/content-services/src/lib/search/services/base-query-builder.service.ts index e3e7fc4d311..b0ea72013fb 100644 --- a/lib/content-services/src/lib/search/services/base-query-builder.service.ts +++ b/lib/content-services/src/lib/search/services/base-query-builder.service.ts @@ -73,8 +73,14 @@ export abstract class BaseQueryBuilderService { /* Stream that emits the initial value for some or all search filters */ populateFilters = new BehaviorSubject<{ [key: string]: any }>({}); + /* Stream that emits every time queryFragments change */ + queryFragmentsUpdate = new BehaviorSubject<{ [key: string]: any }>({}); + + /* Stream that emits every time userFacetBuckets change */ + userFacetBucketsUpdate = new BehaviorSubject<{ [key: string]: FacetFieldBucket[] }>({}); + categories: SearchCategory[] = []; - queryFragments: { [id: string]: string } = {}; + _queryFragments: { [id: string]: string } = {}; filterQueries: FilterQuery[] = []; filterRawParams: { [key: string]: any } = {}; paging: { maxItems?: number; skipCount?: number } = null; @@ -87,6 +93,15 @@ export abstract class BaseQueryBuilderService { protected userFacetBuckets: { [key: string]: FacetFieldBucket[] } = {}; + get queryFragments(): { [key: string]: any } { + return this._queryFragments; + } + + set queryFragments(value: { [key: string]: any }) { + this._queryFragments = this.createQueryFragmentsProxy(value); + this.queryFragmentsUpdate.next(this._queryFragments); + } + get userQuery(): string { return this._userQuery; } @@ -108,6 +123,7 @@ export abstract class BaseQueryBuilderService { protected readonly alfrescoApiService: AlfrescoApiService ) { this.resetToDefaults(); + this._queryFragments = this.createQueryFragmentsProxy({}); } public abstract loadConfiguration(): SearchConfiguration | SearchConfiguration[]; @@ -209,7 +225,6 @@ export abstract class BaseQueryBuilderService { * @param bucket Bucket to add */ addUserFacetBucket(field: string, bucket: FacetFieldBucket) { - // console.log('Adding user facet bucket', field, bucket); if (field && bucket) { const buckets = this.userFacetBuckets[field] || []; const existing = buckets.find((facetBucket) => facetBucket.label === bucket.label); @@ -217,6 +232,7 @@ export abstract class BaseQueryBuilderService { buckets.push(bucket); } this.userFacetBuckets[field] = buckets; + this.userFacetBucketsUpdate.next(this.userFacetBuckets); } } @@ -241,6 +257,7 @@ export abstract class BaseQueryBuilderService { if (field && bucket) { const buckets = this.userFacetBuckets[field] || []; this.userFacetBuckets[field] = buckets.filter((facetBucket) => facetBucket.label !== bucket.label); + this.userFacetBucketsUpdate.next(this.userFacetBuckets); } } @@ -617,4 +634,16 @@ export abstract class BaseQueryBuilderService { queryParamsHandling: 'merge' }); } + + private readonly queryFragmentsHandler: ProxyHandler<{ [key: string]: any }> = { + set: (target: { [key: string]: any }, property: string, value: any) => { + target[property as keyof typeof target] = value; + this.queryFragmentsUpdate.next(this._queryFragments); + return true; + } + }; + + private createQueryFragmentsProxy(target: { [key: string]: any }): { [key: string]: any } { + return new Proxy(target, this.queryFragmentsHandler); + } } 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 a003db3cdc7..d2746da4929 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 @@ -22,9 +22,7 @@ import { ContentTestingModule } from '../../testing/content.testing.module'; import { SearchQueryBuilderService } from './search-query-builder.service'; import { EMPTY, of } from 'rxjs'; import { CategoryService } from '../../category/services/category.service'; -import { FacetBucketSortBy, FacetBucketSortDirection, FacetField } from '../models/facet-field.interface'; -import { FacetFieldBucket } from '../models/facet-field-bucket.interface'; -import { TabbedFacetField } from '../models/tabbed-facet-field.interface'; +import { FacetBucketSortBy, FacetBucketSortDirection } from '../models/facet-field.interface'; describe('SearchFacetFiltersService', () => { let searchFacetFiltersService: SearchFacetFiltersService; @@ -683,42 +681,4 @@ describe('SearchFacetFiltersService', () => { expect(searchFacetFiltersService.responseFacets[0].buckets.items.map((b) => b.label)).toEqual(['baz', 'foo', 'xyzzy', 'qux', 'bar']); }); }); - - describe('hasActiveFilters getter', () => { - it('should return false when there are no facets, no tabbed facet and no selected buckets', () => { - searchFacetFiltersService.responseFacets = []; - searchFacetFiltersService.tabbedFacet = null; - searchFacetFiltersService.selectedBuckets = []; - - expect(searchFacetFiltersService.hasActiveFilters).toBeFalse(); - }); - - it('should return true when responseFacets has items', () => { - const facet = { field: 'f1', label: 'f1' } as FacetField; - searchFacetFiltersService.responseFacets = [facet]; - searchFacetFiltersService.tabbedFacet = null; - searchFacetFiltersService.selectedBuckets = []; - - expect(searchFacetFiltersService.hasActiveFilters).toBeTrue(); - }); - - it('should return true when tabbedFacet is present', () => { - searchFacetFiltersService.responseFacets = []; - const tab = { fields: ['creator'], label: 'label', facets: {} } as TabbedFacetField; - searchFacetFiltersService.tabbedFacet = tab; - searchFacetFiltersService.selectedBuckets = []; - - expect(searchFacetFiltersService.hasActiveFilters).toBeTrue(); - }); - - it('should return true when selectedBuckets has items', () => { - searchFacetFiltersService.responseFacets = []; - searchFacetFiltersService.tabbedFacet = null; - const facet = { field: 'f1', label: 'f1' } as FacetField; - const bucket = { label: 'b1', filterQuery: 'fq', count: 1 } as FacetFieldBucket; - searchFacetFiltersService.selectedBuckets = [{ field: facet, bucket }]; - - expect(searchFacetFiltersService.hasActiveFilters).toBeTrue(); - }); - }); }); 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 38fe4e5ea82..9e8a4741aee 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 @@ -76,10 +76,6 @@ export class SearchFacetFiltersService { }); } - get hasActiveFilters(): boolean { - return this.responseFacets?.length > 0 || this.tabbedFacet !== null || this.selectedBuckets?.length > 0; - } - onDataLoaded(data: any) { const context = data.list.context; From 6d83f12eeb168f3d56b17c0314ad8b0f15fa5d55 Mon Sep 17 00:00:00 2001 From: Anton Ramanovich Date: Wed, 26 Nov 2025 08:55:39 +0100 Subject: [PATCH 4/6] [MNT-25408]: adds tests --- .../services/base-query-builder.service.ts | 1 - .../search-query-builder.service.spec.ts | 66 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/lib/content-services/src/lib/search/services/base-query-builder.service.ts b/lib/content-services/src/lib/search/services/base-query-builder.service.ts index b0ea72013fb..0982fcfc8af 100644 --- a/lib/content-services/src/lib/search/services/base-query-builder.service.ts +++ b/lib/content-services/src/lib/search/services/base-query-builder.service.ts @@ -253,7 +253,6 @@ export abstract class BaseQueryBuilderService { * @param bucket Bucket to remove */ removeUserFacetBucket(field: string, bucket: FacetFieldBucket) { - // console.log('Removing user facet bucket', field, bucket); if (field && bucket) { const buckets = this.userFacetBuckets[field] || []; this.userFacetBuckets[field] = buckets.filter((facetBucket) => facetBucket.label !== bucket.label); diff --git a/lib/content-services/src/lib/search/services/search-query-builder.service.spec.ts b/lib/content-services/src/lib/search/services/search-query-builder.service.spec.ts index bb3c7d6c6cb..3e0107d82ab 100644 --- a/lib/content-services/src/lib/search/services/search-query-builder.service.spec.ts +++ b/lib/content-services/src/lib/search/services/search-query-builder.service.spec.ts @@ -15,6 +15,8 @@ * limitations under the License. */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + import { SearchQueryBuilderService } from './search-query-builder.service'; import { SearchConfiguration } from '../models/search-configuration.interface'; import { AppConfigService } from '@alfresco/adf-core'; @@ -24,6 +26,7 @@ import { TestBed } from '@angular/core/testing'; import { ContentTestingModule } from '../../testing/content.testing.module'; import { ADF_SEARCH_CONFIGURATION } from '../search-configuration.token'; import { ActivatedRoute, Router } from '@angular/router'; +import { skip } from 'rxjs/operators'; const buildConfig = (searchSettings = {}): AppConfigService => { let config: AppConfigService; @@ -840,4 +843,67 @@ describe('SearchQueryBuilder', () => { }); }); }); + + describe('userFacetBucketsUpdate', () => { + it('should emit updated list of UserFacetBuckets on adding the bucket', (done) => { + const service = TestBed.inject(SearchQueryBuilderService); + + service.userFacetBucketsUpdate.pipe(skip(1)).subscribe((buckets) => { + expect(buckets).toEqual({ test: [{ checked: true, filterQuery: 'f1-q1', label: 'f1-q1', count: 1 }] }); + done(); + }); + + const currentBuckets = service.getUserFacetBuckets('test'); + expect(currentBuckets).toEqual([]); + service.addUserFacetBucket('test', { checked: true, filterQuery: 'f1-q1', label: 'f1-q1', count: 1 }); + }); + + it('should emit updated list of UserFacetBuckets on removing the bucket', (done) => { + const service = TestBed.inject(SearchQueryBuilderService); + service.addUserFacetBucket('test', { checked: true, filterQuery: 'f1-q1', label: 'toStay', count: 1 }); + service.addUserFacetBucket('test', { checked: true, filterQuery: 'f1-q1', label: 'toLeave', count: 1 }); + + service.userFacetBucketsUpdate.pipe(skip(1)).subscribe((buckets) => { + expect(buckets).toEqual({ test: [{ checked: true, filterQuery: 'f1-q1', label: 'toStay', count: 1 }] }); + done(); + }); + + const currentBuckets = service.getUserFacetBuckets('test'); + expect(currentBuckets).toEqual([ + { checked: true, filterQuery: 'f1-q1', label: 'toStay', count: 1 }, + { checked: true, filterQuery: 'f1-q1', label: 'toLeave', count: 1 } + ]); + service.removeUserFacetBucket('test', { checked: true, filterQuery: 'f1-q1', label: 'toLeave', count: 1 }); + }); + }); + + describe('queryFragments proxy set up', () => { + it('should emit queryFragmentsUpdate when proxy property is set', (done) => { + const service = TestBed.inject(SearchQueryBuilderService); + + service.queryFragmentsUpdate.pipe(skip(1)).subscribe((fragments) => { + expect(fragments).toEqual({ test: 'test_fragment' }); + done(); + }); + + const currentFragments = service.queryFragments; + expect(currentFragments).toEqual({}); + service.queryFragments['test'] = 'test_fragment'; + }); + + it('should emit queryFragmentsUpdate when setter replaces the proxy', (done) => { + const service = TestBed.inject(SearchQueryBuilderService); + + service.queryFragments['test'] = 'test_fragment'; + const currentFragments = service.queryFragments; + + expect(currentFragments).toEqual({ test: 'test_fragment' }); + + service.queryFragmentsUpdate.pipe(skip(1)).subscribe((fragments) => { + expect(fragments).toEqual({}); + done(); + }); + service.queryFragments = {}; + }); + }); }); From d466b9b1df5e44939389243fb653df313c8758b8 Mon Sep 17 00:00:00 2001 From: Anton Ramanovich Date: Thu, 27 Nov 2025 13:14:55 +0100 Subject: [PATCH 5/6] [MNT-25408]: minor fixes --- .../services/base-query-builder.service.ts | 18 +++++++++--------- .../search-query-builder.service.spec.ts | 2 -- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/content-services/src/lib/search/services/base-query-builder.service.ts b/lib/content-services/src/lib/search/services/base-query-builder.service.ts index 0982fcfc8af..a2a99627f2e 100644 --- a/lib/content-services/src/lib/search/services/base-query-builder.service.ts +++ b/lib/content-services/src/lib/search/services/base-query-builder.service.ts @@ -80,7 +80,7 @@ export abstract class BaseQueryBuilderService { userFacetBucketsUpdate = new BehaviorSubject<{ [key: string]: FacetFieldBucket[] }>({}); categories: SearchCategory[] = []; - _queryFragments: { [id: string]: string } = {}; + private _queryFragments: { [id: string]: string } = {}; filterQueries: FilterQuery[] = []; filterRawParams: { [key: string]: any } = {}; paging: { maxItems?: number; skipCount?: number } = null; @@ -91,6 +91,14 @@ export abstract class BaseQueryBuilderService { private selectedConfiguration: number; private _userQuery = ''; + private readonly queryFragmentsHandler: ProxyHandler<{ [key: string]: any }> = { + set: (target: { [key: string]: any }, property: string, value: any) => { + target[property as keyof typeof target] = value; + this.queryFragmentsUpdate.next(this._queryFragments); + return true; + } + }; + protected userFacetBuckets: { [key: string]: FacetFieldBucket[] } = {}; get queryFragments(): { [key: string]: any } { @@ -634,14 +642,6 @@ export abstract class BaseQueryBuilderService { }); } - private readonly queryFragmentsHandler: ProxyHandler<{ [key: string]: any }> = { - set: (target: { [key: string]: any }, property: string, value: any) => { - target[property as keyof typeof target] = value; - this.queryFragmentsUpdate.next(this._queryFragments); - return true; - } - }; - private createQueryFragmentsProxy(target: { [key: string]: any }): { [key: string]: any } { return new Proxy(target, this.queryFragmentsHandler); } diff --git a/lib/content-services/src/lib/search/services/search-query-builder.service.spec.ts b/lib/content-services/src/lib/search/services/search-query-builder.service.spec.ts index 3e0107d82ab..b720c762165 100644 --- a/lib/content-services/src/lib/search/services/search-query-builder.service.spec.ts +++ b/lib/content-services/src/lib/search/services/search-query-builder.service.spec.ts @@ -15,8 +15,6 @@ * limitations under the License. */ -/* eslint-disable @typescript-eslint/no-explicit-any */ - import { SearchQueryBuilderService } from './search-query-builder.service'; import { SearchConfiguration } from '../models/search-configuration.interface'; import { AppConfigService } from '@alfresco/adf-core'; From a2f1899b17c2bc3bbad96bd55ed8e3f37b69fcc7 Mon Sep 17 00:00:00 2001 From: Anton Ramanovich Date: Thu, 27 Nov 2025 16:14:11 +0100 Subject: [PATCH 6/6] [MNT-25408]: minor fixes --- .../src/lib/search/services/base-query-builder.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/content-services/src/lib/search/services/base-query-builder.service.ts b/lib/content-services/src/lib/search/services/base-query-builder.service.ts index a2a99627f2e..e127de44f64 100644 --- a/lib/content-services/src/lib/search/services/base-query-builder.service.ts +++ b/lib/content-services/src/lib/search/services/base-query-builder.service.ts @@ -80,16 +80,17 @@ export abstract class BaseQueryBuilderService { userFacetBucketsUpdate = new BehaviorSubject<{ [key: string]: FacetFieldBucket[] }>({}); categories: SearchCategory[] = []; - private _queryFragments: { [id: string]: string } = {}; filterQueries: FilterQuery[] = []; filterRawParams: { [key: string]: any } = {}; paging: { maxItems?: number; skipCount?: number } = null; sorting: SearchSortingDefinition[] = []; sortingOptions: SearchSortingDefinition[] = []; + private encodedQuery: string; private scope: RequestScope; private selectedConfiguration: number; private _userQuery = ''; + private _queryFragments: { [id: string]: string } = {}; private readonly queryFragmentsHandler: ProxyHandler<{ [key: string]: any }> = { set: (target: { [key: string]: any }, property: string, value: any) => {