diff --git a/docs/process-services/services/task-filter.service.md b/docs/process-services/services/task-filter.service.md index 0d47dd8e06..f537b02df7 100644 --- a/docs/process-services/services/task-filter.service.md +++ b/docs/process-services/services/task-filter.service.md @@ -30,8 +30,8 @@ Manage Task Filters, which are pre-configured Task Instance queries. - _appId:_ `number` - ID of the target app - _index:_ `number` - (Optional) of the filter (optional) - **Returns** `UserTaskFilterRepresentation` - The newly created filter -- **getInvolvedTasksFilterInstance**(appId: `number`, index?: `number`): `UserTaskFilterRepresentation`
- Creates and returns a filter for "Involved" task instances. +- **getOverdueTasksFilterInstance**(appId: `number`, index?: `number`): `UserTaskFilterRepresentation`
+ Creates and returns a filter for "Overdue" task instances. - _appId:_ `number` - ID of the target app - _index:_ `number` - (Optional) of the filter (optional) - **Returns** `UserTaskFilterRepresentation` - The newly created filter @@ -40,8 +40,8 @@ Manage Task Filters, which are pre-configured Task Instance queries. - _appId:_ `number` - ID of the target app - _index:_ `number` - (Optional) of the filter (optional) - **Returns** `UserTaskFilterRepresentation` - The newly created filter -- **getQueuedTasksFilterInstance**(appId: `number`, index?: `number`): `UserTaskFilterRepresentation`
- Creates and returns a filter for "Queued Tasks" task instances. +- **getUnassignedTasksFilterInstance**(appId: `number`, index?: `number`): `UserTaskFilterRepresentation`
+ Creates and returns a filter for "Unassigned Tasks" task instances. - _appId:_ `number` - ID of the target app - _index:_ `number` - (Optional) of the filter (optional) - **Returns** `UserTaskFilterRepresentation` - The newly created filter @@ -55,6 +55,11 @@ Manage Task Filters, which are pre-configured Task Instance queries. - _taskName:_ `string` - Name of the filter - _appId:_ `number` - (Optional) ID of the app for the filter - **Returns** `ObservableUserTaskFilterRepresentation>` - Details of task filter +- **updateTaskFilter**(filterId: `number`, updatedFilter: `UserTaskFilterRepresentation`): `Observable`
+ Updates the instance of the filter. + - filterId:_ `number` - ID of a filter to update + - updatedFilter:_ `UserTaskFilterRepresentation` - new filter body + - **Returns** `Observable` - Updated filter instance. - **getTaskListFilters**(appId?: `number`): `UserTaskFilterRepresentation[]>`
Gets all task filters for a process app. - _appId:_ `number` - (Optional) Optional ID for a specific app @@ -84,16 +89,16 @@ The response is an array of `UserTaskFilterRepresentation` objects: filters: 0: {id: 10, appId: 2, name: "Involved Tasks", recent: true, icon: "glyphicon-align-left", …} 1: {id: 9, appId: 2, name: "My Tasks", recent: false, icon: "glyphicon-inbox", …} - 2: {id: 11, appId: 2, name: "Queued Tasks", recent: false, icon: "glyphicon-record", …} + 2: {id: 11, appId: 2, name: "Unassigned Tasks", recent: false, icon: "glyphicon-record", …} 3: {id: 12, appId: 2, name: "Completed Tasks", recent: false, icon: "glyphicon-ok-sign", …} 4: {id: 4004, appId: 2, name: "Completed Tasks", recent: false, icon: "glyphicon-ok-sign", …} 5: {id: 4005, appId: 2, name: "My Tasks", recent: false, icon: "glyphicon-inbox", …} - 6: {id: 4006, appId: 2, name: "Queued Tasks", recent: false, icon: "glyphicon-record", …} + 6: {id: 4006, appId: 2, name: "Unassigned Tasks", recent: false, icon: "glyphicon-record", …} 7: {id: 4007, appId: 2, name: "Involved Tasks", recent: false, icon: "glyphicon-align-left", …} ``` These filters can now be used to get matching task instances for the process app with ID 2, -such as 'Involved Tasks', 'My Tasks', 'Queued Tasks', and 'Completed Tasks'. +such as 'Involved Tasks', 'My Tasks', 'Unassigned Tasks', and 'Completed Tasks'. ### Importing diff --git a/lib/process-services/src/lib/task-list/components/task-filters/task-filters.component.spec.ts b/lib/process-services/src/lib/task-list/components/task-filters/task-filters.component.spec.ts index 86559c7cce..d98809e692 100644 --- a/lib/process-services/src/lib/task-list/components/task-filters/task-filters.component.spec.ts +++ b/lib/process-services/src/lib/task-list/components/task-filters/task-filters.component.spec.ts @@ -117,6 +117,50 @@ describe('TaskFiltersComponent', () => { expect(createDefaultFiltersSpy).toHaveBeenCalledWith(appId); }); + it('should migrate obsolete filters to their new instances', () => { + const involvedFilter = new UserTaskFilterRepresentation({ + name: 'Involved Tasks', + icon: 'glyphicon-align-left', + id: 10, + filter: { state: 'open', assignment: 'involved' } + }); + const queuedFilter = new UserTaskFilterRepresentation({ + name: 'Queued Tasks', + icon: 'glyphicon-ok-sign', + id: 11, + filter: { state: 'open', assignment: 'candidate' } + }); + const taskFilters = [involvedFilter, queuedFilter]; + spyOn(taskFilterService, 'getTaskListFilters').and.returnValue(of(taskFilters)); + spyOn(taskFilterService, 'updateTaskFilter').and.returnValue(of({})); + component.getFiltersByAppId(1); + fixture.detectChanges(); + expect(taskFilterService.updateTaskFilter).toHaveBeenCalledTimes(2); + expect(taskFilterService.updateTaskFilter).toHaveBeenCalledWith(10, taskFilterService.getOverdueTasksFilterInstance(undefined)); + expect(taskFilterService.updateTaskFilter).toHaveBeenCalledWith(11, taskFilterService.getUnassignedTasksFilterInstance(undefined)); + }); + + it('should not migrate other filters', () => { + const myTasksFilter = new UserTaskFilterRepresentation({ + name: 'My Tasks', + icon: 'glyphicon-align-left', + id: 10, + filter: { state: 'open', assignment: 'assignee' } + }); + const completedFilter = new UserTaskFilterRepresentation({ + name: 'Completed Tasks', + icon: 'glyphicon-ok-sign', + id: 11, + filter: { state: 'completed', assignment: 'involved' } + }); + const taskFilters = [myTasksFilter, completedFilter]; + spyOn(taskFilterService, 'getTaskListFilters').and.returnValue(of(taskFilters)); + spyOn(taskFilterService, 'updateTaskFilter').and.returnValue(of({})); + component.getFiltersByAppId(1); + fixture.detectChanges(); + expect(taskFilterService.updateTaskFilter).not.toHaveBeenCalled(); + }); + it('should return the filter task list, filtered By Name', () => { const deployApp = spyOn(appsProcessService, 'getDeployedApplicationsByName').and.returnValue(of({} as any)); spyOn(taskFilterService, 'getTaskListFilters').and.returnValue(of(fakeTaskFilters)); diff --git a/lib/process-services/src/lib/task-list/components/task-filters/task-filters.component.ts b/lib/process-services/src/lib/task-list/components/task-filters/task-filters.component.ts index 8d779a8ebe..fbedaecd11 100644 --- a/lib/process-services/src/lib/task-list/components/task-filters/task-filters.component.ts +++ b/lib/process-services/src/lib/task-list/components/task-filters/task-filters.component.ts @@ -28,6 +28,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { MatButtonModule } from '@angular/material/button'; import { IconComponent } from '@alfresco/adf-core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { forkJoin, Observable } from 'rxjs'; @Component({ selector: 'adf-task-filters', @@ -136,10 +137,14 @@ export class TaskFiltersComponent implements OnInit, OnChanges { if (res.length === 0 && this.isFilterListEmpty()) { this.createFiltersByAppId(appId); } else { - this.resetFilter(); - this.filters = res; - this.selectFilter(this.filterParam); - this.success.emit(res); + const migratedFilters = this.migrateObsoleteFilters(res); + if (migratedFilters.length > 0) { + forkJoin(migratedFilters).subscribe(() => { + this.setTaskFilters(res); + }); + } else { + this.setTaskFilters(res); + } } }, (err: any) => { @@ -172,10 +177,7 @@ export class TaskFiltersComponent implements OnInit, OnChanges { private createFiltersByAppId(appId?: number): void { this.taskFilterService.createDefaultFilters(appId).subscribe( (resDefault) => { - this.resetFilter(); - this.filters = resDefault; - this.selectFilter(this.filterParam); - this.success.emit(resDefault); + this.setTaskFilters(resDefault); }, (errDefault: any) => { this.error.emit(errDefault); @@ -272,4 +274,41 @@ export class TaskFiltersComponent implements OnInit, OnChanges { this.filters = []; this.currentFilter = undefined; } + + /** + * Migrate "Involved" and "Queued" filters to "Overdue" and "Unassigned" filters + * + * @param filters - list of filters to migrate + * @returns list of observables for each migrated filter + */ + private migrateObsoleteFilters(filters: UserTaskFilterRepresentation[]): Observable[] { + const migratedFilters: Observable[] = []; + filters.forEach((filterToMigrate) => { + switch (filterToMigrate.name) { + case 'Involved Tasks': + migratedFilters.push( + this.taskFilterService.updateTaskFilter(filterToMigrate.id, this.taskFilterService.getOverdueTasksFilterInstance(this.appId)) + ); + break; + case 'Queued Tasks': + migratedFilters.push( + this.taskFilterService.updateTaskFilter( + filterToMigrate.id, + this.taskFilterService.getUnassignedTasksFilterInstance(this.appId) + ) + ); + break; + default: + break; + } + }); + return migratedFilters; + } + + private setTaskFilters(taskFilters: UserTaskFilterRepresentation[]): void { + this.resetFilter(); + this.filters = taskFilters; + this.selectFilter(this.filterParam); + this.success.emit(taskFilters); + } } diff --git a/lib/process-services/src/lib/task-list/services/task-filter.service.spec.ts b/lib/process-services/src/lib/task-list/services/task-filter.service.spec.ts new file mode 100644 index 0000000000..6749ef1f65 --- /dev/null +++ b/lib/process-services/src/lib/task-list/services/task-filter.service.spec.ts @@ -0,0 +1,166 @@ +/*! + * @license + * Copyright © 2005-2025 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 { TestBed } from '@angular/core/testing'; +import { TaskFilterService } from './task-filter.service'; +import { CoreTestingModule } from '@alfresco/adf-core'; +import { AlfrescoApiService, AlfrescoApiServiceMock } from '@alfresco/adf-content-services'; + +describe('TaskListService', () => { + let service: TaskFilterService; + + const mockTaskFilters = [ + { id: 1, name: 'first one' }, + { id: 2, name: 'second one' }, + { id: 3, name: 'third one' } + ]; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [CoreTestingModule], + providers: [{ provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock }] + }); + service = TestBed.inject(TaskFilterService); + }); + + it('should provide my tasks filter instance', () => { + const myTasksFilter = service.getMyTasksFilterInstance(1, 11); + expect(myTasksFilter.name).toBe('My Tasks'); + expect(myTasksFilter.appId).toBe(1); + expect(myTasksFilter.recent).toBe(false); + expect(myTasksFilter.icon).toBe('glyphicon-inbox'); + expect(myTasksFilter.index).toBe(11); + expect(myTasksFilter.filter.sort).toBe('created-desc'); + expect(myTasksFilter.filter.name).toBe(''); + expect(myTasksFilter.filter.state).toBe('open'); + expect(myTasksFilter.filter.assignment).toBe('assignee'); + }); + + it('should provide overdue tasks filter instance', () => { + const overdueTasksFilter = service.getOverdueTasksFilterInstance(1, 11); + expect(overdueTasksFilter.name).toBe('Overdue Tasks'); + expect(overdueTasksFilter.appId).toBe(1); + expect(overdueTasksFilter.recent).toBe(false); + expect(overdueTasksFilter.icon).toBe('glyphicon-align-left'); + expect(overdueTasksFilter.index).toBe(11); + expect(overdueTasksFilter.filter.sort).toBe('created-desc'); + expect(overdueTasksFilter.filter.name).toBe(''); + expect(overdueTasksFilter.filter.state).toBe('open'); + expect(overdueTasksFilter.filter.assignment).toBe('assignee'); + }); + + it('should provide unassigned tasks filter instance', () => { + const unassignedTasksFilter = service.getUnassignedTasksFilterInstance(1, 11); + expect(unassignedTasksFilter.name).toBe('Unassigned Tasks'); + expect(unassignedTasksFilter.appId).toBe(1); + expect(unassignedTasksFilter.recent).toBe(false); + expect(unassignedTasksFilter.icon).toBe('glyphicon-record'); + expect(unassignedTasksFilter.index).toBe(11); + expect(unassignedTasksFilter.filter.sort).toBe('created-desc'); + expect(unassignedTasksFilter.filter.name).toBe(''); + expect(unassignedTasksFilter.filter.state).toBe('open'); + expect(unassignedTasksFilter.filter.assignment).toBe('candidate'); + }); + + it('should provide completed tasks filter instance', () => { + const completedTasksFilter = service.getCompletedTasksFilterInstance(1, 11); + expect(completedTasksFilter.name).toBe('Completed Tasks'); + expect(completedTasksFilter.appId).toBe(1); + expect(completedTasksFilter.recent).toBe(true); + expect(completedTasksFilter.icon).toBe('glyphicon-ok-sign'); + expect(completedTasksFilter.index).toBe(11); + expect(completedTasksFilter.filter.sort).toBe('created-desc'); + expect(completedTasksFilter.filter.name).toBe(''); + expect(completedTasksFilter.filter.state).toBe('completed'); + expect(completedTasksFilter.filter.assignment).toBe('involved'); + }); + + it('should call right task filters api', () => { + spyOn(service.userFiltersApi, 'getUserTaskFilters').and.returnValue(Promise.resolve({})); + service.callApiTaskFilters(1); + service.callApiTaskFilters(); + expect(service.userFiltersApi.getUserTaskFilters).toHaveBeenCalledTimes(2); + expect(service.userFiltersApi.getUserTaskFilters).toHaveBeenCalledWith({ appId: 1 }); + }); + + it('should call right update task filter api', () => { + spyOn(service.userFiltersApi, 'updateUserTaskFilter').and.returnValue(Promise.resolve({})); + service.updateTaskFilter(1, { name: 'test' }); + expect(service.userFiltersApi.updateUserTaskFilter).toHaveBeenCalledTimes(1); + expect(service.userFiltersApi.updateUserTaskFilter).toHaveBeenCalledWith(1, { name: 'test' }); + }); + + it('should call right add task filter api', () => { + spyOn(service.userFiltersApi, 'createUserTaskFilter').and.returnValue(Promise.resolve({})); + service.addFilter({ name: 'test' }); + expect(service.userFiltersApi.createUserTaskFilter).toHaveBeenCalledTimes(1); + expect(service.userFiltersApi.createUserTaskFilter).toHaveBeenCalledWith({ name: 'test' }); + }); + + it('should get task filter by name if response contain matching one', (done) => { + spyOn(service.userFiltersApi, 'getUserTaskFilters').and.returnValue(Promise.resolve({ data: mockTaskFilters })); + service.getTaskFilterByName('first one').subscribe((filter) => { + expect(filter.name).toBe('first one'); + done(); + }); + }); + + it('should return undefined if task with given name is not found in the response', (done) => { + spyOn(service.userFiltersApi, 'getUserTaskFilters').and.returnValue(Promise.resolve({ data: mockTaskFilters })); + service.getTaskFilterByName('other one').subscribe((filter) => { + expect(filter).toBeUndefined(); + done(); + }); + }); + + it('should get task filter by id if response contain matching one', (done) => { + spyOn(service.userFiltersApi, 'getUserTaskFilters').and.returnValue(Promise.resolve({ data: mockTaskFilters })); + service.getTaskFilterById(2).subscribe((filter) => { + expect(filter.id).toBe(2); + expect(filter.name).toBe('second one'); + done(); + }); + }); + + it('should return undefined if task with given id is not found in the response', (done) => { + spyOn(service.userFiltersApi, 'getUserTaskFilters').and.returnValue(Promise.resolve({ data: mockTaskFilters })); + service.getTaskFilterByName('other one').subscribe((filter) => { + expect(filter).toBeUndefined(); + done(); + }); + }); + + it('should return true if filter with given name exists', () => { + expect(service.isFilterAlreadyExisting(mockTaskFilters, 'first one')).toBe(true); + }); + + it('should return false if filter with given name does not exist', () => { + expect(service.isFilterAlreadyExisting(mockTaskFilters, 'another one')).toBe(false); + }); + + it('should return list of task filters without duplications', (done) => { + mockTaskFilters.push({ id: 1, name: 'first one' }); + spyOn(service.userFiltersApi, 'getUserTaskFilters').and.returnValue(Promise.resolve({ data: mockTaskFilters })); + service.getTaskListFilters().subscribe((filters) => { + expect(filters.length).toBe(3); + expect(filters[0].name).toBe('first one'); + expect(filters[1].name).toBe('second one'); + expect(filters[2].name).toBe('third one'); + done(); + }); + }); +}); diff --git a/lib/process-services/src/lib/task-list/services/task-filter.service.ts b/lib/process-services/src/lib/task-list/services/task-filter.service.ts index c6e40cffac..6ae14178b6 100644 --- a/lib/process-services/src/lib/task-list/services/task-filter.service.ts +++ b/lib/process-services/src/lib/task-list/services/task-filter.service.ts @@ -43,25 +43,25 @@ export class TaskFilterService { const myTasksFilter = this.getMyTasksFilterInstance(appId, 0); const myTaskObservable = this.addFilter(myTasksFilter); - const involvedTasksFilter = this.getInvolvedTasksFilterInstance(appId, 1); - const involvedObservable = this.addFilter(involvedTasksFilter); + const overdueTasksFilter = this.getOverdueTasksFilterInstance(appId, 1); + const overdueObservable = this.addFilter(overdueTasksFilter); - const queuedTasksFilter = this.getQueuedTasksFilterInstance(appId, 2); - const queuedObservable = this.addFilter(queuedTasksFilter); + const unassignedTasksFilter = this.getUnassignedTasksFilterInstance(appId, 2); + const unassignedObservable = this.addFilter(unassignedTasksFilter); const completedTasksFilter = this.getCompletedTasksFilterInstance(appId, 3); const completeObservable = this.addFilter(completedTasksFilter); return new Observable((observer) => { - forkJoin([myTaskObservable, involvedObservable, queuedObservable, completeObservable]).subscribe((res) => { + forkJoin([myTaskObservable, overdueObservable, unassignedObservable, completeObservable]).subscribe((res) => { const filters: UserTaskFilterRepresentation[] = []; res.forEach((filter) => { if (!this.isFilterAlreadyExisting(filters, filter.name)) { - if (filter.name === involvedTasksFilter.name) { + if (filter.name === overdueTasksFilter.name) { filters.push( new UserTaskFilterRepresentation({ ...filter, - filter: involvedTasksFilter.filter, + filter: overdueTasksFilter.filter, appId }) ); @@ -73,11 +73,11 @@ export class TaskFilterService { appId }) ); - } else if (filter.name === queuedTasksFilter.name) { + } else if (filter.name === unassignedTasksFilter.name) { filters.push( new UserTaskFilterRepresentation({ ...filter, - filter: queuedTasksFilter.filter, + filter: unassignedTasksFilter.filter, appId }) ); @@ -162,6 +162,17 @@ export class TaskFilterService { return from(this.userFiltersApi.createUserTaskFilter(filter)); } + /** + * Update a task filter + * + * @param filterId existing filter id + * @param updatedFilter updated filter body + * @returns Observable + */ + updateTaskFilter(filterId: number, updatedFilter: UserTaskFilterRepresentation): Observable { + return from(this.userFiltersApi.updateUserTaskFilter(filterId, updatedFilter)); + } + /** * Calls `getUserTaskFilters` from the Alfresco JS API. * @@ -200,33 +211,33 @@ export class TaskFilterService { } /** - * Creates and returns a filter for "Involved" task instances. + * Creates and returns a filter for "Overdue" task instances. * * @param appId ID of the target app * @param index of the filter (optional) * @returns The newly created filter */ - getInvolvedTasksFilterInstance(appId: number, index?: number): UserTaskFilterRepresentation { + getOverdueTasksFilterInstance(appId: number, index?: number): UserTaskFilterRepresentation { return new UserTaskFilterRepresentation({ - name: 'Involved Tasks', + name: 'Overdue Tasks', appId, recent: false, icon: 'glyphicon-align-left', - filter: { sort: 'created-desc', name: '', state: 'open', assignment: 'involved' }, + filter: { sort: 'created-desc', name: '', state: 'open', assignment: 'assignee' }, index }); } /** - * Creates and returns a filter for "Queued Tasks" task instances. + * Creates and returns a filter for "Unassigned Tasks" task instances. * * @param appId ID of the target app * @param index of the filter (optional) * @returns The newly created filter */ - getQueuedTasksFilterInstance(appId: number, index?: number): UserTaskFilterRepresentation { + getUnassignedTasksFilterInstance(appId: number, index?: number): UserTaskFilterRepresentation { return new UserTaskFilterRepresentation({ - name: 'Queued Tasks', + name: 'Unassigned Tasks', appId, recent: false, icon: 'glyphicon-record',