diff --git a/lib/process-services-cloud/src/lib/services/base-cloud.service.ts b/lib/process-services-cloud/src/lib/services/base-cloud.service.ts index ca0e6662d7..2c0e12930d 100644 --- a/lib/process-services-cloud/src/lib/services/base-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/services/base-cloud.service.ts @@ -84,6 +84,18 @@ export class BaseCloudService { ); } + protected getWithBody(url: string, data?: T, queryParams?: any): Observable { + return from( + this.callApi(url, { + ...this.defaultParams, + path: url, + httpMethod: 'GET', + bodyParam: data, + queryParams + }) + ); + } + protected callApi(url: string, params: RequestOptions): Promise { return this.adfHttpClient.request(url, params); } diff --git a/lib/process-services-cloud/src/lib/services/task-list-cloud.service.interface.ts b/lib/process-services-cloud/src/lib/services/task-list-cloud.service.interface.ts index a43aa2fca1..f19e2d07d9 100644 --- a/lib/process-services-cloud/src/lib/services/task-list-cloud.service.interface.ts +++ b/lib/process-services-cloud/src/lib/services/task-list-cloud.service.interface.ts @@ -34,8 +34,18 @@ export interface TaskListCloudServiceInterface { * Retrieves a list of tasks using an object with optional query properties. * * @param requestNode Query object - * @param queryUrl Query url + * @param queryUrl Query url. If empty, query service will be called. * @returns List of tasks */ fetchTaskList(requestNode: TaskListRequestModel, queryUrl?: string): Observable; + + /** + * Available from Activiti version 8.7.0 onwards. + * Retrieves a list of tasks using an object with optional query properties. + * Calls runtime bundle service. + * + * @param requestNode Query object + * @returns List of tasks + */ + fetchTaskList_UsingRuntimeBundleService(requestNode: TaskListRequestModel): Observable; } diff --git a/lib/process-services-cloud/src/lib/task/services/task-cloud.service.spec.ts b/lib/process-services-cloud/src/lib/task/services/task-cloud.service.spec.ts index 3000735a4d..ce80d6c446 100644 --- a/lib/process-services-cloud/src/lib/task/services/task-cloud.service.spec.ts +++ b/lib/process-services-cloud/src/lib/task/services/task-cloud.service.spec.ts @@ -26,6 +26,7 @@ import { } from '../task-header/mocks/task-details-cloud.mock'; import { IdentityUserService } from '../../people/services/identity-user.service'; import { AdfHttpClient } from '@alfresco/adf-core/api'; +import { TASK_COMPLETED_STATE, TASK_CREATED_STATE, TASK_ASSIGNED_STATE } from '../models/task-details-cloud.model'; const fakeTaskDetailsCloud = { entry: { @@ -478,4 +479,46 @@ describe('Task Cloud Service', () => { } ); }); + + describe('wasTaskCompletedByCurrentUser', () => { + it('should return true when task was completed by current user', () => { + const completedTaskByCurrentUser = { + ...assignedTaskDetailsCloudMock, + status: TASK_COMPLETED_STATE, + assignee: 'AssignedTaskUser' + }; + const result = service.wasTaskCompletedByCurrentUser(completedTaskByCurrentUser); + expect(result).toBe(true); + }); + + it('should return false when task was completed but not by current user', () => { + const completedTaskByDifferentUser = { + ...assignedTaskDetailsCloudMock, + status: TASK_COMPLETED_STATE, + assignee: 'DifferentUser' + }; + const result = service.wasTaskCompletedByCurrentUser(completedTaskByDifferentUser); + expect(result).toBe(false); + }); + + it('should return false when task is not completed', () => { + const uncompletedTask = { + ...assignedTaskDetailsCloudMock, + status: TASK_ASSIGNED_STATE, + assignee: 'AssignedTaskUser' + }; + const result = service.wasTaskCompletedByCurrentUser(uncompletedTask); + expect(result).toBe(false); + }); + + it('should return false when task is in other states', () => { + const createdTask = { + ...assignedTaskDetailsCloudMock, + status: TASK_CREATED_STATE, + assignee: 'AssignedTaskUser' + }; + const result = service.wasTaskCompletedByCurrentUser(createdTask); + expect(result).toBe(false); + }); + }); }); diff --git a/lib/process-services-cloud/src/lib/task/services/task-cloud.service.ts b/lib/process-services-cloud/src/lib/task/services/task-cloud.service.ts index 3eac1b86c8..0ed69ab973 100644 --- a/lib/process-services-cloud/src/lib/task/services/task-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/task/services/task-cloud.service.ts @@ -25,7 +25,8 @@ import { TASK_ASSIGNED_STATE, TASK_CLAIM_PERMISSION, TASK_CREATED_STATE, - TASK_RELEASE_PERMISSION + TASK_RELEASE_PERMISSION, + TASK_COMPLETED_STATE } from '../models/task-details-cloud.model'; import { BaseCloudService } from '../../services/base-cloud.service'; import { StartTaskCloudRequestModel } from '../models/start-task-cloud-request.model'; @@ -67,7 +68,7 @@ export class TaskCloudService extends BaseCloudService { * @returns Boolean value if the task can be completed */ canCompleteTask(taskDetails: TaskDetailsCloudModel): boolean { - return taskDetails && taskDetails.status === TASK_ASSIGNED_STATE && this.isAssignedToMe(taskDetails.assignee); + return taskDetails?.status === TASK_ASSIGNED_STATE && this.isAssignedToMe(taskDetails.assignee); } /** @@ -77,7 +78,7 @@ export class TaskCloudService extends BaseCloudService { * @returns Boolean value if the task is editable */ isTaskEditable(taskDetails: TaskDetailsCloudModel): boolean { - return taskDetails && taskDetails.status === TASK_ASSIGNED_STATE && this.isAssignedToMe(taskDetails.assignee); + return this.canCompleteTask(taskDetails); } isAssigneePropertyClickable( @@ -93,6 +94,16 @@ export class TaskCloudService extends BaseCloudService { return isClickable; } + /** + * Validates if a task was completed by the current user. + * + * @param taskDetails task details object + * @returns Boolean value if the task was completed by the current user + */ + wasTaskCompletedByCurrentUser(taskDetails: TaskDetailsCloudModel): boolean { + return taskDetails?.status === TASK_COMPLETED_STATE && this.isAssignedToMe(taskDetails.assignee); + } + /** * Validate if a task can be claimed. * @@ -169,11 +180,12 @@ export class TaskCloudService extends BaseCloudService { * * @param appName Name of the app * @param taskId ID of the task whose details you want + * @param service The service to call. Either Query Service or Runtime Bundle Service. * @returns Task details */ - getTaskById(appName: string, taskId: string): Observable { + getTaskById(appName: string, taskId: string, service: 'query' | 'rb' = 'query'): Observable { if ((appName || appName === '') && taskId) { - const queryUrl = `${this.getBasePath(appName)}/query/v1/tasks/${taskId}`; + const queryUrl = `${this.getBasePath(appName)}/${service}/v1/tasks/${taskId}`; return this.get(queryUrl).pipe(map((res: any) => res.entry)); } else { diff --git a/lib/process-services-cloud/src/lib/task/task-list/services/task-list-cloud.service.spec.ts b/lib/process-services-cloud/src/lib/task/task-list/services/task-list-cloud.service.spec.ts index a4a1f742d7..1666a3eb32 100644 --- a/lib/process-services-cloud/src/lib/task/task-list/services/task-list-cloud.service.spec.ts +++ b/lib/process-services-cloud/src/lib/task/task-list/services/task-list-cloud.service.spec.ts @@ -158,6 +158,49 @@ describe('TaskListCloudService', () => { expect(res).toBe('Appname not configured'); }); }); + + describe('fetchTaskList_UsingRuntimeBundleService', () => { + it('should call runtime bundle endpoint using GET', async () => { + const taskRequest = { + appName: 'fakeName', + pagination: { skipCount: 0, maxItems: 20 } + } as TaskListRequestModel; + requestSpy.and.callFake(returnCallQueryParameters); + + const res = await firstValueFrom(service.fetchTaskList_UsingRuntimeBundleService(taskRequest)); + + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res.skipCount).toBe(0); + expect(res.maxItems).toBe(20); + expect(requestSpy.calls.mostRecent().args[0]).toContain('/fakeName/rb/v1/tasks'); + expect(requestSpy.calls.mostRecent().args[1].httpMethod).toBe('GET'); + }); + + it('should use default pagination values when pagination is not specified', async () => { + const taskRequest = { + appName: 'fakeName' + } as TaskListRequestModel; + requestSpy.and.callFake(returnCallQueryParameters); + + const res = await firstValueFrom(service.fetchTaskList_UsingRuntimeBundleService(taskRequest)); + + expect(res.skipCount).toBe(0); + expect(res.maxItems).toBe(25); + }); + + it('should return an error when app name is not specified', async () => { + const taskRequest = { appName: null } as TaskListRequestModel; + requestSpy.and.callFake(returnCallUrl); + + const res = await firstValueFrom( + service.fetchTaskList_UsingRuntimeBundleService(taskRequest).pipe(catchError((error) => of(error.message))) + ); + + expect(res).toBe('Appname not configured'); + }); + }); + describe('getTaskListCount', () => { it('should concat the app name to the request url', async () => { const taskRequest = { diff --git a/lib/process-services-cloud/src/lib/task/task-list/services/task-list-cloud.service.ts b/lib/process-services-cloud/src/lib/task/task-list/services/task-list-cloud.service.ts index 2c8983fc41..9dfc8ce151 100644 --- a/lib/process-services-cloud/src/lib/task/task-list/services/task-list-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/task/task-list/services/task-list-cloud.service.ts @@ -62,7 +62,7 @@ export class TaskListCloudService extends BaseCloudService implements TaskListCl * Retrieves a list of tasks using an object with optional query properties. * * @param requestNode Query object - * @param queryUrl Query url + * @param queryUrl Query url. If empty, query service will be called. * @returns List of tasks */ fetchTaskList(requestNode: TaskListRequestModel, queryUrl?: string): Observable { @@ -90,6 +90,31 @@ export class TaskListCloudService extends BaseCloudService implements TaskListCl ); } + fetchTaskList_UsingRuntimeBundleService(requestNode: TaskListRequestModel): Observable { + if (!requestNode?.appName) { + return throwError(() => new Error('Appname not configured')); + } + + const url = `${this.getBasePath(requestNode.appName)}/rb/v1/tasks`; + + const queryParams = { + maxItems: requestNode.pagination?.maxItems || 25, + skipCount: requestNode.pagination?.skipCount || 0 + }; + + const queryData = this.buildQueryData(requestNode); + + return this.getWithBody(url, queryData, queryParams).pipe( + map((response) => { + const entries = response.list?.entries; + if (entries) { + response.list.entries = entries.map((entryData) => entryData.entry) as any; + } + return response; + }) + ); + } + getTaskListCounter(requestNode: TaskListRequestModel): Observable { if (!requestNode.appName) { return throwError(() => new Error('Appname not configured'));