mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
AAE-49653 Improving type safety
This commit is contained in:
@@ -41,7 +41,8 @@ export interface FilterCountersQuery {
|
||||
status?: string[];
|
||||
assignee?: string[];
|
||||
sort?: FilterCountersQuerySort;
|
||||
[criteria: string]: any;
|
||||
/** Every other criteria of the filter the query was built from. */
|
||||
[criteria: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,13 +62,6 @@ export interface FilterCounterCandidate {
|
||||
showCounter?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters of every entity type the counters are resolved for.
|
||||
*/
|
||||
export type FilterCountersFilters = {
|
||||
[entityType in FilterCounterEntityType]: any[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Counts returned by the batched count request, keyed by entity type and then by the `requestId`
|
||||
* of the query the count was resolved for.
|
||||
|
||||
+7
-7
@@ -33,7 +33,7 @@ import { MatIconHarness } from '@angular/material/icon/testing';
|
||||
import { ActivatedRoute, provideRouter, Router } from '@angular/router';
|
||||
import { RouterTestingHarness } from '@angular/router/testing';
|
||||
import { FilterCountersCloudService } from '../../../../services/filter-counters-cloud.service';
|
||||
import { FilterCounterEntityType } from '../../../../models/filter-counters-cloud.model';
|
||||
import { FilterCounterEntityType, FilterCountersResult } from '../../../../models/filter-counters-cloud.model';
|
||||
import { ProcessFilterCloudModel } from '../../models/process-filter-cloud.model';
|
||||
|
||||
@Component({ selector: 'adf-cloud-dummy', template: '' })
|
||||
@@ -560,12 +560,12 @@ describe('ProcessFiltersCloudComponent', () => {
|
||||
});
|
||||
|
||||
it('should keep the counters in sync with the counters stream', fakeAsync(() => {
|
||||
const counters$ = new Subject<any>();
|
||||
const counters$ = new Subject<FilterCountersResult>();
|
||||
getFilterCountersSpy.and.returnValue(counters$.asObservable());
|
||||
component.appName = 'mock-app-name';
|
||||
|
||||
fixture.detectChanges();
|
||||
component.filters = mockProcessFilters.map((filter) => ({ ...filter, showCounter: true })) as any;
|
||||
component.filters = mockProcessFilters.map((filter) => new ProcessFilterCloudModel({ ...filter, showCounter: true }));
|
||||
|
||||
counters$.next({ counters: { FakeRunningProcesses: 7 }, batched: true });
|
||||
|
||||
@@ -920,12 +920,12 @@ describe('ProcessFiltersCloudComponent', () => {
|
||||
});
|
||||
|
||||
it('should keep the counters in sync with the counters stream', fakeAsync(() => {
|
||||
const counters$ = new Subject<any>();
|
||||
const counters$ = new Subject<FilterCountersResult>();
|
||||
getFilterCountersSpy.and.returnValue(counters$.asObservable());
|
||||
component.appName = 'mock-app-name';
|
||||
|
||||
fixture.detectChanges();
|
||||
component.filters = mockProcessFilters.map((filter) => ({ ...filter, showCounter: true })) as any;
|
||||
component.filters = mockProcessFilters.map((filter) => new ProcessFilterCloudModel({ ...filter, showCounter: true }));
|
||||
|
||||
counters$.next({ counters: { FakeRunningProcesses: 7 }, batched: true });
|
||||
|
||||
@@ -934,12 +934,12 @@ describe('ProcessFiltersCloudComponent', () => {
|
||||
}));
|
||||
|
||||
it('should resolve the counters one filter at a time when the batched endpoint is not available', fakeAsync(() => {
|
||||
const counters$ = new Subject<any>();
|
||||
const counters$ = new Subject<FilterCountersResult>();
|
||||
getFilterCountersSpy.and.returnValue(counters$.asObservable());
|
||||
component.appName = 'mock-app-name';
|
||||
|
||||
fixture.detectChanges();
|
||||
component.filters = mockProcessFilters.map((filter) => ({ ...filter, showCounter: true })) as any;
|
||||
component.filters = mockProcessFilters.map((filter) => new ProcessFilterCloudModel({ ...filter, showCounter: true }));
|
||||
getProcessCounterSpy.calls.reset();
|
||||
|
||||
counters$.next({ counters: {}, batched: false });
|
||||
|
||||
+1
-1
@@ -143,7 +143,7 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges {
|
||||
this.success.emit(res);
|
||||
this.loadFilterCounters(appName);
|
||||
},
|
||||
error: (err: any) => {
|
||||
error: (err: unknown) => {
|
||||
this.error.emit(err);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
import { AppConfigService, NoopAuthModule } from '@alfresco/adf-core';
|
||||
import { firstValueFrom, of, Subject, throwError } from 'rxjs';
|
||||
import { firstValueFrom, Observable, of, Subject, throwError } from 'rxjs';
|
||||
import { ApolloTestingModule } from 'apollo-angular/testing';
|
||||
import { FilterCountersCloudService } from './filter-counters-cloud.service';
|
||||
import { NotificationCloudService } from './notification-cloud.service';
|
||||
@@ -27,16 +27,32 @@ import { TaskFilterCloudService } from '../task/task-filters/services/task-filte
|
||||
import { ProcessFilterCloudService } from '../process/process-filters/services/process-filter-cloud.service';
|
||||
import { TaskFilterCloudModel } from '../task/task-filters/models/filter-cloud.model';
|
||||
import { ProcessFilterCloudModel } from '../process/process-filters/models/process-filter-cloud.model';
|
||||
import { FilterCounterEntityType, FilterCounters } from '../models/filter-counters-cloud.model';
|
||||
import {
|
||||
FilterCounterEntityType,
|
||||
FilterCounters,
|
||||
FilterCountersQuery,
|
||||
FilterCountersRequest,
|
||||
FilterCountersResult
|
||||
} from '../models/filter-counters-cloud.model';
|
||||
import { TaskCloudEngineEvent } from '../models/engine-event-cloud.model';
|
||||
import { FetchResult } from '@apollo/client/core';
|
||||
|
||||
type EngineEventsResult = FetchResult<{ engineEvents?: TaskCloudEngineEvent[] }>;
|
||||
|
||||
interface CountEndpoint {
|
||||
post: (url: string, request: FilterCountersRequest) => Observable<FilterCounters>;
|
||||
}
|
||||
|
||||
describe('FilterCountersCloudService', () => {
|
||||
let service: FilterCountersCloudService;
|
||||
let notificationCloudService: NotificationCloudService;
|
||||
let appConfigService: AppConfigService;
|
||||
let engineEvents$: Subject<any>;
|
||||
let engineEvents$: Subject<EngineEventsResult>;
|
||||
let makeGQLQuerySpy: jasmine.Spy;
|
||||
let postSpy: jasmine.Spy;
|
||||
/** Payload the batched count endpoint was called with. */
|
||||
const countRequest = (): FilterCountersRequest => postSpy.calls.mostRecent().args[1];
|
||||
const countUrl = (): string => postSpy.calls.mostRecent().args[0];
|
||||
let getTaskListFiltersSpy: jasmine.Spy;
|
||||
let getProcessFiltersSpy: jasmine.Spy;
|
||||
|
||||
@@ -45,8 +61,10 @@ describe('FilterCountersCloudService', () => {
|
||||
PROCESS_INSTANCE: { 'running-processes': 5 }
|
||||
};
|
||||
|
||||
const taskFilter = (filter: any) => new TaskFilterCloudModel({ appName: 'mock-app', sort: 'createdDate', order: 'DESC', ...filter });
|
||||
const processFilter = (filter: any) => new ProcessFilterCloudModel({ appName: 'mock-app', sort: 'startDate', order: 'DESC', ...filter });
|
||||
const taskFilter = (filter: Partial<TaskFilterCloudModel>) =>
|
||||
new TaskFilterCloudModel({ appName: 'mock-app', sort: 'createdDate', order: 'DESC', ...filter });
|
||||
const processFilter = (filter: Partial<ProcessFilterCloudModel>) =>
|
||||
new ProcessFilterCloudModel({ appName: 'mock-app', sort: 'startDate', order: 'DESC', ...filter });
|
||||
|
||||
const taskFiltersMock = [
|
||||
taskFilter({ key: 'my-tasks', status: 'ASSIGNED', assignee: 'mock-user', showCounter: true }),
|
||||
@@ -58,7 +76,8 @@ describe('FilterCountersCloudService', () => {
|
||||
processFilter({ key: 'all-processes', status: '', showCounter: false })
|
||||
];
|
||||
|
||||
const emitEvent = (eventType = 'TASK_CREATED') => engineEvents$.next({ data: { engineEvents: [{ eventType, entity: {} }] } });
|
||||
const emitEvent = (eventType = 'TASK_CREATED') =>
|
||||
engineEvents$.next({ data: { engineEvents: [{ eventType, entity: {} } as TaskCloudEngineEvent] } });
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@@ -74,9 +93,10 @@ describe('FilterCountersCloudService', () => {
|
||||
appConfigService = TestBed.inject(AppConfigService);
|
||||
appConfigService.config.bpmHost = 'https://fake-bpm-host.com';
|
||||
|
||||
engineEvents$ = new Subject<any>();
|
||||
makeGQLQuerySpy = spyOn(notificationCloudService, 'makeGQLQuery').and.returnValue(engineEvents$.asObservable() as any);
|
||||
postSpy = spyOn<any>(service, 'post').and.returnValue(of(countersMock));
|
||||
engineEvents$ = new Subject<EngineEventsResult>();
|
||||
makeGQLQuerySpy = spyOn(notificationCloudService, 'makeGQLQuery').and.returnValue(engineEvents$.asObservable());
|
||||
/* `post` is protected on BaseCloudService, so it is reached through the shape it is spied on. */
|
||||
postSpy = spyOn(service as unknown as CountEndpoint, 'post').and.returnValue(of(countersMock));
|
||||
getTaskListFiltersSpy = spyOn(TestBed.inject(TaskFilterCloudService), 'getTaskListFilters').and.returnValue(of(taskFiltersMock));
|
||||
getProcessFiltersSpy = spyOn(TestBed.inject(ProcessFilterCloudService), 'getProcessFilters').and.returnValue(of(processFiltersMock));
|
||||
});
|
||||
@@ -132,7 +152,7 @@ describe('FilterCountersCloudService', () => {
|
||||
});
|
||||
|
||||
it('should resolve the counters of both entity types with a single request', () => {
|
||||
const results: any[] = [];
|
||||
const results: FilterCountersResult[] = [];
|
||||
/* Both filter components hold their subscription, so one request resolves the counters of both. */
|
||||
service.getFilterCounters('mock-app', FilterCounterEntityType.TASK).subscribe((result) => results.push(result));
|
||||
service.getFilterCounters('mock-app', FilterCounterEntityType.PROCESS_INSTANCE).subscribe((result) => results.push(result));
|
||||
@@ -147,23 +167,23 @@ describe('FilterCountersCloudService', () => {
|
||||
it('should send the queries of both entity types to the batched count endpoint', async () => {
|
||||
await taskCounters();
|
||||
|
||||
const [url, body] = postSpy.calls.mostRecent().args;
|
||||
expect(url).toBe('https://fake-bpm-host.com/mock-app/query/v1/count');
|
||||
expect(countUrl()).toBe('https://fake-bpm-host.com/mock-app/query/v1/count');
|
||||
const body = countRequest();
|
||||
expect(Object.keys(body)).toEqual([FilterCounterEntityType.TASK, FilterCounterEntityType.PROCESS_INSTANCE]);
|
||||
});
|
||||
|
||||
it('should identify the query of every filter by the key of the filter', async () => {
|
||||
await taskCounters();
|
||||
|
||||
const [, body] = postSpy.calls.mostRecent().args;
|
||||
expect(body.TASK.map((query: any) => query.requestId)).toEqual(['my-tasks', 'queued-tasks']);
|
||||
expect(body.PROCESS_INSTANCE.map((query: any) => query.requestId)).toEqual(['running-processes']);
|
||||
const body = countRequest();
|
||||
expect(body.TASK.map((query: FilterCountersQuery) => query.requestId)).toEqual(['my-tasks', 'queued-tasks']);
|
||||
expect(body.PROCESS_INSTANCE.map((query: FilterCountersQuery) => query.requestId)).toEqual(['running-processes']);
|
||||
});
|
||||
|
||||
it('should send the criteria of every filter along with its request id', async () => {
|
||||
await taskCounters();
|
||||
|
||||
const [, body] = postSpy.calls.mostRecent().args;
|
||||
const body = countRequest();
|
||||
expect(body.TASK[0]).toEqual({
|
||||
requestId: 'my-tasks',
|
||||
status: ['ASSIGNED'],
|
||||
@@ -175,8 +195,8 @@ describe('FilterCountersCloudService', () => {
|
||||
it('should not send the filters without a counter enabled', async () => {
|
||||
await taskCounters();
|
||||
|
||||
const [, body] = postSpy.calls.mostRecent().args;
|
||||
expect(body.TASK.map((query: any) => query.requestId)).not.toContain('completed-tasks');
|
||||
const body = countRequest();
|
||||
expect(body.TASK.map((query: FilterCountersQuery) => query.requestId)).not.toContain('completed-tasks');
|
||||
});
|
||||
|
||||
it('should send the query of a filter targeting every status', async () => {
|
||||
@@ -184,8 +204,8 @@ describe('FilterCountersCloudService', () => {
|
||||
|
||||
await processCounters();
|
||||
|
||||
const [, body] = postSpy.calls.mostRecent().args;
|
||||
expect(body.PROCESS_INSTANCE.map((query: any) => query.requestId)).toEqual(['all-processes']);
|
||||
const body = countRequest();
|
||||
expect(body.PROCESS_INSTANCE.map((query: FilterCountersQuery) => query.requestId)).toEqual(['all-processes']);
|
||||
});
|
||||
|
||||
it('should omit an entity type without filters with a counter enabled', async () => {
|
||||
@@ -193,7 +213,7 @@ describe('FilterCountersCloudService', () => {
|
||||
|
||||
await taskCounters();
|
||||
|
||||
const [, body] = postSpy.calls.mostRecent().args;
|
||||
const body = countRequest();
|
||||
expect(body.PROCESS_INSTANCE).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -204,8 +224,8 @@ describe('FilterCountersCloudService', () => {
|
||||
|
||||
await taskCounters();
|
||||
|
||||
const [, body] = postSpy.calls.mostRecent().args;
|
||||
expect(body.TASK.map((query: any) => query.requestId)).toEqual(['queued-tasks']);
|
||||
const body = countRequest();
|
||||
expect(body.TASK.map((query: FilterCountersQuery) => query.requestId)).toEqual(['queued-tasks']);
|
||||
});
|
||||
|
||||
it('should leave out a filter without a key, since it holds no request id', async () => {
|
||||
@@ -213,7 +233,7 @@ describe('FilterCountersCloudService', () => {
|
||||
|
||||
await processCounters();
|
||||
|
||||
const [, body] = postSpy.calls.mostRecent().args;
|
||||
const body = countRequest();
|
||||
expect(body.PROCESS_INSTANCE).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -222,8 +242,8 @@ describe('FilterCountersCloudService', () => {
|
||||
|
||||
await taskCounters();
|
||||
|
||||
const [, body] = postSpy.calls.mostRecent().args;
|
||||
expect(body.TASK.map((query: any) => query.requestId)).toEqual(['my-tasks', 'queued-tasks']);
|
||||
const body = countRequest();
|
||||
expect(body.TASK.map((query: FilterCountersQuery) => query.requestId)).toEqual(['my-tasks', 'queued-tasks']);
|
||||
expect(body.PROCESS_INSTANCE).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -274,7 +294,7 @@ describe('FilterCountersCloudService', () => {
|
||||
|
||||
describe('refreshFilterCounters', () => {
|
||||
it('should resolve the counters again with a single request', fakeAsync(() => {
|
||||
const results: any[] = [];
|
||||
const results: FilterCountersResult[] = [];
|
||||
service.getFilterCounters('mock-app', FilterCounterEntityType.TASK).subscribe((result) => results.push(result));
|
||||
service.getFilterCounters('mock-app', FilterCounterEntityType.PROCESS_INSTANCE).subscribe();
|
||||
|
||||
@@ -363,7 +383,7 @@ describe('FilterCountersCloudService', () => {
|
||||
}));
|
||||
|
||||
it('should emit the counters resolved for the batch of events', fakeAsync(() => {
|
||||
const results: any[] = [];
|
||||
const results: FilterCountersResult[] = [];
|
||||
service.getFilterCounters('mock-app', FilterCounterEntityType.TASK).subscribe((result) => results.push(result));
|
||||
|
||||
postSpy.and.returnValue(of({ TASK: { 'my-tasks': 9 } }));
|
||||
@@ -375,7 +395,7 @@ describe('FilterCountersCloudService', () => {
|
||||
}));
|
||||
|
||||
it('should not subscribe to the engine events when notifications are disabled', fakeAsync(() => {
|
||||
spyOn(appConfigService, 'get').and.callFake((key: string, defaultValue: any) => (key === 'notifications' ? false : defaultValue));
|
||||
appConfigService.config.notifications = false;
|
||||
|
||||
service.getFilterCounters('mock-app', FilterCounterEntityType.TASK).subscribe();
|
||||
tick(3000);
|
||||
|
||||
@@ -33,11 +33,11 @@ import {
|
||||
FilterCounterCandidate,
|
||||
FilterCounterEntityType,
|
||||
FilterCounters,
|
||||
FilterCountersFilters,
|
||||
FilterCountersQuery,
|
||||
FilterCountersRequest,
|
||||
FilterCountersResult
|
||||
} from '../models/filter-counters-cloud.model';
|
||||
import { FetchResult } from '@apollo/client/core';
|
||||
|
||||
/**
|
||||
* Single subscription covering both the task and the process engine events, so that a batch of
|
||||
@@ -45,6 +45,15 @@ import {
|
||||
*/
|
||||
const BATCHED_COUNTERS_UNAVAILABLE_STATUSES = [404, 501];
|
||||
|
||||
/** Filters of both entity types, to resolve the counters of both filter components with one request. */
|
||||
interface FilterCountersFilters {
|
||||
[FilterCounterEntityType.TASK]: TaskFilterCloudModel[];
|
||||
[FilterCounterEntityType.PROCESS_INSTANCE]: ProcessFilterCloudModel[];
|
||||
}
|
||||
|
||||
/** Payload of the engine event subscription. */
|
||||
type EngineEventsResult = FetchResult<{ engineEvents?: TaskCloudEngineEvent[] }>;
|
||||
|
||||
const FILTER_COUNTERS_EVENT_SUBSCRIPTION_QUERY = `
|
||||
subscription {
|
||||
engineEvents(eventType: [
|
||||
@@ -157,7 +166,7 @@ export class FilterCountersCloudService extends BaseCloudService {
|
||||
let events$ = this.eventsPerApp.get(appName);
|
||||
if (!events$) {
|
||||
events$ = defer(() => this.notificationCloudService.makeGQLQuery(appName, FILTER_COUNTERS_EVENT_SUBSCRIPTION_QUERY)).pipe(
|
||||
map((events: any) => (events?.data?.engineEvents ?? []) as TaskCloudEngineEvent[]),
|
||||
map((result: EngineEventsResult) => result.data?.engineEvents ?? []),
|
||||
debounceTime(this.notificationDebounceTime),
|
||||
shareReplay({ bufferSize: 1, refCount: true })
|
||||
);
|
||||
|
||||
+2
-1
@@ -37,6 +37,7 @@ import { ActivatedRoute, provideRouter, Router } from '@angular/router';
|
||||
import { RouterTestingHarness } from '@angular/router/testing';
|
||||
import { FilterCountersCloudService } from '../../../../services/filter-counters-cloud.service';
|
||||
import { FilterCounterEntityType } from '../../../../models/filter-counters-cloud.model';
|
||||
import { TaskCloudEngineEvent } from '../../../../models/engine-event-cloud.model';
|
||||
|
||||
@Component({ selector: 'adf-cloud-dummy', template: '' })
|
||||
class DummyComponent {}
|
||||
@@ -334,7 +335,7 @@ describe('TaskFiltersCloudComponent', () => {
|
||||
});
|
||||
|
||||
it('should emit the events of the debounced batch', fakeAsync(() => {
|
||||
const events$ = new Subject<any>();
|
||||
const events$ = new Subject<TaskCloudEngineEvent[]>();
|
||||
getEngineEventsSpy.and.returnValue(events$.asObservable());
|
||||
const filterCounterUpdatedSpy = spyOn(component.filterCounterUpdated, 'emit');
|
||||
component.appName = 'my-app-1';
|
||||
|
||||
Reference in New Issue
Block a user