mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
AAE-49653 fixed the DI coupling and type safety
This commit is contained in:
@@ -53,6 +53,9 @@ describe('FilterCountersCloudService', () => {
|
|||||||
/** Payload the batched count endpoint was called with. */
|
/** Payload the batched count endpoint was called with. */
|
||||||
const countRequest = (): FilterCountersRequest => postSpy.calls.mostRecent().args[1];
|
const countRequest = (): FilterCountersRequest => postSpy.calls.mostRecent().args[1];
|
||||||
const countUrl = (): string => postSpy.calls.mostRecent().args[0];
|
const countUrl = (): string => postSpy.calls.mostRecent().args[0];
|
||||||
|
/** Queries the batched count endpoint was called with for an entity type. */
|
||||||
|
const countQueries = (entityType: FilterCounterEntityType): FilterCountersQuery[] => countRequest()[entityType] ?? [];
|
||||||
|
const countRequestIds = (entityType: FilterCounterEntityType): string[] => countQueries(entityType).map((query) => query.requestId);
|
||||||
let getTaskListFiltersSpy: jasmine.Spy;
|
let getTaskListFiltersSpy: jasmine.Spy;
|
||||||
let getProcessFiltersSpy: jasmine.Spy;
|
let getProcessFiltersSpy: jasmine.Spy;
|
||||||
|
|
||||||
@@ -168,23 +171,20 @@ describe('FilterCountersCloudService', () => {
|
|||||||
await taskCounters();
|
await taskCounters();
|
||||||
|
|
||||||
expect(countUrl()).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(countRequest())).toEqual([FilterCounterEntityType.TASK, FilterCounterEntityType.PROCESS_INSTANCE]);
|
||||||
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 () => {
|
it('should identify the query of every filter by the key of the filter', async () => {
|
||||||
await taskCounters();
|
await taskCounters();
|
||||||
|
|
||||||
const body = countRequest();
|
expect(countRequestIds(FilterCounterEntityType.TASK)).toEqual(['my-tasks', 'queued-tasks']);
|
||||||
expect(body.TASK.map((query: FilterCountersQuery) => query.requestId)).toEqual(['my-tasks', 'queued-tasks']);
|
expect(countRequestIds(FilterCounterEntityType.PROCESS_INSTANCE)).toEqual(['running-processes']);
|
||||||
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 () => {
|
it('should send the criteria of every filter along with its request id', async () => {
|
||||||
await taskCounters();
|
await taskCounters();
|
||||||
|
|
||||||
const body = countRequest();
|
expect(countQueries(FilterCounterEntityType.TASK)[0]).toEqual({
|
||||||
expect(body.TASK[0]).toEqual({
|
|
||||||
requestId: 'my-tasks',
|
requestId: 'my-tasks',
|
||||||
status: ['ASSIGNED'],
|
status: ['ASSIGNED'],
|
||||||
assignee: ['mock-user'],
|
assignee: ['mock-user'],
|
||||||
@@ -195,8 +195,7 @@ describe('FilterCountersCloudService', () => {
|
|||||||
it('should not send the filters without a counter enabled', async () => {
|
it('should not send the filters without a counter enabled', async () => {
|
||||||
await taskCounters();
|
await taskCounters();
|
||||||
|
|
||||||
const body = countRequest();
|
expect(countRequestIds(FilterCounterEntityType.TASK)).not.toContain('completed-tasks');
|
||||||
expect(body.TASK.map((query: FilterCountersQuery) => query.requestId)).not.toContain('completed-tasks');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should send the query of a filter targeting every status', async () => {
|
it('should send the query of a filter targeting every status', async () => {
|
||||||
@@ -204,8 +203,7 @@ describe('FilterCountersCloudService', () => {
|
|||||||
|
|
||||||
await processCounters();
|
await processCounters();
|
||||||
|
|
||||||
const body = countRequest();
|
expect(countRequestIds(FilterCounterEntityType.PROCESS_INSTANCE)).toEqual(['all-processes']);
|
||||||
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 () => {
|
it('should omit an entity type without filters with a counter enabled', async () => {
|
||||||
@@ -213,8 +211,7 @@ describe('FilterCountersCloudService', () => {
|
|||||||
|
|
||||||
await taskCounters();
|
await taskCounters();
|
||||||
|
|
||||||
const body = countRequest();
|
expect(countRequest().PROCESS_INSTANCE).toBeUndefined();
|
||||||
expect(body.PROCESS_INSTANCE).toBeUndefined();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should leave out a filter the query cannot be built for', async () => {
|
it('should leave out a filter the query cannot be built for', async () => {
|
||||||
@@ -224,8 +221,7 @@ describe('FilterCountersCloudService', () => {
|
|||||||
|
|
||||||
await taskCounters();
|
await taskCounters();
|
||||||
|
|
||||||
const body = countRequest();
|
expect(countRequestIds(FilterCounterEntityType.TASK)).toEqual(['queued-tasks']);
|
||||||
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 () => {
|
it('should leave out a filter without a key, since it holds no request id', async () => {
|
||||||
@@ -233,8 +229,7 @@ describe('FilterCountersCloudService', () => {
|
|||||||
|
|
||||||
await processCounters();
|
await processCounters();
|
||||||
|
|
||||||
const body = countRequest();
|
expect(countRequest().PROCESS_INSTANCE).toBeUndefined();
|
||||||
expect(body.PROCESS_INSTANCE).toBeUndefined();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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 () => {
|
||||||
@@ -242,9 +237,8 @@ describe('FilterCountersCloudService', () => {
|
|||||||
|
|
||||||
await taskCounters();
|
await taskCounters();
|
||||||
|
|
||||||
const body = countRequest();
|
expect(countRequestIds(FilterCounterEntityType.TASK)).toEqual(['my-tasks', 'queued-tasks']);
|
||||||
expect(body.TASK.map((query: FilterCountersQuery) => query.requestId)).toEqual(['my-tasks', 'queued-tasks']);
|
expect(countRequest().PROCESS_INSTANCE).toBeUndefined();
|
||||||
expect(body.PROCESS_INSTANCE).toBeUndefined();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should resolve no counter when no filter has a counter enabled', async () => {
|
it('should resolve no counter when no filter has a counter enabled', async () => {
|
||||||
@@ -311,6 +305,42 @@ describe('FilterCountersCloudService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when only one of the two filter families is wired', () => {
|
||||||
|
/* An app holding only the task filters provides the task preferences service alone. */
|
||||||
|
const configureTasksOnly = () => {
|
||||||
|
TestBed.resetTestingModule();
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [NoopAuthModule, ApolloTestingModule],
|
||||||
|
providers: [{ provide: TASK_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService }]
|
||||||
|
});
|
||||||
|
|
||||||
|
const tasksOnlyService = TestBed.inject(FilterCountersCloudService);
|
||||||
|
TestBed.inject(AppConfigService).config.bpmHost = 'https://fake-bpm-host.com';
|
||||||
|
spyOn(TestBed.inject(NotificationCloudService), 'makeGQLQuery').and.returnValue(new Subject<EngineEventsResult>().asObservable());
|
||||||
|
spyOn(TestBed.inject(TaskFilterCloudService), 'getTaskListFilters').and.returnValue(of(taskFiltersMock));
|
||||||
|
postSpy = spyOn(tasksOnlyService as unknown as CountEndpoint, 'post').and.returnValue(of(countersMock));
|
||||||
|
|
||||||
|
return tasksOnlyService;
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should resolve the counters of the wired family', async () => {
|
||||||
|
const tasksOnlyService = configureTasksOnly();
|
||||||
|
|
||||||
|
const result = await firstValueFrom(tasksOnlyService.getFilterCounters('mock-app', FilterCounterEntityType.TASK));
|
||||||
|
|
||||||
|
expect(result).toEqual({ counters: { 'my-tasks': 5, 'queued-tasks': 0 }, batched: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should leave the filters of the family that is not wired out of the request', async () => {
|
||||||
|
const tasksOnlyService = configureTasksOnly();
|
||||||
|
|
||||||
|
await firstValueFrom(tasksOnlyService.getFilterCounters('mock-app', FilterCounterEntityType.TASK));
|
||||||
|
|
||||||
|
expect(countRequestIds(FilterCounterEntityType.TASK)).toEqual(['my-tasks', 'queued-tasks']);
|
||||||
|
expect(countRequest().PROCESS_INSTANCE).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('getEngineEvents', () => {
|
describe('getEngineEvents', () => {
|
||||||
it('should return EMPTY when appName is not set', () => {
|
it('should return EMPTY when appName is not set', () => {
|
||||||
let completed = false;
|
let completed = false;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable, Injector } from '@angular/core';
|
||||||
import { combineLatest, defer, EMPTY, merge, Observable, of, Subject } from 'rxjs';
|
import { combineLatest, defer, EMPTY, merge, Observable, of, Subject } from 'rxjs';
|
||||||
import { catchError, debounceTime, map, shareReplay, switchMap, take } from 'rxjs/operators';
|
import { catchError, debounceTime, map, shareReplay, switchMap, take } from 'rxjs/operators';
|
||||||
import { BaseCloudService } from './base-cloud.service';
|
import { BaseCloudService } from './base-cloud.service';
|
||||||
@@ -37,7 +37,6 @@ import {
|
|||||||
FilterCountersRequest,
|
FilterCountersRequest,
|
||||||
FilterCountersResult
|
FilterCountersResult
|
||||||
} from '../models/filter-counters-cloud.model';
|
} 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
|
* Single subscription covering both the task and the process engine events, so that a batch of
|
||||||
@@ -51,8 +50,10 @@ interface FilterCountersFilters {
|
|||||||
[FilterCounterEntityType.PROCESS_INSTANCE]: ProcessFilterCloudModel[];
|
[FilterCounterEntityType.PROCESS_INSTANCE]: ProcessFilterCloudModel[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Payload of the engine event subscription. */
|
/** Data selected by the engine event subscription. */
|
||||||
type EngineEventsResult = FetchResult<{ engineEvents?: TaskCloudEngineEvent[] }>;
|
interface EngineEventsData {
|
||||||
|
engineEvents?: TaskCloudEngineEvent[];
|
||||||
|
}
|
||||||
|
|
||||||
const FILTER_COUNTERS_EVENT_SUBSCRIPTION_QUERY = `
|
const FILTER_COUNTERS_EVENT_SUBSCRIPTION_QUERY = `
|
||||||
subscription {
|
subscription {
|
||||||
@@ -87,11 +88,13 @@ const FILTER_COUNTERS_EVENT_SUBSCRIPTION_QUERY = `
|
|||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class FilterCountersCloudService extends BaseCloudService {
|
export class FilterCountersCloudService extends BaseCloudService {
|
||||||
private readonly notificationCloudService = inject(NotificationCloudService);
|
private readonly notificationCloudService = inject(NotificationCloudService);
|
||||||
|
|
||||||
private readonly taskFilterCloudService = inject(TaskFilterCloudService);
|
|
||||||
private readonly processFilterCloudService = inject(ProcessFilterCloudService);
|
|
||||||
private readonly taskListCloudService = inject(TaskListCloudService);
|
private readonly taskListCloudService = inject(TaskListCloudService);
|
||||||
private readonly processListCloudService = inject(ProcessListCloudService);
|
private readonly processListCloudService = inject(ProcessListCloudService);
|
||||||
|
/**
|
||||||
|
* The filter services are resolved on demand, so that an app holding only one of the two filter
|
||||||
|
* components is not forced to provide the preferences service of the other one.
|
||||||
|
*/
|
||||||
|
private readonly injector = inject(Injector);
|
||||||
|
|
||||||
private readonly eventsPerApp = new Map<string, Observable<TaskCloudEngineEvent[]>>();
|
private readonly eventsPerApp = new Map<string, Observable<TaskCloudEngineEvent[]>>();
|
||||||
private readonly refreshPerApp = new Map<string, Subject<void>>();
|
private readonly refreshPerApp = new Map<string, Subject<void>>();
|
||||||
@@ -112,7 +115,7 @@ export class FilterCountersCloudService extends BaseCloudService {
|
|||||||
* @returns Task filters of the app
|
* @returns Task filters of the app
|
||||||
*/
|
*/
|
||||||
getTaskFilters(appName: string): Observable<TaskFilterCloudModel[]> {
|
getTaskFilters(appName: string): Observable<TaskFilterCloudModel[]> {
|
||||||
return this.shareFilters(this.taskFiltersPerApp, appName, () => this.taskFilterCloudService.getTaskListFilters(appName));
|
return this.shareFilters(this.taskFiltersPerApp, appName, () => this.injector.get(TaskFilterCloudService).getTaskListFilters(appName));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -123,7 +126,7 @@ export class FilterCountersCloudService extends BaseCloudService {
|
|||||||
* @returns Process filters of the app
|
* @returns Process filters of the app
|
||||||
*/
|
*/
|
||||||
getProcessFilters(appName: string): Observable<ProcessFilterCloudModel[]> {
|
getProcessFilters(appName: string): Observable<ProcessFilterCloudModel[]> {
|
||||||
return this.shareFilters(this.processFiltersPerApp, appName, () => this.processFilterCloudService.getProcessFilters(appName));
|
return this.shareFilters(this.processFiltersPerApp, appName, () => this.injector.get(ProcessFilterCloudService).getProcessFilters(appName));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -165,9 +168,12 @@ export class FilterCountersCloudService extends BaseCloudService {
|
|||||||
|
|
||||||
let events$ = this.eventsPerApp.get(appName);
|
let events$ = this.eventsPerApp.get(appName);
|
||||||
if (!events$) {
|
if (!events$) {
|
||||||
events$ = defer(() => this.notificationCloudService.makeGQLQuery(appName, FILTER_COUNTERS_EVENT_SUBSCRIPTION_QUERY)).pipe(
|
events$ = defer(() =>
|
||||||
map((result: EngineEventsResult) => result.data?.engineEvents ?? []),
|
this.notificationCloudService.makeGQLQuery<EngineEventsData>(appName, FILTER_COUNTERS_EVENT_SUBSCRIPTION_QUERY)
|
||||||
|
).pipe(
|
||||||
|
map((result) => result.data?.engineEvents ?? []),
|
||||||
debounceTime(this.notificationDebounceTime),
|
debounceTime(this.notificationDebounceTime),
|
||||||
|
catchError(() => EMPTY),
|
||||||
shareReplay({ bufferSize: 1, refCount: true })
|
shareReplay({ bufferSize: 1, refCount: true })
|
||||||
);
|
);
|
||||||
this.eventsPerApp.set(appName, events$);
|
this.eventsPerApp.set(appName, events$);
|
||||||
|
|||||||
@@ -15,8 +15,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { gql } from '@apollo/client/core';
|
import { FetchResult, gql } from '@apollo/client/core';
|
||||||
import { Injectable, inject } from '@angular/core';
|
import { Injectable, inject } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
import { WebSocketService } from './web-socket.service';
|
import { WebSocketService } from './web-socket.service';
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@@ -24,8 +25,15 @@ import { WebSocketService } from './web-socket.service';
|
|||||||
export class NotificationCloudService {
|
export class NotificationCloudService {
|
||||||
private readonly webSocketService = inject(WebSocketService);
|
private readonly webSocketService = inject(WebSocketService);
|
||||||
|
|
||||||
makeGQLQuery(appName: string, gqlQuery: string) {
|
/**
|
||||||
return this.webSocketService.getSubscription({
|
* Opens a GraphQL subscription over the notifications of an app.
|
||||||
|
*
|
||||||
|
* @param appName Name of the target app
|
||||||
|
* @param gqlQuery Subscription to make
|
||||||
|
* @returns Results of the subscription, holding the data the subscription selected
|
||||||
|
*/
|
||||||
|
makeGQLQuery<T = unknown>(appName: string, gqlQuery: string): Observable<FetchResult<T>> {
|
||||||
|
return this.webSocketService.getSubscription<T>({
|
||||||
apolloClientName: appName,
|
apolloClientName: appName,
|
||||||
wsUrl: `${appName}/notifications`,
|
wsUrl: `${appName}/notifications`,
|
||||||
httpUrl: `${appName}/notifications/v2/ws/graphql`,
|
httpUrl: `${appName}/notifications/v2/ws/graphql`,
|
||||||
|
|||||||
Reference in New Issue
Block a user