From 133a5c3df6c957e704fcb5484b2f4a8d49789db5 Mon Sep 17 00:00:00 2001 From: arditdomi <32884230+arditdomi@users.noreply.github.com> Date: Tue, 31 Dec 2019 15:41:54 +0000 Subject: [PATCH] [AAE-1137] Refactor delete process directive (#5317) * [AAE-1137] Refactor delete process directive * [AAE-1137] Fix unit tests * [AAE-1137] Add can show directive output * [AAE-1137] Remove from demo-shell, update documentation * [AAE-1137] Refactor cancel process directive * [AAE-1137] documentation typo * [AAE-1137] Add unsubscription * [AAE-1137] Fix build errors * [AAE-1137] Remove unused declaration --- .../process-details-cloud-demo.component.html | 2 - .../process-details-cloud-demo.component.scss | 1 + .../directives/cancel-process.directive.md | 23 +++ .../cancel-process.directive.spec.ts | 84 ++++++++++ .../directives/cancel-process.directive.ts | 94 +++++++++++ .../delete-process.directive.spec.ts | 149 ------------------ .../directives/delete-process.directive.ts | 108 ------------- .../directives/process-directive.module.ts | 6 +- .../src/lib/process/directives/public-api.ts | 2 +- .../lib/process/mock/process-details.mock.ts | 22 +++ 10 files changed, 228 insertions(+), 263 deletions(-) create mode 100644 docs/process-services-cloud/directives/cancel-process.directive.md create mode 100644 lib/process-services-cloud/src/lib/process/directives/cancel-process.directive.spec.ts create mode 100644 lib/process-services-cloud/src/lib/process/directives/cancel-process.directive.ts delete mode 100644 lib/process-services-cloud/src/lib/process/directives/delete-process.directive.spec.ts delete mode 100644 lib/process-services-cloud/src/lib/process/directives/delete-process.directive.ts create mode 100644 lib/process-services-cloud/src/lib/process/mock/process-details.mock.ts diff --git a/demo-shell/src/app/components/cloud/process-details-cloud-demo.component.html b/demo-shell/src/app/components/cloud/process-details-cloud-demo.component.html index e777e9aa04..a40f50d9f1 100644 --- a/demo-shell/src/app/components/cloud/process-details-cloud-demo.component.html +++ b/demo-shell/src/app/components/cloud/process-details-cloud-demo.component.html @@ -1,4 +1,3 @@ - @@ -22,4 +21,3 @@ [processInstanceId]="processInstanceId"> - diff --git a/demo-shell/src/app/components/cloud/process-details-cloud-demo.component.scss b/demo-shell/src/app/components/cloud/process-details-cloud-demo.component.scss index 91e2fbe20d..c65339b039 100644 --- a/demo-shell/src/app/components/cloud/process-details-cloud-demo.component.scss +++ b/demo-shell/src/app/components/cloud/process-details-cloud-demo.component.scss @@ -11,4 +11,5 @@ margin-left: 10px; width: 25%; } + } diff --git a/docs/process-services-cloud/directives/cancel-process.directive.md b/docs/process-services-cloud/directives/cancel-process.directive.md new file mode 100644 index 0000000000..3c9bc6735c --- /dev/null +++ b/docs/process-services-cloud/directives/cancel-process.directive.md @@ -0,0 +1,23 @@ +--- +Title: Cancel Process Directive +Added: v3.7.0 +Status: Experimental +Last reviewed: 2019-12-09 +--- + +# [Cancel process directive](../../../lib/process-services-cloud/src/lib/process/directives/cancel-process.directive.ts "Defined in cancel-process.directive.ts") + +Cancels a process + +## Basic Usage + +```html + +``` + +### Events + +| Name | Type | Description | +| ---- | ---- | ----------- | +| error | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`` | Emitted when the process can not be cancelled. | +| success | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`` | Emitted when the process is cancelled. | diff --git a/lib/process-services-cloud/src/lib/process/directives/cancel-process.directive.spec.ts b/lib/process-services-cloud/src/lib/process/directives/cancel-process.directive.spec.ts new file mode 100644 index 0000000000..1862a8d46c --- /dev/null +++ b/lib/process-services-cloud/src/lib/process/directives/cancel-process.directive.spec.ts @@ -0,0 +1,84 @@ +/*! + * @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, ViewChild } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { CoreModule, IdentityUserService, setupTestBed } from '@alfresco/adf-core'; +import { CancelProcessDirective } from './cancel-process.directive'; +import { processDetailsMockRunning, processDetailsMockCompleted } from '../mock/process-details.mock'; + +describe('CancelProcessDirective', () => { + + @Component({ + selector: 'adf-cloud-cancel-process-test-component', + template: '' + }) + class TestComponent { + + @ViewChild(CancelProcessDirective) + cancelProcessDirective: CancelProcessDirective; + } + + let fixture: ComponentFixture; + let identityUserService: IdentityUserService; + let component: TestComponent; + + setupTestBed({ + imports: [ + CoreModule.forRoot() + ], + declarations: [ + TestComponent, + CancelProcessDirective + ] + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TestComponent); + component = fixture.componentInstance; + identityUserService = TestBed.get(IdentityUserService); + spyOn(identityUserService, 'getCurrentUserInfo').and.returnValue({username: 'usermock'}); + fixture.detectChanges(); + }); + + it('should directive call cancelProcess when button is clicked', () => { + const cancelProcessSpy = spyOn(component.cancelProcessDirective, 'cancelProcess').and.callThrough(); + const button = fixture.nativeElement.querySelector('button'); + button.click(); + expect(cancelProcessSpy).toHaveBeenCalled(); + }); + + it('should checkCanCancelProcess return false when process status is COMPLETED', () => { + component.cancelProcessDirective.processInstanceDetails = processDetailsMockCompleted; + fixture.detectChanges(); + expect(component.cancelProcessDirective.checkCanCancelProcess()).toBeFalsy(); + }); + + it('should checkCanCancelProcess return true when process status is RUNNING and logged in user is the processInitiator', () => { + component.cancelProcessDirective.processInstanceDetails = processDetailsMockRunning; + fixture.detectChanges(); + expect(component.cancelProcessDirective.checkCanCancelProcess()).toBeTruthy(); + }); + + it('should checkCanCancelProcess return false when logged in user is not the processInitiator', () => { + component.cancelProcessDirective.processInstanceDetails = processDetailsMockRunning; + component.cancelProcessDirective.processInstanceDetails.initiator = 'mock-user'; + fixture.detectChanges(); + expect(component.cancelProcessDirective.checkCanCancelProcess()).toBeFalsy(); + }); + +}); diff --git a/lib/process-services-cloud/src/lib/process/directives/cancel-process.directive.ts b/lib/process-services-cloud/src/lib/process/directives/cancel-process.directive.ts new file mode 100644 index 0000000000..f25e418808 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process/directives/cancel-process.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, HostListener, Output, EventEmitter, OnInit, OnDestroy, ElementRef } from '@angular/core'; +import { IdentityUserService } from '@alfresco/adf-core'; +import { ProcessCloudService } from '../services/process-cloud.service'; +import { takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; +import { ProcessInstanceCloud } from '../start-process/models/process-instance-cloud.model'; + +@Directive({ + selector: '[adf-cloud-cancel-process]' +}) +export class CancelProcessDirective implements OnInit, OnDestroy { + + /** Emitted when the process is cancelled. */ + @Output() + success: EventEmitter = new EventEmitter(); + + /** Emitted when the process cannot be cancelled. */ + @Output() + error: EventEmitter = new EventEmitter(); + + processInstanceDetails: ProcessInstanceCloud; + + canCancelProcess = false; + + private onDestroy$ = new Subject(); + + constructor( + private elementRef: ElementRef, + private processCloudService: ProcessCloudService, + private identityUserService: IdentityUserService) {} + + ngOnInit() { + this.processCloudService.dataChangesDetected + .pipe(takeUntil(this.onDestroy$)) + .subscribe((processDetails: ProcessInstanceCloud) => { + this.processInstanceDetails = processDetails; + this.canCancelProcess = this.checkCanCancelProcess(); + this.setElementVisibility(); + }); + } + + @HostListener('click') + async onClick() { + try { + this.cancelProcess(); + } catch (error) { + this.error.emit(error); + } + } + + private setElementVisibility() { + this.elementRef.nativeElement.disabled = !this.canCancelProcess; + } + + checkCanCancelProcess(): boolean { + const currentUser = this.identityUserService.getCurrentUserInfo().username; + return this.processInstanceDetails.initiator === currentUser && this.processInstanceDetails.status === 'RUNNING'; + } + + async cancelProcess() { + if (this.canCancelProcess) { + await this.processCloudService.cancelProcess(this.processInstanceDetails.appName, this.processInstanceDetails.id) + .pipe(takeUntil(this.onDestroy$)) + .subscribe((response) => { + this.success.emit(response); + }, ((error) => { + this.error.emit(error); + })); + } else { + this.error.emit('Permission denied, only process initiator can cancel the process'); + } + } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } +} diff --git a/lib/process-services-cloud/src/lib/process/directives/delete-process.directive.spec.ts b/lib/process-services-cloud/src/lib/process/directives/delete-process.directive.spec.ts deleted file mode 100644 index 38a0bc88fe..0000000000 --- a/lib/process-services-cloud/src/lib/process/directives/delete-process.directive.spec.ts +++ /dev/null @@ -1,149 +0,0 @@ -/*! - * @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, ViewChild } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { CoreModule, IdentityUserService, setupTestBed } from '@alfresco/adf-core'; -import { DeleteProcessDirective } from './delete-process.directive'; -import { ProcessCloudService } from '../services/process-cloud.service'; -import { of } from 'rxjs'; - -describe('DeleteProcessDirective', () => { - - @Component({ - selector: 'adf-cloud-delete-process-test-component', - template: '' - }) - class TestComponent { - - processIdMock = 'process-id-mock'; - appNameMock = 'app-mock'; - initiatorMock = 'user-mock'; - - @ViewChild(DeleteProcessDirective) - deleteProcessDirective: DeleteProcessDirective; - } - - let fixture: ComponentFixture; - let processCloudService: ProcessCloudService; - let identityUserService: IdentityUserService; - - setupTestBed({ - imports: [ - CoreModule.forRoot() - ], - declarations: [ - TestComponent, - DeleteProcessDirective - ] - }); - - beforeEach(() => { - processCloudService = TestBed.get(ProcessCloudService); - fixture = TestBed.createComponent(TestComponent); - identityUserService = TestBed.get(IdentityUserService); - spyOn(identityUserService, 'getCurrentUserInfo').and.returnValue({username: 'user-mock'}); - fixture.detectChanges(); - }); - - it('should call delete process service when click', () => { - spyOn(processCloudService, 'cancelProcess').and.returnValue(of({})); - const button = fixture.nativeElement.querySelector('button'); - button.click(); - expect(processCloudService.cancelProcess).toHaveBeenCalled(); - }); - -}); - -describe('Validation Errors', () => { - let fixture; - - @Component({ - template: '' - }) - class DeleteProcessMissingInputsDirectiveComponent { - @ViewChild(DeleteProcessDirective) - deleteProcessDirective: DeleteProcessDirective; - } - - @Component({ - template: '' - }) - class DeleteProcessMissingAppNameDirectiveComponent { - @ViewChild(DeleteProcessDirective) - deleteProcessDirective: DeleteProcessDirective; - - processId = 'id-mock'; - processInitiator = 'user-mock'; - } - - @Component({ - template: '' - }) - class DeleteProcessMissingProcessIdDirectiveComponent { - @ViewChild(DeleteProcessDirective) - deleteProcessDirective: DeleteProcessDirective; - - appName = 'app-mock'; - processInitiator = 'user-mock'; - } - - @Component({ - template: '' - }) - class DeleteProcessMissingProcessInitiatorDirectiveComponent { - @ViewChild(DeleteProcessDirective) - deleteProcessDirective: DeleteProcessDirective; - - appName = 'app-mock'; - processId = 'id-mock'; - } - - setupTestBed({ - imports: [ - CoreModule.forRoot() - ], - declarations: [ - DeleteProcessMissingInputsDirectiveComponent, - DeleteProcessMissingAppNameDirectiveComponent, - DeleteProcessMissingProcessIdDirectiveComponent, - DeleteProcessMissingProcessInitiatorDirectiveComponent, - DeleteProcessDirective - ] - }); - - it('should throw an error when appName processId and processInitiator are undefined', () => { - fixture = TestBed.createComponent(DeleteProcessMissingInputsDirectiveComponent); - expect(() => fixture.detectChanges()).toThrowError('Attribute processId, appName, processInitiator is required'); - }); - - it('should throw an error when appName is missing', () => { - fixture = TestBed.createComponent(DeleteProcessMissingAppNameDirectiveComponent); - expect(() => fixture.detectChanges()).toThrowError('Attribute appName is required'); - }); - - it('should throw an error when processId is missing', () => { - fixture = TestBed.createComponent(DeleteProcessMissingProcessIdDirectiveComponent); - expect(() => fixture.detectChanges()).toThrowError('Attribute processId is required'); - }); - - it('should throw an error when processInitiator is missing', () => { - fixture = TestBed.createComponent(DeleteProcessMissingProcessInitiatorDirectiveComponent); - expect(() => fixture.detectChanges()).toThrowError('Attribute processInitiator is required'); - }); - -}); diff --git a/lib/process-services-cloud/src/lib/process/directives/delete-process.directive.ts b/lib/process-services-cloud/src/lib/process/directives/delete-process.directive.ts deleted file mode 100644 index 4f86108124..0000000000 --- a/lib/process-services-cloud/src/lib/process/directives/delete-process.directive.ts +++ /dev/null @@ -1,108 +0,0 @@ -/*! - * @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 { ProcessCloudService } from '../services/process-cloud.service'; - -@Directive({ - // tslint:disable-next-line: directive-selector - selector: '[adf-cloud-delete-process]' -}) -export class DeleteProcessDirective implements OnInit { - - /** (Required) The id of the process. */ - @Input() - processId: string; - - /** (Required) The name of the application. */ - @Input() - appName: string; - - /** (Required) The process initiator */ - @Input() - processInitiator: string; - - /** Emitted when the process is deleted. */ - @Output() - success: EventEmitter = new EventEmitter(); - - /** Emitted when the process cannot be deleted. */ - @Output() - error: EventEmitter = new EventEmitter(); - - invalidParams: string[] = []; - - constructor( - private processCloudService: ProcessCloudService, - private identityUserService: IdentityUserService) {} - - ngOnInit() { - this.validateInputs(); - } - - validateInputs() { - - if (!this.isProcessValid()) { - this.invalidParams.push('processId'); - } - if (!this.isAppValid()) { - this.invalidParams.push('appName'); - } - if (!this.isProcessInitiatorValid()) { - this.invalidParams.push('processInitiator'); - } - if (this.invalidParams.length) { - throw new Error(`Attribute ${this.invalidParams.join(', ')} is required`); - } - } - - isProcessValid(): boolean { - return this.processId && this.processId.length > 0; - } - - isAppValid(): boolean { - return (this.appName && this.appName.length > 0); - } - - isProcessInitiatorValid(): boolean { - return (this.processInitiator && this.processInitiator.length > 0); - } - - @HostListener('click') - async onClick() { - try { - this.deleteProcess(); - } catch (error) { - this.error.emit(error); - } - } - - private async deleteProcess() { - const currentUser: string = this.identityUserService.getCurrentUserInfo().username; - if (currentUser === this.processInitiator) { - await this.processCloudService.cancelProcess(this.appName, this.processId) - .subscribe((response) => { - this.success.emit(response); - }, ((error) => { - this.error.emit(error); - })); - } else { - this.error.emit('Permission denied'); - } - - } -} diff --git a/lib/process-services-cloud/src/lib/process/directives/process-directive.module.ts b/lib/process-services-cloud/src/lib/process/directives/process-directive.module.ts index d544102f15..cf094bce36 100644 --- a/lib/process-services-cloud/src/lib/process/directives/process-directive.module.ts +++ b/lib/process-services-cloud/src/lib/process/directives/process-directive.module.ts @@ -16,14 +16,14 @@ */ import { NgModule } from '@angular/core'; -import { DeleteProcessDirective } from './delete-process.directive'; +import { CancelProcessDirective } from './cancel-process.directive'; @NgModule({ declarations: [ - DeleteProcessDirective + CancelProcessDirective ], exports: [ - DeleteProcessDirective + CancelProcessDirective ] }) export class ProcessDirectiveModule { } diff --git a/lib/process-services-cloud/src/lib/process/directives/public-api.ts b/lib/process-services-cloud/src/lib/process/directives/public-api.ts index 8919c86169..505e0c9b7f 100644 --- a/lib/process-services-cloud/src/lib/process/directives/public-api.ts +++ b/lib/process-services-cloud/src/lib/process/directives/public-api.ts @@ -15,6 +15,6 @@ * limitations under the License. */ -export * from './delete-process.directive'; +export * from './cancel-process.directive'; export * from './process-directive.module'; diff --git a/lib/process-services-cloud/src/lib/process/mock/process-details.mock.ts b/lib/process-services-cloud/src/lib/process/mock/process-details.mock.ts new file mode 100644 index 0000000000..f64d130c0a --- /dev/null +++ b/lib/process-services-cloud/src/lib/process/mock/process-details.mock.ts @@ -0,0 +1,22 @@ +/*! + * @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 { ProcessInstanceCloud } from '../start-process/models/process-instance-cloud.model'; + +export let processDetailsMockRunning = new ProcessInstanceCloud({ initiator: 'usermock', status: 'RUNNING' }); + +export let processDetailsMockCompleted = new ProcessInstanceCloud({ initiator: 'usermock', status: 'COMPLETED' });