AAE-27327 New process search API (#10365)

* AAE-27327 New process search API

* fix lint issue
This commit is contained in:
Robert Duda 2024-11-04 13:37:37 +01:00 committed by GitHub
parent f07636e297
commit 3bcaeef682
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 1692 additions and 954 deletions

View File

@ -80,6 +80,10 @@ when the process list is empty:
| stickyHeader | `boolean` | false | Toggles the sticky header mode. | | stickyHeader | `boolean` | false | Toggles the sticky header mode. |
| suspendedFrom | `string` | "" | Filter the processes. Display only process with suspendedFrom equal to the supplied date. | | suspendedFrom | `string` | "" | Filter the processes. Display only process with suspendedFrom equal to the supplied date. |
| suspendedTo | `string` | "" | Filter the processes. Display only process with suspendedTo equal to the supplied date. | | suspendedTo | `string` | "" | Filter the processes. Display only process with suspendedTo equal to the supplied date. |
| names | `string[]` | [] | Filter the processes. Display only processes with names matching any of the supplied strings. This input will be used only if `PROCESS_SEARCH_API_METHOD_TOKEN` is provided with `POST` value. |
initiators | `string[]` | [] | Filter the processes. Display only processes started by any of the users whose usernames are present in the array. This input will be used only if `PROCESS_SEARCH_API_METHOD_TOKEN` is provided with `POST` value. |
| appVersions | `string[]` | [] | Filter the processes. Display only processes present in any of the specified app versions. This input will be used only if `PROCESS_SEARCH_API_METHOD_TOKEN` is provided with `POST` value. |
| statuses | `string[]` | [] | Filter the processes. Display only processes with provided statuses. This input will be used only if `PROCESS_SEARCH_API_METHOD_TOKEN` is provided with `POST` value. |
### Events ### Events

View File

@ -21,7 +21,7 @@ import { of, throwError } from 'rxjs';
import { ProcessFilterCloudService } from '../services/process-filter-cloud.service'; import { ProcessFilterCloudService } from '../services/process-filter-cloud.service';
import { ProcessFiltersCloudComponent } from './process-filters-cloud.component'; import { ProcessFiltersCloudComponent } from './process-filters-cloud.component';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { PROCESS_FILTERS_SERVICE_TOKEN } from '../../../services/cloud-token.service'; import { PROCESS_FILTERS_SERVICE_TOKEN, PROCESS_SEARCH_API_METHOD_TOKEN } from '../../../services/cloud-token.service';
import { LocalPreferenceCloudService } from '../../../services/local-preference-cloud.service'; import { LocalPreferenceCloudService } from '../../../services/local-preference-cloud.service';
import { mockProcessFilters } from '../mock/process-filters-cloud.mock'; import { mockProcessFilters } from '../mock/process-filters-cloud.mock';
import { AppConfigService, AppConfigServiceMock, NoopTranslateModule } from '@alfresco/adf-core'; import { AppConfigService, AppConfigServiceMock, NoopTranslateModule } from '@alfresco/adf-core';
@ -44,16 +44,20 @@ describe('ProcessFiltersCloudComponent', () => {
let getProcessFiltersSpy: jasmine.Spy; let getProcessFiltersSpy: jasmine.Spy;
let getProcessNotificationSubscriptionSpy: jasmine.Spy; let getProcessNotificationSubscriptionSpy: jasmine.Spy;
beforeEach(() => { const configureTestingModule = (providers: any[]) => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [NoopTranslateModule, NoopAnimationsModule, MatListModule], imports: [NoopTranslateModule, NoopAnimationsModule, MatListModule],
providers: [ providers: [
{ provide: PROCESS_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService }, { provide: PROCESS_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService },
{ provide: AppConfigService, useClass: AppConfigServiceMock }, { provide: AppConfigService, useClass: AppConfigServiceMock },
{ provide: ProcessListCloudService, useValue: { getProcessCounter: () => of(10) } }, { provide: ProcessListCloudService, useValue: {
getProcessCounter: () => of(10),
getProcessListCounter: () => of(10)
}},
{ provide: ProcessFilterCloudService, useValue: ProcessFilterCloudServiceMock }, { provide: ProcessFilterCloudService, useValue: ProcessFilterCloudServiceMock },
NotificationCloudService, NotificationCloudService,
ApolloModule ApolloModule,
...providers
] ]
}); });
fixture = TestBed.createComponent(ProcessFiltersCloudComponent); fixture = TestBed.createComponent(ProcessFiltersCloudComponent);
@ -62,400 +66,580 @@ describe('ProcessFiltersCloudComponent', () => {
processFilterService = TestBed.inject(ProcessFilterCloudService); processFilterService = TestBed.inject(ProcessFilterCloudService);
getProcessFiltersSpy = spyOn(processFilterService, 'getProcessFilters').and.returnValue(of(mockProcessFilters)); getProcessFiltersSpy = spyOn(processFilterService, 'getProcessFilters').and.returnValue(of(mockProcessFilters));
getProcessNotificationSubscriptionSpy = spyOn(processFilterService, 'getProcessNotificationSubscription').and.returnValue(of([])); getProcessNotificationSubscriptionSpy = spyOn(processFilterService, 'getProcessNotificationSubscription').and.returnValue(of([]));
}); };
afterEach(() => { afterEach(() => {
fixture.destroy(); fixture.destroy();
}); });
it('should attach specific icon for each filter if hasIcon is true', async () => { describe('PROCESS_SEARCH_API_METHOD_TOKEN injected with GET value', () => {
const change = new SimpleChange(undefined, 'my-app-1', true); beforeEach(() => {
component.ngOnChanges({ appName: change }); configureTestingModule([{ provide: PROCESS_SEARCH_API_METHOD_TOKEN, useValue: 'GET' }]);
fixture.detectChanges();
await fixture.whenStable();
component.showIcons = true;
fixture.detectChanges();
await fixture.whenStable();
expect(component.filters.length).toBe(3);
const filters = fixture.nativeElement.querySelectorAll('.adf-icon');
expect(filters.length).toBe(3);
expect(filters[0].innerText).toContain('adjust');
expect(filters[1].innerText).toContain('inbox');
expect(filters[2].innerText).toContain('done');
});
it('should not attach icons for each filter if hasIcon is false', async () => {
component.showIcons = false;
const change = new SimpleChange(undefined, 'my-app-1', true);
component.ngOnChanges({ appName: change });
fixture.detectChanges();
await fixture.whenStable();
const filters: any = fixture.debugElement.queryAll(By.css('.adf-icon'));
expect(filters.length).toBe(0);
});
it('should display the filters', async () => {
const change = new SimpleChange(undefined, 'my-app-1', true);
component.ngOnChanges({ appName: change });
fixture.detectChanges();
await fixture.whenStable();
component.showIcons = true;
fixture.detectChanges();
await fixture.whenStable();
const filters = fixture.debugElement.queryAll(By.css('.adf-process-filters__entry'));
expect(component.filters.length).toBe(3);
expect(filters.length).toBe(3);
expect(filters[0].nativeElement.innerText).toContain('FakeAllProcesses');
expect(filters[1].nativeElement.innerText).toContain('FakeRunningProcesses');
expect(filters[2].nativeElement.innerText).toContain('FakeCompletedProcesses');
expect(Object.keys(component.counters).length).toBe(3);
});
it('should emit an error with a bad response', () => {
getProcessFiltersSpy.and.returnValue(throwError('wrong request'));
const appName = 'my-app-1';
const change = new SimpleChange(null, appName, true);
let lastValue: any;
component.error.subscribe((err) => (lastValue = err));
component.ngOnChanges({ appName: change });
fixture.detectChanges();
expect(lastValue).toBeDefined();
});
it('should emit success with the filters when filters are loaded', async () => {
const successSpy = spyOn(component.success, 'emit');
const appName = 'my-app-1';
const change = new SimpleChange(null, appName, true);
component.ngOnChanges({ appName: change });
fixture.detectChanges();
await fixture.whenStable();
expect(successSpy).toHaveBeenCalledWith(mockProcessFilters);
expect(component.filters).toBeDefined();
expect(component.filters[0].name).toEqual('FakeAllProcesses');
expect(component.filters[1].name).toEqual('FakeRunningProcesses');
expect(component.filters[2].name).toEqual('FakeCompletedProcesses');
expect(Object.keys(component.counters).length).toBe(3);
});
it('should not select any filter as default', async () => {
const appName = 'my-app-1';
const change = new SimpleChange(null, appName, true);
component.ngOnChanges({ appName: change });
fixture.detectChanges();
await fixture.whenStable();
expect(component.currentFilter).toBeUndefined();
});
it('should not select any process filter if filter input does not exist', async () => {
const change = new SimpleChange(null, { name: 'nonexistentFilter' }, true);
fixture.detectChanges();
await fixture.whenStable();
component.ngOnChanges({ filterParam: change });
expect(component.currentFilter).toBeUndefined();
});
it('should select the filter based on the input by name param', async () => {
const filterSelectedSpy = spyOn(component.filterSelected, 'emit');
const change = new SimpleChange(null, { name: 'FakeRunningProcesses' }, true);
fixture.detectChanges();
await fixture.whenStable();
component.ngOnChanges({ filterParam: change });
expect(component.currentFilter).toEqual(mockProcessFilters[1]);
expect(filterSelectedSpy).toHaveBeenCalledWith(mockProcessFilters[1]);
});
it('should select the filter based on the input by key param', async () => {
const filterSelectedSpy = spyOn(component.filterSelected, 'emit');
const change = new SimpleChange(null, { key: 'completed-processes' }, true);
fixture.detectChanges();
await fixture.whenStable();
component.ngOnChanges({ filterParam: change });
expect(component.currentFilter).toEqual(mockProcessFilters[2]);
expect(filterSelectedSpy).toHaveBeenCalledWith(mockProcessFilters[2]);
});
it('should select the filter based on the input by index param', async () => {
const filterSelectedSpy = spyOn(component.filterSelected, 'emit');
const change = new SimpleChange(null, { index: 2 }, true);
fixture.detectChanges();
await fixture.whenStable();
component.ngOnChanges({ filterParam: change });
expect(component.currentFilter).toEqual(mockProcessFilters[2]);
expect(filterSelectedSpy).toHaveBeenCalledWith(mockProcessFilters[2]);
});
it('should select the filter based on the input by id param', async () => {
const filterSelectedSpy = spyOn(component.filterSelected, 'emit');
const change = new SimpleChange(null, { id: '12' }, true);
fixture.detectChanges();
await fixture.whenStable();
component.ngOnChanges({ filterParam: change });
expect(component.currentFilter).toEqual(mockProcessFilters[2]);
expect(filterSelectedSpy).toHaveBeenCalledWith(mockProcessFilters[2]);
});
it('should filterClicked emit when a filter is clicked from the UI', async () => {
const filterClickedSpy = spyOn(component.filterClicked, 'emit');
const appName = 'my-app-1';
const change = new SimpleChange(null, appName, true);
component.ngOnChanges({ appName: change });
fixture.detectChanges();
await fixture.whenStable();
const filterButton = fixture.debugElement.nativeElement.querySelector(`[data-automation-id="${mockProcessFilters[0].key}_filter"]`);
filterButton.click();
fixture.detectChanges();
await fixture.whenStable();
expect(component.currentFilter).toEqual(mockProcessFilters[0]);
expect(filterClickedSpy).toHaveBeenCalledWith(mockProcessFilters[0]);
});
it('should reset the filter when the param is undefined', () => {
const change = new SimpleChange(mockProcessFilters[0], undefined, false);
component.currentFilter = mockProcessFilters[0];
component.ngOnChanges({ filterParam: change });
expect(component.currentFilter).toEqual(undefined);
});
it('should not emit a filter clicked event when a filter is selected through the filterParam input (filterClicked emits only through a UI click action)', async () => {
const filterClickedSpy = spyOn(component.filterClicked, 'emit');
const change = new SimpleChange(null, { id: '10' }, true);
fixture.detectChanges();
await fixture.whenStable();
component.ngOnChanges({ filterParam: change });
expect(component.currentFilter).toBe(mockProcessFilters[0]);
expect(filterClickedSpy).not.toHaveBeenCalled();
});
it('should reload filters by appName on binding changes', () => {
spyOn(component, 'getFilters').and.stub();
const appName = 'my-app-1';
const change = new SimpleChange(null, appName, true);
component.ngOnChanges({ appName: change });
expect(component.getFilters).toHaveBeenCalledWith(appName);
});
it('should not reload filters by appName null on binding changes', () => {
spyOn(component, 'getFilters').and.stub();
const appName = null;
const change = new SimpleChange(undefined, appName, true);
component.ngOnChanges({ appName: change });
expect(component.getFilters).not.toHaveBeenCalledWith(appName);
});
it('should reload filters by app name on binding changes', () => {
spyOn(component, 'getFilters').and.stub();
const appName = 'fake-app-name';
const change = new SimpleChange(null, appName, true);
component.ngOnChanges({ appName: change });
expect(component.getFilters).toHaveBeenCalledWith(appName);
});
it('should return the current filter after one is selected', () => {
const filter = mockProcessFilters[1];
component.filters = mockProcessFilters;
expect(component.currentFilter).toBeUndefined();
component.selectFilter({ id: filter.id });
expect(component.getCurrentFilter()).toBe(filter);
});
it('should remove key from set of updated filters when received refreshed filter key', async () => {
const filterKeyTest = 'filter-key-test';
component.updatedFiltersSet.add(filterKeyTest);
expect(component.updatedFiltersSet.size).toBe(1);
processFilterService.filterKeyToBeRefreshed$ = of(filterKeyTest);
fixture.detectChanges();
expect(component.updatedFiltersSet.size).toBe(0);
});
describe('Highlight Selected Filter', () => {
const allProcessesFilterKey = mockProcessFilters[0].key;
const runningProcessesFilterKey = mockProcessFilters[1].key;
const completedProcessesFilterKey = mockProcessFilters[2].key;
const getActiveFilterElement = (filterKey: string): Element => {
const activeFilter = fixture.debugElement.query(By.css(`.adf-active`));
return activeFilter.nativeElement.querySelector(`[data-automation-id="${filterKey}_filter"]`);
};
const clickOnFilter = async (filterKey: string) => {
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 });
fixture.detectChanges();
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();
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeDefined();
}); });
it('Should apply active CSS class when filterParam input changed', async () => { it('should attach specific icon for each filter if hasIcon is true', async () => {
fixture.detectChanges(); const change = new SimpleChange(undefined, 'my-app-1', true);
component.ngOnChanges({ filterParam: new SimpleChange(null, { key: allProcessesFilterKey }, true) }); component.ngOnChanges({ appName: change });
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); await fixture.whenStable();
expect(getActiveFilterElement(allProcessesFilterKey)).toBeDefined(); component.showIcons = true;
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeNull();
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeNull();
component.ngOnChanges({ filterParam: new SimpleChange(null, { key: runningProcessesFilterKey }, true) });
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); await fixture.whenStable();
expect(getActiveFilterElement(allProcessesFilterKey)).toBeNull(); expect(component.filters.length).toBe(3);
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeDefined(); const filters = fixture.nativeElement.querySelectorAll('.adf-icon');
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeNull(); expect(filters.length).toBe(3);
expect(filters[0].innerText).toContain('adjust');
expect(filters[1].innerText).toContain('inbox');
expect(filters[2].innerText).toContain('done');
});
it('should not attach icons for each filter if hasIcon is false', async () => {
component.showIcons = false;
const change = new SimpleChange(undefined, 'my-app-1', true);
component.ngOnChanges({ appName: change });
component.ngOnChanges({ filterParam: new SimpleChange(null, { key: completedProcessesFilterKey }, true) });
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); await fixture.whenStable();
expect(getActiveFilterElement(allProcessesFilterKey)).toBeNull(); const filters: any = fixture.debugElement.queryAll(By.css('.adf-icon'));
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeNull(); expect(filters.length).toBe(0);
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeDefined();
}); });
it('should made sbscription', () => { it('should display the filters', async () => {
component.enableNotifications = true; const change = new SimpleChange(undefined, 'my-app-1', true);
component.appName = 'mock-app-name'; component.ngOnChanges({ appName: change });
const appNameChange = new SimpleChange(null, 'mock-app-name', true);
component.ngOnChanges({ appName: appNameChange });
fixture.detectChanges(); fixture.detectChanges();
expect(getProcessNotificationSubscriptionSpy).toHaveBeenCalled(); await fixture.whenStable();
component.showIcons = true;
fixture.detectChanges();
await fixture.whenStable();
const filters = fixture.debugElement.queryAll(By.css('.adf-process-filters__entry'));
expect(component.filters.length).toBe(3);
expect(filters.length).toBe(3);
expect(filters[0].nativeElement.innerText).toContain('FakeAllProcesses');
expect(filters[1].nativeElement.innerText).toContain('FakeRunningProcesses');
expect(filters[2].nativeElement.innerText).toContain('FakeCompletedProcesses');
expect(Object.keys(component.counters).length).toBe(3);
}); });
it('should not emit filter key when filter counter is set for first time', () => { it('should emit success with the filters when filters are loaded', async () => {
component.currentFiltersValues = {}; const successSpy = spyOn(component.success, 'emit');
const fakeFilterKey = 'testKey'; const appName = 'my-app-1';
const fakeFilterValue = 10; const change = new SimpleChange(null, appName, true);
const updatedFilterSpy = spyOn(component.updatedFilter, 'emit');
component.checkIfFilterValuesHasBeenUpdated(fakeFilterKey, fakeFilterValue);
fixture.detectChanges();
expect(component.currentFiltersValues).not.toEqual({}); component.ngOnChanges({ appName: change });
expect(component.currentFiltersValues[fakeFilterKey]).toBe(fakeFilterValue); fixture.detectChanges();
expect(updatedFilterSpy).not.toHaveBeenCalled(); await fixture.whenStable();
expect(successSpy).toHaveBeenCalledWith(mockProcessFilters);
expect(component.filters).toBeDefined();
expect(component.filters[0].name).toEqual('FakeAllProcesses');
expect(component.filters[1].name).toEqual('FakeRunningProcesses');
expect(component.filters[2].name).toEqual('FakeCompletedProcesses');
expect(Object.keys(component.counters).length).toBe(3);
}); });
it('should not emit filter key when filter counter has not changd', () => { it('should not select any filter as default', async () => {
component.currentFiltersValues = {}; const appName = 'my-app-1';
const fakeFilterKey = 'testKey'; const change = new SimpleChange(null, appName, true);
const fakeFilterValue = 10;
const updatedFilterSpy = spyOn(component.updatedFilter, 'emit'); component.ngOnChanges({ appName: change });
component.checkIfFilterValuesHasBeenUpdated(fakeFilterKey, fakeFilterValue);
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable();
expect(component.currentFiltersValues).not.toEqual({}); expect(component.currentFilter).toBeUndefined();
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', () => { it('should filterClicked emit when a filter is clicked from the UI', async () => {
component.currentFiltersValues = {}; const filterClickedSpy = spyOn(component.filterClicked, 'emit');
const fakeFilterKey = 'testKey'; const appName = 'my-app-1';
const updatedFilterSpy = spyOn(component.updatedFilter, 'emit'); const change = new SimpleChange(null, appName, true);
component.checkIfFilterValuesHasBeenUpdated(fakeFilterKey, 10); component.ngOnChanges({ appName: change });
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable();
expect(updatedFilterSpy).not.toHaveBeenCalledWith(fakeFilterKey); const filterButton = fixture.debugElement.nativeElement.querySelector(`[data-automation-id="${mockProcessFilters[0].key}_filter"]`);
expect(component.currentFiltersValues[fakeFilterKey]).toBe(10); filterButton.click();
component.checkIfFilterValuesHasBeenUpdated(fakeFilterKey, 20);
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable();
expect(updatedFilterSpy).toHaveBeenCalledWith(fakeFilterKey); expect(component.currentFilter).toEqual(mockProcessFilters[0]);
expect(component.currentFiltersValues[fakeFilterKey]).toBe(20); expect(filterClickedSpy).toHaveBeenCalledWith(mockProcessFilters[0]);
}); });
it('should emit filter key when filter counter is decreased', () => { describe('Highlight Selected Filter', () => {
component.currentFiltersValues = {}; const allProcessesFilterKey = mockProcessFilters[0].key;
const fakeFilterKey = 'testKey'; const runningProcessesFilterKey = mockProcessFilters[1].key;
const updatedFilterSpy = spyOn(component.updatedFilter, 'emit'); const completedProcessesFilterKey = mockProcessFilters[2].key;
component.checkIfFilterValuesHasBeenUpdated(fakeFilterKey, 10);
const getActiveFilterElement = (filterKey: string): Element => {
const activeFilter = fixture.debugElement.query(By.css(`.adf-active`));
return activeFilter.nativeElement.querySelector(`[data-automation-id="${filterKey}_filter"]`);
};
const clickOnFilter = async (filterKey: string) => {
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 });
fixture.detectChanges();
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();
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeDefined();
});
});
});
describe('PROCESS_SEARCH_API_METHOD_TOKEN injected with POST value', () => {
beforeEach(() => {
configureTestingModule([{ provide: PROCESS_SEARCH_API_METHOD_TOKEN, useValue: 'POST' }]);
});
it('should attach specific icon for each filter if hasIcon is true', async () => {
const change = new SimpleChange(undefined, 'my-app-1', true);
component.ngOnChanges({ appName: change });
fixture.detectChanges();
await fixture.whenStable();
component.showIcons = true;
fixture.detectChanges();
await fixture.whenStable();
expect(component.filters.length).toBe(3);
const filters = fixture.nativeElement.querySelectorAll('.adf-icon');
expect(filters.length).toBe(3);
expect(filters[0].innerText).toContain('adjust');
expect(filters[1].innerText).toContain('inbox');
expect(filters[2].innerText).toContain('done');
});
it('should not attach icons for each filter if hasIcon is false', async () => {
component.showIcons = false;
const change = new SimpleChange(undefined, 'my-app-1', true);
component.ngOnChanges({ appName: change });
fixture.detectChanges();
await fixture.whenStable();
const filters: any = fixture.debugElement.queryAll(By.css('.adf-icon'));
expect(filters.length).toBe(0);
});
it('should display the filters', async () => {
const change = new SimpleChange(undefined, 'my-app-1', true);
component.ngOnChanges({ appName: change });
fixture.detectChanges();
await fixture.whenStable();
component.showIcons = true;
fixture.detectChanges();
await fixture.whenStable();
const filters = fixture.debugElement.queryAll(By.css('.adf-process-filters__entry'));
expect(component.filters.length).toBe(3);
expect(filters.length).toBe(3);
expect(filters[0].nativeElement.innerText).toContain('FakeAllProcesses');
expect(filters[1].nativeElement.innerText).toContain('FakeRunningProcesses');
expect(filters[2].nativeElement.innerText).toContain('FakeCompletedProcesses');
expect(Object.keys(component.counters).length).toBe(3);
});
it('should emit success with the filters when filters are loaded', async () => {
const successSpy = spyOn(component.success, 'emit');
const appName = 'my-app-1';
const change = new SimpleChange(null, appName, true);
component.ngOnChanges({ appName: change });
fixture.detectChanges();
await fixture.whenStable();
expect(successSpy).toHaveBeenCalledWith(mockProcessFilters);
expect(component.filters).toBeDefined();
expect(component.filters[0].name).toEqual('FakeAllProcesses');
expect(component.filters[1].name).toEqual('FakeRunningProcesses');
expect(component.filters[2].name).toEqual('FakeCompletedProcesses');
expect(Object.keys(component.counters).length).toBe(3);
});
it('should not select any filter as default', async () => {
const appName = 'my-app-1';
const change = new SimpleChange(null, appName, true);
component.ngOnChanges({ appName: change });
fixture.detectChanges();
await fixture.whenStable();
expect(component.currentFilter).toBeUndefined();
});
it('should filterClicked emit when a filter is clicked from the UI', async () => {
const filterClickedSpy = spyOn(component.filterClicked, 'emit');
const appName = 'my-app-1';
const change = new SimpleChange(null, appName, true);
component.ngOnChanges({ appName: change });
fixture.detectChanges();
await fixture.whenStable();
const filterButton = fixture.debugElement.nativeElement.querySelector(`[data-automation-id="${mockProcessFilters[0].key}_filter"]`);
filterButton.click();
fixture.detectChanges();
await fixture.whenStable();
expect(component.currentFilter).toEqual(mockProcessFilters[0]);
expect(filterClickedSpy).toHaveBeenCalledWith(mockProcessFilters[0]);
});
describe('Highlight Selected Filter', () => {
const allProcessesFilterKey = mockProcessFilters[0].key;
const runningProcessesFilterKey = mockProcessFilters[1].key;
const completedProcessesFilterKey = mockProcessFilters[2].key;
const getActiveFilterElement = (filterKey: string): Element => {
const activeFilter = fixture.debugElement.query(By.css(`.adf-active`));
return activeFilter.nativeElement.querySelector(`[data-automation-id="${filterKey}_filter"]`);
};
const clickOnFilter = async (filterKey: string) => {
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 });
fixture.detectChanges();
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();
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeDefined();
});
});
});
describe('API agnostic', () => {
beforeEach(() => {
configureTestingModule([]);
});
it('should emit an error with a bad response', () => {
getProcessFiltersSpy.and.returnValue(throwError('wrong request'));
const appName = 'my-app-1';
const change = new SimpleChange(null, appName, true);
let lastValue: any;
component.error.subscribe((err) => (lastValue = err));
component.ngOnChanges({ appName: change });
fixture.detectChanges();
expect(lastValue).toBeDefined();
});
it('should not select any process filter if filter input does not exist', async () => {
const change = new SimpleChange(null, { name: 'nonexistentFilter' }, true);
fixture.detectChanges();
await fixture.whenStable();
component.ngOnChanges({ filterParam: change });
expect(component.currentFilter).toBeUndefined();
});
it('should select the filter based on the input by name param', async () => {
const filterSelectedSpy = spyOn(component.filterSelected, 'emit');
const change = new SimpleChange(null, { name: 'FakeRunningProcesses' }, true);
fixture.detectChanges();
await fixture.whenStable();
component.ngOnChanges({ filterParam: change });
expect(component.currentFilter).toEqual(mockProcessFilters[1]);
expect(filterSelectedSpy).toHaveBeenCalledWith(mockProcessFilters[1]);
});
it('should select the filter based on the input by key param', async () => {
const filterSelectedSpy = spyOn(component.filterSelected, 'emit');
const change = new SimpleChange(null, { key: 'completed-processes' }, true);
fixture.detectChanges();
await fixture.whenStable();
component.ngOnChanges({ filterParam: change });
expect(component.currentFilter).toEqual(mockProcessFilters[2]);
expect(filterSelectedSpy).toHaveBeenCalledWith(mockProcessFilters[2]);
});
it('should select the filter based on the input by index param', async () => {
const filterSelectedSpy = spyOn(component.filterSelected, 'emit');
const change = new SimpleChange(null, { index: 2 }, true);
fixture.detectChanges();
await fixture.whenStable();
component.ngOnChanges({ filterParam: change });
expect(component.currentFilter).toEqual(mockProcessFilters[2]);
expect(filterSelectedSpy).toHaveBeenCalledWith(mockProcessFilters[2]);
});
it('should select the filter based on the input by id param', async () => {
const filterSelectedSpy = spyOn(component.filterSelected, 'emit');
const change = new SimpleChange(null, { id: '12' }, true);
fixture.detectChanges();
await fixture.whenStable();
component.ngOnChanges({ filterParam: change });
expect(component.currentFilter).toEqual(mockProcessFilters[2]);
expect(filterSelectedSpy).toHaveBeenCalledWith(mockProcessFilters[2]);
});
it('should reset the filter when the param is undefined', () => {
const change = new SimpleChange(mockProcessFilters[0], undefined, false);
component.currentFilter = mockProcessFilters[0];
component.ngOnChanges({ filterParam: change });
expect(component.currentFilter).toEqual(undefined);
});
it('should not emit a filter clicked event when a filter is selected through the filterParam input (filterClicked emits only through a UI click action)', async () => {
const filterClickedSpy = spyOn(component.filterClicked, 'emit');
const change = new SimpleChange(null, { id: '10' }, true);
fixture.detectChanges();
await fixture.whenStable();
component.ngOnChanges({ filterParam: change });
expect(component.currentFilter).toBe(mockProcessFilters[0]);
expect(filterClickedSpy).not.toHaveBeenCalled();
});
it('should reload filters by appName on binding changes', () => {
spyOn(component, 'getFilters').and.stub();
const appName = 'my-app-1';
const change = new SimpleChange(null, appName, true);
component.ngOnChanges({ appName: change });
expect(component.getFilters).toHaveBeenCalledWith(appName);
});
it('should not reload filters by appName null on binding changes', () => {
spyOn(component, 'getFilters').and.stub();
const appName = null;
const change = new SimpleChange(undefined, appName, true);
component.ngOnChanges({ appName: change });
expect(component.getFilters).not.toHaveBeenCalledWith(appName);
});
it('should reload filters by app name on binding changes', () => {
spyOn(component, 'getFilters').and.stub();
const appName = 'fake-app-name';
const change = new SimpleChange(null, appName, true);
component.ngOnChanges({ appName: change });
expect(component.getFilters).toHaveBeenCalledWith(appName);
});
it('should return the current filter after one is selected', () => {
const filter = mockProcessFilters[1];
component.filters = mockProcessFilters;
expect(component.currentFilter).toBeUndefined();
component.selectFilter({ id: filter.id });
expect(component.getCurrentFilter()).toBe(filter);
});
it('should remove key from set of updated filters when received refreshed filter key', async () => {
const filterKeyTest = 'filter-key-test';
component.updatedFiltersSet.add(filterKeyTest);
expect(component.updatedFiltersSet.size).toBe(1);
processFilterService.filterKeyToBeRefreshed$ = of(filterKeyTest);
fixture.detectChanges(); fixture.detectChanges();
expect(updatedFilterSpy).not.toHaveBeenCalledWith(fakeFilterKey); expect(component.updatedFiltersSet.size).toBe(0);
expect(component.currentFiltersValues[fakeFilterKey]).toBe(10); });
component.checkIfFilterValuesHasBeenUpdated(fakeFilterKey, 5); describe('Highlight Selected Filter', () => {
fixture.detectChanges(); const allProcessesFilterKey = mockProcessFilters[0].key;
const runningProcessesFilterKey = mockProcessFilters[1].key;
const completedProcessesFilterKey = mockProcessFilters[2].key;
expect(updatedFilterSpy).toHaveBeenCalledWith(fakeFilterKey); const getActiveFilterElement = (filterKey: string): Element => {
expect(component.currentFiltersValues[fakeFilterKey]).toBe(5); const activeFilter = fixture.debugElement.query(By.css(`.adf-active`));
return activeFilter.nativeElement.querySelector(`[data-automation-id="${filterKey}_filter"]`);
};
it('Should apply active CSS class when filterParam input changed', async () => {
fixture.detectChanges();
component.ngOnChanges({ filterParam: new SimpleChange(null, { key: allProcessesFilterKey }, true) });
fixture.detectChanges();
await fixture.whenStable();
expect(getActiveFilterElement(allProcessesFilterKey)).toBeDefined();
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeNull();
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeNull();
component.ngOnChanges({ filterParam: new SimpleChange(null, { key: runningProcessesFilterKey }, true) });
fixture.detectChanges();
await fixture.whenStable();
expect(getActiveFilterElement(allProcessesFilterKey)).toBeNull();
expect(getActiveFilterElement(runningProcessesFilterKey)).toBeDefined();
expect(getActiveFilterElement(completedProcessesFilterKey)).toBeNull();
component.ngOnChanges({ filterParam: new SimpleChange(null, { key: completedProcessesFilterKey }, true) });
fixture.detectChanges();
await fixture.whenStable();
expect(getActiveFilterElement(allProcessesFilterKey)).toBeNull();
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

@ -23,6 +23,8 @@ import { AppConfigService, TranslationService } from '@alfresco/adf-core';
import { FilterParamsModel } from '../../../task/task-filters/models/filter-cloud.model'; import { FilterParamsModel } from '../../../task/task-filters/models/filter-cloud.model';
import { debounceTime, takeUntil, tap } from 'rxjs/operators'; import { debounceTime, takeUntil, tap } from 'rxjs/operators';
import { ProcessListCloudService } from '../../../process/process-list/services/process-list-cloud.service'; import { ProcessListCloudService } from '../../../process/process-list/services/process-list-cloud.service';
import { PROCESS_SEARCH_API_METHOD_TOKEN } from '../../../services/cloud-token.service';
import { ProcessFilterCloudAdapter } from '../../process-list/models/process-cloud-query-request.model';
@Component({ @Component({
selector: 'adf-cloud-process-filters', selector: 'adf-cloud-process-filters',
@ -77,6 +79,7 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges, OnDestro
private readonly translationService = inject(TranslationService); private readonly translationService = inject(TranslationService);
private readonly appConfigService = inject(AppConfigService); private readonly appConfigService = inject(AppConfigService);
private readonly processListCloudService = inject(ProcessListCloudService); private readonly processListCloudService = inject(ProcessListCloudService);
private readonly searchMethod = inject<'GET' | 'POST'>(PROCESS_SEARCH_API_METHOD_TOKEN, { optional: true });
ngOnInit() { ngOnInit() {
this.enableNotifications = this.appConfigService.get('notifications', true); this.enableNotifications = this.appConfigService.get('notifications', true);
@ -272,8 +275,7 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges, OnDestro
* @param filter filter * @param filter filter
*/ */
updateFilterCounter(filter: ProcessFilterCloudModel): void { updateFilterCounter(filter: ProcessFilterCloudModel): void {
this.processListCloudService this.fetchProcessFilterCounter(filter)
.getProcessCounter(filter.appName, filter.status)
.pipe( .pipe(
tap((filterCounter) => { tap((filterCounter) => {
this.checkIfFilterValuesHasBeenUpdated(filter.key, filterCounter); this.checkIfFilterValuesHasBeenUpdated(filter.key, filterCounter);
@ -312,4 +314,10 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges, OnDestro
this.updatedFiltersSet.delete(filterKey); this.updatedFiltersSet.delete(filterKey);
}); });
} }
private fetchProcessFilterCounter(filter: ProcessFilterCloudModel): Observable<number> {
return this.searchMethod === 'POST'
? this.processListCloudService.getProcessListCounter(new ProcessFilterCloudAdapter(filter))
: this.processListCloudService.getProcessCounter(filter.appName, filter.status)
}
} }

View File

@ -54,6 +54,7 @@ export const fakeProcessCloudFilters = [
export const mockProcessFilters: any[] = [ export const mockProcessFilters: any[] = [
{ {
appName: 'mock-app-name',
name: 'FakeAllProcesses', name: 'FakeAllProcesses',
key: 'FakeAllProcesses', key: 'FakeAllProcesses',
icon: 'adjust', icon: 'adjust',
@ -61,6 +62,7 @@ export const mockProcessFilters: any[] = [
status: '' status: ''
}, },
{ {
appName: 'mock-app-name',
name: 'FakeRunningProcesses', name: 'FakeRunningProcesses',
key: 'FakeRunningProcesses', key: 'FakeRunningProcesses',
icon: 'inbox', icon: 'inbox',
@ -68,6 +70,7 @@ export const mockProcessFilters: any[] = [
status: 'RUNNING' status: 'RUNNING'
}, },
{ {
appName: 'mock-app-name',
name: 'FakeCompletedProcesses', name: 'FakeCompletedProcesses',
key: 'completed-processes', key: 'completed-processes',
icon: 'done', icon: 'done',

View File

@ -48,6 +48,11 @@ export class ProcessFilterCloudModel {
completedDate: Date; completedDate: Date;
environmentId?: string; environmentId?: string;
processDefinitionNames: string[] | null;
initiators: string[] | null;
appVersions: string[] | null;
statuses: string[] | null;
private dateRangeFilterService = new DateRangeFilterService(); private dateRangeFilterService = new DateRangeFilterService();
private _completedFrom: string; private _completedFrom: string;
private _completedTo: string; private _completedTo: string;
@ -94,6 +99,11 @@ export class ProcessFilterCloudModel {
this.completedDate = obj.completedDate || null; this.completedDate = obj.completedDate || null;
this._suspendedFrom = obj._suspendedFrom || null; this._suspendedFrom = obj._suspendedFrom || null;
this._suspendedTo = obj._suspendedTo || null; this._suspendedTo = obj._suspendedTo || null;
this.processDefinitionNames = obj.processDefinitionNames || null;
this.initiators = obj.initiators || null;
this.appVersions = obj.appVersions || null;
this.statuses = obj.statuses || null;
} }
} }

View File

@ -86,16 +86,19 @@ describe('ProcessFilterCloudService', () => {
expect(res[0].id).toBe('1'); expect(res[0].id).toBe('1');
expect(res[0].name).toBe('MOCK_PROCESS_NAME_1'); expect(res[0].name).toBe('MOCK_PROCESS_NAME_1');
expect(res[0].status).toBe('MOCK_ALL'); expect(res[0].status).toBe('MOCK_ALL');
expect(res[0].statuses).toContain('MOCK_ALL');
expect(res[1].appName).toBe('mock-appName'); expect(res[1].appName).toBe('mock-appName');
expect(res[1].id).toBe('2'); expect(res[1].id).toBe('2');
expect(res[1].name).toBe('MOCK_PROCESS_NAME_2'); expect(res[1].name).toBe('MOCK_PROCESS_NAME_2');
expect(res[1].status).toBe('MOCK-RUNNING'); expect(res[1].status).toBe('MOCK-RUNNING');
expect(res[1].statuses).toContain('MOCK-RUNNING');
expect(res[2].appName).toBe('mock-appName'); expect(res[2].appName).toBe('mock-appName');
expect(res[2].id).toBe('3'); expect(res[2].id).toBe('3');
expect(res[2].name).toBe('MOCK_PROCESS_NAME_3'); expect(res[2].name).toBe('MOCK_PROCESS_NAME_3');
expect(res[2].status).toBe('MOCK-COMPLETED'); expect(res[2].status).toBe('MOCK-COMPLETED');
expect(res[2].statuses).toContain('MOCK-COMPLETED');
expect(createPreferenceSpy).toHaveBeenCalled(); expect(createPreferenceSpy).toHaveBeenCalled();
done(); done();
@ -112,16 +115,19 @@ describe('ProcessFilterCloudService', () => {
expect(res[0].id).toBe('1'); expect(res[0].id).toBe('1');
expect(res[0].name).toBe('MOCK_PROCESS_NAME_1'); expect(res[0].name).toBe('MOCK_PROCESS_NAME_1');
expect(res[0].status).toBe('MOCK_ALL'); expect(res[0].status).toBe('MOCK_ALL');
expect(res[0].statuses).toContain('MOCK_ALL');
expect(res[1].appName).toBe('mock-appName'); expect(res[1].appName).toBe('mock-appName');
expect(res[1].id).toBe('2'); expect(res[1].id).toBe('2');
expect(res[1].name).toBe('MOCK_PROCESS_NAME_2'); expect(res[1].name).toBe('MOCK_PROCESS_NAME_2');
expect(res[1].status).toBe('MOCK-RUNNING'); expect(res[1].status).toBe('MOCK-RUNNING');
expect(res[1].statuses).toContain('MOCK-RUNNING');
expect(res[2].appName).toBe('mock-appName'); expect(res[2].appName).toBe('mock-appName');
expect(res[2].id).toBe('3'); expect(res[2].id).toBe('3');
expect(res[2].name).toBe('MOCK_PROCESS_NAME_3'); expect(res[2].name).toBe('MOCK_PROCESS_NAME_3');
expect(res[2].status).toBe('MOCK-COMPLETED'); expect(res[2].status).toBe('MOCK-COMPLETED');
expect(res[2].statuses).toContain('MOCK-COMPLETED');
expect(getPreferencesSpy).toHaveBeenCalled(); expect(getPreferencesSpy).toHaveBeenCalled();
done(); done();
@ -140,16 +146,19 @@ describe('ProcessFilterCloudService', () => {
expect(res[0].id).toBe('1'); expect(res[0].id).toBe('1');
expect(res[0].name).toBe('MOCK_PROCESS_NAME_1'); expect(res[0].name).toBe('MOCK_PROCESS_NAME_1');
expect(res[0].status).toBe('MOCK_ALL'); expect(res[0].status).toBe('MOCK_ALL');
expect(res[0].statuses).toContain('MOCK_ALL');
expect(res[1].appName).toBe('mock-appName'); expect(res[1].appName).toBe('mock-appName');
expect(res[1].id).toBe('2'); expect(res[1].id).toBe('2');
expect(res[1].name).toBe('MOCK_PROCESS_NAME_2'); expect(res[1].name).toBe('MOCK_PROCESS_NAME_2');
expect(res[1].status).toBe('MOCK-RUNNING'); expect(res[1].status).toBe('MOCK-RUNNING');
expect(res[1].statuses).toContain('MOCK-RUNNING');
expect(res[2].appName).toBe('mock-appName'); expect(res[2].appName).toBe('mock-appName');
expect(res[2].id).toBe('3'); expect(res[2].id).toBe('3');
expect(res[2].name).toBe('MOCK_PROCESS_NAME_3'); expect(res[2].name).toBe('MOCK_PROCESS_NAME_3');
expect(res[2].status).toBe('MOCK-COMPLETED'); expect(res[2].status).toBe('MOCK-COMPLETED');
expect(res[2].statuses).toContain('MOCK-COMPLETED');
expect(getPreferencesSpy).toHaveBeenCalled(); expect(getPreferencesSpy).toHaveBeenCalled();
expect(createPreferenceSpy).toHaveBeenCalled(); expect(createPreferenceSpy).toHaveBeenCalled();

View File

@ -131,7 +131,8 @@ export class ProcessFilterCloudService {
} else { } else {
return of(this.findFiltersByKeyInPreferences(preferences, key)); return of(this.findFiltersByKeyInPreferences(preferences, key));
} }
}) }),
switchMap((filters) => this.handleCreateFilterBackwardsCompatibility(appName, key, filters))
) )
.subscribe((filters) => { .subscribe((filters) => {
this.addFiltersToStream(filters); this.addFiltersToStream(filters);
@ -414,4 +415,39 @@ export class ProcessFilterCloudService {
refreshFilter(filterKey: string): void { refreshFilter(filterKey: string): void {
this.filterKeyToBeRefreshedSource.next(filterKey); this.filterKeyToBeRefreshedSource.next(filterKey);
} }
/**
* This method is run after retrieving the filter array from preferences.
* It handles the backwards compatibility with the new API by looking for the new properties and their counterparts in each passed filter.
* If the new property is not found, it is created and assigned the value constructed from the old property.
* The filters are then updated in the preferences and returned.
* Old properties are left untouched for purposes like feature toggling.
*
* @param appName Name of the target app.
* @param key Key of the process filters.
* @param filters Array of process filters to be checked for backward compatibility.
* @returns Observable of process filters with updated properties.
*/
private handleCreateFilterBackwardsCompatibility(
appName: string,
key: string,
filters: ProcessFilterCloudModel[]
): Observable<ProcessFilterCloudModel[]> {
filters.forEach((filter) => {
if (filter.processDefinitionName && !filter.processDefinitionNames) {
filter.processDefinitionNames = [filter.processDefinitionName];
}
if (filter.initiator && !filter.initiators) {
filter.initiators = [filter.initiator];
}
if (filter.appVersion && !filter.appVersions) {
filter.appVersions = [filter.appVersion.toString()];
}
if (filter.status && !filter.statuses) {
filter.statuses = [filter.status];
}
});
return this.updateProcessFilters(appName, key, filters);
}
} }

View File

@ -27,7 +27,8 @@ import {
Input, Input,
ViewChild, ViewChild,
Inject, Inject,
OnDestroy OnDestroy,
Optional
} from '@angular/core'; } from '@angular/core';
import { import {
DataTableSchema, DataTableSchema,
@ -45,13 +46,13 @@ import {
DataColumn DataColumn
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { ProcessListCloudService } from '../services/process-list-cloud.service'; import { ProcessListCloudService } from '../services/process-list-cloud.service';
import { BehaviorSubject, Subject, of } from 'rxjs'; import { BehaviorSubject, Subject } from 'rxjs';
import { processCloudPresetsDefaultModel } from '../models/process-cloud-preset.model'; import { processCloudPresetsDefaultModel } from '../models/process-cloud-preset.model';
import { ProcessQueryCloudRequestModel } from '../models/process-cloud-query-request.model'; import { ProcessListRequestModel, ProcessQueryCloudRequestModel } from '../models/process-cloud-query-request.model';
import { ProcessListCloudSortingModel } from '../models/process-list-sorting.model'; import { ProcessListCloudSortingModel } from '../models/process-list-sorting.model';
import { map, switchMap, take, takeUntil, tap } from 'rxjs/operators'; import { filter, map, switchMap, take, takeUntil } from 'rxjs/operators';
import { PreferenceCloudServiceInterface } from '../../../services/preference-cloud.interface'; import { PreferenceCloudServiceInterface } from '../../../services/preference-cloud.interface';
import { PROCESS_LISTS_PREFERENCES_SERVICE_TOKEN } from '../../../services/cloud-token.service'; import { PROCESS_LISTS_PREFERENCES_SERVICE_TOKEN, PROCESS_SEARCH_API_METHOD_TOKEN } from '../../../services/cloud-token.service';
import { ProcessListCloudPreferences } from '../models/process-cloud-preferences'; import { ProcessListCloudPreferences } from '../models/process-cloud-preferences';
import { ProcessListDatatableAdapter } from '../datatable/process-list-datatable-adapter'; import { ProcessListDatatableAdapter } from '../datatable/process-list-datatable-adapter';
import { ProcessListDataColumnCustomData, PROCESS_LIST_CUSTOM_VARIABLE_COLUMN } from '../../../models/data-column-custom-data'; import { ProcessListDataColumnCustomData, PROCESS_LIST_CUSTOM_VARIABLE_COLUMN } from '../../../models/data-column-custom-data';
@ -66,10 +67,8 @@ const PRESET_KEY = 'adf-cloud-process-list.presets';
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
export class ProcessListCloudComponent export class ProcessListCloudComponent
extends DataTableSchema<ProcessListDataColumnCustomData> extends DataTableSchema<ProcessListDataColumnCustomData>
implements OnChanges, AfterContentInit, PaginatedComponent, OnDestroy implements OnChanges, AfterContentInit, PaginatedComponent, OnDestroy {
// eslint-disable-next-line @typescript-eslint/brace-style
{
@ViewChild(DataTableComponent) @ViewChild(DataTableComponent)
dataTable: DataTableComponent; dataTable: DataTableComponent;
@ -202,6 +201,34 @@ export class ProcessListCloudComponent
@Input() @Input()
isResizingEnabled: boolean = false; isResizingEnabled: boolean = false;
/**
* Filter the processes. Display only processes with names matching any of the supplied strings.
* This input will be used only if PROCESS_SEARCH_API_METHOD_TOKEN is provided with 'POST' value.
*/
@Input()
names: string[] = [];
/**
* Filter the processes. Display only processes started by any of the users whose usernames are present in the array.
* This input will be used only if PROCESS_SEARCH_API_METHOD_TOKEN is provided with 'POST' value.
*/
@Input()
initiators: string[] = [];
/**
* Filter the processes. Display only processes present in any of the specified app versions.
* This input will be used only if PROCESS_SEARCH_API_METHOD_TOKEN is provided with 'POST' value.
*/
@Input()
appVersions: string[] = [];
/**
* Filter the processes. Display only processes with provided statuses.
* This input will be used only if PROCESS_SEARCH_API_METHOD_TOKEN is provided with 'POST' value.
*/
@Input()
statuses: string[] = [];
/** Emitted when a row in the process list is clicked. */ /** Emitted when a row in the process list is clicked. */
@Output() @Output()
rowClick: EventEmitter<string> = new EventEmitter<string>(); rowClick: EventEmitter<string> = new EventEmitter<string>();
@ -242,11 +269,13 @@ export class ProcessListCloudComponent
rows: any[] = []; rows: any[] = [];
formattedSorting: any[]; formattedSorting: any[];
requestNode: ProcessQueryCloudRequestModel; requestNode: ProcessQueryCloudRequestModel;
processListRequestNode: ProcessListRequestModel;
dataAdapter: ProcessListDatatableAdapter; dataAdapter: ProcessListDatatableAdapter;
private defaultSorting = { key: 'startDate', direction: 'desc' }; private defaultSorting = { key: 'startDate', direction: 'desc' };
constructor( constructor(
@Inject(PROCESS_SEARCH_API_METHOD_TOKEN) @Optional() private searchMethod: 'GET' | 'POST',
private processListCloudService: ProcessListCloudService, private processListCloudService: ProcessListCloudService,
appConfigService: AppConfigService, appConfigService: AppConfigService,
private userPreferences: UserPreferencesService, private userPreferences: UserPreferencesService,
@ -333,13 +362,23 @@ export class ProcessListCloudComponent
this.isColumnSchemaCreated$ this.isColumnSchemaCreated$
.pipe( .pipe(
switchMap(() => of(this.createRequestNode())), filter((isColumnSchemaCreated) => !!isColumnSchemaCreated),
tap((requestNode) => (this.requestNode = requestNode)), take(1),
switchMap((requestNode) => this.processListCloudService.getProcessByRequest(requestNode)), switchMap(() => {
if (this.searchMethod === 'POST') {
const requestNode = this.createProcessListRequestNode();
this.processListRequestNode = requestNode;
return this.processListCloudService.fetchProcessList(requestNode).pipe(take(1));
} else {
const requestNode = this.createRequestNode();
this.requestNode = requestNode;
return this.processListCloudService.getProcessByRequest(requestNode).pipe(take(1));
}
}),
takeUntil(this.onDestroy$) takeUntil(this.onDestroy$)
) )
.subscribe( .subscribe({
(processes) => { next: (processes) => {
this.rows = this.variableMapperService.mapVariablesByColumnTitle(processes.list.entries, this.columns); this.rows = this.variableMapperService.mapVariablesByColumnTitle(processes.list.entries, this.columns);
this.dataAdapter = new ProcessListDatatableAdapter(this.rows, this.columns); this.dataAdapter = new ProcessListDatatableAdapter(this.rows, this.columns);
@ -348,11 +387,11 @@ export class ProcessListCloudComponent
this.isLoading = false; this.isLoading = false;
this.pagination.next(processes.list.pagination); this.pagination.next(processes.list.pagination);
}, },
(error) => { error: (error) => {
this.error.emit(error); this.error.emit(error);
this.isLoading = false; this.isLoading = false;
} }
); });
} }
private isAnyPropertyChanged(changes: SimpleChanges): boolean { private isAnyPropertyChanged(changes: SimpleChanges): boolean {
@ -481,6 +520,32 @@ export class ProcessListCloudComponent
this.executeRowAction.emit(row); this.executeRowAction.emit(row);
} }
private createProcessListRequestNode(): ProcessListRequestModel {
const requestNode = {
appName: this.appName,
pagination: {
maxItems: this.size,
skipCount: this.skipCount
},
sorting: this.sorting,
name: this.names,
initiator: this.initiators,
appVersion: this.appVersions,
status: this.statuses,
lastModifiedFrom: this.lastModifiedFrom?.toISOString() || '',
lasModifiedTo: this.lastModifiedTo?.toISOString() || '',
startFrom: this.startFrom,
startTo: this.startTo,
completedFrom: this.completedFrom,
completedTo: this.completedTo,
suspendedFrom: this.suspendedFrom,
suspendedTo: this.suspendedTo,
variableKeys: this.getVariableDefinitionsRequestModel()
};
return new ProcessListRequestModel(requestNode);
}
private createRequestNode(): ProcessQueryCloudRequestModel { private createRequestNode(): ProcessQueryCloudRequestModel {
const requestNode = { const requestNode = {
appName: this.appName, appName: this.appName,

View File

@ -15,7 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
import { Pagination } from '@alfresco/js-api';
import { ProcessListCloudSortingModel } from './process-list-sorting.model'; import { ProcessListCloudSortingModel } from './process-list-sorting.model';
import { ProcessFilterCloudModel } from '../../process-filters/models/process-filter-cloud.model';
export class ProcessQueryCloudRequestModel { export class ProcessQueryCloudRequestModel {
appName: string; appName: string;
@ -76,3 +78,80 @@ export class ProcessQueryCloudRequestModel {
} }
} }
} }
export interface ProcessListRequestProcessVariableFilter {
processDefinitionKey?: string;
name?: string;
type?: string;
value?: string;
operator?: string;
}
export class ProcessListRequestModel {
appName: string;
pagination?: Pagination;
sorting?: ProcessListCloudSortingModel[];
name?: string[];
initiator?: string[];
appVersion?: string[];
status?: string[];
lastModifiedFrom?: string;
lasModifiedTo?: string;
startFrom?: string;
startTo?: string;
completedFrom?: string;
completedTo?: string;
suspendedFrom?: string;
suspendedTo?: string;
processVariableFilters?: ProcessListRequestProcessVariableFilter[];
variableKeys?: string[];
constructor(obj: Partial<ProcessListRequestModel>) {
if (!obj.appName) {
throw new Error('appName not configured');
}
this.appName = obj.appName;
this.pagination = obj.pagination;
this.sorting = obj.sorting;
this.name = obj.name;
this.initiator = obj.initiator;
this.appVersion = obj.appVersion;
this.status = obj.status;
this.lastModifiedFrom = obj.lastModifiedFrom;
this.lasModifiedTo = obj.lasModifiedTo;
this.startFrom = obj.startFrom;
this.startTo = obj.startTo;
this.completedFrom = obj.completedFrom;
this.completedTo = obj.completedTo;
this.suspendedFrom = obj.suspendedFrom;
this.suspendedTo = obj.suspendedTo;
this.variableKeys = obj.variableKeys;
}
}
export class ProcessFilterCloudAdapter extends ProcessListRequestModel {
constructor(filter: ProcessFilterCloudModel) {
super({
appName: filter.appName,
pagination: { maxItems: 25, skipCount: 0 },
sorting: [{ orderBy: filter.sort, direction: filter.order }],
name: filter.processDefinitionNames,
initiator: filter.initiators,
appVersion: filter.appVersions,
status: filter.statuses,
lastModifiedFrom: filter.lastModifiedFrom?.toISOString(),
lasModifiedTo: filter.lastModifiedTo?.toISOString(),
startFrom: filter.startFrom,
startTo: filter.startTo,
completedFrom: filter.completedFrom,
completedTo: filter.completedTo,
suspendedFrom: filter.suspendedFrom,
suspendedTo: filter.suspendedTo
});
}
}

View File

@ -23,6 +23,5 @@ export * from './models/process-list-sorting.model';
export * from './models/process-cloud-preferences'; export * from './models/process-cloud-preferences';
export * from './services/process-list-cloud.service'; export * from './services/process-list-cloud.service';
export * from './services/process-task-list-cloud.service';
export * from './process-list-cloud.module'; export * from './process-list-cloud.module';

View File

@ -17,10 +17,10 @@
import { fakeAsync, TestBed } from '@angular/core/testing'; import { fakeAsync, TestBed } from '@angular/core/testing';
import { ProcessListCloudService } from './process-list-cloud.service'; import { ProcessListCloudService } from './process-list-cloud.service';
import { ProcessQueryCloudRequestModel } from '../models/process-cloud-query-request.model'; import { ProcessListRequestModel, ProcessQueryCloudRequestModel } from '../models/process-cloud-query-request.model';
import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module'; import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module';
import { AdfHttpClient } from '@alfresco/adf-core/api'; import { AdfHttpClient } from '@alfresco/adf-core/api';
import { firstValueFrom } from 'rxjs'; import { catchError, firstValueFrom, of } from 'rxjs';
describe('ProcessListCloudService', () => { describe('ProcessListCloudService', () => {
let service: ProcessListCloudService; let service: ProcessListCloudService;
@ -44,72 +44,132 @@ describe('ProcessListCloudService', () => {
requestSpy = spyOn(adfHttpClient, 'request'); requestSpy = spyOn(adfHttpClient, 'request');
})); }));
it('should append to the call all the parameters', (done) => { describe('getProcessByRequest', () => {
const processRequest = { appName: 'fakeName', skipCount: 0, maxItems: 20, service: 'fake-service' } as ProcessQueryCloudRequestModel; it('should append to the call all the parameters', (done) => {
requestSpy.and.callFake(returnCallQueryParameters); const processRequest = { appName: 'fakeName', skipCount: 0, maxItems: 20, service: 'fake-service' } as ProcessQueryCloudRequestModel;
service.getProcessByRequest(processRequest).subscribe((res) => { requestSpy.and.callFake(returnCallQueryParameters);
service.getProcessByRequest(processRequest).subscribe((res) => {
expect(res).toBeDefined();
expect(res).not.toBeNull();
expect(res.skipCount).toBe(0);
expect(res.maxItems).toBe(20);
expect(res.service).toBe('fake-service');
done();
});
});
it('should concat the app name to the request url', (done) => {
const processRequest = { appName: 'fakeName', skipCount: 0, maxItems: 20, service: 'fake-service' } as ProcessQueryCloudRequestModel;
requestSpy.and.callFake(returnCallUrl);
service.getProcessByRequest(processRequest).subscribe((requestUrl) => {
expect(requestUrl).toBeDefined();
expect(requestUrl).not.toBeNull();
expect(requestUrl).toContain('/fakeName/query/v1/process-instances');
done();
});
});
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' }
]
} as ProcessQueryCloudRequestModel;
requestSpy.and.callFake(returnCallQueryParameters);
service.getProcessByRequest(processRequest).subscribe((res) => {
expect(res).toBeDefined();
expect(res).not.toBeNull();
expect(res.sort).toBe('NAME,DESC&TITLE,ASC');
done();
});
});
it('should return an error when app name is not specified', (done) => {
const processRequest = { appName: null } as ProcessQueryCloudRequestModel;
requestSpy.and.callFake(returnCallUrl);
service.getProcessByRequest(processRequest).subscribe(
() => {},
(error) => {
expect(error).toBe('Appname not configured');
done();
}
);
});
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('fetchProcessList', () => {
it('should append to the call all the parameters', async () => {
const processRequest = {
appName: 'fakeName',
pagination: { skipCount: 0, maxItems: 20 }
} as ProcessListRequestModel;
requestSpy.and.callFake(returnCallQueryParameters);
const res = await firstValueFrom(service.fetchProcessList(processRequest));
expect(res).toBeDefined(); expect(res).toBeDefined();
expect(res).not.toBeNull(); expect(res).not.toBeNull();
expect(res.skipCount).toBe(0); expect(res.skipCount).toBe(0);
expect(res.maxItems).toBe(20); expect(res.maxItems).toBe(20);
expect(res.service).toBe('fake-service');
done();
}); });
});
it('should concat the app name to the request url', (done) => { it('should concat the app name to the request url', async () => {
const processRequest = { appName: 'fakeName', skipCount: 0, maxItems: 20, service: 'fake-service' } as ProcessQueryCloudRequestModel; const processRequest = {
requestSpy.and.callFake(returnCallUrl); appName: 'fakeName',
service.getProcessByRequest(processRequest).subscribe((requestUrl) => { pagination: { skipCount: 0, maxItems: 20 }
expect(requestUrl).toBeDefined(); } as ProcessListRequestModel;
expect(requestUrl).not.toBeNull(); requestSpy.and.callFake(returnCallUrl);
expect(requestUrl).toContain('/fakeName/query/v1/process-instances');
done(); const res = await firstValueFrom(service.fetchProcessList(processRequest));
expect(res).toBeDefined();
expect(res).not.toBeNull();
expect(res).toContain('/fakeName/query/v1/process-instances/search');
}); });
});
it('should concat the sorting to append as parameters', (done) => { it('should concat the sorting to append as parameters', async () => {
const processRequest = { const processRequest = {
appName: 'fakeName', appName: 'fakeName',
skipCount: 0, pagination: { skipCount: 0, maxItems: 20 },
maxItems: 20, sorting: [
service: 'fake-service', { orderBy: 'NAME', direction: 'DESC' },
sorting: [ { orderBy: 'TITLE', direction: 'ASC' }
{ orderBy: 'NAME', direction: 'DESC' }, ]
{ orderBy: 'TITLE', direction: 'ASC' } } as ProcessListRequestModel;
] requestSpy.and.callFake(returnCallQueryParameters);
} as ProcessQueryCloudRequestModel;
requestSpy.and.callFake(returnCallQueryParameters); const res = await firstValueFrom(service.fetchProcessList(processRequest));
service.getProcessByRequest(processRequest).subscribe((res) => {
expect(res).toBeDefined(); expect(res).toBeDefined();
expect(res).not.toBeNull(); expect(res).not.toBeNull();
expect(res.sort).toBe('NAME,DESC&TITLE,ASC'); expect(res.sort).toBe('NAME,DESC&TITLE,ASC');
done();
}); });
});
it('should return an error when app name is not specified', (done) => { it('should return an error when app name is not specified', async () => {
const processRequest = { appName: null } as ProcessQueryCloudRequestModel; const taskRequest = { appName: null } as ProcessListRequestModel;
requestSpy.and.callFake(returnCallUrl); requestSpy.and.callFake(returnCallUrl);
service.getProcessByRequest(processRequest).subscribe(
() => {},
(error) => {
expect(error).toBe('Appname not configured');
done();
}
);
});
it('should return number of total items of processes ', async () => { const res = await firstValueFrom(service.fetchProcessList(taskRequest).pipe(catchError((error) => of(error.message))));
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(res).toBe('Appname not configured');
expect(result).not.toBeNull(); });
expect(result.skipCount).toBe(0);
expect(result.maxItems).toBe(1);
expect(result.service).toBe('fake-service');
}); });
describe('getAdminProcessRequest', () => { describe('getAdminProcessRequest', () => {

View File

@ -16,7 +16,7 @@
*/ */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { ProcessQueryCloudRequestModel } from '../models/process-cloud-query-request.model'; import { ProcessListRequestModel, ProcessQueryCloudRequestModel } from '../models/process-cloud-query-request.model';
import { Observable, throwError } from 'rxjs'; import { Observable, throwError } from 'rxjs';
import { ProcessListCloudSortingModel } from '../models/process-list-sorting.model'; import { ProcessListCloudSortingModel } from '../models/process-list-sorting.model';
import { BaseCloudService } from '../../../services/base-cloud.service'; import { BaseCloudService } from '../../../services/base-cloud.service';
@ -56,6 +56,7 @@ export class ProcessListCloudService extends BaseCloudService {
/** /**
* Finds a process using an object with optional query properties. * Finds a process using an object with optional query properties.
* *
* @deprecated From Activiti 8.7.0 forward, use ProcessListCloudService.fetchProcessList instead.
* @param requestNode Query object * @param requestNode Query object
* @param queryUrl Query url * @param queryUrl Query url
* @returns Process information * @returns Process information
@ -67,6 +68,76 @@ export class ProcessListCloudService extends BaseCloudService {
return this.getProcess(callback, defaultQueryUrl, requestNode, queryUrl); return this.getProcess(callback, defaultQueryUrl, requestNode, queryUrl);
} }
/**
* Available from Activiti version 8.7.0 onwards.
* Retrieves a list of processes using an object with optional query properties.
*
* @param requestNode Query object
* @param queryUrl Query url
* @returns List of processes
*/
fetchProcessList(requestNode: ProcessListRequestModel, queryUrl?: string): Observable<any> {
if (!requestNode?.appName) {
return throwError(() => new Error('Appname not configured'));
}
queryUrl = queryUrl || `${this.getBasePath(requestNode.appName)}/query/v1/process-instances/search`;
const queryParams = {
maxItems: requestNode.pagination?.maxItems || 25,
skipCount: requestNode.pagination?.skipCount || 0,
sort: this.buildSortingParam(requestNode.sorting || [])
};
const queryData = this.buildQueryData(requestNode);
return this.post<any, any>(queryUrl, queryData, queryParams).pipe(
map((response: any) => {
const entries = response.list?.entries;
if (entries) {
response.list.entries = entries.map((entryData) => entryData.entry);
}
return response;
})
);
}
protected buildQueryData(requestNode: ProcessListRequestModel) {
const variableKeys = requestNode.variableKeys?.length > 0 ? requestNode.variableKeys.join(',') : undefined;
const queryData: any = {
name: requestNode.name,
initiator: requestNode.initiator,
appVersion: requestNode.appVersion,
status: requestNode.status,
lastModifiedFrom: requestNode.lastModifiedFrom,
lasModifiedTo: requestNode.lasModifiedTo,
startFrom: requestNode.startFrom,
startTo: requestNode.startTo,
completedFrom: requestNode.completedFrom,
completedTo: requestNode.completedTo,
suspendedFrom: requestNode.suspendedFrom,
suspendedTo: requestNode.suspendedTo,
variableKeys: variableKeys
};
Object.keys(queryData).forEach((key) => {
const value = queryData[key];
const isValueEmpty = !value;
const isValueArrayWithEmptyValue = Array.isArray(value) && (value.length === 0 || value[0] === null);
if (isValueEmpty || isValueArrayWithEmptyValue) {
delete queryData[key];
}
});
return queryData;
}
getProcessListCounter(requestNode: ProcessListRequestModel): Observable<number> {
if (!requestNode.appName) {
return throwError(() => new Error('Appname not configured'));
}
return this.fetchProcessList(requestNode).pipe(map((processes) => processes.list.pagination.totalItems));
}
/** /**
* Finds a process using an object with optional query properties. * Finds a process using an object with optional query properties.
* *

View File

@ -1,93 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { Observable, throwError } from 'rxjs';
import { BaseCloudService } from '../../../services/base-cloud.service';
import { map } from 'rxjs/operators';
import { TaskQueryCloudRequestModel } from '../../../models/filter-cloud-model';
import { TaskCloudNodePaging } from '../../../models/task-cloud.model';
import { TaskListCloudSortingModel } from '../../../models/task-list-sorting.model';
@Injectable({ providedIn: 'root' })
export class ProcessTaskListCloudService extends BaseCloudService {
/**
* Finds a task using an object with optional query properties.
*
* @param requestNode Query object
* @param queryUrl Query url
* @returns Task information
*/
getTaskByRequest(requestNode: TaskQueryCloudRequestModel, queryUrl?: string): Observable<any> {
if (requestNode.appName || requestNode.appName === '') {
queryUrl = queryUrl || `${this.getBasePath(requestNode.appName)}/query/v1/process-instances/${requestNode.processInstanceId}/tasks`;
const queryParams = this.buildQueryParams(requestNode);
const sortingParams = this.buildSortingParam(requestNode.sorting);
if (sortingParams) {
queryParams['sort'] = sortingParams;
}
return this.get<TaskCloudNodePaging>(queryUrl, queryParams).pipe(
map((response) => {
const entries = response.list?.entries;
if (entries) {
// TODO: this is a hack of the model and should be revisited
response.list.entries = entries.map((entryData: any) => entryData.entry);
}
return response;
})
);
} else {
return throwError('Appname not configured');
}
}
protected buildQueryParams(requestNode: TaskQueryCloudRequestModel): any {
const queryParam: any = {};
for (const property in requestNode) {
if (
Object.prototype.hasOwnProperty.call(requestNode, property) &&
!this.isExcludedField(property) &&
this.isPropertyValueValid(requestNode, property)
) {
queryParam[property] = requestNode[property];
}
}
return queryParam;
}
protected isExcludedField(property: string): boolean {
return property === 'appName' || property === 'sorting';
}
protected isPropertyValueValid(requestNode: TaskQueryCloudRequestModel, property: string): boolean {
return requestNode[property] !== '' && requestNode[property] !== null && requestNode[property] !== undefined;
}
protected buildSortingParam(models: TaskListCloudSortingModel[]): string {
let finalSorting: string = '';
if (models) {
for (const sort of models) {
if (!finalSorting) {
finalSorting = `${sort.orderBy},${sort.direction}`;
} else {
finalSorting = `${finalSorting}&${sort.orderBy},${sort.direction}`;
}
}
}
return encodeURI(finalSorting);
}
}

View File

@ -34,3 +34,9 @@ export const TASK_LIST_CLOUD_TOKEN = new InjectionToken<TaskListCloudServiceInte
* 'POST' value should be provided only if the used Activiti version is 8.7.0 or higher. * 'POST' value should be provided only if the used Activiti version is 8.7.0 or higher.
*/ */
export const TASK_SEARCH_API_METHOD_TOKEN = new InjectionToken<'GET' | 'POST'>('task-search-method'); export const TASK_SEARCH_API_METHOD_TOKEN = new InjectionToken<'GET' | 'POST'>('task-search-method');
/**
* Token used to indicate the API used to search for processes.
* 'POST' value should be provided only if the used Activiti version is 8.7.0 or higher.
*/
export const PROCESS_SEARCH_API_METHOD_TOKEN = new InjectionToken<'GET' | 'POST'>('process-search-method');