[ACA-4259] Task filter counter notifications (#6607)

* [ACA-4259] Task Filter Counter Notifications

* Improve Apollo connection for multiple apps

* Improve Notification Cloud Service

* Rebase branch

* Rebase branch

* Add peer dependencies

* Rebase

* Remove apollo-angular-link-http

* Fix linting

* Rebase branch

* Fix package lock

* Push package lock
This commit is contained in:
davidcanonieto 2021-02-16 15:27:38 +01:00 committed by GitHub
parent 7996f70b26
commit cf0c95b3d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 688 additions and 57 deletions

View File

@ -214,6 +214,11 @@
"STORE": "Store",
"RESTORE": "Restore"
},
"NOTIFICATIONS": {
"TASK_ASSIGNED": "{{taskName}} task has been assigned to {{assignee}}",
"PROCESS_STARTED": "{{processName}} process has been started",
"TASK_UPDATED": "{{taskName}} task details has been updated"
},
"FORM-LOADING": {
"FORM_DATA": "Form Data",
"FORM_DATA_MESSAGE": "Enter values to populate the form",

View File

@ -17,6 +17,23 @@
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { CloudLayoutService } from './services/cloud-layout.service';
import { NotificationModel, NotificationService } from '@alfresco/adf-core';
import { map } from 'rxjs/operators';
import { NotificationCloudService } from '@alfresco/adf-process-services-cloud';
import { TranslateService } from '@ngx-translate/core';
const SUBSCRIPTION_QUERY = `
subscription {
engineEvents(eventType: [
PROCESS_STARTED
TASK_ASSIGNED
TASK_UPDATED
]) {
eventType
entity
}
}
`;
@Component({
selector: 'app-cloud-layout',
@ -31,13 +48,26 @@ export class CloudLayoutComponent implements OnInit {
constructor(
private router: Router,
private route: ActivatedRoute,
private cloudLayoutService: CloudLayoutService
private cloudLayoutService: CloudLayoutService,
private notificationCloudService: NotificationCloudService,
private notificationService: NotificationService,
private translateService: TranslateService
) { }
ngOnInit() {
let root: string = '';
this.route.params.subscribe((params) => {
this.appName = params.appName;
this.notificationCloudService.makeGQLQuery(
this.appName, SUBSCRIPTION_QUERY
)
.pipe(map((events: any) => events.data.engineEvents))
.subscribe((result) => {
result.map((engineEvent) => {
this.notifyEvent(engineEvent);
});
});
});
if (this.route.snapshot && this.route.snapshot.firstChild) {
@ -62,4 +92,37 @@ export class CloudLayoutComponent implements OnInit {
onStartProcess() {
this.router.navigate([`/cloud/${this.appName}/start-process/`]);
}
notifyEvent(engineEvent) {
let message;
switch (engineEvent.eventType) {
case 'TASK_ASSIGNED':
message = this.translateService.instant('NOTIFICATIONS.TASK_ASSIGNED',
{ taskName: engineEvent.entity.name || '', assignee: engineEvent.entity.assignee });
this.pushNotification(engineEvent, message);
break;
case 'PROCESS_STARTED':
message = this.translateService.instant('NOTIFICATIONS.PROCESS_STARTED',
{ processName: engineEvent.entity.name });
this.pushNotification(engineEvent, message);
break;
case 'TASK_UPDATED':
message = this.translateService.instant('NOTIFICATIONS.TASK_UPDATED',
{ taskName: engineEvent.entity.name || '' });
this.pushNotification(engineEvent, message);
break;
default:
}
}
pushNotification(engineEvent: any, message: string) {
const notification = {
messages: [message],
icon: 'info',
datetime: new Date(),
initiator: { displayName: engineEvent.entity.initiator || 'System' }
} as NotificationModel;
this.notificationService.pushToNotificationHistory(notification);
}
}

View File

@ -46,7 +46,7 @@
</ng-template>
<p class="adf-notification-history-menu-message"
*ngFor="let message of notification.messages"
mat-line>{{ message }}</p>
mat-line [matTooltip]="message" matTooltipShowDelay="1000">{{ message }}</p>
<p class="adf-notification-history-menu-date"
mat-line> {{notification.datetime | adfTimeAgo}} </p>
</mat-list-item>

View File

@ -21,8 +21,11 @@
"@alfresco/js-api": "4.2.0",
"@alfresco/adf-core": "4.2.0",
"@alfresco/adf-content-services": "4.2.0",
"@apollo/client": "^3.3.7",
"@ngx-translate/core": ">=13.0.0",
"moment": ">=2.22.2"
"apollo-angular": "^2.2.0",
"moment": ">=2.22.2",
"subscriptions-transport-ws": "^0.9.18"
},
"keywords": [
"process-services-cloud",

View File

@ -0,0 +1,23 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { TaskDetailsCloudModel } from '../task/start-task/models/task-details-cloud.model';
export interface TaskCloudEngineEvent {
eventType: string;
entity: TaskDetailsCloudModel;
}

View File

@ -0,0 +1,72 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { TestBed, async } from '@angular/core/testing';
import { setupTestBed } from '@alfresco/adf-core';
import { ProcessServiceCloudTestingModule } from '../testing/process-service-cloud.testing.module';
import { TranslateModule } from '@ngx-translate/core';
import { NotificationCloudService } from './notification-cloud.service';
import { Apollo } from 'apollo-angular';
describe('NotificationCloudService', () => {
let service: NotificationCloudService;
let apollo: Apollo;
const queryMock = `
subscription {
engineEvents(eventType: [
MY_EVENT
]) {
eventType
entity
}
}
`;
setupTestBed({
imports: [
TranslateModule.forRoot(),
ProcessServiceCloudTestingModule
]
});
beforeEach(async(() => {
service = TestBed.inject(NotificationCloudService);
apollo = TestBed.inject(Apollo);
}));
it('should not create more than one websocket per app if it was already created', () => {
const apolloCreateSpy = spyOn(apollo, 'create');
const apolloSubscribeSpy = spyOn(apollo, 'subscribe');
service.makeGQLQuery('myAppName', queryMock);
expect(service.appsListening.length).toBe(1);
expect(service.appsListening[0]).toBe('myAppName');
service.makeGQLQuery('myAppName', queryMock);
expect(service.appsListening.length).toBe(1);
expect(service.appsListening[0]).toBe('myAppName');
service.makeGQLQuery('myAppName2', queryMock);
expect(service.appsListening.length).toBe(2);
expect(service.appsListening[0]).toBe('myAppName');
expect(service.appsListening[1]).toBe('myAppName2');
expect(apolloCreateSpy).toHaveBeenCalledTimes(2);
expect(apolloSubscribeSpy).toHaveBeenCalledTimes(3);
});
});

View File

@ -0,0 +1,85 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Apollo } from 'apollo-angular';
import { HttpLink } from 'apollo-angular/http';
import { split, gql, InMemoryCache } from '@apollo/client/core';
import { WebSocketLink } from '@apollo/client/link/ws';
import { getMainDefinition } from '@apollo/client/utilities';
import { Injectable } from '@angular/core';
import { StorageService, AppConfigService, AlfrescoApiService } from '@alfresco/adf-core';
import { BaseCloudService } from './base-cloud.service';
@Injectable({
providedIn: 'root'
})
export class NotificationCloudService extends BaseCloudService {
appsListening = [];
constructor(apiService: AlfrescoApiService,
appConfigService: AppConfigService,
public apollo: Apollo,
private http: HttpLink,
private storageService: StorageService) {
super(apiService, appConfigService);
}
private getUrlDomain(appName: string) {
return this.getBasePath(appName).split('://')[1];
}
initNotificationsForApp(appName: string) {
if (!this.appsListening.includes(appName)) {
this.appsListening.push(appName);
const httpLink = this.http.create({
uri: `${this.getBasePath(appName)}/notifications/graphql`
});
const webSocketLink = new WebSocketLink({
uri: `wss://${this.getUrlDomain(appName)}/notifications/ws/graphql`,
options: {
reconnect: true,
lazy: true,
connectionParams: {
kaInterval: 2000,
'X-Authorization': 'Bearer ' + this.storageService.getItem('access_token')
}
}
});
const link = split(
({ query }) => {
const definition = getMainDefinition(query);
return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';
},
webSocketLink,
httpLink
);
this.apollo.create(<any> {
link,
cache: new InMemoryCache({})
});
}
}
makeGQLQuery(appName: string, gqlQuery: string) {
this.initNotificationsForApp(appName);
return this.apollo.subscribe({ query : gql(gqlQuery) });
}
}

View File

@ -18,5 +18,6 @@
export * from './user-preference-cloud.service';
export * from './local-preference-cloud.service';
export * from './cloud-token.service';
export * from './notification-cloud.service';
export * from './preference-cloud.interface';
export * from './form-fields.interfaces';

View File

@ -1,16 +1,25 @@
<ng-container *ngIf="filters$ | async as filterList; else loading">
<div *ngFor="let filter of filterList" class="adf-task-filters__entry">
<div *ngFor="let filter of filterList"
class="adf-task-filters__entry">
<button (click)="onFilterClick(filter)"
[attr.aria-label]="filter.name | translate"
[id]="filter.id"
[attr.data-automation-id]="filter.key + '_filter'"
mat-button
[class.adf-active]="currentFilter === filter"
class="adf-filter-action-button adf-full-width" fxLayout="row" fxLayoutAlign="space-between center">
<adf-icon data-automation-id="adf-filter-icon" *ngIf="showIcons" [value]="filter.icon"></adf-icon>
<span data-automation-id="adf-filter-label" class="adf-filter-action-button__label">{{ filter.name | translate }}</span>
class="adf-filter-action-button adf-full-width"
fxLayout="row"
fxLayoutAlign="space-between center">
<adf-icon data-automation-id="adf-filter-icon"
*ngIf="showIcons"
[value]="filter.icon"></adf-icon>
<span data-automation-id="adf-filter-label"
class="adf-filter-action-button__label">{{ filter.name | translate }}</span>
</button>
<span *ngIf="counters$[filter.key]" class="adf-filter-action-button__counter">
<span *ngIf="counters$[filter.key]"
[attr.data-automation-id]="filter.key + '_filter-counter'"
class="adf-filter-action-button__counter"
[class.adf-active]=wasFilterUpdated(filter.key)>
{{ counters$[filter.key] | async }}
</span>
</div>

View File

@ -1,5 +1,7 @@
@mixin adf-cloud-task-filters-theme($theme) {
$primary: map-get($theme, primary);
$accent: map-get($theme, accent);
$background: map-get($theme, background);
.adf {
&-task-filters__entry {
@ -7,7 +9,6 @@
padding: 12px 0 !important;
height: 24px;
width: 100%;
cursor: pointer;
font-size: 14px !important;
font-weight: bold;
opacity: 1;
@ -20,6 +21,7 @@
.adf-filter-action-button {
opacity: 0.54;
padding: 16px;
cursor: pointer;
.adf-filter-action-button__label {
padding-left: 20px;
@ -29,13 +31,26 @@
.adf-filter-action-button__counter {
opacity: 0.54;
padding-left: 10px;
padding-top: 6px;
margin-left: 10px;
margin-top: 6px;
&.adf-active {
margin-left: 8px;
margin-top: 5px;
padding: 0 5px;
border-radius: 15px;
background-color: mat-color($accent);
color: mat-color($mat-grey, 50);
font-size: smaller;
}
}
&:hover {
color: mat-color($primary);
.adf-filter-action-button__counter, .adf-filter-action-button {
.adf-filter-action-button__counter,
.adf-filter-action-button {
opacity: 1;
}
}

View File

@ -46,6 +46,7 @@ export abstract class BaseTaskFiltersCloudComponent implements OnDestroy {
error: EventEmitter<any> = new EventEmitter<any>();
counters$: {[key: string]: Observable<number>} = {};
updatedCounters: string[] = [];
protected onDestroy$ = new Subject<boolean>();
@ -53,4 +54,21 @@ export abstract class BaseTaskFiltersCloudComponent implements OnDestroy {
this.onDestroy$.next(true);
this.onDestroy$.complete();
}
wasFilterUpdated(filterKey: string): boolean {
return this.updatedCounters.includes(filterKey);
}
addToUpdatedCounters(filterKey: string) {
if (!this.updatedCounters.includes(filterKey)) {
this.updatedCounters.push(filterKey);
}
}
resetFilterCounter(filterKey: string) {
const filterIndex = this.updatedCounters.indexOf(filterKey);
if (filterIndex > -1) {
this.updatedCounters.splice(filterIndex, 1);
}
}
}

View File

@ -26,7 +26,7 @@ import { TaskFiltersCloudComponent } from './task-filters-cloud.component';
import { By } from '@angular/platform-browser';
import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module';
import { TaskFiltersCloudModule } from '../task-filters-cloud.module';
import { fakeGlobalFilter } from '../mock/task-filters-cloud.mock';
import { fakeGlobalFilter, taskNotifications } from '../mock/task-filters-cloud.mock';
import { TranslateModule } from '@ngx-translate/core';
describe('TaskFiltersCloudComponent', () => {
@ -69,6 +69,7 @@ describe('TaskFiltersCloudComponent', () => {
taskFilterService = TestBed.inject(TaskFilterCloudService);
spyOn(taskFilterService, 'getTaskFilterCounter').and.returnValue(of(11));
spyOn(taskFilterService, 'getTaskNotificationSubscription').and.returnValue(of(taskNotifications));
});
it('should attach specific icon for each filter if hasIcon is true', async(() => {
@ -379,4 +380,44 @@ describe('TaskFiltersCloudComponent', () => {
expect(filterCounters[0].nativeElement.innerText).toContain('11');
});
}));
it('should update filter counter when notification received', async(() => {
spyOn(taskFilterService, 'getTaskListFilters').and.returnValue(fakeGlobalFilterObservable);
const change = new SimpleChange(undefined, 'my-app-1', true);
component.ngOnChanges({ 'appName': change });
fixture.detectChanges();
component.showIcons = true;
fixture.whenStable().then(() => {
fixture.detectChanges();
const updatedFilterCounters = fixture.debugElement.queryAll(By.css('span.adf-active'));
expect(updatedFilterCounters.length).toBe(1);
expect(Object.keys(component.counters$).length).toBe(1);
expect(component.counters$['fake-involved-tasks']).toBeDefined();
});
}));
it('should reset filter counter notification when filter is selected', async(() => {
spyOn(taskFilterService, 'getTaskListFilters').and.returnValue(fakeGlobalFilterObservable);
let change = new SimpleChange(undefined, 'my-app-1', true);
component.ngOnChanges({ 'appName': change });
fixture.detectChanges();
component.showIcons = true;
fixture.whenStable().then(() => {
fixture.detectChanges();
let updatedFilterCounters = fixture.debugElement.queryAll(By.css('span.adf-active'));
expect(updatedFilterCounters.length).toBe(1);
component.filters = fakeGlobalFilter;
component.currentFilter = null;
change = new SimpleChange(null, { key: fakeGlobalFilter[0].key }, true);
component.ngOnChanges({ 'filterParam': change });
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
updatedFilterCounters = fixture.debugElement.queryAll(By.css('span.adf-active'));
expect(updatedFilterCounters.length).toBe(0);
});
});
}));
});

View File

@ -20,8 +20,10 @@ import { Observable } from 'rxjs';
import { TaskFilterCloudService } from '../services/task-filter-cloud.service';
import { TaskFilterCloudModel, FilterParamsModel } from '../models/filter-cloud.model';
import { TranslationService } from '@alfresco/adf-core';
import { takeUntil } from 'rxjs/operators';
import { map, takeUntil } from 'rxjs/operators';
import { BaseTaskFiltersCloudComponent } from './base-task-filters-cloud.component';
import { TaskDetailsCloudModel } from '../../start-task/models/task-details-cloud.model';
import { TaskCloudEngineEvent } from '../../../models/engine-event-cloud.model';
@Component({
selector: 'adf-cloud-task-filters',
@ -37,10 +39,13 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp
@Output()
filterClicked = new EventEmitter<TaskFilterCloudModel>();
/** Emitted when filter counters are updated. */
@Output()
filterCounterUpdated: EventEmitter<TaskCloudEngineEvent[]> = new EventEmitter<TaskCloudEngineEvent[]>();
filters$: Observable<TaskFilterCloudModel[]>;
filters: TaskFilterCloudModel[] = [];
currentFilter: TaskFilterCloudModel;
counters = {};
constructor(private taskFilterCloudService: TaskFilterCloudService,
private translationService: TranslationService) {
@ -48,6 +53,7 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp
}
ngOnInit() {
this.initFilterCounterNotifications();
this.getFilters(this.appName);
}
@ -89,6 +95,30 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp
});
}
initFilterCounterNotifications() {
this.taskFilterCloudService.getTaskNotificationSubscription(this.appName)
.subscribe((result: TaskCloudEngineEvent[]) => {
result.map((taskEvent: TaskCloudEngineEvent) => {
this.updateFilterCounter(taskEvent.entity);
});
this.filterCounterUpdated.emit(result);
});
}
updateFilterCounter(filterNotification: TaskDetailsCloudModel) {
this.filters.map((filter) => {
if (this.isFilterPresent(filter, filterNotification)) {
this.counters$[filter.key] = this.counters$[filter.key].pipe(map((counter) => counter + 1));
this.addToUpdatedCounters(filter.key);
}
});
}
isFilterPresent(filter: TaskFilterCloudModel, filterNotification: TaskDetailsCloudModel): boolean {
return filter.status === filterNotification.status
&& filter.assignee === filterNotification.assignee;
}
public selectFilter(paramFilter: FilterParamsModel) {
if (paramFilter) {
this.currentFilter = this.filters.find((filter, index) =>
@ -104,7 +134,10 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp
public selectFilterAndEmit(newParamFilter: FilterParamsModel) {
if (newParamFilter) {
this.selectFilter(newParamFilter);
if (this.currentFilter) {
this.resetFilterCounter(this.currentFilter.key);
this.filterSelected.emit(this.currentFilter);
}
} else {
this.currentFilter = undefined;
}

View File

@ -15,6 +15,7 @@
* limitations under the License.
*/
import { assignedTaskDetailsCloudMock } from '../../task-header/mocks/task-details-cloud.mock';
import { TaskFilterCloudModel, ServiceTaskFilterCloudModel } from '../models/filter-cloud.model';
export const fakeGlobalFilter = [
@ -23,8 +24,8 @@ export const fakeGlobalFilter = [
key: 'fake-involved-tasks',
icon: 'adjust',
id: '10',
status: 'open',
assignee: 'fake-involved',
status: 'ASSIGNED',
assignee: 'AssignedTaskUser',
showCounter: true
}),
new TaskFilterCloudModel({
@ -273,3 +274,16 @@ export const fakeTaskCloudFilters = [
order: 'DESC'
}
];
export const taskNotifications = [
{
eventType: 'TASK_ASSIGNED',
entity: assignedTaskDetailsCloudMock
}
];
export const taskCloudEngineEventsMock = {
data: {
engineEvents: taskNotifications
}
};

View File

@ -33,15 +33,19 @@ import {
fakePreferenceWithNoTaskFilterPreference,
fakeTaskCloudFilters,
fakeTaskCloudPreferenceList,
fakeTaskFilter
fakeTaskFilter,
taskCloudEngineEventsMock
} from '../mock/task-filters-cloud.mock';
import { UserPreferenceCloudService } from '../../../services/user-preference-cloud.service';
import { PreferenceCloudServiceInterface } from '../../../services/preference-cloud.interface';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { TaskFilterCloudModel } from '../models/filter-cloud.model';
import { NotificationCloudService } from '../../../services/notification-cloud.service';
import { TaskCloudEngineEvent } from './../../../models/engine-event-cloud.model';
describe('TaskFilterCloudService', () => {
let service: TaskFilterCloudService;
let notificationCloudService: NotificationCloudService;
let getPreferencesSpy: jasmine.Spy;
let getPreferenceByKeySpy: jasmine.Spy;
@ -64,6 +68,7 @@ describe('TaskFilterCloudService', () => {
beforeEach(() => {
service = TestBed.inject(TaskFilterCloudService);
notificationCloudService = TestBed.inject(NotificationCloudService);
const preferenceCloudService = service.preferenceService;
createPreferenceSpy = spyOn(preferenceCloudService, 'createPreference').and.returnValue(of(fakeTaskCloudFilters));
@ -229,6 +234,17 @@ describe('TaskFilterCloudService', () => {
expect(service.isDefaultFilter(defaultFilterName)).toBe(true);
expect(service.isDefaultFilter(fakeFilterName)).toBe(false);
});
it('should return engine event task subscription', (done) => {
spyOn(notificationCloudService, 'makeGQLQuery').and.returnValue(of(taskCloudEngineEventsMock));
service.getTaskNotificationSubscription('myAppName').subscribe((res: TaskCloudEngineEvent[]) => {
expect(res.length).toBe(1);
expect(res[0].eventType).toBe('TASK_ASSIGNED');
expect(res[0].entity.name).toBe('This is a new task');
done();
});
});
});
describe('Inject [LocalPreferenceCloudService] into the TaskFilterCloudService', () => {
@ -240,9 +256,7 @@ describe('Inject [LocalPreferenceCloudService] into the TaskFilterCloudService',
const identityUserMock = { username: 'fakeusername', firstName: 'fake-identity-first-name', lastName: 'fake-identity-last-name', email: 'fakeIdentity@email.com' };
setupTestBed({
imports: [
HttpClientTestingModule
],
imports: [ HttpClientTestingModule ],
providers: [
{ provide: TASK_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService }
]

View File

@ -24,6 +24,24 @@ import { BaseCloudService } from '../../../services/base-cloud.service';
import { PreferenceCloudServiceInterface } from '../../../services/preference-cloud.interface';
import { TASK_FILTERS_SERVICE_TOKEN } from '../../../services/cloud-token.service';
import { TaskCloudNodePaging } from '../../task-list/models/task-cloud.model';
import { NotificationCloudService } from '../../../services/notification-cloud.service';
import { TaskCloudEngineEvent } from '../../../models/engine-event-cloud.model';
const TASK_EVENT_SUBSCRIPTION_QUERY = `
subscription {
engineEvents(eventType: [
TASK_COMPLETED
TASK_ASSIGNED
TASK_ACTIVATED
TASK_SUSPENDED
TASK_CANCELLED
TASK_UPDATED
]) {
eventType
entity
}
}
`;
@Injectable({
providedIn: 'root'
@ -37,7 +55,8 @@ export class TaskFilterCloudService extends BaseCloudService {
@Inject(TASK_FILTERS_SERVICE_TOKEN)
public preferenceService: PreferenceCloudServiceInterface,
apiService: AlfrescoApiService,
appConfigService: AppConfigService) {
appConfigService: AppConfigService,
private notificationCloudService: NotificationCloudService) {
super(apiService, appConfigService);
this.filtersSubject = new BehaviorSubject([]);
this.filters$ = this.filtersSubject.asObservable();
@ -315,4 +334,9 @@ export class TaskFilterCloudService extends BaseCloudService {
})
];
}
getTaskNotificationSubscription(appName: string): Observable<TaskCloudEngineEvent[]> {
return this.notificationCloudService.makeGQLQuery(appName, TASK_EVENT_SUBSCRIPTION_QUERY)
.pipe(map((events: any) => events.data.engineEvents));
}
}

View File

@ -29,3 +29,4 @@ export * from './lib/pipes/process-services-cloud-pipe.module';
export * from './lib/models/process-definition-cloud.model';
export * from './lib/models/date-cloud-filter.model';
export * from './lib/models/application-version.model';
export * from './lib/models/engine-event-cloud.model';

View File

@ -29,7 +29,7 @@
"@alfresco/adf-core": ["./core/"],
"@alfresco/adf-insights": ["./analytics"]
},
"lib": ["es2018", "esnext.array", "dom"],
"lib": ["es2018", "esnext.array", "esnext.asynciterable", "dom"],
"suppressImplicitAnyIndexErrors": true
},
"exclude": [

232
package-lock.json generated
View File

@ -1411,6 +1411,38 @@
"tslib": "^2.0.0"
}
},
"@apollo/client": {
"version": "3.3.10",
"resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.3.10.tgz",
"integrity": "sha512-OL5y8q5evSbvsqMAHKZJgfgB/MS9PLGF7f0MtHEkGGCDvmcmA81lsjYwk0xpDm/3XdnrYp/duN2tYUu2LnpJmw==",
"requires": {
"@graphql-typed-document-node/core": "^3.0.0",
"@types/zen-observable": "^0.8.0",
"@wry/context": "^0.5.2",
"@wry/equality": "^0.3.0",
"fast-json-stable-stringify": "^2.0.0",
"graphql-tag": "^2.12.0",
"hoist-non-react-statics": "^3.3.2",
"optimism": "^0.14.0",
"prop-types": "^15.7.2",
"symbol-observable": "^2.0.0",
"ts-invariant": "^0.6.0",
"tslib": "^1.10.0",
"zen-observable": "^0.8.14"
},
"dependencies": {
"symbol-observable": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-2.0.3.tgz",
"integrity": "sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA=="
},
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@babel/code-frame": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
@ -2936,6 +2968,11 @@
}
}
},
"@graphql-typed-document-node/core": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.0.tgz",
"integrity": "sha512-wYn6r8zVZyQJ6rQaALBEln5B1pzxb9shV5Ef97kTvn6yVGrqyXVnDqnU24MXnFubR+rZjBY9NWuxX3FB2sTsjg=="
},
"@istanbuljs/schema": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz",
@ -5862,6 +5899,11 @@
"integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==",
"dev": true
},
"@types/ungap__global-this": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@types/ungap__global-this/-/ungap__global-this-0.3.1.tgz",
"integrity": "sha512-+/DsiV4CxXl6ZWefwHZDXSe1Slitz21tom38qPCaG0DYCS1NnDPIQDTKcmQ/tvK/edJUKkmuIDBJbmKDiB0r/g=="
},
"@types/unist": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz",
@ -5902,6 +5944,16 @@
"integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==",
"dev": true
},
"@types/zen-observable": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.2.tgz",
"integrity": "sha512-HrCIVMLjE1MOozVoD86622S7aunluLb2PJdPfb3nYiEtohm8mIB/vyv0Fd37AdeMFrTUQXEunw78YloMA3Qilg=="
},
"@ungap/global-this": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/@ungap/global-this/-/global-this-0.4.4.tgz",
"integrity": "sha512-mHkm6FvepJECMNthFuIgpAEFmPOk71UyXuIxYfjytvFTnSDBIz7jmViO+LfHI/AjrazWije0PnSP3+/NlwzqtA=="
},
"@webassemblyjs/ast": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
@ -6077,6 +6129,51 @@
"@xtuc/long": "4.2.2"
}
},
"@wry/context": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@wry/context/-/context-0.5.4.tgz",
"integrity": "sha512-/pktJKHUXDr4D6TJqWgudOPJW2Z+Nb+bqk40jufA3uTkLbnCRKdJPiYDIa/c7mfcPH8Hr6O8zjCERpg5Sq04Zg==",
"requires": {
"tslib": "^1.14.1"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@wry/equality": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.3.2.tgz",
"integrity": "sha512-yi0VRqw+ygqM/WVZUze5meAhe2evOHBFXqK8onNVdNNB+Tyn8/07FZpeDklECBHeT9KN9DY2JpCVGNQY6RCRDg==",
"requires": {
"tslib": "^1.14.1"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@wry/trie": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.2.2.tgz",
"integrity": "sha512-OxqBB39x6MfHaa2HpMiRMfhuUnQTddD32Ko020eBeJXq87ivX6xnSSnzKHVbA21p7iqBASz8n/07b6W5wW1BVQ==",
"requires": {
"tslib": "^1.14.1"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@xtuc/ieee754": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
@ -6370,6 +6467,16 @@
"picomatch": "^2.0.4"
}
},
"apollo-angular": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/apollo-angular/-/apollo-angular-2.2.0.tgz",
"integrity": "sha512-/38ynj2gMJ1M/JlSDB6vv1fGYJy7WDukJn/3wQnoOYay5ZeU0rsAJdBaSZQiOPxqAJgVoGfU787/aXjIC1PWZg==",
"requires": {
"extract-files": "^9.0.0",
"semver": "^7.0.0",
"tslib": "^2.0.0"
}
},
"app-root-path": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.0.1.tgz",
@ -6645,8 +6752,7 @@
"async-limiter": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
"dev": true
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
},
"asynckit": {
"version": "0.4.0",
@ -6852,8 +6958,7 @@
"backo2": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=",
"dev": true
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
},
"bail": {
"version": "1.0.5",
@ -11579,8 +11684,7 @@
"extract-files": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/extract-files/-/extract-files-9.0.0.tgz",
"integrity": "sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==",
"dev": true
"integrity": "sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ=="
},
"extsprintf": {
"version": "1.3.0",
@ -11628,8 +11732,7 @@
"fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"dev": true
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
},
"fast-levenshtein": {
"version": "2.0.6",
@ -12820,6 +12923,21 @@
"form-data": "^3.0.0"
}
},
"graphql-tag": {
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.1.tgz",
"integrity": "sha512-LPewEE1vzGkHnCO8zdOGogKsHHBdtpGyihow1UuMwp6RnZa0lAS7NcbvltLOuo4pi5diQCPASAXZkQq44ffixA==",
"requires": {
"tslib": "^1.14.1"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"graphviz": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/graphviz/-/graphviz-0.0.8.tgz",
@ -13076,6 +13194,14 @@
"minimalistic-crypto-utils": "^1.0.1"
}
},
"hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"requires": {
"react-is": "^16.7.0"
}
},
"homedir-polyfill": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
@ -14537,6 +14663,11 @@
"integrity": "sha512-O62gD5ADMUGtJoOoM9U6LQ7i4byPXUNoHJ6mqsmkQJcom331ZJGDApWgDESWyBMEHEJRjtHozgIiTzYo9RU4UA==",
"dev": true
},
"iterall": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz",
"integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg=="
},
"jake": {
"version": "10.8.2",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz",
@ -14899,8 +15030,7 @@
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"js-yaml": {
"version": "4.0.0",
@ -16439,7 +16569,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true,
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
@ -18531,8 +18660,7 @@
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"dev": true
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"object-copy": {
"version": "0.1.0",
@ -18775,6 +18903,15 @@
}
}
},
"optimism": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/optimism/-/optimism-0.14.0.tgz",
"integrity": "sha512-ygbNt8n4DOCVpkwiLF+IrKKeNHOjtr9aXLWGP9HNJGoblSGsnVbJLstcH6/nE9Xy5ZQtlkSioFQNnthmENW6FQ==",
"requires": {
"@wry/context": "^0.5.2",
"@wry/trie": "^0.2.1"
}
},
"optimize-css-assets-webpack-plugin": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.4.tgz",
@ -20483,6 +20620,16 @@
"sisteransi": "^1.0.4"
}
},
"prop-types": {
"version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.8.1"
}
},
"proto-list": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
@ -21386,6 +21533,11 @@
"strip-json-comments": "~2.0.1"
}
},
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@ -25494,6 +25646,38 @@
}
}
},
"subscriptions-transport-ws": {
"version": "0.9.18",
"resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.18.tgz",
"integrity": "sha512-tztzcBTNoEbuErsVQpTN2xUNN/efAZXyCyL5m3x4t6SKrEiTL2N8SaKWBFWM4u56pL79ULif3zjyeq+oV+nOaA==",
"requires": {
"backo2": "^1.0.2",
"eventemitter3": "^3.1.0",
"iterall": "^1.2.1",
"symbol-observable": "^1.0.4",
"ws": "^5.2.0"
},
"dependencies": {
"eventemitter3": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
"integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="
},
"symbol-observable": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
},
"ws": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz",
"integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==",
"requires": {
"async-limiter": "~1.0.0"
}
}
}
},
"sugarss": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/sugarss/-/sugarss-2.0.0.tgz",
@ -26200,6 +26384,23 @@
"glob": "^7.1.2"
}
},
"ts-invariant": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.6.0.tgz",
"integrity": "sha512-caoafsfgb8QxdrKzFfjKt627m4i8KTtfAiji0DYJfWI4A/S9ORNNpzYuD9br64kyKFgxn9UNaLLbSupam84mCA==",
"requires": {
"@types/ungap__global-this": "^0.3.1",
"@ungap/global-this": "^0.4.2",
"tslib": "^1.9.3"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"ts-loader": {
"version": "5.4.5",
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-5.4.5.tgz",
@ -29053,6 +29254,11 @@
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true
},
"zen-observable": {
"version": "0.8.15",
"resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz",
"integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ=="
},
"zone.js": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.10.3.tgz",

View File

@ -84,10 +84,12 @@
"@angular/platform-browser": "^10.0.4",
"@angular/platform-browser-dynamic": "^10.0.4",
"@angular/router": "^10.0.4",
"@apollo/client": "^3.3.7",
"@mat-datetimepicker/core": "^5.1.1",
"@mat-datetimepicker/moment": "^5.1.1",
"@ngx-translate/core": "^13.0.0",
"adf-tslint-rules": "0.0.7",
"apollo-angular": "^2.2.0",
"chart.js": "2.9.4",
"classlist.js": "1.1.20150312",
"custom-event-polyfill": "^1.0.7",
@ -101,6 +103,7 @@
"raphael": "2.3.0",
"rxjs": "^6.6.3",
"snyk": "^1.452.0",
"subscriptions-transport-ws": "^0.9.18",
"tslib": "^2.0.3",
"zone.js": "~0.10.2"
},

View File

@ -25,6 +25,7 @@
"lib": [
"es2018",
"esnext.array",
"esnext.asynciterable",
"dom"
],
"paths": {