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); 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 () => { it('should refresh the counters of every filter when a filter is clicked', async () => {
await bindAppName('mock-app-name'); await bindAppName('mock-app-name');
@@ -879,6 +900,27 @@ describe('ProcessFiltersCloudComponent', () => {
expect(component.counters['FakeRunningProcesses']).toBe(10); 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 () => { it('should refresh the counters of every filter when a filter is clicked', async () => {
await bindAppName('mock-app-name'); await bindAppName('mock-app-name');
@@ -16,7 +16,7 @@
*/ */
import { Component, DestroyRef, EventEmitter, inject, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; 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 { ProcessFilterCloudService } from '../../services/process-filter-cloud.service';
import { ProcessFilterCloudModel } from '../../models/process-filter-cloud.model'; import { ProcessFilterCloudModel } from '../../models/process-filter-cloud.model';
import { AppConfigService, IconModule, TranslationService } from '@alfresco/adf-core'; import { AppConfigService, IconModule, TranslationService } from '@alfresco/adf-core';
@@ -153,7 +153,11 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges {
* Initialize counter collection for filters * Initialize counter collection for filters
*/ */
initFilterCounters(): void { 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. * of one filter, for the backends without the batched count endpoint.
*/ */
updateFilterCounter(filter: ProcessFilterCloudModel): void { updateFilterCounter(filter: ProcessFilterCloudModel): void {
if (!filter?.showCounter) { const filterKey = filter?.showCounter ? filter.key : undefined;
if (!filterKey) {
return; return;
} }
this.fetchProcessFilterCounter(filter) /* `defer` turns the query building of the count request into a failure of the stream, so that a
.pipe(takeUntilDestroyed(this.destroyRef)) 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) => { .subscribe((counter) => {
this.checkIfFilterValuesHasBeenUpdated(filter.key, counter); this.checkIfFilterValuesHasBeenUpdated(filterKey, counter);
this.counters = { ...this.counters, [filter.key]: 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 * @param counters counters keyed by filter key
*/ */
private applyFilterCounters(counters: { [filterKey: string]: number }): void { 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.checkIfFilterValuesHasBeenUpdated(filterKey, counter);
this.counters = { ...this.counters, [filterKey]: counter }; this.counters = { ...this.counters, [filterKey]: counter };
}); });
@@ -370,10 +395,10 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges {
* *
* @param filter filter that was clicked * @param filter filter that was clicked
*/ */
private refreshFilterCounter(filter: ProcessFilterCloudModel): void { private refreshFilterCounter(filter?: ProcessFilterCloudModel): void {
if (this.batchedCounters) { if (this.batchedCounters) {
this.filterCountersCloudService.refreshFilterCounters(this.appName); this.filterCountersCloudService.refreshFilterCounters(this.appName);
} else { } else if (filter) {
this.updateFilterCounter(filter); this.updateFilterCounter(filter);
} }
} }
@@ -405,8 +405,8 @@ export class ProcessFilterCloudService {
} }
/** /**
* @deprecated use FilterCountersCloudService.getFilterCountersNotifications instead, which shares a single * @deprecated use FilterCountersCloudService.getEngineEvents instead, which shares a single
* subscription with the task filters and resolves the counters with a single request. * subscription with the task filters and provides a debounced engine-event stream used to drive counter refreshes.
* @param appName Name of the target app * @param appName Name of the target app
* @returns Process engine events * @returns Process engine events
*/ */
@@ -310,7 +310,7 @@ export class FilterCountersCloudService extends BaseCloudService {
return undefined; return undefined;
} }
}) })
.filter((query) => !!query); .filter((query): query is FilterCountersQuery => !!query);
} }
/** /**
@@ -717,6 +717,27 @@ describe('TaskFiltersCloudComponent', () => {
expect(updatedFilterSpy).toHaveBeenCalledWith('fake-involved-tasks'); 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 () => { 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 })); getFilterCountersSpy.and.returnValue(of({ counters: {}, batched: false }));
@@ -16,7 +16,7 @@
*/ */
import { Component, EventEmitter, inject, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; 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 { TaskFilterCloudService } from '../../services/task-filter-cloud.service';
import { FilterParamsModel, TaskFilterCloudModel } from '../../models/filter-cloud.model'; import { FilterParamsModel, TaskFilterCloudModel } from '../../models/filter-cloud.model';
import { AppConfigService, IconModule, TranslationService } from '@alfresco/adf-core'; import { AppConfigService, IconModule, TranslationService } from '@alfresco/adf-core';
@@ -163,8 +163,13 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp
return; return;
} }
this.fetchTaskFilterCounter(filter) /* `defer` turns the query building of the count request into a failure of the stream, so that a
.pipe(takeUntilDestroyed(this.destroyRef)) 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) => { .subscribe((counter) => {
this.checkIfFilterValuesHasBeenUpdated(filter.key, counter); this.checkIfFilterValuesHasBeenUpdated(filter.key, counter);
this.counters = { ...this.counters, [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 * @param counters counters keyed by filter key
*/ */
private applyFilterCounters(counters: { [filterKey: string]: number }): void { 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.checkIfFilterValuesHasBeenUpdated(filterKey, counter);
this.counters = { ...this.counters, [filterKey]: counter }; this.counters = { ...this.counters, [filterKey]: counter };
}); });