mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
AAE-49653 Making filter key mandatory string
This commit is contained in:
@@ -15,9 +15,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* Entity types accepted by the `POST /query/v1/count` endpoint.
|
|
||||||
*/
|
|
||||||
export const FilterCounterEntityType = {
|
export const FilterCounterEntityType = {
|
||||||
TASK: 'TASK',
|
TASK: 'TASK',
|
||||||
PROCESS_INSTANCE: 'PROCESS_INSTANCE'
|
PROCESS_INSTANCE: 'PROCESS_INSTANCE'
|
||||||
@@ -31,7 +28,6 @@ export interface FilterCountersQuerySort {
|
|||||||
isProcessVariable: boolean;
|
isProcessVariable: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A single query of the batched count request, holding the criteria of one filter. */
|
|
||||||
export interface FilterCountersQuery {
|
export interface FilterCountersQuery {
|
||||||
/** Identifies the query, so its counter can be read back from the response. */
|
/** Identifies the query, so its counter can be read back from the response. */
|
||||||
requestId: string;
|
requestId: string;
|
||||||
@@ -41,28 +37,19 @@ export interface FilterCountersQuery {
|
|||||||
[criteria: string]: unknown;
|
[criteria: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Payload of the batched count request, one entry per counter to be resolved.
|
|
||||||
*/
|
|
||||||
export type FilterCountersRequest = {
|
export type FilterCountersRequest = {
|
||||||
[entityType in FilterCounterEntityType]?: FilterCountersQuery[];
|
[entityType in FilterCounterEntityType]?: FilterCountersQuery[];
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Shape of a task or process filter the counters are resolved for. Its key is the `requestId`. */
|
|
||||||
export interface FilterCounterCandidate {
|
export interface FilterCounterCandidate {
|
||||||
key?: string | null;
|
key: string;
|
||||||
showCounter?: boolean;
|
showCounter?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Counts returned by the batched count request, keyed by entity type and then by `requestId`.
|
|
||||||
* e.g. `{ TASK: { 'my-tasks': 5 }, PROCESS_INSTANCE: { 'running-processes': 5 } }`
|
|
||||||
*/
|
|
||||||
export type FilterCounters = {
|
export type FilterCounters = {
|
||||||
[entityType in FilterCounterEntityType]?: { [requestId: string]: number };
|
[entityType in FilterCounterEntityType]?: { [requestId: string]: number };
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Counters of the filters of one entity type, keyed by filter key. */
|
|
||||||
export interface FilterCountersResult {
|
export interface FilterCountersResult {
|
||||||
[filterKey: string]: number;
|
[filterKey: string]: number;
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-22
@@ -133,7 +133,6 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/* Read along with the filters, not once they arrive, so both components share one request. */
|
|
||||||
this.loadFilterCounters(appName, filters$);
|
this.loadFilterCounters(appName, filters$);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,11 +140,7 @@ 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.filters.forEach((filter) => (this.counters[filter.key] = 0));
|
||||||
if (filter.key) {
|
|
||||||
this.counters[filter.key] = 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -283,7 +278,6 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
private loadFilterCounters(appName: string, filters$: Observable<ProcessFilterCloudModel[]>): void {
|
private loadFilterCounters(appName: string, filters$: Observable<ProcessFilterCloudModel[]>): void {
|
||||||
this.countersSubscription?.unsubscribe();
|
this.countersSubscription?.unsubscribe();
|
||||||
/* Counters are keyed by filter key, so they are applied once the filters are known. */
|
|
||||||
this.countersSubscription = combineLatest([
|
this.countersSubscription = combineLatest([
|
||||||
filters$.pipe(catchError(() => EMPTY)),
|
filters$.pipe(catchError(() => EMPTY)),
|
||||||
this.filterCountersCloudService.getFilterCounters(appName, FilterCounterEntityType.PROCESS_INSTANCE)
|
this.filterCountersCloudService.getFilterCounters(appName, FilterCounterEntityType.PROCESS_INSTANCE)
|
||||||
@@ -293,21 +287,16 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private applyFilterCounters(counters: FilterCountersResult): void {
|
private applyFilterCounters(counters: FilterCountersResult): void {
|
||||||
this.filters.forEach((filter) => {
|
this.filters
|
||||||
/* A filter without a key holds no request id. */
|
.filter((filter) => filter?.showCounter)
|
||||||
const filterKey = filter?.showCounter ? filter.key : undefined;
|
.forEach((filter) => {
|
||||||
if (!filterKey) {
|
const counter = counters[filter.key];
|
||||||
return;
|
if (counter === undefined) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const counter = counters[filterKey];
|
this.checkIfFilterValuesHasBeenUpdated(filter.key, counter);
|
||||||
/* A filter the request left out keeps the counter it holds, rather than showing a wrong one. */
|
this.counters = { ...this.counters, [filter.key]: counter };
|
||||||
if (counter === undefined) {
|
});
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.checkIfFilterValuesHasBeenUpdated(filterKey, counter);
|
|
||||||
this.counters = { ...this.counters, [filterKey]: counter };
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -25,7 +25,7 @@ import { ProcessVariableFilterModel } from '../../../models/process-variable-fil
|
|||||||
export class ProcessFilterCloudModel {
|
export class ProcessFilterCloudModel {
|
||||||
id: string;
|
id: string;
|
||||||
name: string | null;
|
name: string | null;
|
||||||
key: string | null;
|
key: string;
|
||||||
icon: string | null;
|
icon: string | null;
|
||||||
index: number | null;
|
index: number | null;
|
||||||
appName: string | null;
|
appName: string | null;
|
||||||
@@ -79,7 +79,7 @@ export class ProcessFilterCloudModel {
|
|||||||
|
|
||||||
this.id = obj.id || Math.random().toString(36).substring(2, 9);
|
this.id = obj.id || Math.random().toString(36).substring(2, 9);
|
||||||
this.name = obj.name || null;
|
this.name = obj.name || null;
|
||||||
this.key = obj.key || null;
|
this.key = obj.key;
|
||||||
this.environmentId = obj.environmentId || null;
|
this.environmentId = obj.environmentId || null;
|
||||||
this.showCounter = obj.showCounter || false;
|
this.showCounter = obj.showCounter || false;
|
||||||
this.icon = obj.icon || null;
|
this.icon = obj.icon || null;
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ describe('FilterCountersCloudService', () => {
|
|||||||
const counters = (entityType: FilterCounterEntityType, appName = 'mock-app') => firstValueFrom(service.getFilterCounters(appName, entityType));
|
const counters = (entityType: FilterCounterEntityType, appName = 'mock-app') => firstValueFrom(service.getFilterCounters(appName, entityType));
|
||||||
const taskCounters = (appName = 'mock-app') => counters(FilterCounterEntityType.TASK, appName);
|
const taskCounters = (appName = 'mock-app') => counters(FilterCounterEntityType.TASK, appName);
|
||||||
const processCounters = (appName = 'mock-app') => counters(FilterCounterEntityType.PROCESS_INSTANCE, appName);
|
const processCounters = (appName = 'mock-app') => counters(FilterCounterEntityType.PROCESS_INSTANCE, appName);
|
||||||
/** As read when both filter components are on screen. */
|
|
||||||
const bothCounters = (appName = 'mock-app') =>
|
const bothCounters = (appName = 'mock-app') =>
|
||||||
firstValueFrom(
|
firstValueFrom(
|
||||||
combineLatest([
|
combineLatest([
|
||||||
@@ -114,7 +113,6 @@ describe('FilterCountersCloudService', () => {
|
|||||||
taskEvents$ = new Subject<EngineEventsResult>();
|
taskEvents$ = new Subject<EngineEventsResult>();
|
||||||
processEvents$ = new Subject<EngineEventsResult>();
|
processEvents$ = new Subject<EngineEventsResult>();
|
||||||
makeGQLQuerySpy = spyOn(notificationCloudService, 'makeGQLQuery');
|
makeGQLQuerySpy = spyOn(notificationCloudService, 'makeGQLQuery');
|
||||||
/* Every entity type holds its own subscription. */
|
|
||||||
makeGQLQuerySpy.and.callFake((_appName: string, query: string) =>
|
makeGQLQuerySpy.and.callFake((_appName: string, query: string) =>
|
||||||
(query.includes('TASK_CREATED') ? taskEvents$ : processEvents$).asObservable()
|
(query.includes('TASK_CREATED') ? taskEvents$ : processEvents$).asObservable()
|
||||||
);
|
);
|
||||||
@@ -146,7 +144,6 @@ describe('FilterCountersCloudService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should share the filters with the batched count request', async () => {
|
it('should share the filters with the batched count request', async () => {
|
||||||
/* The filter component holds its subscription while the counters are resolved. */
|
|
||||||
const subscription = service.getTaskFilters('mock-app').subscribe();
|
const subscription = service.getTaskFilters('mock-app').subscribe();
|
||||||
await taskCounters();
|
await taskCounters();
|
||||||
subscription.unsubscribe();
|
subscription.unsubscribe();
|
||||||
@@ -232,13 +229,6 @@ describe('FilterCountersCloudService', () => {
|
|||||||
expect(countRequestIds(FilterCounterEntityType.TASK)).toEqual(['queued-tasks']);
|
expect(countRequestIds(FilterCounterEntityType.TASK)).toEqual(['queued-tasks']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should leave out a filter without a key, since it holds no request id', async () => {
|
|
||||||
getProcessFiltersSpy.and.returnValue(of([processFilter({ key: null, status: 'RUNNING', showCounter: true })]));
|
|
||||||
|
|
||||||
expect(await processCounters()).toEqual({});
|
|
||||||
expect(postSpy).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should resolve the counters of an entity type when the filters of the other one fail to load', async () => {
|
it('should resolve the counters of an entity type when the filters of the other one fail to load', async () => {
|
||||||
getProcessFiltersSpy.and.returnValue(throwError(() => new Error('filters failed')));
|
getProcessFiltersSpy.and.returnValue(throwError(() => new Error('filters failed')));
|
||||||
|
|
||||||
@@ -330,7 +320,6 @@ describe('FilterCountersCloudService', () => {
|
|||||||
service.getFilterCounters('mock-app', FilterCounterEntityType.TASK).subscribe();
|
service.getFilterCounters('mock-app', FilterCounterEntityType.TASK).subscribe();
|
||||||
tick(0);
|
tick(0);
|
||||||
|
|
||||||
/* Opened again, so the first subscription was closed rather than left behind. */
|
|
||||||
expect(makeGQLQuerySpy).toHaveBeenCalledTimes(2);
|
expect(makeGQLQuerySpy).toHaveBeenCalledTimes(2);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -348,11 +337,9 @@ describe('FilterCountersCloudService', () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should release the filters subscription once nothing reads them', fakeAsync(() => {
|
it('should release the filters subscription once nothing reads them', fakeAsync(() => {
|
||||||
/* `TaskFilterCloudService.filters$` never completes, so a subscription left behind would be held. */
|
|
||||||
const filters$ = new BehaviorSubject(taskFiltersMock);
|
const filters$ = new BehaviorSubject(taskFiltersMock);
|
||||||
getTaskListFiltersSpy.and.returnValue(filters$.asObservable());
|
getTaskListFiltersSpy.and.returnValue(filters$.asObservable());
|
||||||
|
|
||||||
/* As the filter component does: the filters are held while the counters are read. */
|
|
||||||
const subscriptions = [
|
const subscriptions = [
|
||||||
service.getTaskFilters('mock-app').subscribe(),
|
service.getTaskFilters('mock-app').subscribe(),
|
||||||
service.getFilterCounters('mock-app', FilterCounterEntityType.TASK).subscribe()
|
service.getFilterCounters('mock-app', FilterCounterEntityType.TASK).subscribe()
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ interface EngineEventsData {
|
|||||||
engineEvents?: TaskCloudEngineEvent[];
|
engineEvents?: TaskCloudEngineEvent[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** One subscription per entity type, so an app showing one of them is not notified of the other. */
|
|
||||||
const ENGINE_EVENTS_SUBSCRIPTION_QUERIES: Record<FilterCounterEntityType, string> = {
|
const ENGINE_EVENTS_SUBSCRIPTION_QUERIES: Record<FilterCounterEntityType, string> = {
|
||||||
[FilterCounterEntityType.TASK]: `
|
[FilterCounterEntityType.TASK]: `
|
||||||
subscription {
|
subscription {
|
||||||
@@ -264,7 +263,6 @@ export class FilterCountersCloudService extends BaseCloudService {
|
|||||||
this.recountTrigger(appName).next();
|
this.recountTrigger(appName).next();
|
||||||
}
|
}
|
||||||
|
|
||||||
// The filters of an entity type that fails to load are left out, so the other one is still counted.
|
|
||||||
private getFiltersForCounters(appName: string): Observable<FilterCountersFilters> {
|
private getFiltersForCounters(appName: string): Observable<FilterCountersFilters> {
|
||||||
const activeEntityTypes = this.activeEntityTypes(appName);
|
const activeEntityTypes = this.activeEntityTypes(appName);
|
||||||
|
|
||||||
@@ -305,7 +303,6 @@ export class FilterCountersCloudService extends BaseCloudService {
|
|||||||
return this.getFiltersForCounters(appName).pipe(
|
return this.getFiltersForCounters(appName).pipe(
|
||||||
take(1),
|
take(1),
|
||||||
switchMap((filters) => this.fetchFilterCounters(appName, this.buildRequest(filters))),
|
switchMap((filters) => this.fetchFilterCounters(appName, this.buildRequest(filters))),
|
||||||
/* A failed count leaves the counters as they are, rather than breaking the stream. */
|
|
||||||
catchError(() => of({}))
|
catchError(() => of({}))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -314,7 +311,6 @@ export class FilterCountersCloudService extends BaseCloudService {
|
|||||||
return merge(
|
return merge(
|
||||||
/* Reads landing in the same task are merged, so both filter components share one request. */
|
/* Reads landing in the same task are merged, so both filter components share one request. */
|
||||||
merge(of(undefined), this.recountTrigger(appName)).pipe(debounceTime(0, asapScheduler)),
|
merge(of(undefined), this.recountTrigger(appName)).pipe(debounceTime(0, asapScheduler)),
|
||||||
/* One debounce over every entity type, so a batch of events also results in one request. */
|
|
||||||
this.eventRecountTrigger(appName).pipe(debounceTime(this.notificationDebounceTime))
|
this.eventRecountTrigger(appName).pipe(debounceTime(this.notificationDebounceTime))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -364,12 +360,12 @@ export class FilterCountersCloudService extends BaseCloudService {
|
|||||||
buildQuery: (filter: T) => Omit<FilterCountersQuery, 'requestId'>
|
buildQuery: (filter: T) => Omit<FilterCountersQuery, 'requestId'>
|
||||||
): FilterCountersQuery[] {
|
): FilterCountersQuery[] {
|
||||||
return (filters ?? [])
|
return (filters ?? [])
|
||||||
.filter((filter) => filter?.showCounter && this.isCounterBatched(filter))
|
.filter((filter) => filter?.showCounter)
|
||||||
.map((filter) => {
|
.map((filter) => {
|
||||||
try {
|
try {
|
||||||
return { ...buildQuery(filter), requestId: filter.key as string };
|
return { ...buildQuery(filter), requestId: filter.key };
|
||||||
} catch {
|
} catch {
|
||||||
/* A malformed filter is left without a counter, so the others still hold one. */
|
/* Left without a counter, so the other filters still hold theirs. */
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -385,9 +381,4 @@ export class FilterCountersCloudService extends BaseCloudService {
|
|||||||
|
|
||||||
return this.post<FilterCountersRequest, FilterCounters>(queryUrl, request).pipe(map((counters) => counters || {}));
|
return this.post<FilterCountersRequest, FilterCounters>(queryUrl, request).pipe(map((counters) => counters || {}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// A filter without a key holds no `requestId` its counter could be keyed by.
|
|
||||||
private isCounterBatched(filter: FilterCounterCandidate): boolean {
|
|
||||||
return !!filter?.key;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -108,7 +108,7 @@ export class ServiceTaskFiltersCloudComponent extends BaseTaskFiltersCloudCompon
|
|||||||
this.filters.find(
|
this.filters.find(
|
||||||
(filter, index) =>
|
(filter, index) =>
|
||||||
paramFilter.index === index ||
|
paramFilter.index === index ||
|
||||||
paramFilter.key === filter.key ||
|
(!!paramFilter.key && paramFilter.key === filter.key) ||
|
||||||
paramFilter.id === filter.id ||
|
paramFilter.id === filter.id ||
|
||||||
(paramFilter.name && paramFilter.name.toLocaleLowerCase() === this.translationService.instant(filter.name).toLocaleLowerCase())
|
(paramFilter.name && paramFilter.name.toLocaleLowerCase() === this.translationService.instant(filter.name).toLocaleLowerCase())
|
||||||
); // fallback to preserve the previous behavior
|
); // fallback to preserve the previous behavior
|
||||||
|
|||||||
+11
-18
@@ -118,7 +118,6 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/* Read along with the filters, not once they arrive, so both components share one request. */
|
|
||||||
this.loadFilterCounters(appName, filters$);
|
this.loadFilterCounters(appName, filters$);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,7 +169,7 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp
|
|||||||
this.filters.find(
|
this.filters.find(
|
||||||
(filter, index) =>
|
(filter, index) =>
|
||||||
paramFilter.index === index ||
|
paramFilter.index === index ||
|
||||||
paramFilter.key === filter.key ||
|
(!!paramFilter.key && paramFilter.key === filter.key) ||
|
||||||
paramFilter.id === filter.id ||
|
paramFilter.id === filter.id ||
|
||||||
(paramFilter.name && paramFilter.name.toLocaleLowerCase() === this.translationService.instant(filter.name).toLocaleLowerCase())
|
(paramFilter.name && paramFilter.name.toLocaleLowerCase() === this.translationService.instant(filter.name).toLocaleLowerCase())
|
||||||
); // fallback to preserve the previous behavior
|
); // fallback to preserve the previous behavior
|
||||||
@@ -241,7 +240,6 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp
|
|||||||
|
|
||||||
private loadFilterCounters(appName: string, filters$: Observable<TaskFilterCloudModel[]>): void {
|
private loadFilterCounters(appName: string, filters$: Observable<TaskFilterCloudModel[]>): void {
|
||||||
this.countersSubscription?.unsubscribe();
|
this.countersSubscription?.unsubscribe();
|
||||||
/* Counters are keyed by filter key, so they are applied once the filters are known. */
|
|
||||||
this.countersSubscription = combineLatest([
|
this.countersSubscription = combineLatest([
|
||||||
filters$.pipe(catchError(() => of([]))),
|
filters$.pipe(catchError(() => of([]))),
|
||||||
this.filterCountersCloudService.getFilterCounters(appName, FilterCounterEntityType.TASK)
|
this.filterCountersCloudService.getFilterCounters(appName, FilterCounterEntityType.TASK)
|
||||||
@@ -251,22 +249,17 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp
|
|||||||
}
|
}
|
||||||
|
|
||||||
private applyFilterCounters(counters: FilterCountersResult): void {
|
private applyFilterCounters(counters: FilterCountersResult): void {
|
||||||
this.filters.forEach((filter) => {
|
this.filters
|
||||||
/* A filter without a key holds no request id. */
|
.filter((filter) => filter?.showCounter)
|
||||||
const filterKey = filter?.showCounter ? filter.key : undefined;
|
.forEach((filter) => {
|
||||||
if (!filterKey) {
|
const counter = counters[filter.key];
|
||||||
return;
|
if (counter === undefined) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const counter = counters[filterKey];
|
this.checkIfFilterValuesHasBeenUpdated(filter.key, counter);
|
||||||
/* A filter the request left out keeps the counter it holds, rather than showing a wrong one. */
|
this.counters = { ...this.counters, [filter.key]: counter };
|
||||||
if (counter === undefined) {
|
});
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.checkIfFilterValuesHasBeenUpdated(filterKey, counter);
|
|
||||||
this.counters = { ...this.counters, [filterKey]: counter };
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export class TaskFilterCloudModel {
|
|||||||
if (obj) {
|
if (obj) {
|
||||||
this.id = obj.id || Math.random().toString(36).substr(2, 9);
|
this.id = obj.id || Math.random().toString(36).substr(2, 9);
|
||||||
this.name = obj.name || null;
|
this.name = obj.name || null;
|
||||||
this.key = obj.key || null;
|
this.key = obj.key;
|
||||||
this.environmentId = obj.environmentId || null;
|
this.environmentId = obj.environmentId || null;
|
||||||
this.icon = obj.icon || null;
|
this.icon = obj.icon || null;
|
||||||
this.index = obj.index || null;
|
this.index = obj.index || null;
|
||||||
|
|||||||
Reference in New Issue
Block a user