From b06098c500f76dcf00a6c1806dd3e53cb90ecf9b Mon Sep 17 00:00:00 2001 From: Ehsan Rezaei Date: Mon, 3 Aug 2026 17:02:18 +0200 Subject: [PATCH] AAE-49541 Make notification debounce time configurable and increase default (#12124) * AAE-49541 Make notification debounce time configurable and increase default * AAE-49541 Code improvement --- .../process-filters-cloud.component.spec.ts | 66 +++++++++++++++++- .../process-filters-cloud.component.ts | 6 +- .../task-filters-cloud.component.spec.ts | 69 ++++++++++++++++++- .../task-filters-cloud.component.ts | 6 +- 4 files changed, 138 insertions(+), 9 deletions(-) diff --git a/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters/process-filters-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters/process-filters-cloud.component.spec.ts index bbff070269..5a6cd7a1be 100644 --- a/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters/process-filters-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters/process-filters-cloud.component.spec.ts @@ -16,8 +16,8 @@ */ import { Component, SimpleChange } from '@angular/core'; -import { ComponentFixture, fakeAsync, flush, TestBed } from '@angular/core/testing'; -import { first, of, throwError } from 'rxjs'; +import { ComponentFixture, fakeAsync, flush, TestBed, tick } from '@angular/core/testing'; +import { first, of, Subject, throwError } from 'rxjs'; import { ProcessFilterCloudService } from '../../services/process-filter-cloud.service'; import { ProcessFiltersCloudComponent } from './process-filters-cloud.component'; import { By } from '@angular/platform-browser'; @@ -32,6 +32,7 @@ import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { MatIconHarness } from '@angular/material/icon/testing'; import { ActivatedRoute, provideRouter, Router } from '@angular/router'; import { RouterTestingHarness } from '@angular/router/testing'; +import { TaskCloudEngineEvent } from '../../../../models/engine-event-cloud.model'; @Component({ selector: 'adf-cloud-dummy', template: '' }) class DummyComponent {} @@ -520,6 +521,57 @@ describe('ProcessFiltersCloudComponent', () => { expect(fetchSpy).not.toHaveBeenCalledWith(filterWithoutCounter); }); + describe('Notifications config', () => { + it('should read enableNotifications and notificationDebounceTime from app config on init', () => { + const appConfigService = TestBed.inject(AppConfigService); + const getSpy = spyOn(appConfigService, 'get').and.callThrough(); + + fixture.detectChanges(); + + expect(getSpy).toHaveBeenCalledWith('notifications', true); + expect(getSpy).toHaveBeenCalledWith('notificationDebounceTime', 3000); + }); + + it('should default notificationDebounceTime to 3000 when not set in app config', () => { + fixture.detectChanges(); + + expect(component.notificationDebounceTime).toBe(3000); + }); + + it('should use notificationDebounceTime from app config', () => { + const appConfigService: AppConfigService = TestBed.inject(AppConfigService); + spyOn(appConfigService, 'get').and.callFake((key: string, defaultValue: any) => { + if (key === 'notificationDebounceTime') { + return 5000; + } + return defaultValue; + }); + + fixture.detectChanges(); + + expect(component.notificationDebounceTime).toBe(5000); + }); + + it('should debounce notification subscription using the configured debounce time', fakeAsync(() => { + const notifications$ = new Subject(); + getProcessNotificationSubscriptionSpy.and.returnValue(notifications$.asObservable()); + component.appName = 'mock-app-name'; + + fixture.detectChanges(); + + const updateFilterCountersSpy = spyOn(component, 'updateFilterCounters'); + + notifications$.next([]); + tick(1000); + expect(updateFilterCountersSpy).not.toHaveBeenCalled(); + + tick(2000); + expect(updateFilterCountersSpy).toHaveBeenCalledTimes(1); + + flush(); + })); + }); + describe('Highlight Selected Filter', () => { it('should make subscription', () => { component.enableNotifications = true; @@ -530,6 +582,16 @@ describe('ProcessFiltersCloudComponent', () => { expect(getProcessNotificationSubscriptionSpy).toHaveBeenCalled(); }); + it('should not make subscription when notifications are disabled', () => { + const appConfigService = TestBed.inject(AppConfigService); + spyOn(appConfigService, 'get').and.callFake((key: string, defaultValue: any) => (key === 'notifications' ? false : defaultValue)); + component.appName = 'mock-app-name'; + + fixture.detectChanges(); + + expect(getProcessNotificationSubscriptionSpy).not.toHaveBeenCalled(); + }); + it('should emit filter key when filter counter is set for first time', () => { component.currentFiltersValues = {}; const fakeFilterKey = 'testKey'; diff --git a/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters/process-filters-cloud.component.ts b/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters/process-filters-cloud.component.ts index 181f724a7d..70611d4139 100644 --- a/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters/process-filters-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters/process-filters-cloud.component.ts @@ -79,7 +79,8 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges { currentFilter?: ProcessFilterCloudModel; filters: ProcessFilterCloudModel[] = []; counters: { [key: string]: number } = {}; - enableNotifications: boolean; + enableNotifications = true; + notificationDebounceTime = 3000; currentFiltersValues: { [key: string]: number } = {}; updatedFiltersSet = new Set(); @@ -93,6 +94,7 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges { ngOnInit() { this.enableNotifications = this.appConfigService.get('notifications', true); + this.notificationDebounceTime = this.appConfigService.get('notificationDebounceTime', 3000); if (this.appName === '') { this.getFilters(this.appName); } @@ -258,7 +260,7 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges { if (this.appName && this.enableNotifications) { this.processFilterCloudService .getProcessNotificationSubscription(this.appName) - .pipe(debounceTime(1000), takeUntilDestroyed(this.destroyRef)) + .pipe(debounceTime(this.notificationDebounceTime), takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.updateFilterCounters(); }); diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters/task-filters-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters/task-filters-cloud.component.spec.ts index 8a484d7007..4ec74ee071 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters/task-filters-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters/task-filters-cloud.component.spec.ts @@ -17,9 +17,9 @@ import { AppConfigService, NoopAuthModule } from '@alfresco/adf-core'; import { Component, SimpleChange } from '@angular/core'; -import { ComponentFixture, TestBed, fakeAsync, flush } from '@angular/core/testing'; +import { ComponentFixture, TestBed, fakeAsync, flush, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { first, of, throwError } from 'rxjs'; +import { first, of, Subject, throwError } from 'rxjs'; import { TASK_FILTERS_SERVICE_TOKEN } from '../../../../services/cloud-token.service'; import { LocalPreferenceCloudService } from '../../../../services/local-preference-cloud.service'; import { defaultTaskFiltersMock, fakeGlobalFilter, taskNotifications } from '../../mock/task-filters-cloud.mock'; @@ -50,6 +50,7 @@ describe('TaskFiltersCloudComponent', () => { let getTaskFilterCounterSpy: jasmine.Spy; let getTaskListFiltersSpy: jasmine.Spy; let getTaskListCountSpy: jasmine.Spy; + let getTaskNotificationSubscriptionSpy: jasmine.Spy; let router: Router; const configureTestingModule = async (searchApiMethod: 'GET' | 'POST') => { @@ -77,7 +78,7 @@ describe('TaskFiltersCloudComponent', () => { taskListService = TestBed.inject(TaskListCloudService); getTaskFilterCounterSpy = spyOn(taskFilterService, 'getTaskFilterCounter').and.returnValue(of(11)); getTaskListCountSpy = spyOn(taskListService, 'getTaskListCount').and.returnValue(of(11)); - spyOn(taskFilterService, 'getTaskNotificationSubscription').and.returnValue(of(taskNotifications)); + getTaskNotificationSubscriptionSpy = spyOn(taskFilterService, 'getTaskNotificationSubscription').and.returnValue(of(taskNotifications)); getTaskListFiltersSpy = spyOn(taskFilterService, 'getTaskListFilters').and.returnValue(of(fakeGlobalFilter)); appConfigService = TestBed.inject(AppConfigService); @@ -268,6 +269,68 @@ describe('TaskFiltersCloudComponent', () => { fixture.detectChanges(); expect(getTaskFilterCounterSpy).toHaveBeenCalledWith(fakeGlobalFilter[0]); }); + + describe('Notifications config', () => { + it('should read enableNotifications and notificationDebounceTime from app config on init', () => { + const getSpy = spyOn(appConfigService, 'get').and.callThrough(); + component.appName = 'my-app-1'; + + fixture.detectChanges(); + + expect(getSpy).toHaveBeenCalledWith('notifications', true); + expect(getSpy).toHaveBeenCalledWith('notificationDebounceTime', 3000); + }); + + it('should default notificationDebounceTime to 3000 when not set in app config', () => { + component.appName = 'my-app-1'; + + fixture.detectChanges(); + + expect(component.notificationDebounceTime).toBe(3000); + }); + + it('should use notificationDebounceTime from app config', () => { + spyOn(appConfigService, 'get').and.callFake((key: string, defaultValue: any) => { + if (key === 'notificationDebounceTime') { + return 5000; + } + return defaultValue; + }); + component.appName = 'my-app-1'; + + fixture.detectChanges(); + + expect(component.notificationDebounceTime).toBe(5000); + }); + + it('should not subscribe to notifications when appName is missing', () => { + getTaskNotificationSubscriptionSpy.calls.reset(); + component.appName = ''; + + fixture.detectChanges(); + + expect(getTaskNotificationSubscriptionSpy).not.toHaveBeenCalled(); + }); + + it('should debounce notification subscription using the configured debounce time', fakeAsync(() => { + const notifications$ = new Subject(); + getTaskNotificationSubscriptionSpy.and.returnValue(notifications$.asObservable()); + component.appName = 'my-app-1'; + + fixture.detectChanges(); + + const updateFilterCountersSpy = spyOn(component, 'updateFilterCounters'); + + notifications$.next(taskNotifications); + tick(1000); + expect(updateFilterCountersSpy).not.toHaveBeenCalled(); + + tick(2000); + expect(updateFilterCountersSpy).toHaveBeenCalledTimes(1); + + flush(); + })); + }); }); describe('searchApiMethod set to POST', () => { diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters/task-filters-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters/task-filters-cloud.component.ts index e83dff3c3d..cf09f583d6 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters/task-filters-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters/task-filters-cloud.component.ts @@ -65,7 +65,8 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp filters$: Observable; filters: TaskFilterCloudModel[] = []; currentFilter: TaskFilterCloudModel; - enableNotifications: boolean; + enableNotifications = true; + notificationDebounceTime = 3000; currentFiltersValues: { [key: string]: number } = {}; private readonly taskFilterCloudService = inject(TaskFilterCloudService); @@ -77,6 +78,7 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp ngOnInit() { this.enableNotifications = this.appConfigService.get('notifications', true); + this.notificationDebounceTime = this.appConfigService.get('notificationDebounceTime', 3000); this.getFilters(this.appName); this.initFilterCounterNotifications(); this.getFilterKeysAfterExternalRefreshing(); @@ -165,7 +167,7 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp if (this.enableNotifications) { this.taskFilterCloudService .getTaskNotificationSubscription(this.appName) - .pipe(debounceTime(1000)) + .pipe(debounceTime(this.notificationDebounceTime), takeUntilDestroyed(this.destroyRef)) .subscribe((result) => { result.forEach((taskEvent) => { this.checkFilterCounter(taskEvent.entity);