AAE-49653 Code improvement

This commit is contained in:
Ehsan Rezaei
2026-08-21 10:56:43 +02:00
parent 32c4a821a3
commit 5c4abba87c
6 changed files with 126 additions and 19 deletions
@@ -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');
@@ -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);
}
}
@@ -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
*/
@@ -310,7 +310,7 @@ export class FilterCountersCloudService extends BaseCloudService {
return undefined;
}
})
.filter((query) => !!query);
.filter((query): query is FilterCountersQuery => !!query);
}
/**
@@ -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 }));
@@ -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 };
});