From 5c4abba87c4c13ec1f606ce310992d03a3b34e1f Mon Sep 17 00:00:00 2001 From: Ehsan Rezaei Date: Fri, 21 Aug 2026 10:55:25 +0200 Subject: [PATCH] AAE-49653 Code improvement --- .../process-filters-cloud.component.spec.ts | 42 +++++++++++++++++ .../process-filters-cloud.component.ts | 47 ++++++++++++++----- .../services/process-filter-cloud.service.ts | 4 +- .../services/filter-counters-cloud.service.ts | 2 +- .../task-filters-cloud.component.spec.ts | 21 +++++++++ .../task-filters-cloud.component.ts | 29 ++++++++++-- 6 files changed, 126 insertions(+), 19 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 cbc420112e..508543c054 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 @@ -519,6 +519,27 @@ describe('ProcessFiltersCloudComponent', () => { expect(component.counters['FakeRunningProcesses']).toBe(10); }); + it('should resolve the counters of the filters the batch left out on their own', async () => { + /* A filter without a key, or one the query cannot be built for, is left out of the batch. */ + getFilterCountersSpy.and.returnValue(of({ counters: { FakeRunningProcesses: 9 }, batched: true })); + + await bindAppName('mock-app-name'); + + expect(component.counters['FakeRunningProcesses']).toBe(9); + /* The model holds no status for the filter targeting every status. */ + expect(getProcessCounterSpy.calls.allArgs().map(([, status]) => status)).toEqual([null, 'COMPLETED']); + }); + + it('should keep the counters of the other filters when one counter cannot be resolved', async () => { + getFilterCountersSpy.and.returnValue(of({ counters: { FakeRunningProcesses: 9 }, batched: true })); + getProcessCounterSpy.and.throwError('the query of the filter cannot be built'); + + await bindAppName('mock-app-name'); + + expect(component.counters['FakeRunningProcesses']).toBe(9); + expect(component.counters['completed-processes']).toBe(0); + }); + it('should refresh the counters of every filter when a filter is clicked', async () => { await bindAppName('mock-app-name'); @@ -879,6 +900,27 @@ describe('ProcessFiltersCloudComponent', () => { expect(component.counters['FakeRunningProcesses']).toBe(10); }); + it('should resolve the counters of the filters the batch left out on their own', async () => { + /* A filter without a key, or one the query cannot be built for, is left out of the batch. */ + getFilterCountersSpy.and.returnValue(of({ counters: { FakeRunningProcesses: 9 }, batched: true })); + + await bindAppName('mock-app-name'); + + expect(component.counters['FakeRunningProcesses']).toBe(9); + /* The model holds no status for the filter targeting every status. */ + expect(getProcessCounterSpy.calls.allArgs().map(([, status]) => status)).toEqual([null, 'COMPLETED']); + }); + + it('should keep the counters of the other filters when one counter cannot be resolved', async () => { + getFilterCountersSpy.and.returnValue(of({ counters: { FakeRunningProcesses: 9 }, batched: true })); + getProcessCounterSpy.and.throwError('the query of the filter cannot be built'); + + await bindAppName('mock-app-name'); + + expect(component.counters['FakeRunningProcesses']).toBe(9); + expect(component.counters['completed-processes']).toBe(0); + }); + it('should refresh the counters of every filter when a filter is clicked', async () => { await bindAppName('mock-app-name'); 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 ee8c1eac40..1c29355937 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 @@ -16,7 +16,7 @@ */ import { Component, DestroyRef, EventEmitter, inject, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; -import { EMPTY, Observable, Subscription } from 'rxjs'; +import { defer, EMPTY, Observable, Subscription } from 'rxjs'; import { ProcessFilterCloudService } from '../../services/process-filter-cloud.service'; import { ProcessFilterCloudModel } from '../../models/process-filter-cloud.model'; import { AppConfigService, IconModule, TranslationService } from '@alfresco/adf-core'; @@ -153,7 +153,11 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges { * Initialize counter collection for filters */ initFilterCounters(): void { - this.filters.forEach((filter) => (this.counters[filter.key] = 0)); + this.filters.forEach((filter) => { + if (filter.key) { + this.counters[filter.key] = 0; + } + }); } /** @@ -276,15 +280,21 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges { * of one filter, for the backends without the batched count endpoint. */ updateFilterCounter(filter: ProcessFilterCloudModel): void { - if (!filter?.showCounter) { + const filterKey = filter?.showCounter ? filter.key : undefined; + if (!filterKey) { return; } - this.fetchProcessFilterCounter(filter) - .pipe(takeUntilDestroyed(this.destroyRef)) + /* `defer` turns the query building of the count request into a failure of the stream, so that a + filter the counter cannot be resolved for is left without one instead of breaking the others. */ + defer(() => this.fetchProcessFilterCounter(filter)) + .pipe( + catchError(() => EMPTY), + takeUntilDestroyed(this.destroyRef) + ) .subscribe((counter) => { - this.checkIfFilterValuesHasBeenUpdated(filter.key, counter); - this.counters = { ...this.counters, [filter.key]: counter }; + this.checkIfFilterValuesHasBeenUpdated(filterKey, counter); + this.counters = { ...this.counters, [filterKey]: counter }; }); } @@ -353,12 +363,27 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges { } /** - * Holds the counters resolved by the batched count request, which are keyed by filter key. + * Holds the counters resolved by the batched count request, which are keyed by filter key. The + * counter of a filter the request holds none for is resolved on its own: a filter without a key, + * or one the count query cannot be built for, is left out of the batch. * * @param counters counters keyed by filter key */ private applyFilterCounters(counters: { [filterKey: string]: number }): void { - Object.entries(counters).forEach(([filterKey, counter]) => { + this.filters.forEach((filter) => { + /* A filter without a key holds no request id, so no counter can be keyed by it. */ + const filterKey = filter?.showCounter ? filter.key : undefined; + if (!filterKey) { + return; + } + + const counter = counters[filterKey]; + if (counter === undefined) { + /* The batch holds no counter for a filter the count query cannot be built for. */ + this.updateFilterCounter(filter); + return; + } + this.checkIfFilterValuesHasBeenUpdated(filterKey, counter); this.counters = { ...this.counters, [filterKey]: counter }; }); @@ -370,10 +395,10 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges { * * @param filter filter that was clicked */ - private refreshFilterCounter(filter: ProcessFilterCloudModel): void { + private refreshFilterCounter(filter?: ProcessFilterCloudModel): void { if (this.batchedCounters) { this.filterCountersCloudService.refreshFilterCounters(this.appName); - } else { + } else if (filter) { this.updateFilterCounter(filter); } } diff --git a/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.ts b/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.ts index ab600bed74..3a02efaeea 100644 --- a/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.ts @@ -405,8 +405,8 @@ export class ProcessFilterCloudService { } /** - * @deprecated use FilterCountersCloudService.getFilterCountersNotifications instead, which shares a single - * subscription with the task filters and resolves the counters with a single request. + * @deprecated use FilterCountersCloudService.getEngineEvents instead, which shares a single + * subscription with the task filters and provides a debounced engine-event stream used to drive counter refreshes. * @param appName Name of the target app * @returns Process engine events */ diff --git a/lib/process-services-cloud/src/lib/services/filter-counters-cloud.service.ts b/lib/process-services-cloud/src/lib/services/filter-counters-cloud.service.ts index c7d71f8eb0..60354b07ba 100644 --- a/lib/process-services-cloud/src/lib/services/filter-counters-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/services/filter-counters-cloud.service.ts @@ -310,7 +310,7 @@ export class FilterCountersCloudService extends BaseCloudService { return undefined; } }) - .filter((query) => !!query); + .filter((query): query is FilterCountersQuery => !!query); } /** 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 8a1d598393..96ee175964 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 @@ -717,6 +717,27 @@ describe('TaskFiltersCloudComponent', () => { expect(updatedFilterSpy).toHaveBeenCalledWith('fake-involved-tasks'); }); + it('should resolve the counter of a filter the batch left out on its own', async () => { + /* A filter without a key, or one the query cannot be built for, is left out of the batch. */ + getFilterCountersSpy.and.returnValue(of({ counters: {}, batched: true })); + + await bindAppName(); + + expect(getTaskFilterCounterSpy).toHaveBeenCalledWith(fakeGlobalFilter[0]); + expect(component.counters['fake-involved-tasks']).toBe(11); + }); + + it('should keep the counters of the other filters when one counter cannot be resolved', async () => { + getTaskListFiltersSpy.and.returnValue(of([fakeGlobalFilter[0], { ...fakeGlobalFilter[1], showCounter: true }])); + getFilterCountersSpy.and.returnValue(of({ counters: { 'fake-involved-tasks': 4 }, batched: true })); + getTaskFilterCounterSpy.and.throwError('the query of the filter cannot be built'); + + await bindAppName(); + + expect(component.counters['fake-involved-tasks']).toBe(4); + expect(component.counters['fake-my-task1']).toBe(0); + }); + it('should resolve the counters one filter at a time when the batched endpoint is not available', async () => { getFilterCountersSpy.and.returnValue(of({ counters: {}, batched: false })); 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 e6486945f9..70457326f8 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 @@ -16,7 +16,7 @@ */ import { Component, EventEmitter, inject, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; -import { Observable, of, Subscription } from 'rxjs'; +import { defer, EMPTY, Observable, of, Subscription } from 'rxjs'; import { TaskFilterCloudService } from '../../services/task-filter-cloud.service'; import { FilterParamsModel, TaskFilterCloudModel } from '../../models/filter-cloud.model'; import { AppConfigService, IconModule, TranslationService } from '@alfresco/adf-core'; @@ -163,8 +163,13 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp return; } - this.fetchTaskFilterCounter(filter) - .pipe(takeUntilDestroyed(this.destroyRef)) + /* `defer` turns the query building of the count request into a failure of the stream, so that a + filter the counter cannot be resolved for is left without one instead of breaking the others. */ + defer(() => this.fetchTaskFilterCounter(filter)) + .pipe( + catchError(() => EMPTY), + takeUntilDestroyed(this.destroyRef) + ) .subscribe((counter) => { this.checkIfFilterValuesHasBeenUpdated(filter.key, counter); this.counters = { ...this.counters, [filter.key]: counter }; @@ -309,12 +314,26 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp } /** - * Holds the counters resolved by the batched count request, which are keyed by filter key. + * Holds the counters resolved by the batched count request, which are keyed by filter key. The + * counter of a filter the request holds none for is resolved on its own: a filter without a key, + * or one the count query cannot be built for, is left out of the batch. * * @param counters counters keyed by filter key */ private applyFilterCounters(counters: { [filterKey: string]: number }): void { - Object.entries(counters).forEach(([filterKey, counter]) => { + this.filters.forEach((filter) => { + /* A filter without a key holds no request id, so no counter can be keyed by it. */ + const filterKey = filter?.showCounter ? filter.key : undefined; + if (!filterKey) { + return; + } + + const counter = counters[filterKey]; + if (counter === undefined) { + this.updateFilterCounter(filter); + return; + } + this.checkIfFilterValuesHasBeenUpdated(filterKey, counter); this.counters = { ...this.counters, [filterKey]: counter }; });