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. */
|
||||
const countRequest = (): FilterCountersRequest => postSpy.calls.mostRecent().args[1];
|
||||
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 getProcessFiltersSpy: jasmine.Spy;
|
||||
|
||||
@@ -168,23 +171,20 @@ describe('FilterCountersCloudService', () => {
|
||||
await taskCounters();
|
||||
|
||||
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]);
|
||||
expect(Object.keys(countRequest())).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 = 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']);
|
||||
expect(countRequestIds(FilterCounterEntityType.TASK)).toEqual(['my-tasks', 'queued-tasks']);
|
||||
expect(countRequestIds(FilterCounterEntityType.PROCESS_INSTANCE)).toEqual(['running-processes']);
|
||||
});
|
||||
|
||||
it('should send the criteria of every filter along with its request id', async () => {
|
||||
await taskCounters();
|
||||
|
||||
const body = countRequest();
|
||||
expect(body.TASK[0]).toEqual({
|
||||
expect(countQueries(FilterCounterEntityType.TASK)[0]).toEqual({
|
||||
requestId: 'my-tasks',
|
||||
status: ['ASSIGNED'],
|
||||
assignee: ['mock-user'],
|
||||
@@ -195,8 +195,7 @@ describe('FilterCountersCloudService', () => {
|
||||
it('should not send the filters without a counter enabled', async () => {
|
||||
await taskCounters();
|
||||
|
||||
const body = countRequest();
|
||||
expect(body.TASK.map((query: FilterCountersQuery) => query.requestId)).not.toContain('completed-tasks');
|
||||
expect(countRequestIds(FilterCounterEntityType.TASK)).not.toContain('completed-tasks');
|
||||
});
|
||||
|
||||
it('should send the query of a filter targeting every status', async () => {
|
||||
@@ -204,8 +203,7 @@ describe('FilterCountersCloudService', () => {
|
||||
|
||||
await processCounters();
|
||||
|
||||
const body = countRequest();
|
||||
expect(body.PROCESS_INSTANCE.map((query: FilterCountersQuery) => query.requestId)).toEqual(['all-processes']);
|
||||
expect(countRequestIds(FilterCounterEntityType.PROCESS_INSTANCE)).toEqual(['all-processes']);
|
||||
});
|
||||
|
||||
it('should omit an entity type without filters with a counter enabled', async () => {
|
||||
@@ -213,8 +211,7 @@ describe('FilterCountersCloudService', () => {
|
||||
|
||||
await taskCounters();
|
||||
|
||||
const body = countRequest();
|
||||
expect(body.PROCESS_INSTANCE).toBeUndefined();
|
||||
expect(countRequest().PROCESS_INSTANCE).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should leave out a filter the query cannot be built for', async () => {
|
||||
@@ -224,8 +221,7 @@ describe('FilterCountersCloudService', () => {
|
||||
|
||||
await taskCounters();
|
||||
|
||||
const body = countRequest();
|
||||
expect(body.TASK.map((query: FilterCountersQuery) => query.requestId)).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 () => {
|
||||
@@ -233,8 +229,7 @@ describe('FilterCountersCloudService', () => {
|
||||
|
||||
await processCounters();
|
||||
|
||||
const body = countRequest();
|
||||
expect(body.PROCESS_INSTANCE).toBeUndefined();
|
||||
expect(countRequest().PROCESS_INSTANCE).toBeUndefined();
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
const body = countRequest();
|
||||
expect(body.TASK.map((query: FilterCountersQuery) => query.requestId)).toEqual(['my-tasks', 'queued-tasks']);
|
||||
expect(body.PROCESS_INSTANCE).toBeUndefined();
|
||||
expect(countRequestIds(FilterCounterEntityType.TASK)).toEqual(['my-tasks', 'queued-tasks']);
|
||||
expect(countRequest().PROCESS_INSTANCE).toBeUndefined();
|
||||
});
|
||||
|
||||
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', () => {
|
||||
it('should return EMPTY when appName is not set', () => {
|
||||
let completed = false;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* 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 { catchError, debounceTime, map, shareReplay, switchMap, take } from 'rxjs/operators';
|
||||
import { BaseCloudService } from './base-cloud.service';
|
||||
@@ -37,7 +37,6 @@ import {
|
||||
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
|
||||
@@ -51,8 +50,10 @@ interface FilterCountersFilters {
|
||||
[FilterCounterEntityType.PROCESS_INSTANCE]: ProcessFilterCloudModel[];
|
||||
}
|
||||
|
||||
/** Payload of the engine event subscription. */
|
||||
type EngineEventsResult = FetchResult<{ engineEvents?: TaskCloudEngineEvent[] }>;
|
||||
/** Data selected by the engine event subscription. */
|
||||
interface EngineEventsData {
|
||||
engineEvents?: TaskCloudEngineEvent[];
|
||||
}
|
||||
|
||||
const FILTER_COUNTERS_EVENT_SUBSCRIPTION_QUERY = `
|
||||
subscription {
|
||||
@@ -87,11 +88,13 @@ const FILTER_COUNTERS_EVENT_SUBSCRIPTION_QUERY = `
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class FilterCountersCloudService extends BaseCloudService {
|
||||
private readonly notificationCloudService = inject(NotificationCloudService);
|
||||
|
||||
private readonly taskFilterCloudService = inject(TaskFilterCloudService);
|
||||
private readonly processFilterCloudService = inject(ProcessFilterCloudService);
|
||||
private readonly taskListCloudService = inject(TaskListCloudService);
|
||||
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 refreshPerApp = new Map<string, Subject<void>>();
|
||||
@@ -112,7 +115,7 @@ export class FilterCountersCloudService extends BaseCloudService {
|
||||
* @returns Task filters of the app
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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);
|
||||
if (!events$) {
|
||||
events$ = defer(() => this.notificationCloudService.makeGQLQuery(appName, FILTER_COUNTERS_EVENT_SUBSCRIPTION_QUERY)).pipe(
|
||||
map((result: EngineEventsResult) => result.data?.engineEvents ?? []),
|
||||
events$ = defer(() =>
|
||||
this.notificationCloudService.makeGQLQuery<EngineEventsData>(appName, FILTER_COUNTERS_EVENT_SUBSCRIPTION_QUERY)
|
||||
).pipe(
|
||||
map((result) => result.data?.engineEvents ?? []),
|
||||
debounceTime(this.notificationDebounceTime),
|
||||
catchError(() => EMPTY),
|
||||
shareReplay({ bufferSize: 1, refCount: true })
|
||||
);
|
||||
this.eventsPerApp.set(appName, events$);
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { gql } from '@apollo/client/core';
|
||||
import { FetchResult, gql } from '@apollo/client/core';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { WebSocketService } from './web-socket.service';
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@@ -24,8 +25,15 @@ import { WebSocketService } from './web-socket.service';
|
||||
export class NotificationCloudService {
|
||||
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,
|
||||
wsUrl: `${appName}/notifications`,
|
||||
httpUrl: `${appName}/notifications/v2/ws/graphql`,
|
||||
|
||||
Reference in New Issue
Block a user