diff --git a/demo-shell/src/app/components/cloud/service-task-list-cloud-demo.component.html b/demo-shell/src/app/components/cloud/service-task-list-cloud-demo.component.html index aa2dc97788..a60a0573fe 100644 --- a/demo-shell/src/app/components/cloud/service-task-list-cloud-demo.component.html +++ b/demo-shell/src/app/components/cloud/service-task-list-cloud-demo.component.html @@ -1,15 +1,12 @@ -
+
- + fxFlex>
{ let component: ServiceTaskListCloudComponent; let fixture: ComponentFixture; let appConfig: AppConfigService; - let taskListCloudService: TaskListCloudService; + let serviceTaskListCloudService: ServiceTaskListCloudService; setupTestBed({ imports: [ @@ -89,7 +89,7 @@ describe('ServiceTaskListCloudComponent', () => { beforeEach(() => { appConfig = TestBed.inject(AppConfigService); - taskListCloudService = TestBed.inject(TaskListCloudService); + serviceTaskListCloudService = TestBed.inject(ServiceTaskListCloudService); fixture = TestBed.createComponent(ServiceTaskListCloudComponent); component = fixture.componentInstance; appConfig.config = Object.assign(appConfig.config, { @@ -126,7 +126,7 @@ describe('ServiceTaskListCloudComponent', () => { it('should display empty content when process list is empty', () => { const emptyList = { list: { entries: [] } }; - spyOn(taskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(emptyList)); + spyOn(serviceTaskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(emptyList)); fixture.detectChanges(); expect(component.isLoading).toBe(true); @@ -145,7 +145,7 @@ describe('ServiceTaskListCloudComponent', () => { }); it('should load spinner and show the content', () => { - spyOn(taskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); + spyOn(serviceTaskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); const appName = new SimpleChange(null, 'FAKE-APP-NAME', true); fixture.detectChanges(); @@ -187,7 +187,7 @@ describe('ServiceTaskListCloudComponent', () => { }); it('should return the results if an application name is given', (done) => { - spyOn(taskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); + spyOn(serviceTaskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); const appName = new SimpleChange(null, 'FAKE-APP-NAME', true); component.success.subscribe((res) => { expect(res).toBeDefined(); @@ -216,7 +216,7 @@ describe('ServiceTaskListCloudComponent', () => { it('should reload tasks when reload() is called', (done) => { component.appName = 'fake'; - spyOn(taskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); + spyOn(serviceTaskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); component.success.subscribe((res) => { expect(res).toBeDefined(); expect(component.rows).toBeDefined(); @@ -250,14 +250,14 @@ describe('ServiceTaskListCloudComponent', () => { }); it('should NOT reload the task list when no parameters changed', () => { - spyOn(taskListCloudService, 'getTaskByRequest'); + spyOn(serviceTaskListCloudService, 'getServiceTaskByRequest'); component.rows = null; fixture.detectChanges(); expect(component.isListEmpty()).toBeTruthy(); }); it('should reload the task list when input parameters changed', () => { - const getServiceTaskByRequestSpy = spyOn(taskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); + const getServiceTaskByRequestSpy = spyOn(serviceTaskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); component.appName = 'mock-app-name'; component.queryParams.status = 'mock-status'; const queryParams = new SimpleChange(undefined, { status: 'mock-status' }, true); @@ -270,7 +270,7 @@ describe('ServiceTaskListCloudComponent', () => { }); it('should set formattedSorting if sorting input changes', () => { - spyOn(taskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); + spyOn(serviceTaskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); spyOn(component, 'formatSorting').and.callThrough(); component.appName = 'mock-app-name'; @@ -290,7 +290,7 @@ describe('ServiceTaskListCloudComponent', () => { }); it('should reload task list when sorting on a column changes', () => { - const getServiceTaskByRequestSpy = spyOn(taskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); + const getServiceTaskByRequestSpy = spyOn(serviceTaskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); component.onSortingChanged(new CustomEvent('sorting-changed', { detail: { key: 'fakeName', @@ -311,7 +311,7 @@ describe('ServiceTaskListCloudComponent', () => { }); it('should reset pagination when resetPaginationValues is called', async (done) => { - spyOn(taskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); + spyOn(serviceTaskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); const appName = new SimpleChange(null, 'FAKE-APP-NAME', true); component.ngOnChanges({ appName }); @@ -339,7 +339,7 @@ describe('ServiceTaskListCloudComponent', () => { }); it('should set pagination and reload when updatePagination is called', (done) => { - spyOn(taskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); + spyOn(serviceTaskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); spyOn(component, 'reload').and.stub(); const appName = new SimpleChange(null, 'FAKE-APP-NAME', true); component.ngOnChanges({ appName }); @@ -382,7 +382,7 @@ describe('ServiceTaskListCloudComponent', () => { }); beforeEach(() => { - spyOn(taskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); + spyOn(serviceTaskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); fixtureCustom = TestBed.createComponent(CustomTaskListComponent); copyFixture = TestBed.createComponent(CustomCopyContentTaskListComponent); fixtureCustom.detectChanges(); @@ -451,7 +451,7 @@ describe('ServiceTaskListCloudComponent', () => { beforeEach(() => { appConfig = TestBed.inject(AppConfigService); - taskListCloudService = TestBed.inject(TaskListCloudService); + serviceTaskListCloudService = TestBed.inject(ServiceTaskListCloudService); appConfig.config = Object.assign(appConfig.config, { 'adf-cloud-service-task-list': { 'presets': { @@ -476,7 +476,7 @@ describe('ServiceTaskListCloudComponent', () => { fixture = TestBed.createComponent(ServiceTaskListCloudComponent); component = fixture.componentInstance; element = fixture.debugElement.nativeElement; - taskSpy = spyOn(taskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); + taskSpy = spyOn(serviceTaskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); }); afterEach(() => { diff --git a/lib/process-services-cloud/src/lib/task/task-list/components/service-task-list-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-list/components/service-task-list-cloud.component.ts index 6af500ccdd..80053db966 100644 --- a/lib/process-services-cloud/src/lib/task/task-list/components/service-task-list-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/task/task-list/components/service-task-list-cloud.component.ts @@ -19,9 +19,9 @@ import { Component, ViewEncapsulation, Input } from '@angular/core'; import { AppConfigService, UserPreferencesService } from '@alfresco/adf-core'; -import { ServiceTaskQueryCloudRequestModel } from '../models/filter-cloud-model'; -import { TaskListCloudService } from '../services/task-list-cloud.service'; +import { ServiceTaskQueryCloudRequestModel } from '../models/service-task-cloud.model'; import { BaseTaskListCloudComponent } from './base-task-list-cloud.component'; +import { ServiceTaskListCloudService } from '../services/service-task-list-cloud.service'; @Component({ selector: 'adf-cloud-service-task-list', @@ -36,7 +36,7 @@ export class ServiceTaskListCloudComponent extends BaseTaskListCloudComponent { @Input() queryParams: { [key: string]: any } = {}; - constructor(private taskListCloudService: TaskListCloudService, + constructor(private serviceTaskListCloudService: ServiceTaskListCloudService, appConfigService: AppConfigService, userPreferences: UserPreferencesService) { super(appConfigService, userPreferences, ServiceTaskListCloudComponent.PRESET_KEY); @@ -44,7 +44,7 @@ export class ServiceTaskListCloudComponent extends BaseTaskListCloudComponent { load(requestNode: ServiceTaskQueryCloudRequestModel) { this.isLoading = true; - this.taskListCloudService.getServiceTaskByRequest(requestNode).subscribe( + this.serviceTaskListCloudService.getServiceTaskByRequest(requestNode).subscribe( (tasks) => { this.rows = tasks.list.entries; this.success.emit(tasks); diff --git a/lib/process-services-cloud/src/lib/task/task-list/models/filter-cloud-model.ts b/lib/process-services-cloud/src/lib/task/task-list/models/filter-cloud-model.ts index 10cffbe076..dc02be9218 100644 --- a/lib/process-services-cloud/src/lib/task/task-list/models/filter-cloud-model.ts +++ b/lib/process-services-cloud/src/lib/task/task-list/models/filter-cloud-model.ts @@ -80,26 +80,3 @@ export class TaskQueryCloudRequestModel { } } } - -export interface ServiceTaskQueryCloudRequestModel { - appName: string; - appVersion?: string; - id?: string; - status?: string; - maxItems: number; - skipCount: number; - sorting?: TaskListCloudSortingModel[]; - activityName?: string; - activityType?: string; - completedDate?: Date; - elementId?: string; - executionId?: string; - processDefinitionId?: string; - processDefinitionKey?: string; - processDefinitionVersion?: number; - processInstanceId?: string; - serviceFullName?: string; - serviceName?: string; - serviceVersion?: string; - startedDate?: Date; -} diff --git a/lib/process-services-cloud/src/lib/task/task-list/models/service-task-cloud.model.ts b/lib/process-services-cloud/src/lib/task/task-list/models/service-task-cloud.model.ts new file mode 100644 index 0000000000..954d28453f --- /dev/null +++ b/lib/process-services-cloud/src/lib/task/task-list/models/service-task-cloud.model.ts @@ -0,0 +1,48 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * 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 { TaskListCloudSortingModel } from './task-list-sorting.model'; + +export interface ServiceTaskQueryCloudRequestModel { + appName: string; + appVersion?: string; + id?: string; + status?: string; + maxItems: number; + skipCount: number; + sorting?: TaskListCloudSortingModel[]; + activityName?: string; + activityType?: string; + completedDate?: Date; + elementId?: string; + executionId?: string; + processDefinitionId?: string; + processDefinitionKey?: string; + processDefinitionVersion?: number; + processInstanceId?: string; + serviceFullName?: string; + serviceName?: string; + serviceVersion?: string; + startedDate?: Date; +} + +export interface ServiceTaskIntegrationContextCloudModel extends ServiceTaskQueryCloudRequestModel { + errorDate?: Date; + errorClassName?: string; + errorCode?: string; + errorMessage?: string; +} diff --git a/lib/process-services-cloud/src/lib/task/task-list/public-api.ts b/lib/process-services-cloud/src/lib/task/task-list/public-api.ts index 7f17dc60c8..37fc568f2e 100644 --- a/lib/process-services-cloud/src/lib/task/task-list/public-api.ts +++ b/lib/process-services-cloud/src/lib/task/task-list/public-api.ts @@ -19,6 +19,7 @@ export * from './components/task-list-cloud.component'; export * from './components/service-task-list-cloud.component'; export * from './models/filter-cloud-model'; +export * from './models/service-task-cloud.model'; export * from './models/task-list-sorting.model'; export * from './models/task-preset-cloud.model'; diff --git a/lib/process-services-cloud/src/lib/task/task-list/services/service-task-list-cloud.service.spec.ts b/lib/process-services-cloud/src/lib/task/task-list/services/service-task-list-cloud.service.spec.ts new file mode 100644 index 0000000000..09b58997d8 --- /dev/null +++ b/lib/process-services-cloud/src/lib/task/task-list/services/service-task-list-cloud.service.spec.ts @@ -0,0 +1,134 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * 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 { async } from '@angular/core/testing'; +import { setupTestBed, StorageService, AlfrescoApiServiceMock, LogService, AppConfigService, CoreModule } from '@alfresco/adf-core'; +import { fakeTaskCloudList } from '../mock/fake-task-response.mock'; +import { ServiceTaskListCloudService } from './service-task-list-cloud.service'; +import { ServiceTaskQueryCloudRequestModel } from '../models/service-task-cloud.model'; + +describe('Activiti ServiceTaskList Cloud Service', () => { + + let service: ServiceTaskListCloudService; + let alfrescoApiMock: AlfrescoApiServiceMock; + + function returnFakeTaskListResults() { + return { + oauth2Auth: { + callCustomApi: () => { + return Promise.resolve(fakeTaskCloudList); + } + } + }; + } + + function returnCallQueryParameters() { + return { + oauth2Auth: { + callCustomApi: (_queryUrl, _operation, _context, queryParams) => { + return Promise.resolve(queryParams); + } + } + }; + } + + function returnCallUrl() { + return { + oauth2Auth: { + callCustomApi: (queryUrl) => { + return Promise.resolve(queryUrl); + } + } + }; + } + + setupTestBed({ + imports: [ + CoreModule.forRoot() + ] + }); + + beforeEach(async(() => { + alfrescoApiMock = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService()); + service = new ServiceTaskListCloudService(alfrescoApiMock, + new AppConfigService(null), + new LogService(new AppConfigService(null))); + })); + + it('should return the tasks', (done) => { + const taskRequest: ServiceTaskQueryCloudRequestModel = { appName: 'fakeName' }; + spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskListResults); + service.getServiceTaskByRequest(taskRequest).subscribe((res) => { + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res.list.entries.length).toBe(2); + expect(res.list.entries[0].entry.appName).toBe('save-the-cheerleader'); + expect(res.list.entries[1].entry.appName).toBe('save-the-cheerleader'); + done(); + }); + }); + + it('should append to the call all the parameters', (done) => { + const taskRequest: ServiceTaskQueryCloudRequestModel = { appName: 'fakeName', skipCount: 0, maxItems: 20, service: 'fake-service' }; + spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnCallQueryParameters); + service.getServiceTaskByRequest(taskRequest).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 taskRequest: ServiceTaskQueryCloudRequestModel = { appName: 'fakeName', skipCount: 0, maxItems: 20, service: 'fake-service' }; + spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnCallUrl); + service.getServiceTaskByRequest(taskRequest).subscribe((requestUrl) => { + expect(requestUrl).toBeDefined(); + expect(requestUrl).not.toBeNull(); + expect(requestUrl).toContain('/fakeName/query/admin/v1/service-tasks'); + done(); + }); + }); + + it('should concat the sorting to append as parameters', (done) => { + const taskRequest: ServiceTaskQueryCloudRequestModel = { + appName: 'fakeName', skipCount: 0, maxItems: 20, service: 'fake-service', + sorting: [{ orderBy: 'NAME', direction: 'DESC' }, { orderBy: 'TITLE', direction: 'ASC' }] + }; + spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnCallQueryParameters); + service.getServiceTaskByRequest(taskRequest).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 taskRequest: ServiceTaskQueryCloudRequestModel = { appName: null }; + spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnCallUrl); + service.getServiceTaskByRequest(taskRequest).subscribe( + () => { }, + (error) => { + expect(error).toBe('Appname not configured'); + done(); + } + ); + }); +}); diff --git a/lib/process-services-cloud/src/lib/task/task-list/services/service-task-list-cloud.service.ts b/lib/process-services-cloud/src/lib/task/task-list/services/service-task-list-cloud.service.ts new file mode 100644 index 0000000000..d12de6b6be --- /dev/null +++ b/lib/process-services-cloud/src/lib/task/task-list/services/service-task-list-cloud.service.ts @@ -0,0 +1,103 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * 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 { AlfrescoApiService, AppConfigService, LogService } from '@alfresco/adf-core'; +import { ServiceTaskQueryCloudRequestModel, ServiceTaskIntegrationContextCloudModel } from '../models/service-task-cloud.model'; +import { Observable, throwError } from 'rxjs'; +import { TaskListCloudSortingModel } from '../models/task-list-sorting.model'; +import { BaseCloudService } from '../../../services/base-cloud.service'; + +@Injectable({ providedIn: 'root' }) +export class ServiceTaskListCloudService extends BaseCloudService { + + constructor(apiService: AlfrescoApiService, + appConfigService: AppConfigService, + private logService: LogService) { + super(apiService, appConfigService); + } + + /** + * Finds a task using an object with optional query properties. + * @param requestNode Query object + * @returns Task information + */ + getServiceTaskByRequest(requestNode: ServiceTaskQueryCloudRequestModel): Observable { + if (requestNode.appName || requestNode.appName === '') { + const queryUrl = `${this.getBasePath(requestNode.appName)}/query/admin/v1/service-tasks`; + const queryParams = this.buildQueryParams(requestNode); + const sortingParams = this.buildSortingParam(requestNode.sorting); + if (sortingParams) { + queryParams['sort'] = sortingParams; + } + return this.get(queryUrl, queryParams); + } else { + this.logService.error('Appname is mandatory for querying task'); + return throwError('Appname not configured'); + } + } + + /** + * Finds a service task integration context using an object with optional query properties. + * @param appName string + * @param serviceTaskId string + * @returns Service Task Integration Context information + */ + getServiceTaskStatus(appName: string, serviceTaskId: string): Observable { + if (appName) { + const queryUrl = `${this.getBasePath(appName)}/query/admin/v1/service-tasks/${serviceTaskId}/integration-context`; + return this.get(queryUrl); + } else { + this.logService.error('Appname is mandatory for querying task'); + return throwError('Appname not configured'); + } + } + + private buildQueryParams(requestNode: ServiceTaskQueryCloudRequestModel): Object { + const queryParam: Object = {}; + for (const property in requestNode) { + if (requestNode.hasOwnProperty(property) && + !this.isExcludedField(property) && + this.isPropertyValueValid(requestNode, property)) { + queryParam[property] = requestNode[property]; + } + } + return queryParam; + } + + private isExcludedField(property: string): boolean { + return property === 'appName' || property === 'sorting'; + } + + private isPropertyValueValid(requestNode: any, property: string): boolean { + return requestNode[property] !== '' && requestNode[property] !== null && requestNode[property] !== undefined; + } + + private 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); + } +} 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 a64351ab0b..809431fc56 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 @@ -17,7 +17,7 @@ import { Injectable } from '@angular/core'; import { AlfrescoApiService, AppConfigService, LogService } from '@alfresco/adf-core'; -import { TaskQueryCloudRequestModel, ServiceTaskQueryCloudRequestModel } from '../models/filter-cloud-model'; +import { TaskQueryCloudRequestModel } from '../models/filter-cloud-model'; import { Observable, throwError } from 'rxjs'; import { TaskListCloudSortingModel } from '../models/task-list-sorting.model'; import { BaseCloudService } from '../../../services/base-cloud.service'; @@ -51,26 +51,6 @@ export class TaskListCloudService extends BaseCloudService { } } - /** - * Finds a task using an object with optional query properties. - * @param requestNode Query object - * @returns Task information - */ - getServiceTaskByRequest(requestNode: ServiceTaskQueryCloudRequestModel): Observable { - if (requestNode.appName || requestNode.appName === '') { - const queryUrl = `${this.getBasePath(requestNode.appName)}/query/admin/v1/service-tasks`; - const queryParams = this.buildQueryParams(requestNode); - const sortingParams = this.buildSortingParam(requestNode.sorting); - if (sortingParams) { - queryParams['sort'] = sortingParams; - } - return this.get(queryUrl, queryParams); - } else { - this.logService.error('Appname is mandatory for querying task'); - return throwError('Appname not configured'); - } - } - private buildQueryParams(requestNode: TaskQueryCloudRequestModel): Object { const queryParam: Object = {}; for (const property in requestNode) {