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
This commit is contained in:
tomasz hanaj
2024-09-19 15:51:59 +02:00
committed by GitHub
parent 797b800bd6
commit 1ff5e8f43c
15 changed files with 430 additions and 66 deletions

View File

@@ -15,13 +15,18 @@
* 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;
beforeEach(() => {
appListCloudModule = new AppListCloudModule();
TestBed.configureTestingModule({
imports: [AppListCloudModule, NoopTranslateModule]
});
appListCloudModule = TestBed.inject(AppListCloudModule);
});
it('should create an instance', () => {

View File

@@ -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);
});

View File

@@ -9,6 +9,7 @@
[class.adf-active]="currentFilter === filter"
>
<div class="adf-process-filters__entry">
<div>
<adf-icon
data-automation-id="adf-filter-icon"
*ngIf="showIcons"
@@ -20,6 +21,15 @@
{{ filter.name | translate }}
</span>
</div>
<span
*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 }}
</span>
</div>
</button>
</mat-action-list>
<ng-template #loading>

View File

@@ -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 {

View File

@@ -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<ProcessFiltersCloudComponent>;
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);
});
});
});

View File

@@ -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<any>();
/** Emitted when filter is updated. */
@Output()
updatedFilter: EventEmitter<string> = new EventEmitter<string>();
filters$: Observable<ProcessFilterCloudModel[]>;
currentFilter?: ProcessFilterCloudModel;
filters: ProcessFilterCloudModel[] = [];
counters$: { [key: string]: Observable<number> } = {};
enableNotifications: boolean;
currentFiltersValues: { [key: string]: number } = {};
updatedFiltersSet = new Set<string>();
private onDestroy$ = new Subject<boolean>();
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);
}
}

View File

@@ -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
}
};

View File

@@ -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');
});
});

View File

@@ -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<PreferenceCloudServiceInterface>(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<TaskCloudEngineEvent[]> {
return this.notificationCloudService
.makeGQLQuery(appName, PROCESS_EVENT_SUBSCRIPTION_QUERY)
.pipe(map((events: any) => events?.data?.engineEvents));
}
}

View File

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

View File

@@ -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) => {
@@ -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();
@@ -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();

View File

@@ -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<any> {
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.
*

View File

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

View File

@@ -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);

View File

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