From 31658c09744b990532582d5648eca190146358fd Mon Sep 17 00:00:00 2001 From: Silviu Popa Date: Mon, 25 Mar 2019 14:24:26 +0200 Subject: [PATCH] [ADF-4127] ProcessServicesCloud - add claim/unclaim directive (#4464) * [ADF-4127] ProessServicesCloud - claim task directive * [ADF-4127] - fix doc * [ADF-4127] - revert docs changes * [ADF-4127] - revert doc changes * [ADF-4127] - fix doc and reset sourceLinker.js * [ADF-4127] - refractor task-cloud.service. add validation for claim/unclaim and fix unit tests * [ADF-4127] - fix docs files * [ADF-4127[ - add aditional complete task validation * [ADF-4127] - PR changes * [ADF-4127] - complete docs file * [ADF-4127] - more PR changes * [ADF-4127] - change Unclaim task name and wait for task to be claimed and unclaimed before emit the success event * [ADF-4127] - fix unit tests --- .../task-details-cloud-demo.component.html | 8 +- .../task-details-cloud-demo.component.ts | 30 ++- .../claim-task.directive.md | 26 ++ .../services/task-cloud.service.md | 34 ++- .../unclaim-tas.directie.md | 26 ++ .../src/lib/i18n/en.json | 2 +- .../directives/claim-task.directive.spec.ts | 167 ++++++++++++ .../task/directives/claim-task.directive.ts | 94 +++++++ .../complete-task.directive.spec.ts | 2 +- .../directives/complete-task.directive.ts | 6 +- .../task/directives/task-directive.module.ts | 10 +- .../task/directives/unclaim-task.directive.ts | 86 ++++++ .../directives/unclaim-tast.directive.spec.ts | 164 ++++++++++++ .../src/lib/task/models/task.model.ts | 21 ++ .../src/lib/task/public-api.ts | 1 + .../services/task-cloud.service.spec.ts | 248 ++++++++---------- .../services/task-cloud.service.ts | 141 +++++----- .../task/start-task/mock/user-cloud.mock.ts | 4 + .../models/task-details-cloud.model.ts | 10 +- .../task-header-cloud.component.html | 7 - .../task-header-cloud.component.spec.ts | 2 +- .../components/task-header-cloud.component.ts | 66 ++--- .../task-header/mocks/fake-claim-task.mock.ts | 31 +++ .../src/lib/task/task-header/public-api.ts | 2 - .../src/lib/task/task.module.ts | 39 +++ lib/process-services/i18n/en.json | 2 +- 26 files changed, 949 insertions(+), 280 deletions(-) create mode 100644 docs/process-services-cloud/claim-task.directive.md create mode 100644 docs/process-services-cloud/unclaim-tas.directie.md create mode 100644 lib/process-services-cloud/src/lib/task/directives/claim-task.directive.spec.ts create mode 100644 lib/process-services-cloud/src/lib/task/directives/claim-task.directive.ts create mode 100644 lib/process-services-cloud/src/lib/task/directives/unclaim-task.directive.ts create mode 100644 lib/process-services-cloud/src/lib/task/directives/unclaim-tast.directive.spec.ts create mode 100644 lib/process-services-cloud/src/lib/task/models/task.model.ts rename lib/process-services-cloud/src/lib/task/{task-header => }/services/task-cloud.service.spec.ts (85%) rename lib/process-services-cloud/src/lib/task/{task-header => }/services/task-cloud.service.ts (86%) create mode 100644 lib/process-services-cloud/src/lib/task/task-header/mocks/fake-claim-task.mock.ts create mode 100644 lib/process-services-cloud/src/lib/task/task.module.ts diff --git a/demo-shell/src/app/components/app-layout/cloud/task-details-cloud-demo.component.html b/demo-shell/src/app/components/app-layout/cloud/task-details-cloud-demo.component.html index 2a72d8d545..68056e9373 100644 --- a/demo-shell/src/app/components/app-layout/cloud/task-details-cloud-demo.component.html +++ b/demo-shell/src/app/components/app-layout/cloud/task-details-cloud-demo.component.html @@ -4,7 +4,13 @@
+ (success)="onCompletedTask()">{{ 'ADF_TASK_LIST.DETAILS.BUTTON.COMPLETE' | translate }} + + + +
diff --git a/demo-shell/src/app/components/app-layout/cloud/task-details-cloud-demo.component.ts b/demo-shell/src/app/components/app-layout/cloud/task-details-cloud-demo.component.ts index 6838200b2e..25f72933b2 100644 --- a/demo-shell/src/app/components/app-layout/cloud/task-details-cloud-demo.component.ts +++ b/demo-shell/src/app/components/app-layout/cloud/task-details-cloud-demo.component.ts @@ -17,7 +17,7 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { TaskCloudService, TaskDetailsCloudModel } from '@alfresco/adf-process-services-cloud'; +import { TaskDetailsCloudModel, TaskCloudService } from '@alfresco/adf-process-services-cloud'; @Component({ templateUrl: './task-details-cloud-demo.component.html', @@ -47,26 +47,42 @@ export class TaskDetailsCloudDemoComponent implements OnInit { this.loadTaskDetailsById(this.appName, this.taskId); } - loadTaskDetailsById(appName: string, taskId: string): any { + loadTaskDetailsById(appName: string, taskId: string) { this.taskCloudService.getTaskById(appName, taskId).subscribe( - (taskDetails) => { + (taskDetails: TaskDetailsCloudModel ) => { this.taskDetails = taskDetails; }); } - isTaskValid() { - return this.appName && this.taskId; + isTaskValid(): boolean { + return this.appName !== undefined && this.taskId !== undefined; } - canCompleteTask() { + canCompleteTask(): boolean { return this.taskDetails && this.taskCloudService.canCompleteTask(this.taskDetails); } + canClaimTask(): boolean { + return this.taskDetails && this.taskCloudService.canClaimTask(this.taskDetails); + } + + canUnClaimTask(): boolean { + return this.taskDetails && this.taskCloudService.canUnclaimTask(this.taskDetails); + } + goBack() { this.router.navigate([`/cloud/${this.appName}/`]); } - onCompletedTask(evt: any) { + onCompletedTask() { + this.goBack(); + } + + onUnclaimTask() { + this.goBack(); + } + + onClaimTask() { this.goBack(); } } diff --git a/docs/process-services-cloud/claim-task.directive.md b/docs/process-services-cloud/claim-task.directive.md new file mode 100644 index 0000000000..97ccdd8e80 --- /dev/null +++ b/docs/process-services-cloud/claim-task.directive.md @@ -0,0 +1,26 @@ +--- +Title: Claim Task Directive +Added: v3.1.0 +Status: Experimental +Last reviewed: 2019-03-05 +--- + +# [Claim task directive](../../lib/process-services-cloud/src/lib/task/directives/claim-task.directive.ts "Defined in claim-task.directive.ts") + +Claim a task + +## Basic Usage + +```html + +``` +## Class members + +### Properties + +| Name | Type | Default value | Description | +| ---- | ---- | ------------- | ----------- | +| taskId | `string` | empty |(Required) The id of the task. | +| appName | `string` | empty | (Required) The name of the application. | +| success | `EventEmitter` | empty | Emitted when the task is completed. | +| error | `EventEmitter` | empty | Emitted when the task cannot be completed. | \ No newline at end of file diff --git a/docs/process-services-cloud/services/task-cloud.service.md b/docs/process-services-cloud/services/task-cloud.service.md index 4b1675e7e2..e27229d5c5 100644 --- a/docs/process-services-cloud/services/task-cloud.service.md +++ b/docs/process-services-cloud/services/task-cloud.service.md @@ -17,33 +17,41 @@ Manages task cloud. Validate if a task can be completed. - _taskDetails:_ [`TaskDetailsCloudModel`](../../../lib/process-services-cloud/src/lib/task/start-task/models/task-details-cloud.model.ts) - task details object - **Returns** `boolean` - Boolean value if the task can be completed +- **completeTask**(appName: `string`, taskId: `string`)
+ Complete a task + - _appName:_ `string` - Name of the app + - _taskId:_ `string` - ID of the task to complete + - **Returns** [`TaskDetailsCloudModel`](../../../lib/process-services-cloud/src/lib/task/start-task/models/task-details-cloud.model.ts) - Details of the task that was completed +- **canClaimTask**(taskDetails: [`TaskDetailsCloudModel`](../../lib/process-services-cloud/src/lib/task/start-filters/models/task-details-cloud.model.ts))
+ Validate if a task can be claimed. + - _taskDetails:_ [`TaskDetailsCloudModel`](../../lib/process-services-cloud/src/lib/task/start-filters/models/task-details-cloud.model.ts) - Task details object + - **Returns** `boolean` - Boolean value if the task can be claimed +- **canUnclaimTask**(taskDetails: [`TaskDetailsCloudModel`](../../lib/process-services-cloud/src/lib/task/start-filters/models/task-details-cloud.model.ts))
+ Validate if a task can be unclaimed. + - _taskDetails:_ [`TaskDetailsCloudModel`](../../lib/process-services-cloud/src/lib/task/start-filters/models/task-details-cloud.model.ts) - Task details object + - **Returns** `boolean` - Boolean value if the task can be unclaimed - **claimTask**(appName: `string`, taskId: `string`, assignee: `string`): `any`
Claims a task for an assignee. - _appName:_ `string` - Name of the app - _taskId:_ `string` - ID of the task to claim - _assignee:_ `string` - User to assign the task to - - **Returns** `any` - Details of the claimed task -- **completeTask**(appName: `string`, taskId: `string`): `any`
- Complete a task. - - _appName:_ `string` - Name of the app - - _taskId:_ `string` - ID of the task to complete - - **Returns** `any` - Details of the task that was completed -- **getTaskById**(appName: `string`, taskId: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`TaskDetailsCloudModel`](../../../lib/process-services-cloud/src/lib/task/start-task/models/task-details-cloud.model.ts)`>`
- Gets details of a task. - - _appName:_ `string` - Name of the app - - _taskId:_ `string` - ID of the task whose details you want - - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`TaskDetailsCloudModel`](../../../lib/process-services-cloud/src/lib/task/start-task/models/task-details-cloud.model.ts)`>` - Task details + - **Returns** [`TaskDetailsCloudModel`](../../../lib/process-services-cloud/src/lib/task/start-task/models/task-details-cloud.model.ts) - Details of the claimed task - **unclaimTask**(appName: `string`, taskId: `string`): `any`
Un-claims a task. - _appName:_ `string` - Name of the app - _taskId:_ `string` - ID of the task to unclaim - - **Returns** `any` - Details of the task that was unclaimed + - **Returns** [`TaskDetailsCloudModel`](../../../lib/process-services-cloud/src/lib/task/start-task/models/task-details-cloud.model.ts) - Details of the task that was unclaimed +- **getTaskById**(appName: `string`, taskId: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`TaskDetailsCloudModel`](../../lib/process-services-cloud/src/lib/task/start-task/models/task-details-cloud.model.ts)`>`
+ Gets details of a task. + - _appName:_ `string` - Name of the app + - _taskId:_ `string` - ID of the task whose details you want + - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`TaskDetailsCloudModel`](../../lib/process-services-cloud/src/lib/task/start-task/models/task-details-cloud.model.ts)`>` - Task details - **updateTask**(appName: `string`, taskId: `string`, updatePayload: `any`): `any`
Updates the details (name, description, due date) for a task. - _appName:_ `string` - Name of the app - _taskId:_ `string` - ID of the task to update - _updatePayload:_ `any` - Data to update the task - - **Returns** `any` - Updated task details + - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`TaskDetailsCloudModel`](../../lib/process-services-cloud/src/lib/task/start-task/models/task-details-cloud.model.ts)`>` - Updated task details ## Details diff --git a/docs/process-services-cloud/unclaim-tas.directie.md b/docs/process-services-cloud/unclaim-tas.directie.md new file mode 100644 index 0000000000..1bdebdf9b0 --- /dev/null +++ b/docs/process-services-cloud/unclaim-tas.directie.md @@ -0,0 +1,26 @@ +--- +Title: Unclaim Task Directive +Added: v3.1.0 +Status: Experimental +Last reviewed: 2019-03-05 +--- + +# [Unclaim task directive](../../lib/process-services-cloud/src/lib/task/directives/unclaim-task.directive.ts "Defined in unclaim-task.directive.ts") + +Unclaim a task + +## Basic Usage + +```html + +``` +## Class members + +### Properties + +| Name | Type | Default value | Description | +| ---- | ---- | ------------- | ----------- | +| taskId | `string` | empty |(Required) The id of the task. | +| appName | `string` | empty | (Required) The name of the application. | +| success | `EventEmitter` | empty | Emitted when the task is completed. | +| error | `EventEmitter` | empty | Emitted when the task cannot be completed. | \ No newline at end of file diff --git a/lib/process-services-cloud/src/lib/i18n/en.json b/lib/process-services-cloud/src/lib/i18n/en.json index 671168543d..8080e779ee 100644 --- a/lib/process-services-cloud/src/lib/i18n/en.json +++ b/lib/process-services-cloud/src/lib/i18n/en.json @@ -174,7 +174,7 @@ "ADF_CLOUD_TASK_HEADER": { "BUTTON": { "CLAIM": "Claim", - "UNCLAIM": "Requeue" + "UNCLAIM": "Release" }, "PROPERTIES": { "TASK_NAME": "Task", diff --git a/lib/process-services-cloud/src/lib/task/directives/claim-task.directive.spec.ts b/lib/process-services-cloud/src/lib/task/directives/claim-task.directive.spec.ts new file mode 100644 index 0000000000..9a5c93dd95 --- /dev/null +++ b/lib/process-services-cloud/src/lib/task/directives/claim-task.directive.spec.ts @@ -0,0 +1,167 @@ +/*! + * @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 { Component, ContentChildren, ViewChild } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { setupTestBed, CoreModule } from '@alfresco/adf-core'; +import { TaskCloudService } from '../services/task-cloud.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { of } from 'rxjs'; +import { ClaimTaskDirective } from './claim-task.directive'; +import { taskClaimCloudMock } from '../task-header/mocks/fake-claim-task.mock'; + +describe('ClaimTaskDirective', () => { + + @Component({ + selector: 'adf-claim-test-component', + template: '' + }) + class TestComponent { + + taskMock = 'test1234'; + appNameMock = 'simple-app'; + + @ViewChild(ClaimTaskDirective) + claimTaskDirective: ClaimTaskDirective; + + onCompleteTask(event: any) { + return event; + } + } + + let fixture: ComponentFixture; + let taskCloudService: TaskCloudService; + + setupTestBed({ + imports: [ + CoreModule.forRoot(), + RouterTestingModule + ], + declarations: [ + TestComponent, + ClaimTaskDirective + ] + }); + + beforeEach(() => { + taskCloudService = TestBed.get(TaskCloudService); + fixture = TestBed.createComponent(TestComponent); + fixture.detectChanges(); + }); + + it('should directive claim task', () => { + spyOn(taskCloudService, 'claimTask').and.returnValue(of(taskClaimCloudMock)); + const button = fixture.nativeElement.querySelector('button'); + button.click(); + expect(taskCloudService.claimTask).toHaveBeenCalled(); + }); + +}); + +describe('Claim Task Directive validation errors', () => { + + @Component({ + selector: 'adf-claim-no-fields-validation-component', + template: '' + }) + class ClaimTestMissingInputDirectiveComponent { + + appName = 'simple-app'; + appNameUndefined = undefined; + appNameNull = null; + + @ContentChildren(ClaimTaskDirective) + claimTaskValidationDirective: ClaimTaskDirective; + } + + @Component({ + selector: 'adf-claim-no-taskid-validation-component', + template: '' + }) + class ClaimTestMissingTaskIdDirectiveComponent { + + appName = 'simple-app'; + + @ContentChildren(ClaimTaskDirective) + claimTaskValidationDirective: ClaimTaskDirective; + } + + @Component({ + selector: 'adf-claim-undefined-appname-component', + template: '' + }) + class ClaimTestInvalidAppNameUndefineddDirectiveComponent { + + appNameUndefined = undefined; + taskMock = 'test1234'; + + @ContentChildren(ClaimTaskDirective) + claimTaskValidationDirective: ClaimTaskDirective; + } + + @Component({ + selector: 'adf-claim-null-appname-component', + template: '' + }) + class ClaimTestInvalidAppNameNulldDirectiveComponent { + + appNameNull = null; + taskMock = 'test1234'; + + @ViewChild(ClaimTaskDirective) + claimTaskValidationDirective: ClaimTaskDirective; + } + + let fixture: ComponentFixture; + + setupTestBed({ + imports: [ + CoreModule.forRoot(), + RouterTestingModule + ], + declarations: [ + ClaimTestMissingTaskIdDirectiveComponent, + ClaimTestInvalidAppNameUndefineddDirectiveComponent, + ClaimTestInvalidAppNameNulldDirectiveComponent, + ClaimTestMissingInputDirectiveComponent, + ClaimTaskDirective + ] + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ClaimTestMissingInputDirectiveComponent); + }); + + it('should throw error when missing input', () => { + fixture = TestBed.createComponent(ClaimTestMissingInputDirectiveComponent); + expect(() => fixture.detectChanges()).toThrowError(); + }); + + it('should throw error when taskId is not set', () => { + fixture = TestBed.createComponent(ClaimTestMissingTaskIdDirectiveComponent); + expect( () => fixture.detectChanges()).toThrowError('Attribute taskId is required'); + }); + + it('should throw error when appName is undefined', () => { + fixture = TestBed.createComponent(ClaimTestInvalidAppNameUndefineddDirectiveComponent); + expect( () => fixture.detectChanges()).toThrowError('Attribute appName is required'); + }); + + it('should throw error when appName is null', () => { + fixture = TestBed.createComponent(ClaimTestInvalidAppNameUndefineddDirectiveComponent); + expect( () => fixture.detectChanges()).toThrowError('Attribute appName is required'); + }); +}); diff --git a/lib/process-services-cloud/src/lib/task/directives/claim-task.directive.ts b/lib/process-services-cloud/src/lib/task/directives/claim-task.directive.ts new file mode 100644 index 0000000000..54bbd8e46d --- /dev/null +++ b/lib/process-services-cloud/src/lib/task/directives/claim-task.directive.ts @@ -0,0 +1,94 @@ +/*! + * @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 { Directive, Input, HostListener, Output, EventEmitter, OnInit } from '@angular/core'; +import { IdentityUserService } from '@alfresco/adf-core'; +import { TaskCloudService } from '../services/task-cloud.service'; + +@Directive({ + selector: '[adf-cloud-claim-task]' +}) +export class ClaimTaskDirective implements OnInit { + + /** (Required) The id of the task. */ + @Input() + taskId: string; + + /** (Required) The name of the application. */ + @Input() + appName: string; + + /** Emitted when the task is completed. */ + @Output() + success: EventEmitter = new EventEmitter(); + + /** Emitted when the task cannot be completed. */ + @Output() + error: EventEmitter = new EventEmitter(); + + invalidParams: string[] = []; + + constructor( + private taskListService: TaskCloudService, + private identityUserService: IdentityUserService) { } + + ngOnInit() { + this.validateInputs(); + } + + validateInputs() { + + if (!this.isTaskValid()) { + this.invalidParams.push('taskId'); + } + if (!this.isAppValid()) { + this.invalidParams.push('appName'); + } + if (this.invalidParams.length) { + throw new Error(`Attribute ${this.invalidParams.join(', ')} is required`); + } + } + + isTaskValid(): boolean { + return this.taskId && this.taskId.length > 0; + } + + isAppValid(): boolean { + return this.appName && this.appName.length > 0; + } + + @HostListener('click') + async onClick() { + try { + this.claimTask(); + } catch (error) { + this.error.emit(error); + } + + } + + private async claimTask() { + const currentUser: string = this.identityUserService.getCurrentUserInfo().username; + try { + const result = await this.taskListService.claimTask(this.appName, this.taskId, currentUser).toPromise(); + if (result) { + this.success.emit(result); + } + } catch (error) { + this.error.emit(error); + } + } +} diff --git a/lib/process-services-cloud/src/lib/task/directives/complete-task.directive.spec.ts b/lib/process-services-cloud/src/lib/task/directives/complete-task.directive.spec.ts index 0d1fdc413c..8d35553bd2 100644 --- a/lib/process-services-cloud/src/lib/task/directives/complete-task.directive.spec.ts +++ b/lib/process-services-cloud/src/lib/task/directives/complete-task.directive.spec.ts @@ -20,8 +20,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CoreModule, setupTestBed } from '@alfresco/adf-core'; import { RouterTestingModule } from '@angular/router/testing'; import { of } from 'rxjs'; -import { TaskCloudService } from '../task-header/services/task-cloud.service'; import { taskCompleteCloudMock } from '../task-header/mocks/fake-complete-task.mock'; +import { TaskCloudService } from '../services/task-cloud.service'; describe('CompleteTaskDirective', () => { diff --git a/lib/process-services-cloud/src/lib/task/directives/complete-task.directive.ts b/lib/process-services-cloud/src/lib/task/directives/complete-task.directive.ts index 7d604d5bc7..8652123ebf 100644 --- a/lib/process-services-cloud/src/lib/task/directives/complete-task.directive.ts +++ b/lib/process-services-cloud/src/lib/task/directives/complete-task.directive.ts @@ -15,7 +15,7 @@ * limitations under the License. */ import { Directive, Input, HostListener, Output, EventEmitter, OnInit } from '@angular/core'; -import { TaskCloudService } from '../task-header/services/task-cloud.service'; +import { TaskCloudService } from '../services/task-cloud.service'; @Directive({ selector: '[adf-cloud-complete-task]' @@ -59,11 +59,11 @@ export class CompleteTaskDirective implements OnInit { } } - isTaskValid() { + isTaskValid(): boolean { return this.taskId && this.taskId.length > 0; } - isAppValid() { + isAppValid(): boolean { return this.appName && this.appName.length > 0; } diff --git a/lib/process-services-cloud/src/lib/task/directives/task-directive.module.ts b/lib/process-services-cloud/src/lib/task/directives/task-directive.module.ts index 8280bb9385..50aad9c624 100644 --- a/lib/process-services-cloud/src/lib/task/directives/task-directive.module.ts +++ b/lib/process-services-cloud/src/lib/task/directives/task-directive.module.ts @@ -17,13 +17,19 @@ import { NgModule } from '@angular/core'; import { CompleteTaskDirective } from './complete-task.directive'; +import { ClaimTaskDirective } from './claim-task.directive'; +import { UnClaimTaskDirective } from './unclaim-task.directive'; @NgModule({ declarations: [ - CompleteTaskDirective + CompleteTaskDirective, + ClaimTaskDirective, + UnClaimTaskDirective ], exports: [ - CompleteTaskDirective + CompleteTaskDirective, + ClaimTaskDirective, + UnClaimTaskDirective ] }) export class TaskDirectiveModule { } diff --git a/lib/process-services-cloud/src/lib/task/directives/unclaim-task.directive.ts b/lib/process-services-cloud/src/lib/task/directives/unclaim-task.directive.ts new file mode 100644 index 0000000000..492e372450 --- /dev/null +++ b/lib/process-services-cloud/src/lib/task/directives/unclaim-task.directive.ts @@ -0,0 +1,86 @@ +/*! + * @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 { Directive, Input, HostListener, Output, EventEmitter, OnInit } from '@angular/core'; +import { TaskCloudService } from '../services/task-cloud.service'; + +@Directive({ + selector: '[adf-cloud-unclaim-task]' +}) +export class UnClaimTaskDirective implements OnInit { + + /** (Required) The id of the task. */ + @Input() + taskId: string; + + /** (Required) The name of the application. */ + @Input() + appName: string; + + /** Emitted when the task is completed. */ + @Output() + success: EventEmitter = new EventEmitter(); + + /** Emitted when the task cannot be completed. */ + @Output() + error: EventEmitter = new EventEmitter(); + + invalidParams: string[] = []; + + constructor( + private taskListService: TaskCloudService) { } + + ngOnInit() { + this.validateInputs(); + } + + validateInputs() { + + if (!this.isTaskValid()) { + this.invalidParams.push('taskId'); + } + if (!this.isAppValid()) { + this.invalidParams.push('appName'); + } + if (this.invalidParams.length) { + throw new Error(`Attribute ${this.invalidParams.join(', ')} is required`); + } + } + + isTaskValid(): boolean { + return this.taskId && this.taskId.length > 0; + } + + isAppValid(): boolean { + return this.appName && this.appName.length > 0; + } + + @HostListener('click') + async onClick() { + try { + this.unclaimTask(); + } catch (error) { + this.error.emit(error); + } + } + + private async unclaimTask() { + await this.taskListService.unclaimTask(this.appName, this.taskId).subscribe( + () => { + this.success.emit(this.taskId); + }); + } +} diff --git a/lib/process-services-cloud/src/lib/task/directives/unclaim-tast.directive.spec.ts b/lib/process-services-cloud/src/lib/task/directives/unclaim-tast.directive.spec.ts new file mode 100644 index 0000000000..f8c15756ed --- /dev/null +++ b/lib/process-services-cloud/src/lib/task/directives/unclaim-tast.directive.spec.ts @@ -0,0 +1,164 @@ +/*! + * @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 { Component, ContentChildren, ViewChild } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { setupTestBed, CoreModule } from '@alfresco/adf-core'; +import { TaskCloudService } from '../services/task-cloud.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { of } from 'rxjs'; +import { UnClaimTaskDirective } from './unclaim-task.directive'; +import { taskClaimCloudMock } from '../task-header/mocks/fake-claim-task.mock'; + +describe('UnClaimTaskDirective', () => { + + @Component({ + selector: 'adf-test-component', + template: '' + }) + class TestComponent { + + appName = 'simple-app'; + taskIdMock = '1234'; + + @ContentChildren(UnClaimTaskDirective) + unclaimTaskDirective: UnClaimTaskDirective; + } + + let fixture: ComponentFixture; + let taskCloudService: TaskCloudService; + + setupTestBed({ + imports: [ + CoreModule.forRoot(), + RouterTestingModule + ], + declarations: [ + TestComponent, + UnClaimTaskDirective + ], + providers: [ TaskCloudService ] + }); + + beforeEach(() => { + taskCloudService = TestBed.get(TaskCloudService); + fixture = TestBed.createComponent(TestComponent); + fixture.detectChanges(); + }); + + it('should directive unclaim task', () => { + spyOn(taskCloudService, 'unclaimTask').and.returnValue(of(taskClaimCloudMock)); + const button = fixture.nativeElement.querySelector('button'); + button.click(); + expect(taskCloudService.unclaimTask).toHaveBeenCalled(); + }); + +}); + +describe('UnClaim Task Directive validation errors', () => { + + @Component({ + selector: 'adf-claim-no-fields-validation-component', + template: '' + }) + class ClaimTestMissingInputDirectiveComponent { + + appName = 'simple-app'; + appNameUndefined = undefined; + appNameNull = null; + + @ContentChildren(UnClaimTaskDirective) + claimTaskValidationDirective: UnClaimTaskDirective; + } + + @Component({ + selector: 'adf-claim-no-taskid-validation-component', + template: '' + }) + class ClaimTestMissingTaskIdDirectiveComponent { + + appName = 'simple-app'; + + @ContentChildren(UnClaimTaskDirective) + claimTaskValidationDirective: UnClaimTaskDirective; + } + + @Component({ + selector: 'adf-claim-undefined-appname-component', + template: '' + }) + class ClaimTestInvalidAppNameUndefineddDirectiveComponent { + + appNameUndefined = undefined; + taskMock = 'test1234'; + + @ContentChildren(UnClaimTaskDirective) + claimTaskValidationDirective: UnClaimTaskDirective; + } + + @Component({ + selector: 'adf-claim-null-appname-component', + template: '' + }) + class ClaimTestInvalidAppNameNulldDirectiveComponent { + + appNameNull = null; + taskMock = 'test1234'; + + @ViewChild(UnClaimTaskDirective) + claimTaskValidationDirective: UnClaimTaskDirective; + } + + let fixture: ComponentFixture; + + setupTestBed({ + imports: [ + CoreModule.forRoot(), + RouterTestingModule + ], + declarations: [ + ClaimTestMissingTaskIdDirectiveComponent, + ClaimTestInvalidAppNameUndefineddDirectiveComponent, + ClaimTestInvalidAppNameNulldDirectiveComponent, + ClaimTestMissingInputDirectiveComponent, + UnClaimTaskDirective + ] + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ClaimTestMissingInputDirectiveComponent); + }); + + it('should throw error when missing input', () => { + fixture = TestBed.createComponent(ClaimTestMissingInputDirectiveComponent); + expect(() => fixture.detectChanges()).toThrowError(); + }); + + it('should throw error when taskId is not set', () => { + fixture = TestBed.createComponent(ClaimTestMissingTaskIdDirectiveComponent); + expect( () => fixture.detectChanges()).toThrowError('Attribute taskId is required'); + }); + + it('should throw error when appName is undefined', () => { + fixture = TestBed.createComponent(ClaimTestInvalidAppNameUndefineddDirectiveComponent); + expect( () => fixture.detectChanges()).toThrowError('Attribute appName is required'); + }); + + it('should throw error when appName is null', () => { + fixture = TestBed.createComponent(ClaimTestInvalidAppNameUndefineddDirectiveComponent); + expect( () => fixture.detectChanges()).toThrowError('Attribute appName is required'); + }); +}); diff --git a/lib/process-services-cloud/src/lib/task/models/task.model.ts b/lib/process-services-cloud/src/lib/task/models/task.model.ts new file mode 100644 index 0000000000..9a5b9b886a --- /dev/null +++ b/lib/process-services-cloud/src/lib/task/models/task.model.ts @@ -0,0 +1,21 @@ +/*! + * @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. + */ + +export enum ClaimTaskEnum { + claim = 'claim', + unclaim = 'unclaim' +} diff --git a/lib/process-services-cloud/src/lib/task/public-api.ts b/lib/process-services-cloud/src/lib/task/public-api.ts index c2b1682a4e..1d0afddbc5 100644 --- a/lib/process-services-cloud/src/lib/task/public-api.ts +++ b/lib/process-services-cloud/src/lib/task/public-api.ts @@ -22,3 +22,4 @@ export * from './task-header/public-api'; export * from './task-cloud.module'; export * from './directives/task-directive.module'; +export * from './services/task-cloud.service'; diff --git a/lib/process-services-cloud/src/lib/task/task-header/services/task-cloud.service.spec.ts b/lib/process-services-cloud/src/lib/task/services/task-cloud.service.spec.ts similarity index 85% rename from lib/process-services-cloud/src/lib/task/task-header/services/task-cloud.service.spec.ts rename to lib/process-services-cloud/src/lib/task/services/task-cloud.service.spec.ts index 89b464d51d..30bf8eb1e0 100644 --- a/lib/process-services-cloud/src/lib/task/task-header/services/task-cloud.service.spec.ts +++ b/lib/process-services-cloud/src/lib/task/services/task-cloud.service.spec.ts @@ -16,21 +16,19 @@ */ import { async, TestBed } from '@angular/core/testing'; -import { setupTestBed, IdentityUserService, IdentityUserModel, AlfrescoApiServiceMock } from '@alfresco/adf-core'; -import { LogService, AppConfigService, StorageService, CoreModule } from '@alfresco/adf-core'; +import { setupTestBed, IdentityUserService } from '@alfresco/adf-core'; +import { AlfrescoApiServiceMock, LogService, AppConfigService, StorageService, CoreModule } from '@alfresco/adf-core'; import { TaskCloudService } from './task-cloud.service'; -import { taskDetailsCloudMock } from '../mocks/task-details-cloud.mock'; -import { taskCompleteCloudMock } from '../mocks/fake-complete-task.mock'; -import { fakeTaskDetailsCloud } from '../mocks/fake-task-details-response.mock'; +import { taskCompleteCloudMock } from '../task-header/mocks/fake-complete-task.mock'; +import { taskDetailsCloudMock } from '../task-header/mocks/task-details-cloud.mock'; +import { fakeTaskDetailsCloud } from '../task-header/mocks/fake-task-details-response.mock'; +import { cloudMockUser } from '../start-task/mock/user-cloud.mock'; describe('Task Cloud Service', () => { let service: TaskCloudService; let alfrescoApiMock: AlfrescoApiServiceMock; - let identityService: IdentityUserService; - let identityUserWithOutFirstNameMock = { firstName: null, lastName: 'fake-identity-last-name', email: 'fakeIdentity@email.com', username: 'superadminuser' }; - let getCurrentUserInfoStub; - let fakeIdentityUser: IdentityUserModel = new IdentityUserModel(identityUserWithOutFirstNameMock); + let identityUserService: IdentityUserService; function returnFakeTaskCompleteResults() { return { @@ -66,19 +64,17 @@ describe('Task Cloud Service', () => { imports: [ CoreModule.forRoot() ], - providers: [IdentityUserService, LogService] + providers: [ IdentityUserService ] }); beforeEach(async(() => { - - identityService = TestBed.get(IdentityUserService); - getCurrentUserInfoStub = spyOn(identityService, 'getCurrentUserInfo'); - getCurrentUserInfoStub.and.returnValue(fakeIdentityUser); alfrescoApiMock = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService() ); + identityUserService = TestBed.get(IdentityUserService); + spyOn(identityUserService, 'getCurrentUserInfo').and.returnValue(cloudMockUser); service = new TaskCloudService(alfrescoApiMock, new AppConfigService(null), new LogService(new AppConfigService(null)), - identityService); + identityUserService); })); @@ -109,124 +105,7 @@ describe('Task Cloud Service', () => { it('should canCompleteTask', () => { const canCompleteTaskResult = service.canCompleteTask(taskDetailsCloudMock); - expect(canCompleteTaskResult).toBe(true); - }); - - it('should return the task details when querying by id', (done) => { - const appName = 'taskp-app'; - const taskId = '68d54a8f'; - spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults); - service.getTaskById(appName, taskId).subscribe((res: any) => { - expect(res).toBeDefined(); - expect(res).not.toBeNull(); - expect(res.appName).toBe('task-app'); - expect(res.id).toBe('68d54a8f'); - done(); - }); - }); - - it('should throw error if appName is not defined when querying by id', (done) => { - const appName = null; - const taskId = '68d54a8f'; - spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults); - service.getTaskById(appName, taskId).subscribe( - () => { }, - (error) => { - expect(error).toBe('AppName/TaskId not configured'); - done(); - }); - }); - - it('should throw error if taskId is not defined when querying by id', (done) => { - const appName = 'task-app'; - const taskId = null; - spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults); - service.getTaskById(appName, taskId).subscribe( - () => { }, - (error) => { - expect(error).toBe('AppName/TaskId not configured'); - done(); - }); - }); - - it('should return the task details when updating a task', (done) => { - const appName = 'taskp-app'; - const taskId = '68d54a8f'; - const updatePayload = { description: 'New description' }; - spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults); - service.updateTask(appName, taskId, updatePayload).subscribe((res: any) => { - expect(res).toBeDefined(); - expect(res).not.toBeNull(); - expect(res.appName).toBe('task-app'); - expect(res.id).toBe('68d54a8f'); - done(); - }); - }); - - it('should throw error if appName is not defined when updating a task', (done) => { - const appName = null; - const taskId = '68d54a8f'; - const updatePayload = { description: 'New description' }; - spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults); - service.updateTask(appName, taskId, updatePayload).subscribe( - () => { }, - (error) => { - expect(error).toBe('AppName/TaskId not configured'); - done(); - }); - }); - - it('should throw error if taskId is not defined when updating a task', (done) => { - const appName = 'task-app'; - const taskId = null; - const updatePayload = { description: 'New description' }; - spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults); - service.updateTask(appName, taskId, updatePayload).subscribe( - () => { }, - (error) => { - expect(error).toBe('AppName/TaskId not configured'); - done(); - }); - }); - - it('should return the task details when updating a task', (done) => { - const appName = 'taskp-app'; - const taskId = '68d54a8f'; - const updatePayload = { description: 'New description' }; - spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults); - service.updateTask(appName, taskId, updatePayload).subscribe((res: any) => { - expect(res).toBeDefined(); - expect(res).not.toBeNull(); - expect(res.appName).toBe('task-app'); - expect(res.id).toBe('68d54a8f'); - done(); - }); - }); - - it('should throw error if appName is not defined when querying by id', (done) => { - const appName = null; - const taskId = '68d54a8f'; - const updatePayload = { description: 'New description' }; - spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults); - service.updateTask(appName, taskId, updatePayload).subscribe( - () => { }, - (error) => { - expect(error).toBe('AppName/TaskId not configured'); - done(); - }); - }); - - it('should throw error if taskId is not defined updating a task', (done) => { - const appName = 'task-app'; - const taskId = null; - const updatePayload = { description: 'New description' }; - spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults); - service.updateTask(appName, taskId, updatePayload).subscribe( - () => { }, - (error) => { - expect(error).toBe('AppName/TaskId not configured'); - done(); - }); + expect(canCompleteTaskResult).toBeTruthy(); }); it('should return the task details when claiming a task', (done) => { @@ -305,4 +184,107 @@ describe('Task Cloud Service', () => { done(); }); }); + + it('should return the task details when querying by id', (done) => { + const appName = 'taskp-app'; + const taskId = '68d54a8f'; + spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults); + service.getTaskById(appName, taskId).subscribe((res: any) => { + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res.appName).toBe('task-app'); + expect(res.id).toBe('68d54a8f'); + done(); + }); + }); + + it('should throw error if appName is not defined when querying by id', (done) => { + const appName = null; + const taskId = '68d54a8f'; + spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults); + service.getTaskById(appName, taskId).subscribe( + () => { }, + (error) => { + expect(error).toBe('AppName/TaskId not configured'); + done(); + }); + }); + + it('should throw error if taskId is not defined when querying by id', (done) => { + const appName = 'task-app'; + const taskId = null; + spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults); + service.getTaskById(appName, taskId).subscribe( + () => { }, + (error) => { + expect(error).toBe('AppName/TaskId not configured'); + done(); + }); + }); + + it('should throw error if appName is not defined when updating a task', (done) => { + const appName = null; + const taskId = '68d54a8f'; + const updatePayload = { description: 'New description' }; + spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults); + service.updateTask(appName, taskId, updatePayload).subscribe( + () => { }, + (error) => { + expect(error).toBe('AppName/TaskId not configured'); + done(); + }); + }); + + it('should throw error if taskId is not defined when updating a task', (done) => { + const appName = 'task-app'; + const taskId = null; + const updatePayload = { description: 'New description' }; + spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults); + service.updateTask(appName, taskId, updatePayload).subscribe( + () => { }, + (error) => { + expect(error).toBe('AppName/TaskId not configured'); + done(); + }); + }); + + it('should return the task details when updating a task', (done) => { + const appName = 'taskp-app'; + const taskId = '68d54a8f'; + const updatePayload = { description: 'New description' }; + spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults); + service.updateTask(appName, taskId, updatePayload).subscribe((res: any) => { + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res.appName).toBe('task-app'); + expect(res.id).toBe('68d54a8f'); + done(); + }); + }); + + it('should throw error if appName is not defined when querying by id', (done) => { + const appName = null; + const taskId = '68d54a8f'; + const updatePayload = { description: 'New description' }; + spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults); + service.updateTask(appName, taskId, updatePayload).subscribe( + () => { }, + (error) => { + expect(error).toBe('AppName/TaskId not configured'); + done(); + }); + }); + + it('should throw error if taskId is not defined updating a task', (done) => { + const appName = 'task-app'; + const taskId = null; + const updatePayload = { description: 'New description' }; + spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults); + service.updateTask(appName, taskId, updatePayload).subscribe( + () => { }, + (error) => { + expect(error).toBe('AppName/TaskId not configured'); + done(); + }); + }); }); diff --git a/lib/process-services-cloud/src/lib/task/task-header/services/task-cloud.service.ts b/lib/process-services-cloud/src/lib/task/services/task-cloud.service.ts similarity index 86% rename from lib/process-services-cloud/src/lib/task/task-header/services/task-cloud.service.ts rename to lib/process-services-cloud/src/lib/task/services/task-cloud.service.ts index 1c8bb1c25b..c353cb0d3f 100644 --- a/lib/process-services-cloud/src/lib/task/task-header/services/task-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/task/services/task-cloud.service.ts @@ -19,7 +19,7 @@ import { Injectable } from '@angular/core'; import { AlfrescoApiService, LogService, AppConfigService, IdentityUserService } from '@alfresco/adf-core'; import { from, throwError, Observable } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; -import { TaskDetailsCloudModel } from '../../start-task/models/task-details-cloud.model'; +import { TaskDetailsCloudModel } from '../start-task/models/task-details-cloud.model'; @Injectable({ providedIn: 'root' @@ -46,7 +46,7 @@ export class TaskCloudService { * @param taskId ID of the task to complete * @returns Details of the task that was completed */ - completeTask(appName: string, taskId: string) { + completeTask(appName: string, taskId: string): Observable { const queryUrl = this.buildCompleteTaskUrl(appName, taskId); const bodyParam = { 'payloadType': 'CompleteTaskPayload' }; const pathParams = {}, queryParams = {}, headerParams = {}, @@ -71,11 +71,83 @@ export class TaskCloudService { */ canCompleteTask(taskDetails: TaskDetailsCloudModel): boolean { const currentUser = this.identityUserService.getCurrentUserInfo().username; - return taskDetails.owner === currentUser && !taskDetails.isCompleted(); + return taskDetails.assignee && taskDetails.owner === currentUser && !taskDetails.isCompleted(); } - private buildCompleteTaskUrl(appName: string, taskId: string): any { - return `${this.appConfigService.get('bpmHost')}/${appName}-rb/v1/tasks/${taskId}/complete`; + /** + * Validate if a task can be claimed. + * @param taskDetails task details object + * @returns Boolean value if the task can be completed + */ + canClaimTask(taskDetails: TaskDetailsCloudModel): boolean { + return taskDetails && taskDetails.canClaimTask(); + } + + /** + * Validate if a task can be unclaimed. + * @param taskDetails task details object + * @returns Boolean value if the task can be completed + */ + canUnclaimTask(taskDetails: TaskDetailsCloudModel): boolean { + const currentUser = this.identityUserService.getCurrentUserInfo().username; + return taskDetails.canUnclaimTask(currentUser); + } + + /** + * Claims a task for an assignee. + * @param appName Name of the app + * @param taskId ID of the task to claim + * @param assignee User to assign the task to + * @returns Details of the claimed task + */ + claimTask(appName: string, taskId: string, assignee: string): Observable { + if (appName && taskId) { + + let queryUrl = `${this.contextRoot}/${appName}-rb/v1/tasks/${taskId}/claim?assignee=${assignee}`; + return from(this.apiService.getInstance() + .oauth2Auth.callCustomApi(queryUrl, 'POST', + null, null, null, + null, null, + this.contentTypes, this.accepts, + this.returnType, null, null) + ).pipe( + map((res: any) => { + return new TaskDetailsCloudModel(res.entry); + }), + catchError((err) => this.handleError(err)) + ); + } else { + this.logService.error('AppName and TaskId are mandatory for querying a task'); + return throwError('AppName/TaskId not configured'); + } + } + + /** + * Un-claims a task. + * @param appName Name of the app + * @param taskId ID of the task to unclaim + * @returns Details of the task that was unclaimed + */ + unclaimTask(appName: string, taskId: string): Observable { + if (appName && taskId) { + + let queryUrl = `${this.contextRoot}/${appName}-rb/v1/tasks/${taskId}/release`; + return from(this.apiService.getInstance() + .oauth2Auth.callCustomApi(queryUrl, 'POST', + null, null, null, + null, null, + this.contentTypes, this.accepts, + this.returnType, null, null) + ).pipe( + map((res: any) => { + return new TaskDetailsCloudModel(res.entry); + }), + catchError((err) => this.handleError(err)) + ); + } else { + this.logService.error('AppName and TaskId are mandatory for querying a task'); + return throwError('AppName/TaskId not configured'); + } } /** @@ -113,7 +185,7 @@ export class TaskCloudService { * @param updatePayload Data to update the task * @returns Updated task details */ - updateTask(appName: string, taskId: string, updatePayload: any): any { + updateTask(appName: string, taskId: string, updatePayload: any): Observable { if (appName && taskId) { updatePayload.payloadType = 'UpdateTaskPayload'; @@ -137,61 +209,8 @@ export class TaskCloudService { } } - /** - * Claims a task for an assignee. - * @param appName Name of the app - * @param taskId ID of the task to claim - * @param assignee User to assign the task to - * @returns Details of the claimed task - */ - claimTask(appName: string, taskId: string, assignee: string): any { - if (appName && taskId) { - - let queryUrl = `${this.contextRoot}/${appName}-rb/v1/tasks/${taskId}/claim?assignee=${assignee}`; - return from(this.apiService.getInstance() - .oauth2Auth.callCustomApi(queryUrl, 'POST', - null, null, null, - null, null, - this.contentTypes, this.accepts, - this.returnType, null, null) - ).pipe( - map((res: any) => { - return new TaskDetailsCloudModel(res.entry); - }), - catchError((err) => this.handleError(err)) - ); - } else { - this.logService.error('AppName and TaskId are mandatory for querying a task'); - return throwError('AppName/TaskId not configured'); - } - } - - /** - * Un-claims a task. - * @param appName Name of the app - * @param taskId ID of the task to unclaim - * @returns Details of the task that was unclaimed - */ - unclaimTask(appName: string, taskId: string): any { - if (appName && taskId) { - - let queryUrl = `${this.contextRoot}/${appName}-rb/v1/tasks/${taskId}/release`; - return from(this.apiService.getInstance() - .oauth2Auth.callCustomApi(queryUrl, 'POST', - null, null, null, - null, null, - this.contentTypes, this.accepts, - this.returnType, null, null) - ).pipe( - map((res: any) => { - return new TaskDetailsCloudModel(res.entry); - }), - catchError((err) => this.handleError(err)) - ); - } else { - this.logService.error('AppName and TaskId are mandatory for querying a task'); - return throwError('AppName/TaskId not configured'); - } + private buildCompleteTaskUrl(appName: string, taskId: string): string { + return `${this.appConfigService.get('bpmHost')}/${appName}-rb/v1/tasks/${taskId}/complete`; } private handleError(error: any) { diff --git a/lib/process-services-cloud/src/lib/task/start-task/mock/user-cloud.mock.ts b/lib/process-services-cloud/src/lib/task/start-task/mock/user-cloud.mock.ts index c5edc9f95f..1bf9362984 100644 --- a/lib/process-services-cloud/src/lib/task/start-task/mock/user-cloud.mock.ts +++ b/lib/process-services-cloud/src/lib/task/start-task/mock/user-cloud.mock.ts @@ -21,6 +21,10 @@ export const mockUsers = [ { id: 'fake-id-3', username: 'first-name-3 last-name-3', firstName: 'first-name-3', lastName: 'last-name-3', email: 'abcde@xyz.com' } ]; +export const cloudMockUser = { + id: 'fake-id-1', username: 'superadminuser', firstName: 'first-name-1', lastName: 'last-name-1', email: 'abc@xyz.com' +}; + export const mockRoles = [ { id: 'id-1', name: 'MOCK-ADMIN-ROLE'}, { id: 'id-2', name: 'MOCK-USER-ROLE'}, diff --git a/lib/process-services-cloud/src/lib/task/start-task/models/task-details-cloud.model.ts b/lib/process-services-cloud/src/lib/task/start-task/models/task-details-cloud.model.ts index 2668626b93..091ed1ca92 100644 --- a/lib/process-services-cloud/src/lib/task/start-task/models/task-details-cloud.model.ts +++ b/lib/process-services-cloud/src/lib/task/start-task/models/task-details-cloud.model.ts @@ -74,9 +74,17 @@ export class TaskDetailsCloudModel { } } - isCompleted() { + isCompleted(): boolean { return this.status && this.status === TaskStatusEnum.COMPLETED; } + + canClaimTask(): boolean { + return this.status === TaskStatusEnum.CREATED; + } + + canUnclaimTask(user: string): boolean { + return this.status !== TaskStatusEnum.COMPLETED && this.assignee === user; + } } export interface StartTaskCloudResponseModel { diff --git a/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.html b/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.html index a16a0935e7..eb21f20d68 100644 --- a/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.html +++ b/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.html @@ -8,13 +8,6 @@ [editable]="!taskDetails.isCompleted()"> - - - - - diff --git a/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.spec.ts index acf648d631..108a178734 100644 --- a/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.spec.ts @@ -24,7 +24,7 @@ import { By } from '@angular/platform-browser'; import { of } from 'rxjs'; import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module'; import { RouterTestingModule } from '@angular/router/testing'; -import { TaskCloudService } from '../services/task-cloud.service'; +import { TaskCloudService } from '../../services/task-cloud.service'; describe('TaskHeaderCloudComponent', () => { let component: TaskHeaderCloudComponent; diff --git a/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.ts index d53e3a8f00..3aa0fa3c1b 100644 --- a/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.ts @@ -24,11 +24,11 @@ import { TranslationService, AppConfigService, UpdateNotification, - CardViewUpdateService, - IdentityUserService + CardViewUpdateService } from '@alfresco/adf-core'; -import { TaskDetailsCloudModel } from '../../start-task/models/task-details-cloud.model'; -import { TaskCloudService } from '../services/task-cloud.service'; +import { TaskDetailsCloudModel, TaskStatusEnum } from '../../start-task/models/task-details-cloud.model'; +import { Router } from '@angular/router'; +import { TaskCloudService } from '../../services/task-cloud.service'; @Component({ selector: 'adf-cloud-task-header', @@ -61,18 +61,16 @@ export class TaskHeaderCloudComponent implements OnInit { properties: CardViewItem[]; inEdit: boolean = false; parentTaskName: string; - private currentUser: string; constructor( private taskCloudService: TaskCloudService, private translationService: TranslationService, private appConfig: AppConfigService, - private cardViewUpdateService: CardViewUpdateService, - private identityUserService: IdentityUserService + private router: Router, + private cardViewUpdateService: CardViewUpdateService ) { } ngOnInit() { - this.loadCurrentBpmUserId(); if (this.appName && this.taskId) { this.loadTaskDetailsById(this.appName, this.taskId); } @@ -80,10 +78,6 @@ export class TaskHeaderCloudComponent implements OnInit { this.cardViewUpdateService.itemUpdated$.subscribe(this.updateTaskDetails.bind(this)); } - loadCurrentBpmUserId(): any { - this.currentUser = this.identityUserService.getCurrentUserInfo().username; - } - loadTaskDetailsById(appName: string, taskId: string): any { this.taskCloudService.getTaskById(appName, taskId).subscribe( (taskDetails) => { @@ -224,55 +218,35 @@ export class TaskHeaderCloudComponent implements OnInit { ); } - isTaskClaimable(): boolean { - return !this.hasAssignee() && this.isCandidateMember(); + isCompleted() { + return this.taskDetails && this.taskDetails.status && this.taskDetails.status.toUpperCase() === TaskStatusEnum.COMPLETED; } hasAssignee(): boolean { return !!this.taskDetails.assignee ? true : false; } - isCandidateMember() { - return this.taskDetails.managerOfCandidateGroup || this.taskDetails.memberOfCandidateGroup || this.taskDetails.memberOfCandidateUsers; - } - - isTaskClaimedByCandidateMember(): boolean { - return this.isCandidateMember() && this.isAssignedToCurrentUser() && !this.taskDetails.isCompleted(); - } - - isAssignedToCurrentUser(): boolean { - return this.hasAssignee() && this.isAssignedTo(this.currentUser); - } - - isAssignedTo(userName): boolean { - return this.hasAssignee() ? this.taskDetails.assignee === userName : false; - } - isTaskValid() { return this.appName && this.taskId; } + isTaskAssigned() { + return this.taskDetails.assignee !== undefined; + } + isReadOnlyMode() { return !this.readOnly; } - claimTask() { - this.taskCloudService.claimTask(this.appName, this.taskId, this.currentUser).subscribe( - (res: any) => { - this.loadTaskDetailsById(this.appName, this.taskId); - this.claim.emit(this.taskId); - }); - } - - unclaimTask() { - this.taskCloudService.unclaimTask(this.appName, this.taskId).subscribe( - () => { - this.loadTaskDetailsById(this.appName, this.taskId); - this.unclaim.emit(this.taskId); - }); - } - private isValidSelection(filteredProperties: string[], cardItem: CardViewBaseItemModel): boolean { return filteredProperties ? filteredProperties.indexOf(cardItem.key) >= 0 : true; } + + goBack() { + this.router.navigate([`/cloud/${this.appName}/`]); + } + + onCompletedTask(event: any) { + this.goBack(); + } } diff --git a/lib/process-services-cloud/src/lib/task/task-header/mocks/fake-claim-task.mock.ts b/lib/process-services-cloud/src/lib/task/task-header/mocks/fake-claim-task.mock.ts new file mode 100644 index 0000000000..f1dd26bb22 --- /dev/null +++ b/lib/process-services-cloud/src/lib/task/task-header/mocks/fake-claim-task.mock.ts @@ -0,0 +1,31 @@ +/*! + * @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. + */ + +export const taskClaimCloudMock = { + 'entry': { + 'appName': 'simple-app', + 'appVersion': '', + 'serviceName': 'simple-app-rb', + 'serviceFullName': 'simple-app-rb', + 'serviceType': 'runtime-bundle', + 'serviceVersion': '', + 'id': '68d54a8f', + 'name': 'NXltAGmT', + 'priority': 0, + 'status': 'COMPLETED' + } +}; diff --git a/lib/process-services-cloud/src/lib/task/task-header/public-api.ts b/lib/process-services-cloud/src/lib/task/task-header/public-api.ts index 596020d265..0be25b6c89 100644 --- a/lib/process-services-cloud/src/lib/task/task-header/public-api.ts +++ b/lib/process-services-cloud/src/lib/task/task-header/public-api.ts @@ -17,6 +17,4 @@ export * from './components/task-header-cloud.component'; -export * from './services/task-cloud.service'; - export * from './task-header-cloud.module'; diff --git a/lib/process-services-cloud/src/lib/task/task.module.ts b/lib/process-services-cloud/src/lib/task/task.module.ts new file mode 100644 index 0000000000..926e213c5f --- /dev/null +++ b/lib/process-services-cloud/src/lib/task/task.module.ts @@ -0,0 +1,39 @@ +/*! + * @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 { NgModule } from '@angular/core'; +import { CompleteTaskDirective } from './directives/complete-task.directive'; +import { TaskCloudService } from './services/task-cloud.service'; +import { ClaimTaskDirective } from './directives/claim-task.directive'; +import { UnClaimTaskDirective } from './directives/unclaim-task.directive'; + +@NgModule({ + declarations: [ + CompleteTaskDirective, + ClaimTaskDirective, + UnClaimTaskDirective + ], + exports: [ + CompleteTaskDirective, + ClaimTaskDirective, + UnClaimTaskDirective + ], + providers: [ + TaskCloudService + ] +}) +export class TaskModule { } diff --git a/lib/process-services/i18n/en.json b/lib/process-services/i18n/en.json index 6d2c1283ac..199cd86c4d 100644 --- a/lib/process-services/i18n/en.json +++ b/lib/process-services/i18n/en.json @@ -73,7 +73,7 @@ "BUTTON": { "COMPLETE": "Complete", "CLAIM": "Claim", - "UNCLAIM": "Requeue", + "UNCLAIM": "Release", "DRAG-ATTACHMENT": "Drop files to upload", "UPLOAD-ATTACHMENT": "Upload Attachment" },