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';