From 1ff5e8f43c0e570bb1d6e9139ffec182ce7c8ea1 Mon Sep 17 00:00:00 2001
From: tomasz hanaj <12088991+tomaszhanaj@users.noreply.github.com>
Date: Thu, 19 Sep 2024 15:51:59 +0200
Subject: [PATCH] AAE-25271 Refresh button for processes (#10211)
* [AAE-25271] display process notifications
* [AAE-25271] removed unnecessary conditional
* [AAE-25271] replaced observable with promise
* [AAE-25271] replaced observables with promises
* [AAE-25271] updated unit tests
* [AAE-25271] refactored tests to avoid circular dependency error
* [AAE-25271] replaced automatic imports
---
.../src/lib/app/app-list-cloud.module.spec.ts | 19 +--
...dit-process-filter-cloud.component.spec.ts | 94 ++++++++++-----
.../process-filters-cloud.component.html | 26 +++--
.../process-filters-cloud.component.scss | 12 ++
.../process-filters-cloud.component.spec.ts | 108 +++++++++++++++++-
.../process-filters-cloud.component.ts | 63 +++++++++-
.../mock/process-filters-cloud.mock.ts | 22 ++++
.../process-filter-cloud.service.spec.ts | 17 ++-
.../services/process-filter-cloud.service.ts | 26 +++++
.../process-list-cloud.component.spec.ts | 2 +-
.../process-list-cloud.service.spec.ts | 58 ++++++++--
.../services/process-list-cloud.service.ts | 42 +++++++
.../start-process-cloud.component.spec.ts | 3 +-
.../task-filters-cloud.component.ts | 2 +-
.../mock/task-filters-cloud.mock.ts | 2 +-
15 files changed, 430 insertions(+), 66 deletions(-)
diff --git a/lib/process-services-cloud/src/lib/app/app-list-cloud.module.spec.ts b/lib/process-services-cloud/src/lib/app/app-list-cloud.module.spec.ts
index e20cde27fe..ea61d41d32 100644
--- a/lib/process-services-cloud/src/lib/app/app-list-cloud.module.spec.ts
+++ b/lib/process-services-cloud/src/lib/app/app-list-cloud.module.spec.ts
@@ -15,16 +15,21 @@
* limitations under the License.
*/
+import { NoopTranslateModule } from '@alfresco/adf-core';
import { AppListCloudModule } from './app-list-cloud.module';
+import { TestBed } from '@angular/core/testing';
describe('AppListCloudModule', () => {
- let appListCloudModule: AppListCloudModule;
+ let appListCloudModule: AppListCloudModule;
- beforeEach(() => {
- appListCloudModule = new AppListCloudModule();
- });
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [AppListCloudModule, NoopTranslateModule]
+ });
+ appListCloudModule = TestBed.inject(AppListCloudModule);
+ });
- it('should create an instance', () => {
- expect(appListCloudModule).toBeTruthy();
- });
+ it('should create an instance', () => {
+ expect(appListCloudModule).toBeTruthy();
+ });
});
diff --git a/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.spec.ts
index 89b76a88e7..d94aa24dc8 100644
--- a/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.spec.ts
+++ b/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.spec.ts
@@ -15,37 +15,53 @@
* limitations under the License.
*/
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { SimpleChange } from '@angular/core';
import { AlfrescoApiService } from '@alfresco/adf-content-services';
-import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module';
-import { MatDialog } from '@angular/material/dialog';
+import { ADF_DATE_FORMATS, FullNamePipe, NoopTranslateModule, UserPreferencesService } from '@alfresco/adf-core';
+import { HarnessLoader } from '@angular/cdk/testing';
+import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
+import { SimpleChange } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ReactiveFormsModule } from '@angular/forms';
+import { DateFnsAdapter } from '@angular/material-date-fns-adapter';
+import { MatAutocompleteModule } from '@angular/material/autocomplete';
+import { MatChipsModule } from '@angular/material/chips';
+import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core';
+import { MatDatepickerModule } from '@angular/material/datepicker';
+import { MatDialog, MatDialogModule } from '@angular/material/dialog';
+import { MatExpansionPanelHarness } from '@angular/material/expansion/testing';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatIconTestingModule } from '@angular/material/icon/testing';
+import { MatInputModule } from '@angular/material/input';
+import { MatProgressBarModule } from '@angular/material/progress-bar';
+import { MatProgressSpinnerHarness } from '@angular/material/progress-spinner/testing';
+import { MatSelectModule } from '@angular/material/select';
+import { MatSelectHarness } from '@angular/material/select/testing';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { endOfDay, format, isValid, startOfDay, subYears } from 'date-fns';
+import { enUS } from 'date-fns/locale';
import { of } from 'rxjs';
-import { ProcessFilterDialogCloudComponent } from './process-filter-dialog-cloud.component';
+import { AppsProcessCloudService } from '../../../app/services/apps-process-cloud.service';
+import { DateRangeFilterComponent } from '../../../common/date-range-filter/date-range-filter.component';
+import { fakeEnvironmentList } from '../../../common/mock/environment.mock';
+import { DateCloudFilterType } from '../../../models/date-cloud-filter.model';
+import { ProcessDefinitionCloud } from '../../../models/process-definition-cloud.model';
+import { PeopleCloudComponent } from '../../../people/components/people-cloud.component';
+import { IdentityUserServiceMock } from '../../../people/mock/people-cloud.mock';
+import { IDENTITY_USER_SERVICE_TOKEN } from '../../../people/services/identity-user-service.token';
+import { PROCESS_FILTERS_SERVICE_TOKEN } from '../../../services/cloud-token.service';
+import { LocalPreferenceCloudService } from '../../../services/local-preference-cloud.service';
+import { NotificationCloudService } from '../../../services/notification-cloud.service';
+import { ProcessCloudService } from '../../services/process-cloud.service';
+import { mockAppVersions } from '../mock/process-filters-cloud.mock';
+import { ProcessFilterCloudModel } from '../models/process-filter-cloud.model';
+import { ProcessFilterCloudService } from '../services/process-filter-cloud.service';
+import { fakeApplicationInstance, fakeApplicationInstanceWithEnvironment } from './../../../app/mock/app-model.mock';
import {
EditProcessFilterCloudComponent,
PROCESS_FILTER_ACTION_RESTORE,
PROCESS_FILTER_ACTION_SAVE_DEFAULT
} from './edit-process-filter-cloud.component';
-import { ProcessFiltersCloudModule } from '../process-filters-cloud.module';
-import { ProcessFilterCloudModel } from '../models/process-filter-cloud.model';
-import { ProcessFilterCloudService } from '../services/process-filter-cloud.service';
-import { AppsProcessCloudService } from '../../../app/services/apps-process-cloud.service';
-import { fakeApplicationInstance, fakeApplicationInstanceWithEnvironment } from './../../../app/mock/app-model.mock';
-import { PROCESS_FILTERS_SERVICE_TOKEN } from '../../../services/cloud-token.service';
-import { LocalPreferenceCloudService } from '../../../services/local-preference-cloud.service';
-import { ProcessCloudService } from '../../services/process-cloud.service';
-import { DateCloudFilterType } from '../../../models/date-cloud-filter.model';
-import { MatIconTestingModule } from '@angular/material/icon/testing';
-import { ProcessDefinitionCloud } from '../../../models/process-definition-cloud.model';
-import { mockAppVersions } from '../mock/process-filters-cloud.mock';
-import { fakeEnvironmentList } from '../../../common/mock/environment.mock';
-import { endOfDay, format, startOfDay, subYears, isValid } from 'date-fns';
-import { HarnessLoader } from '@angular/cdk/testing';
-import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
-import { MatSelectHarness } from '@angular/material/select/testing';
-import { MatExpansionPanelHarness } from '@angular/material/expansion/testing';
-import { MatProgressSpinnerHarness } from '@angular/material/progress-spinner/testing';
+import { ProcessFilterDialogCloudComponent } from './process-filter-dialog-cloud.component';
describe('EditProcessFilterCloudComponent', () => {
let loader: HarnessLoader;
@@ -59,6 +75,7 @@ describe('EditProcessFilterCloudComponent', () => {
let getRunningApplicationsSpy: jasmine.Spy;
let getProcessFilterByIdSpy: jasmine.Spy;
let alfrescoApiService: AlfrescoApiService;
+ let userPreferencesService: UserPreferencesService;
const fakeFilter = new ProcessFilterCloudModel({
name: 'FakeRunningProcess',
@@ -83,8 +100,30 @@ describe('EditProcessFilterCloudComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
- imports: [ProcessFiltersCloudModule, ProcessServiceCloudTestingModule, MatIconTestingModule],
- providers: [MatDialog, { provide: PROCESS_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService }]
+ imports: [
+ MatIconTestingModule,
+ MatDialogModule,
+ NoopTranslateModule,
+ NoopAnimationsModule,
+ MatSelectModule,
+ MatDatepickerModule,
+ MatAutocompleteModule,
+ FullNamePipe,
+ MatFormFieldModule,
+ MatInputModule,
+ ReactiveFormsModule,
+ MatChipsModule,
+ MatProgressBarModule
+ ],
+ providers: [
+ { provide: PROCESS_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService },
+ { provide: MAT_DATE_LOCALE, useValue: enUS },
+ { provide: DateAdapter, useClass: DateFnsAdapter },
+ { provide: NotificationCloudService, useValue: { makeGQLQuery: () => of([]) } },
+ { provide: MAT_DATE_FORMATS, useValue: ADF_DATE_FORMATS },
+ { provide: IDENTITY_USER_SERVICE_TOKEN, useExisting: IdentityUserServiceMock }
+ ],
+ declarations: [PeopleCloudComponent, DateRangeFilterComponent]
});
fixture = TestBed.createComponent(EditProcessFilterCloudComponent);
component = fixture.componentInstance;
@@ -93,7 +132,9 @@ describe('EditProcessFilterCloudComponent', () => {
appsService = TestBed.inject(AppsProcessCloudService);
processService = TestBed.inject(ProcessCloudService);
alfrescoApiService = TestBed.inject(AlfrescoApiService);
+ userPreferencesService = TestBed.inject(UserPreferencesService);
dialog = TestBed.inject(MatDialog);
+
spyOn(dialog, 'open').and.returnValue({
afterClosed: () =>
of({
@@ -105,6 +146,7 @@ describe('EditProcessFilterCloudComponent', () => {
getProcessFilterByIdSpy = spyOn(service, 'getFilterById').and.returnValue(of(fakeFilter));
getRunningApplicationsSpy = spyOn(appsService, 'getDeployedApplicationsByStatus').and.returnValue(of(fakeApplicationInstance));
spyOn(alfrescoApiService, 'getInstance').and.returnValue(mock);
+ spyOn(userPreferencesService, 'select').and.returnValue(of({ localize: 'en', formatLong: {} }));
fixture.detectChanges();
loader = TestbedHarnessEnvironment.loader(fixture);
});
diff --git a/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.html b/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.html
index b485ce4aaf..a9348f35f7 100644
--- a/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.html
+++ b/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.html
@@ -9,15 +9,25 @@
[class.adf-active]="currentFilter === filter"
>
-
-
+
+
+
+
+ {{ filter.name | translate }}
+
+
- {{ filter.name | translate }}
+ *ngIf="counters$[filter.key]"
+ [attr.data-automation-id]="filter.key + '_filter-counter'"
+ class="adf-process-filters__entry-counter"
+ [class.adf-active]="isFilterUpdated(filter.key)"
+ >
+ {{ counters$[filter.key] | async }}
diff --git a/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.scss b/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.scss
index a6a625ff63..3efc799159 100644
--- a/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.scss
+++ b/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.scss
@@ -6,6 +6,7 @@
color: var(--adf-theme-foreground-text-color-054);
display: flex;
align-items: center;
+ justify-content: space-between;
flex: 1;
height: 100%;
gap: var(--adf-theme-spacing);
@@ -13,6 +14,17 @@
&:hover {
color: var(--theme-primary-color);
}
+
+ &-counter {
+ padding: 0 5px;
+ border-radius: 15px;
+
+ &.adf-active {
+ background-color: var(--theme-accent-color);
+ color: var(--theme-accent-color-default-contrast);
+ font-size: smaller;
+ }
+ }
}
.adf-active {
diff --git a/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.spec.ts
index 93c3292b7b..37d6df01b9 100644
--- a/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.spec.ts
+++ b/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.spec.ts
@@ -21,28 +21,46 @@ import { of, throwError } from 'rxjs';
import { ProcessFilterCloudService } from '../services/process-filter-cloud.service';
import { ProcessFiltersCloudComponent } from './process-filters-cloud.component';
import { By } from '@angular/platform-browser';
-import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module';
-import { ProcessFiltersCloudModule } from '../process-filters-cloud.module';
import { PROCESS_FILTERS_SERVICE_TOKEN } from '../../../services/cloud-token.service';
import { LocalPreferenceCloudService } from '../../../services/local-preference-cloud.service';
import { mockProcessFilters } from '../mock/process-filters-cloud.mock';
+import { AppConfigService, AppConfigServiceMock, NoopTranslateModule } from '@alfresco/adf-core';
+import { ProcessListCloudService } from '../../../process/process-list/services/process-list-cloud.service';
+import { NotificationCloudService } from '../../../services/notification-cloud.service';
+import { ApolloModule } from 'apollo-angular';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { MatListModule } from '@angular/material/list';
+
+const ProcessFilterCloudServiceMock = {
+ getProcessFilters: () => of(mockProcessFilters),
+ getProcessNotificationSubscription: () => of([])
+};
describe('ProcessFiltersCloudComponent', () => {
let processFilterService: ProcessFilterCloudService;
let component: ProcessFiltersCloudComponent;
let fixture: ComponentFixture;
let getProcessFiltersSpy: jasmine.Spy;
+ let getProcessNotificationSubscriptionSpy: jasmine.Spy;
beforeEach(() => {
TestBed.configureTestingModule({
- imports: [ProcessServiceCloudTestingModule, ProcessFiltersCloudModule],
- providers: [{ provide: PROCESS_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService }]
+ imports: [NoopTranslateModule, NoopAnimationsModule, MatListModule],
+ providers: [
+ { provide: PROCESS_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService },
+ { provide: AppConfigService, useClass: AppConfigServiceMock },
+ { provide: ProcessListCloudService, useValue: { getProcessCounter: () => of(10) } },
+ { provide: ProcessFilterCloudService, useValue: ProcessFilterCloudServiceMock },
+ NotificationCloudService,
+ ApolloModule
+ ]
});
fixture = TestBed.createComponent(ProcessFiltersCloudComponent);
component = fixture.componentInstance;
processFilterService = TestBed.inject(ProcessFilterCloudService);
getProcessFiltersSpy = spyOn(processFilterService, 'getProcessFilters').and.returnValue(of(mockProcessFilters));
+ getProcessNotificationSubscriptionSpy = spyOn(processFilterService, 'getProcessNotificationSubscription').and.returnValue(of([]));
});
afterEach(() => {
@@ -288,12 +306,14 @@ describe('ProcessFiltersCloudComponent', () => {
};
const clickOnFilter = async (filterKey: string) => {
- fixture.debugElement.nativeElement.querySelector(`[data-automation-id="${filterKey}_filter"]`).click();
+ const button = fixture.debugElement.nativeElement.querySelector(`[data-automation-id="${filterKey}_filter"]`);
+ button.click();
fixture.detectChanges();
await fixture.whenStable();
};
it('should apply active CSS class on filter click', async () => {
+ component.enableNotifications = true;
component.appName = 'mock-app-name';
const appNameChange = new SimpleChange(null, 'mock-app-name', true);
component.ngOnChanges({ appName: appNameChange });
@@ -301,18 +321,24 @@ describe('ProcessFiltersCloudComponent', () => {
await fixture.whenStable();
await clickOnFilter(allProcessesFilterKey);
+ fixture.detectChanges();
+ await fixture.whenStable();
expect(getActiveFilterElement(allProcessesFilterKey)).toBeDefined();
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeNull();
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeNull();
await clickOnFilter(runningProcessesFilterKey);
+ fixture.detectChanges();
+ await fixture.whenStable();
expect(getActiveFilterElement(allProcessesFilterKey)).toBeNull();
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeDefined();
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeNull();
await clickOnFilter(completedProcessesFilterKey);
+ fixture.detectChanges();
+ await fixture.whenStable();
expect(getActiveFilterElement(allProcessesFilterKey)).toBeNull();
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeNull();
@@ -345,5 +371,77 @@ describe('ProcessFiltersCloudComponent', () => {
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeNull();
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeDefined();
});
+
+ it('should made sbscription', () => {
+ component.enableNotifications = true;
+ component.appName = 'mock-app-name';
+ const appNameChange = new SimpleChange(null, 'mock-app-name', true);
+ component.ngOnChanges({ appName: appNameChange });
+ fixture.detectChanges();
+ expect(getProcessNotificationSubscriptionSpy).toHaveBeenCalled();
+ });
+
+ it('should not emit filter key when filter counter is set for first time', () => {
+ component.currentFiltersValues = {};
+ const fakeFilterKey = 'testKey';
+ const fakeFilterValue = 10;
+ const updatedFilterSpy = spyOn(component.updatedFilter, 'emit');
+ component.checkIfFilterValuesHasBeenUpdated(fakeFilterKey, fakeFilterValue);
+ fixture.detectChanges();
+
+ expect(component.currentFiltersValues).not.toEqual({});
+ expect(component.currentFiltersValues[fakeFilterKey]).toBe(fakeFilterValue);
+ expect(updatedFilterSpy).not.toHaveBeenCalled();
+ });
+
+ it('should not emit filter key when filter counter has not changd', () => {
+ component.currentFiltersValues = {};
+ const fakeFilterKey = 'testKey';
+ const fakeFilterValue = 10;
+ const updatedFilterSpy = spyOn(component.updatedFilter, 'emit');
+ component.checkIfFilterValuesHasBeenUpdated(fakeFilterKey, fakeFilterValue);
+ fixture.detectChanges();
+
+ expect(component.currentFiltersValues).not.toEqual({});
+ expect(component.currentFiltersValues[fakeFilterKey]).toBe(fakeFilterValue);
+
+ component.checkIfFilterValuesHasBeenUpdated(fakeFilterKey, fakeFilterValue);
+ expect(component.currentFiltersValues[fakeFilterKey]).toBe(fakeFilterValue);
+ expect(updatedFilterSpy).not.toHaveBeenCalled();
+ });
+
+ it('should emit filter key when filter counter is increased', () => {
+ component.currentFiltersValues = {};
+ const fakeFilterKey = 'testKey';
+ const updatedFilterSpy = spyOn(component.updatedFilter, 'emit');
+ component.checkIfFilterValuesHasBeenUpdated(fakeFilterKey, 10);
+ fixture.detectChanges();
+
+ expect(updatedFilterSpy).not.toHaveBeenCalledWith(fakeFilterKey);
+ expect(component.currentFiltersValues[fakeFilterKey]).toBe(10);
+
+ component.checkIfFilterValuesHasBeenUpdated(fakeFilterKey, 20);
+ fixture.detectChanges();
+
+ expect(updatedFilterSpy).toHaveBeenCalledWith(fakeFilterKey);
+ expect(component.currentFiltersValues[fakeFilterKey]).toBe(20);
+ });
+
+ it('should emit filter key when filter counter is decreased', () => {
+ component.currentFiltersValues = {};
+ const fakeFilterKey = 'testKey';
+ const updatedFilterSpy = spyOn(component.updatedFilter, 'emit');
+ component.checkIfFilterValuesHasBeenUpdated(fakeFilterKey, 10);
+ fixture.detectChanges();
+
+ expect(updatedFilterSpy).not.toHaveBeenCalledWith(fakeFilterKey);
+ expect(component.currentFiltersValues[fakeFilterKey]).toBe(10);
+
+ component.checkIfFilterValuesHasBeenUpdated(fakeFilterKey, 5);
+ fixture.detectChanges();
+
+ expect(updatedFilterSpy).toHaveBeenCalledWith(fakeFilterKey);
+ expect(component.currentFiltersValues[fakeFilterKey]).toBe(5);
+ });
});
});
diff --git a/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.ts b/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.ts
index 951cbf2631..c113029c41 100644
--- a/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.ts
+++ b/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.ts
@@ -19,9 +19,10 @@ import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, OnDes
import { Observable, Subject } from 'rxjs';
import { ProcessFilterCloudService } from '../services/process-filter-cloud.service';
import { ProcessFilterCloudModel } from '../models/process-filter-cloud.model';
-import { TranslationService } from '@alfresco/adf-core';
+import { AppConfigService, TranslationService } from '@alfresco/adf-core';
import { FilterParamsModel } from '../../../task/task-filters/models/filter-cloud.model';
-import { takeUntil } from 'rxjs/operators';
+import { debounceTime, takeUntil, tap } from 'rxjs/operators';
+import { ProcessListCloudService } from '../../../process/process-list/services/process-list-cloud.service';
@Component({
selector: 'adf-cloud-process-filters',
@@ -58,19 +59,31 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges, OnDestro
@Output()
error = new EventEmitter();
+ /** Emitted when filter is updated. */
+ @Output()
+ updatedFilter: EventEmitter = new EventEmitter();
+
filters$: Observable;
currentFilter?: ProcessFilterCloudModel;
filters: ProcessFilterCloudModel[] = [];
+ counters$: { [key: string]: Observable } = {};
+ enableNotifications: boolean;
+ currentFiltersValues: { [key: string]: number } = {};
+ updatedFiltersSet = new Set();
private onDestroy$ = new Subject();
private readonly processFilterCloudService = inject(ProcessFilterCloudService);
private readonly translationService = inject(TranslationService);
+ private readonly appConfigService = inject(AppConfigService);
+ private readonly processListCloudService = inject(ProcessListCloudService);
ngOnInit() {
+ this.enableNotifications = this.appConfigService.get('notifications', true);
if (this.appName === '') {
this.getFilters(this.appName);
}
+ this.initProcessNotification();
}
ngOnChanges(changes: SimpleChanges) {
@@ -97,6 +110,7 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges, OnDestro
this.filters = res || [];
this.selectFilterAndEmit(this.filterParam);
this.success.emit(res);
+ this.updateFilterCounters();
},
error: (err: any) => {
this.error.emit(err);
@@ -172,6 +186,8 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges, OnDestro
if (filter) {
this.selectFilter(filter);
this.filterClicked.emit(this.currentFilter);
+ this.updateFilterCounter(this.currentFilter);
+ this.updatedFiltersSet.delete(filter.key);
} else {
this.currentFilter = undefined;
}
@@ -220,4 +236,47 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges, OnDestro
isActiveFilter(filter: ProcessFilterCloudModel): boolean {
return this.currentFilter.name === filter.name;
}
+
+ initProcessNotification(): void {
+ if (this.appName && this.enableNotifications) {
+ this.processFilterCloudService
+ .getProcessNotificationSubscription(this.appName)
+ .pipe(debounceTime(1000), takeUntil(this.onDestroy$))
+ .subscribe(() => {
+ this.updateFilterCounters();
+ });
+ }
+ }
+
+ updateFilterCounters(): void {
+ this.filters.forEach((filter: ProcessFilterCloudModel) => {
+ if (filter?.status) {
+ this.updateFilterCounter(filter);
+ }
+ });
+ }
+
+ updateFilterCounter(filter: ProcessFilterCloudModel): void {
+ this.counters$[filter.key] = this.processListCloudService.getProcessCounter(filter.appName, filter.status).pipe(
+ tap((filterCounter) => {
+ this.checkIfFilterValuesHasBeenUpdated(filter.key, filterCounter);
+ })
+ );
+ }
+
+ checkIfFilterValuesHasBeenUpdated(filterKey: string, filterValue: number): void {
+ if (!this.currentFiltersValues[filterKey]) {
+ this.currentFiltersValues[filterKey] = filterValue;
+ return;
+ }
+ if (this.currentFiltersValues[filterKey] !== filterValue) {
+ this.currentFiltersValues[filterKey] = filterValue;
+ this.updatedFilter.emit(filterKey);
+ this.updatedFiltersSet.add(filterKey);
+ }
+ }
+
+ isFilterUpdated(filterName: string): boolean {
+ return this.updatedFiltersSet.has(filterName);
+ }
}
diff --git a/lib/process-services-cloud/src/lib/process/process-filters/mock/process-filters-cloud.mock.ts b/lib/process-services-cloud/src/lib/process/process-filters/mock/process-filters-cloud.mock.ts
index 7eda6562c2..586faee458 100644
--- a/lib/process-services-cloud/src/lib/process/process-filters/mock/process-filters-cloud.mock.ts
+++ b/lib/process-services-cloud/src/lib/process/process-filters/mock/process-filters-cloud.mock.ts
@@ -186,3 +186,25 @@ const mockAppVersion2: ApplicationVersionModel = {
};
export const mockAppVersions = [mockAppVersion1, mockAppVersion2];
+
+export const processNotifications = [
+ {
+ eventType: 'PROCESS_CREATED',
+ entity: {
+ appVersion: '1',
+ id: 'bccc1217-7036-11ef-86f2-bae4749e773e',
+ processDefinitionId: 'Process_XmWTFMqf:1:1b30709b-6ff3-11ef-86f2-bae4749e773e',
+ processDefinitionKey: 'Process_XmWTFMqf',
+ initiator: 'hruser',
+ status: 'CREATED',
+ processDefinitionVersion: 1,
+ processDefinitionName: 'processchild'
+ }
+ }
+];
+
+export const processCloudEngineEventsMock = {
+ data: {
+ engineEvents: processNotifications
+ }
+};
diff --git a/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.spec.ts b/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.spec.ts
index ea38dc2bd0..f3d9f6d173 100644
--- a/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.spec.ts
+++ b/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.spec.ts
@@ -16,7 +16,7 @@
*/
import { TestBed } from '@angular/core/testing';
-import { of } from 'rxjs';
+import { firstValueFrom, of } from 'rxjs';
import { ProcessFilterCloudService } from './process-filter-cloud.service';
import { PROCESS_FILTERS_SERVICE_TOKEN } from '../../../services/cloud-token.service';
import { LocalPreferenceCloudService } from '../../../services/local-preference-cloud.service';
@@ -26,10 +26,12 @@ import {
fakeProcessCloudFilterEntries,
fakeProcessCloudFilters,
fakeProcessCloudFilterWithDifferentEntries,
- fakeProcessFilter
+ fakeProcessFilter,
+ processCloudEngineEventsMock
} from '../mock/process-filters-cloud.mock';
import { ProcessFilterCloudModel } from '../models/process-filter-cloud.model';
import { IdentityUserService } from '../../../people/services/identity-user.service';
+import { NotificationCloudService } from '../../../services/notification-cloud.service';
describe('ProcessFilterCloudService', () => {
let service: ProcessFilterCloudService;
@@ -38,6 +40,7 @@ describe('ProcessFilterCloudService', () => {
let updatePreferenceSpy: jasmine.Spy;
let createPreferenceSpy: jasmine.Spy;
let getCurrentUserInfoSpy: jasmine.Spy;
+ let notificationCloudService: NotificationCloudService;
const identityUserMock = {
username: 'mock-username',
@@ -55,6 +58,7 @@ describe('ProcessFilterCloudService', () => {
const preferenceCloudService = TestBed.inject(PROCESS_FILTERS_SERVICE_TOKEN);
const identityUserService = TestBed.inject(IdentityUserService);
+ notificationCloudService = TestBed.inject(NotificationCloudService);
createPreferenceSpy = spyOn(preferenceCloudService, 'createPreference').and.returnValue(of(fakeProcessCloudFilters));
updatePreferenceSpy = spyOn(preferenceCloudService, 'updatePreference').and.returnValue(of(fakeProcessCloudFilters));
@@ -236,4 +240,13 @@ describe('ProcessFilterCloudService', () => {
expect(updatePreferenceSpy).toHaveBeenCalledWith('mock-appName', 'process-filters-mock-appName-mock-username', fakeProcessCloudFilters);
});
+
+ it('should return engine event task subscription', async () => {
+ spyOn(notificationCloudService, 'makeGQLQuery').and.returnValue(of(processCloudEngineEventsMock));
+
+ const result = await firstValueFrom(service.getProcessNotificationSubscription('testApp'));
+ expect(result.length).toBe(1);
+ expect(result[0].eventType).toBe('PROCESS_CREATED');
+ expect(result[0].entity.status).toBe('CREATED');
+ });
});
diff --git a/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.ts b/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.ts
index d47bce4afe..277a9aecba 100644
--- a/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.ts
+++ b/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.ts
@@ -22,6 +22,25 @@ import { switchMap, map } from 'rxjs/operators';
import { PROCESS_FILTERS_SERVICE_TOKEN } from '../../../services/cloud-token.service';
import { PreferenceCloudServiceInterface } from '../../../services/preference-cloud.interface';
import { IdentityUserService } from '../../../people/services/identity-user.service';
+import { NotificationCloudService } from '../../../services/notification-cloud.service';
+import { TaskCloudEngineEvent } from '../../../models/engine-event-cloud.model';
+
+const PROCESS_EVENT_SUBSCRIPTION_QUERY = `
+ subscription {
+ engineEvents(eventType: [
+ PROCESS_CANCELLED
+ PROCESS_COMPLETED
+ PROCESS_CREATED
+ PROCESS_RESUMED
+ PROCESS_SUSPENDED
+ PROCESS_STARTED
+ ]) {
+ eventType
+ entity
+ }
+ }
+`;
+
@Injectable({
providedIn: 'root'
})
@@ -31,6 +50,7 @@ export class ProcessFilterCloudService {
private readonly preferenceService = inject(PROCESS_FILTERS_SERVICE_TOKEN);
private readonly identityUserService = inject(IdentityUserService);
+ private readonly notificationCloudService = inject(NotificationCloudService);
constructor() {
this.filtersSubject = new BehaviorSubject([]);
@@ -377,4 +397,10 @@ export class ProcessFilterCloudService {
})
];
}
+
+ getProcessNotificationSubscription(appName: string): Observable {
+ return this.notificationCloudService
+ .makeGQLQuery(appName, PROCESS_EVENT_SUBSCRIPTION_QUERY)
+ .pipe(map((events: any) => events?.data?.engineEvents));
+ }
}
diff --git a/lib/process-services-cloud/src/lib/process/process-list/components/process-list-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/process/process-list/components/process-list-cloud.component.spec.ts
index 9b3cc26bbe..c03ad0330a 100644
--- a/lib/process-services-cloud/src/lib/process/process-list/components/process-list-cloud.component.spec.ts
+++ b/lib/process-services-cloud/src/lib/process/process-list/components/process-list-cloud.component.spec.ts
@@ -39,7 +39,7 @@ import { PROCESS_LISTS_PREFERENCES_SERVICE_TOKEN } from '../../../services/cloud
import { ProcessListCloudPreferences } from '../models/process-cloud-preferences';
import { PROCESS_LIST_CUSTOM_VARIABLE_COLUMN } from '../../../models/data-column-custom-data';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
-import { PreferenceCloudServiceInterface } from '@alfresco/adf-process-services-cloud';
+import { PreferenceCloudServiceInterface } from '../../../services/preference-cloud.interface';
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatProgressSpinnerHarness } from '@angular/material/progress-spinner/testing';
diff --git a/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.spec.ts b/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.spec.ts
index 16cb8b0cf0..c5a28c257c 100644
--- a/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.spec.ts
+++ b/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.spec.ts
@@ -20,6 +20,7 @@ import { ProcessListCloudService } from './process-list-cloud.service';
import { ProcessQueryCloudRequestModel } from '../models/process-cloud-query-request.model';
import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module';
import { AdfHttpClient } from '@alfresco/adf-core/api';
+import { firstValueFrom } from 'rxjs';
describe('ProcessListCloudService', () => {
let service: ProcessListCloudService;
@@ -36,9 +37,7 @@ describe('ProcessListCloudService', () => {
beforeEach(fakeAsync(() => {
TestBed.configureTestingModule({
- imports: [
- ProcessServiceCloudTestingModule
- ]
+ imports: [ProcessServiceCloudTestingModule]
});
adfHttpClient = TestBed.inject(AdfHttpClient);
service = TestBed.inject(ProcessListCloudService);
@@ -71,8 +70,14 @@ describe('ProcessListCloudService', () => {
it('should concat the sorting to append as parameters', (done) => {
const processRequest = {
- appName: 'fakeName', skipCount: 0, maxItems: 20, service: 'fake-service',
- sorting: [{ orderBy: 'NAME', direction: 'DESC' }, { orderBy: 'TITLE', direction: 'ASC' }]
+ appName: 'fakeName',
+ skipCount: 0,
+ maxItems: 20,
+ service: 'fake-service',
+ sorting: [
+ { orderBy: 'NAME', direction: 'DESC' },
+ { orderBy: 'TITLE', direction: 'ASC' }
+ ]
} as ProcessQueryCloudRequestModel;
requestSpy.and.callFake(returnCallQueryParameters);
service.getProcessByRequest(processRequest).subscribe((res) => {
@@ -87,7 +92,7 @@ describe('ProcessListCloudService', () => {
const processRequest = { appName: null } as ProcessQueryCloudRequestModel;
requestSpy.and.callFake(returnCallUrl);
service.getProcessByRequest(processRequest).subscribe(
- () => { },
+ () => {},
(error) => {
expect(error).toBe('Appname not configured');
done();
@@ -95,8 +100,19 @@ describe('ProcessListCloudService', () => {
);
});
- describe('getAdminProcessRequest', () => {
+ it('should return number of total items of processes ', async () => {
+ const processRequest = { appName: 'fakeName', skipCount: 0, maxItems: 1, service: 'fake-service' } as ProcessQueryCloudRequestModel;
+ requestSpy.and.callFake(returnCallQueryParameters);
+ const result = await firstValueFrom(service.getProcessByRequest(processRequest));
+ expect(result).toBeDefined();
+ expect(result).not.toBeNull();
+ expect(result.skipCount).toBe(0);
+ expect(result.maxItems).toBe(1);
+ expect(result.service).toBe('fake-service');
+ });
+
+ describe('getAdminProcessRequest', () => {
it('should append to the call all the parameters', async () => {
const processRequest = { appName: 'fakeName', skipCount: 0, maxItems: 20, service: 'fake-service' } as ProcessQueryCloudRequestModel;
requestSpy.and.callFake(returnCallQueryParameters);
@@ -121,8 +137,14 @@ describe('ProcessListCloudService', () => {
it('should concat the sorting to append as parameters', async () => {
const processRequest = {
- appName: 'fakeName', skipCount: 0, maxItems: 20, service: 'fake-service',
- sorting: [{ orderBy: 'NAME', direction: 'DESC' }, { orderBy: 'TITLE', direction: 'ASC' }]
+ appName: 'fakeName',
+ skipCount: 0,
+ maxItems: 20,
+ service: 'fake-service',
+ sorting: [
+ { orderBy: 'NAME', direction: 'DESC' },
+ { orderBy: 'TITLE', direction: 'ASC' }
+ ]
} as ProcessQueryCloudRequestModel;
requestSpy.and.callFake(returnCallQueryParameters);
const request = await service.getAdminProcessByRequest(processRequest).toPromise();
@@ -140,7 +162,7 @@ describe('ProcessListCloudService', () => {
await service.getAdminProcessByRequest(processRequest).toPromise();
fail('Should have thrown error');
- } catch(error) {
+ } catch (error) {
expect(error).toBe('Appname not configured');
}
});
@@ -155,7 +177,13 @@ describe('ProcessListCloudService', () => {
});
it('should not have variable keys as part of query parameters', async () => {
- const processRequest = { appName: 'fakeName', skipCount: 0, maxItems: 20, service: 'fake-service', variableKeys: ['test-one', 'test-two'] } as ProcessQueryCloudRequestModel;
+ const processRequest = {
+ appName: 'fakeName',
+ skipCount: 0,
+ maxItems: 20,
+ service: 'fake-service',
+ variableKeys: ['test-one', 'test-two']
+ } as ProcessQueryCloudRequestModel;
requestSpy.and.callFake(returnCallQueryParameters);
const requestParams = await service.getAdminProcessByRequest(processRequest).toPromise();
@@ -165,7 +193,13 @@ describe('ProcessListCloudService', () => {
});
it('should send right variable keys as post body', async () => {
- const processRequest = { appName: 'fakeName', skipCount: 0, maxItems: 20, service: 'fake-service', variableKeys: ['test-one', 'test-two'] } as ProcessQueryCloudRequestModel;
+ const processRequest = {
+ appName: 'fakeName',
+ skipCount: 0,
+ maxItems: 20,
+ service: 'fake-service',
+ variableKeys: ['test-one', 'test-two']
+ } as ProcessQueryCloudRequestModel;
requestSpy.and.callFake(returnCallBody);
const requestBodyParams = await service.getAdminProcessByRequest(processRequest).toPromise();
diff --git a/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.ts b/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.ts
index abf51a84a5..a51647e2e7 100644
--- a/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.ts
+++ b/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.ts
@@ -67,6 +67,48 @@ export class ProcessListCloudService extends BaseCloudService {
return this.getProcess(callback, defaultQueryUrl, requestNode, queryUrl);
}
+ /**
+ * Finds a process using an object with optional query properties.
+ *
+ * @param appName app name
+ * @param status filter status
+ * @returns Total items
+ */
+ getProcessCounter(appName: string, status: string): Observable {
+ const callback = (url: string, queryParams: any) => this.get(url, queryParams);
+ let queryUrl: string;
+ const defaultQueryUrl = 'query/v1/process-instances';
+ const requestNode: ProcessQueryCloudRequestModel = {
+ appName,
+ appVersion: '',
+ initiator: null,
+ id: '',
+ name: null,
+ processDefinitionId: '',
+ processDefinitionName: null,
+ processDefinitionKey: '',
+ status,
+ businessKey: '',
+ startFrom: null,
+ startTo: null,
+ completedFrom: null,
+ completedTo: null,
+ suspendedFrom: null,
+ suspendedTo: null,
+ completedDate: '',
+ maxItems: 1,
+ skipCount: 0,
+ sorting: [
+ {
+ orderBy: 'startDate',
+ direction: 'DESC'
+ }
+ ]
+ };
+
+ return this.getProcess(callback, defaultQueryUrl, requestNode, queryUrl).pipe(map((tasks) => tasks?.list?.pagination?.totalItems));
+ }
+
/**
* Finds a process using an object with optional query properties in admin app.
*
diff --git a/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.spec.ts
index d2a1ea0844..a8ebd1a0da 100755
--- a/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.spec.ts
+++ b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.spec.ts
@@ -49,7 +49,8 @@ import { ProcessServiceCloudTestingModule } from '../../../testing/process-servi
import { ProcessNameCloudPipe } from '../../../pipes/process-name-cloud.pipe';
import { ProcessInstanceCloud } from '../models/process-instance-cloud.model';
import { ESCAPE } from '@angular/cdk/keycodes';
-import { ProcessDefinitionCloud, TaskVariableCloud } from '@alfresco/adf-process-services-cloud';
+import { ProcessDefinitionCloud } from '../../../models/process-definition-cloud.model';
+import { TaskVariableCloud } from '../../../form/models/task-variable-cloud.model';
import { first } from 'rxjs/operators';
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters-cloud.component.ts
index 952afc891a..3a0eab84b1 100644
--- a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters-cloud.component.ts
+++ b/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters-cloud.component.ts
@@ -52,7 +52,7 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp
filters: TaskFilterCloudModel[] = [];
currentFilter: TaskFilterCloudModel;
enableNotifications: boolean;
- currentFiltersValues = {};
+ currentFiltersValues: { [key: string]: number } = {};
private readonly taskFilterCloudService = inject(TaskFilterCloudService);
private readonly translationService = inject(TranslationService);
diff --git a/lib/process-services-cloud/src/lib/task/task-filters/mock/task-filters-cloud.mock.ts b/lib/process-services-cloud/src/lib/task/task-filters/mock/task-filters-cloud.mock.ts
index 23e165e76e..a8f183b451 100644
--- a/lib/process-services-cloud/src/lib/task/task-filters/mock/task-filters-cloud.mock.ts
+++ b/lib/process-services-cloud/src/lib/task/task-filters/mock/task-filters-cloud.mock.ts
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-import { TaskDetailsCloudModel } from '@alfresco/adf-process-services-cloud';
+import { TaskDetailsCloudModel } from '../../../task/start-task/models/task-details-cloud.model';
import { assignedTaskDetailsCloudMock } from '../../task-header/mocks/task-details-cloud.mock';
import { TaskFilterCloudModel, ServiceTaskFilterCloudModel, AssignmentType, TaskStatusFilter } from '../models/filter-cloud.model';