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
This commit is contained in:
Ehsan Rezaei
2026-08-03 17:02:18 +02:00
committed by GitHub
parent 2579d58d25
commit b06098c500
4 changed files with 138 additions and 9 deletions
@@ -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<TaskCloudEngineEvent[]>();
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';
@@ -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<string>();
@@ -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();
});
@@ -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<typeof taskNotifications>();
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', () => {
@@ -65,7 +65,8 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp
filters$: Observable<TaskFilterCloudModel[]>;
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);