diff --git a/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.html b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.html index 8e62a02ce1..c38d2932ca 100755 --- a/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.html +++ b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.html @@ -109,7 +109,7 @@ <button color="primary" mat-button - [disabled]="disableStartButton || !isProcessFormValid()" + [disabled]="disableStartButton() || !isProcessFormValid()" (click)="startProcess()" data-automation-id="btn-start" id="button-start" diff --git a/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.spec.ts index 9e3ff54b98..244b05121e 100755 --- a/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.spec.ts @@ -34,7 +34,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { fakeProcessDefinitions, fakeStartForm, fakeStartFormNotValid, fakeProcessInstance, fakeNoNameProcessDefinitions, - fakeSingleProcessDefinition, fakeCreatedProcessInstance, + fakeSingleProcessDefinition, fakeSingleProcessDefinitionWithoutForm } from '../mock/start-process.component.mock'; import { By } from '@angular/platform-browser'; @@ -55,7 +55,6 @@ describe('StartProcessCloudComponent', () => { let formCloudService: FormCloudService; let getDefinitionsSpy: jasmine.Spy; let startProcessSpy: jasmine.Spy; - let createProcessSpy: jasmine.Spy; let formDefinitionSpy: jasmine.Spy; let getStartEventFormStaticValuesMappingSpy: jasmine.Spy; @@ -106,8 +105,7 @@ describe('StartProcessCloudComponent', () => { getDefinitionsSpy = spyOn(processService, 'getProcessDefinitions').and.returnValue(of(fakeProcessDefinitions)); spyOn(processService, 'updateProcess').and.returnValue(of()); - startProcessSpy = spyOn(processService, 'startCreatedProcess').and.returnValue(of(fakeProcessInstance)); - createProcessSpy = spyOn(processService, 'createProcess').and.returnValue(of(fakeCreatedProcessInstance)); + startProcessSpy = spyOn(processService, 'startProcess').and.returnValue(of(fakeProcessInstance)); getStartEventFormStaticValuesMappingSpy = spyOn(processService, 'getStartEventFormStaticValuesMapping').and.returnValue(of([])); }); @@ -160,27 +158,6 @@ describe('StartProcessCloudComponent', () => { }); })); - it('should have start button disabled if create operation failed', fakeAsync(() => { - createProcessSpy.and.returnValue(throwError('fake error')); - const change = new SimpleChange(null, 'MyApp', false); - fixture.detectChanges(); - - component.ngOnChanges({ appName: change }); - fixture.detectChanges(); - tick(); - typeValueInto('[data-automation-id="adf-inplace-input"]', 'OLE'); - typeValueInto('#processDefinitionName', 'processwithoutform2'); - fixture.detectChanges(); - tick(550); - - fixture.whenStable().then(() => { - fixture.detectChanges(); - const startBtn = fixture.nativeElement.querySelector('#button-start'); - expect(startBtn.disabled).toBe(true); - expect(component.isProcessFormValid()).toBe(false); - expect(createProcessSpy).toHaveBeenCalledWith('MyApp', component.processPayloadCloud); - }); - })); it('should have start button disabled when no process is selected', async () => { component.name = ''; @@ -378,7 +355,7 @@ describe('StartProcessCloudComponent', () => { }); })); - it('should create a process instance if the selection is valid', fakeAsync(() => { + it('should display enabled start process button if the selection is valid', fakeAsync(() => { component.name = 'testFormWithProcess'; component.processDefinitionName = 'processwithoutform2'; getDefinitionsSpy.and.returnValue(of(fakeSingleProcessDefinition(component.processDefinitionName))); @@ -396,12 +373,6 @@ describe('StartProcessCloudComponent', () => { expect(startBtn.disabled).toBe(false); expect(component.formCloud.isValid).toBe(true); expect(component.isProcessFormValid()).toBe(true); - expect(createProcessSpy).toHaveBeenCalledWith('MyApp', new ProcessPayloadCloud({ - name: 'testFormWithProcess', - processDefinitionKey: fakeProcessDefinitions[1].key - })); - expect(component.currentCreatedProcess.status).toBe('CREATED'); - expect(component.currentCreatedProcess.startDate).toBeNull(); }); })); @@ -693,36 +664,44 @@ describe('StartProcessCloudComponent', () => { component.ngOnChanges({}); }); - it('should call service to start process if required fields provided', () => { - component.currentCreatedProcess = fakeProcessInstance; - component.startProcess(); - expect(startProcessSpy).toHaveBeenCalled(); - }); - - it('should call service to start process with the correct parameters', () => { - component.currentCreatedProcess = fakeProcessInstance; - component.startProcess(); - - expect(startProcessSpy).toHaveBeenCalledWith(component.appName, fakeProcessInstance.id, component.processPayloadCloud); - }); - - it('should call service to start process with the variables setted', async () => { - const inputProcessVariable: Map<string, any>[] = []; - inputProcessVariable['name'] = { value: 'Josh' }; - - component.variables = inputProcessVariable; - component.currentCreatedProcess = fakeProcessInstance; - - component.startProcess(); + it('should see start button', async () => { + component.ngOnChanges({ appName: firstChange }); + fixture.detectChanges(); await fixture.whenStable(); - expect(component.processPayloadCloud.variables).toBe(inputProcessVariable); + + const startButton = fixture.debugElement.query(By.css('#button-start')); + expect(startButton).toBeDefined(); + expect(startButton).not.toBeNull(); }); + it('should call service with the correct parameters when button is clicked', async () => { + component.ngOnChanges({ appName: firstChange }); + component.processForm.controls['processInstanceName'].setValue('My Process 1'); + component.appName = 'test app name'; + const payload: ProcessPayloadCloud = new ProcessPayloadCloud({ + name: component.processInstanceName.value, + ProcessDefinitionKey: component.processPayloadCloud.processDefinitionKey + }); + + fixture.detectChanges(); + await fixture.whenStable(); + const startButton = fixture.debugElement.query(By.css('#button-start')); + expect(startButton).not.toBeNull(); + + startButton.triggerEventHandler('click', null); + expect(startProcessSpy).toHaveBeenCalledWith(component.appName, payload); + + component.success.pipe(first()).subscribe((data: ProcessInstanceCloud) => { + expect(data).not.toBeNull(); + expect(data).toEqual(fakeProcessInstance); + }); + + }); + + it('should output start event when process started successfully', () => { const emitSpy = spyOn(component.success, 'emit'); - component.currentCreatedProcess = fakeProcessInstance; component.startProcess(); - expect(emitSpy).toHaveBeenCalledWith(fakeProcessInstance); }); @@ -730,7 +709,6 @@ describe('StartProcessCloudComponent', () => { const errorSpy = spyOn(component.error, 'emit'); const error = { message: 'My error' }; startProcessSpy = startProcessSpy.and.returnValue(throwError(error)); - component.currentCreatedProcess = fakeProcessInstance; component.startProcess(); await fixture.whenStable(); expect(errorSpy).toHaveBeenCalledWith(error); @@ -739,7 +717,6 @@ describe('StartProcessCloudComponent', () => { it('should indicate an error to the user if process cannot be started', async () => { getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions)); const change = new SimpleChange('myApp', 'myApp1', true); - component.currentCreatedProcess = fakeProcessInstance; component.ngOnChanges({ appName: change }); startProcessSpy = startProcessSpy.and.returnValue(throwError({})); component.startProcess(); @@ -754,10 +731,10 @@ describe('StartProcessCloudComponent', () => { it('should emit start event when start select a process and add a name', (done) => { const disposableStart = component.success.subscribe(() => { disposableStart.unsubscribe(); + expect(startProcessSpy).toHaveBeenCalled(); done(); }); - component.currentCreatedProcess = fakeProcessInstance; component.name = 'NewProcess 1'; component.startProcess(); fixture.detectChanges(); @@ -769,9 +746,9 @@ describe('StartProcessCloudComponent', () => { const disposableStart = component.success.subscribe(() => { disposableStart.unsubscribe(); + expect(startProcessSpy).toHaveBeenCalled(); done(); }); - component.currentCreatedProcess = fakeProcessInstance; component.startProcess(); }); @@ -786,7 +763,7 @@ describe('StartProcessCloudComponent', () => { expect(processInstanceName.valid).toBeTruthy(); }); - it('should have start button disabled process name has a space as the first or last character.', async () => { + it('should have start button disabled if process definition name has a space as the first or last character', async () => { component.appName = 'myApp'; component.processDefinitionName = ' Space in the beginning'; component.ngOnChanges({ appName: firstChange }); @@ -943,28 +920,21 @@ describe('StartProcessCloudComponent', () => { describe('cancel process', () => { beforeEach(() => { - fixture.detectChanges(); component.name = 'NewProcess 1'; component.appName = 'myApp'; - component.ngOnChanges({}); + component.ngOnChanges({ appName: firstChange }); + fixture.detectChanges(); }); it('user should see cancel button', () => { - fixture.whenStable().then(() => { - fixture.detectChanges(); - const cancelBtn = fixture.debugElement.query(By.css('#cancel_process')); - expect(cancelBtn.nativeElement).toBeDefined(); - }); - }); - - it('currentCreatedProcess should be null when cancel button clicked', () => { - component.cancelStartProcess(); - expect(component.currentCreatedProcess).toBeNull(); + const startButton = fixture.debugElement.query(By.css('#cancel_process')); + expect(startButton).toBeDefined(); + expect(startButton).not.toBeNull(); }); it('undefined should be emitted when cancel button clicked', () => { component.cancel.pipe(first()).subscribe((data: any) => { - expect(data).toBe(undefined); + expect(data).not.toBeDefined(); }); component.cancelStartProcess(); }); diff --git a/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.ts b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.ts index eed37993ac..f29e7f3ec6 100755 --- a/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.ts @@ -16,25 +16,24 @@ */ import { - Component, EventEmitter, Input, OnChanges, OnInit, - Output, SimpleChanges, ViewChild, ViewEncapsulation, OnDestroy, HostListener + Component, EventEmitter, HostListener, Input, OnChanges, OnDestroy, OnInit, + Output, SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core'; -import { ProcessInstanceCloud } from '../models/process-instance-cloud.model'; -import { StartProcessCloudService } from '../services/start-process-cloud.service'; -import { UntypedFormControl, Validators, UntypedFormGroup, AbstractControl, UntypedFormBuilder, ValidatorFn } from '@angular/forms'; -import { FormModel, ContentLinkModel } from '@alfresco/adf-core'; +import { ContentLinkModel, FormModel } from '@alfresco/adf-core'; +import { AbstractControl, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, ValidatorFn, Validators } from '@angular/forms'; import { MatAutocompleteTrigger } from '@angular/material/autocomplete'; +import { debounceTime, takeUntil, tap } from 'rxjs/operators'; +import { ProcessInstanceCloud } from '../models/process-instance-cloud.model'; import { ProcessPayloadCloud } from '../models/process-payload-cloud.model'; -import { debounceTime, takeUntil, switchMap, filter, distinctUntilChanged, tap } from 'rxjs/operators'; +import { StartProcessCloudService } from '../services/start-process-cloud.service'; +import { BehaviorSubject, Subject } from 'rxjs'; import { ProcessDefinitionCloud } from '../../../models/process-definition-cloud.model'; -import { Subject, Observable, BehaviorSubject } from 'rxjs'; import { TaskVariableCloud } from '../../../form/models/task-variable-cloud.model'; import { ProcessNameCloudPipe } from '../../../pipes/process-name-cloud.pipe'; const MAX_NAME_LENGTH: number = 255; const PROCESS_DEFINITION_DEBOUNCE: number = 300; -const PROCESS_FORM_DEBOUNCE: number = 400; @Component({ selector: 'adf-cloud-start-process', @@ -107,8 +106,6 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy isLoading = false; isFormCloudLoaded = false; formCloud: FormModel; - currentCreatedProcess: ProcessInstanceCloud; - disableStartButton: boolean = true; staticMappings: TaskVariableCloud[] = []; resolvedValues: TaskVariableCloud[]; @@ -133,19 +130,6 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy .subscribe((processDefinitionName) => { this.selectProcessDefinitionByProcessDefinitionName(processDefinitionName); }); - - this.processForm.valueChanges - .pipe( - debounceTime(PROCESS_FORM_DEBOUNCE), - tap(() => this.disableStartButton = true), - distinctUntilChanged(), - filter(() => this.isProcessSelectionValid()), - switchMap(() => this.generateProcessInstance()) - ).pipe(takeUntil(this.onDestroy$)) - .subscribe((res) => { - this.currentCreatedProcess = res; - this.disableStartButton = false; - }); } ngOnChanges(changes: SimpleChanges) { @@ -176,27 +160,10 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy this.formCloud = form; } - private isProcessSelectionValid(): boolean { - return this.processForm.valid && this.isProcessPayloadValid(); - } - private getMaxNameLength(): number { return this.maxNameLength > MAX_NAME_LENGTH ? MAX_NAME_LENGTH : this.maxNameLength; } - private generateProcessInstance(): Observable<ProcessInstanceCloud> { - const createPayload: ProcessPayloadCloud = new ProcessPayloadCloud({ - name: this.processInstanceName.value, - processDefinitionKey: this.processPayloadCloud.processDefinitionKey - }); - - if (this.currentCreatedProcess && this.processPayloadCloud.processDefinitionKey === this.currentCreatedProcess.processDefinitionKey) { - return this.startProcessCloudService.updateProcess(this.appName, this.currentCreatedProcess.id, createPayload); - } else { - return this.startProcessCloudService.createProcess(this.appName, createPayload); - } - } - private selectProcessDefinitionByProcessDefinitionName(processDefinitionName: string): void { this.filteredProcesses = this.getProcessDefinitionListByNameOrKey(processDefinitionName); @@ -291,10 +258,6 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy } } - private isProcessPayloadValid(): boolean { - return !!this.processPayloadCloud.processDefinitionKey; - } - private getProcessDefinition(processDefinitionCloud: ProcessDefinitionCloud, processDefinitionName: string): boolean { return (this.isValidName(processDefinitionCloud.name) && processDefinitionCloud.name.toLowerCase().includes(processDefinitionName.toLowerCase())) || (processDefinitionCloud.key && processDefinitionCloud.key.toLowerCase().includes(processDefinitionName.toLowerCase())); @@ -317,10 +280,11 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy startProcess() { this.isLoading = true; - this.buildProcessCloudPayload(); - this.startProcessCloudService.startCreatedProcess(this.appName, - this.currentCreatedProcess.id, - this.processPayloadCloud) + const createPayload: ProcessPayloadCloud = new ProcessPayloadCloud({ + name: this.processInstanceName.value, + processDefinitionKey: this.processPayloadCloud.processDefinitionKey + }); + this.startProcessCloudService.startProcess(this.appName, createPayload) .subscribe( (res) => { this.success.emit(res); @@ -335,7 +299,6 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy } cancelStartProcess() { - this.currentCreatedProcess = null; this.cancel.emit(); } @@ -419,4 +382,8 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy this.onDestroy$.next(true); this.onDestroy$.complete(); } + + disableStartButton() { + return !this.appName || !this.processDefinition.valid; + } } diff --git a/lib/process-services-cloud/src/lib/process/start-process/services/start-process-cloud.service.ts b/lib/process-services-cloud/src/lib/process/start-process/services/start-process-cloud.service.ts index 4fc89e5faf..52dbcc894c 100755 --- a/lib/process-services-cloud/src/lib/process/start-process/services/start-process-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/process/start-process/services/start-process-cloud.service.ts @@ -31,8 +31,8 @@ import { TaskVariableCloud } from '../../../form/models/task-variable-cloud.mode export class StartProcessCloudService extends BaseCloudService { constructor(apiService: AlfrescoApiService, - private logService: LogService, - appConfigService: AppConfigService) { + private logService: LogService, + appConfigService: AppConfigService) { super(apiService, appConfigService); } @@ -96,7 +96,9 @@ export class StartProcessCloudService extends BaseCloudService { const url = `${this.getBasePath(appName)}/rb/v1/process-instances`; payload.payloadType = 'StartProcessPayload'; - return this.post(url, payload); + return this.post(url, payload).pipe( + map((result: any) => result.entry) + ); } /** @@ -135,7 +137,7 @@ export class StartProcessCloudService extends BaseCloudService { * @param processDefinitionId ID of the target process definition * @returns Static mappings for the start event */ - getStartEventFormStaticValuesMapping(appName: string, processDefinitionId: string): Observable<TaskVariableCloud[]> { + getStartEventFormStaticValuesMapping(appName: string, processDefinitionId: string): Observable<TaskVariableCloud[]> { const apiUrl = `${this.getBasePath(appName)}/rb/v1/process-definitions/${processDefinitionId}/static-values`; return this.get(apiUrl).pipe( map((res: { [key: string]: any }) => {