[AAE-11740] replaced api for starting process (#8052)

* AAE-11740 replaced api for starting process

* AAE-11740 adjusted tests due to replaced start process api

* AAE-11740 merged similar tests for starting process

* AAE-11740 added condition for disabled start process button
This commit is contained in:
tomasz hanaj 2022-12-22 09:07:54 +01:00 committed by GitHub
parent 2207156f3f
commit bc7c052059
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 68 additions and 129 deletions

View File

@ -109,7 +109,7 @@
<button <button
color="primary" color="primary"
mat-button mat-button
[disabled]="disableStartButton || !isProcessFormValid()" [disabled]="disableStartButton() || !isProcessFormValid()"
(click)="startProcess()" (click)="startProcess()"
data-automation-id="btn-start" data-automation-id="btn-start"
id="button-start" id="button-start"

View File

@ -34,7 +34,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { import {
fakeProcessDefinitions, fakeStartForm, fakeStartFormNotValid, fakeProcessDefinitions, fakeStartForm, fakeStartFormNotValid,
fakeProcessInstance, fakeNoNameProcessDefinitions, fakeProcessInstance, fakeNoNameProcessDefinitions,
fakeSingleProcessDefinition, fakeCreatedProcessInstance, fakeSingleProcessDefinition,
fakeSingleProcessDefinitionWithoutForm fakeSingleProcessDefinitionWithoutForm
} from '../mock/start-process.component.mock'; } from '../mock/start-process.component.mock';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
@ -55,7 +55,6 @@ describe('StartProcessCloudComponent', () => {
let formCloudService: FormCloudService; let formCloudService: FormCloudService;
let getDefinitionsSpy: jasmine.Spy; let getDefinitionsSpy: jasmine.Spy;
let startProcessSpy: jasmine.Spy; let startProcessSpy: jasmine.Spy;
let createProcessSpy: jasmine.Spy;
let formDefinitionSpy: jasmine.Spy; let formDefinitionSpy: jasmine.Spy;
let getStartEventFormStaticValuesMappingSpy: jasmine.Spy; let getStartEventFormStaticValuesMappingSpy: jasmine.Spy;
@ -106,8 +105,7 @@ describe('StartProcessCloudComponent', () => {
getDefinitionsSpy = spyOn(processService, 'getProcessDefinitions').and.returnValue(of(fakeProcessDefinitions)); getDefinitionsSpy = spyOn(processService, 'getProcessDefinitions').and.returnValue(of(fakeProcessDefinitions));
spyOn(processService, 'updateProcess').and.returnValue(of()); spyOn(processService, 'updateProcess').and.returnValue(of());
startProcessSpy = spyOn(processService, 'startCreatedProcess').and.returnValue(of(fakeProcessInstance)); startProcessSpy = spyOn(processService, 'startProcess').and.returnValue(of(fakeProcessInstance));
createProcessSpy = spyOn(processService, 'createProcess').and.returnValue(of(fakeCreatedProcessInstance));
getStartEventFormStaticValuesMappingSpy = spyOn(processService, 'getStartEventFormStaticValuesMapping').and.returnValue(of([])); 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 () => { it('should have start button disabled when no process is selected', async () => {
component.name = ''; 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.name = 'testFormWithProcess';
component.processDefinitionName = 'processwithoutform2'; component.processDefinitionName = 'processwithoutform2';
getDefinitionsSpy.and.returnValue(of(fakeSingleProcessDefinition(component.processDefinitionName))); getDefinitionsSpy.and.returnValue(of(fakeSingleProcessDefinition(component.processDefinitionName)));
@ -396,12 +373,6 @@ describe('StartProcessCloudComponent', () => {
expect(startBtn.disabled).toBe(false); expect(startBtn.disabled).toBe(false);
expect(component.formCloud.isValid).toBe(true); expect(component.formCloud.isValid).toBe(true);
expect(component.isProcessFormValid()).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({}); component.ngOnChanges({});
}); });
it('should call service to start process if required fields provided', () => { it('should see start button', async () => {
component.currentCreatedProcess = fakeProcessInstance; component.ngOnChanges({ appName: firstChange });
component.startProcess(); fixture.detectChanges();
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();
await fixture.whenStable(); 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', () => { it('should output start event when process started successfully', () => {
const emitSpy = spyOn(component.success, 'emit'); const emitSpy = spyOn(component.success, 'emit');
component.currentCreatedProcess = fakeProcessInstance;
component.startProcess(); component.startProcess();
expect(emitSpy).toHaveBeenCalledWith(fakeProcessInstance); expect(emitSpy).toHaveBeenCalledWith(fakeProcessInstance);
}); });
@ -730,7 +709,6 @@ describe('StartProcessCloudComponent', () => {
const errorSpy = spyOn(component.error, 'emit'); const errorSpy = spyOn(component.error, 'emit');
const error = { message: 'My error' }; const error = { message: 'My error' };
startProcessSpy = startProcessSpy.and.returnValue(throwError(error)); startProcessSpy = startProcessSpy.and.returnValue(throwError(error));
component.currentCreatedProcess = fakeProcessInstance;
component.startProcess(); component.startProcess();
await fixture.whenStable(); await fixture.whenStable();
expect(errorSpy).toHaveBeenCalledWith(error); expect(errorSpy).toHaveBeenCalledWith(error);
@ -739,7 +717,6 @@ describe('StartProcessCloudComponent', () => {
it('should indicate an error to the user if process cannot be started', async () => { it('should indicate an error to the user if process cannot be started', async () => {
getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions)); getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
const change = new SimpleChange('myApp', 'myApp1', true); const change = new SimpleChange('myApp', 'myApp1', true);
component.currentCreatedProcess = fakeProcessInstance;
component.ngOnChanges({ appName: change }); component.ngOnChanges({ appName: change });
startProcessSpy = startProcessSpy.and.returnValue(throwError({})); startProcessSpy = startProcessSpy.and.returnValue(throwError({}));
component.startProcess(); component.startProcess();
@ -754,10 +731,10 @@ describe('StartProcessCloudComponent', () => {
it('should emit start event when start select a process and add a name', (done) => { it('should emit start event when start select a process and add a name', (done) => {
const disposableStart = component.success.subscribe(() => { const disposableStart = component.success.subscribe(() => {
disposableStart.unsubscribe(); disposableStart.unsubscribe();
expect(startProcessSpy).toHaveBeenCalled();
done(); done();
}); });
component.currentCreatedProcess = fakeProcessInstance;
component.name = 'NewProcess 1'; component.name = 'NewProcess 1';
component.startProcess(); component.startProcess();
fixture.detectChanges(); fixture.detectChanges();
@ -769,9 +746,9 @@ describe('StartProcessCloudComponent', () => {
const disposableStart = component.success.subscribe(() => { const disposableStart = component.success.subscribe(() => {
disposableStart.unsubscribe(); disposableStart.unsubscribe();
expect(startProcessSpy).toHaveBeenCalled();
done(); done();
}); });
component.currentCreatedProcess = fakeProcessInstance;
component.startProcess(); component.startProcess();
}); });
@ -786,7 +763,7 @@ describe('StartProcessCloudComponent', () => {
expect(processInstanceName.valid).toBeTruthy(); 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.appName = 'myApp';
component.processDefinitionName = ' Space in the beginning'; component.processDefinitionName = ' Space in the beginning';
component.ngOnChanges({ appName: firstChange }); component.ngOnChanges({ appName: firstChange });
@ -943,28 +920,21 @@ describe('StartProcessCloudComponent', () => {
describe('cancel process', () => { describe('cancel process', () => {
beforeEach(() => { beforeEach(() => {
fixture.detectChanges();
component.name = 'NewProcess 1'; component.name = 'NewProcess 1';
component.appName = 'myApp'; component.appName = 'myApp';
component.ngOnChanges({}); component.ngOnChanges({ appName: firstChange });
fixture.detectChanges();
}); });
it('user should see cancel button', () => { it('user should see cancel button', () => {
fixture.whenStable().then(() => { const startButton = fixture.debugElement.query(By.css('#cancel_process'));
fixture.detectChanges(); expect(startButton).toBeDefined();
const cancelBtn = fixture.debugElement.query(By.css('#cancel_process')); expect(startButton).not.toBeNull();
expect(cancelBtn.nativeElement).toBeDefined();
});
});
it('currentCreatedProcess should be null when cancel button clicked', () => {
component.cancelStartProcess();
expect(component.currentCreatedProcess).toBeNull();
}); });
it('undefined should be emitted when cancel button clicked', () => { it('undefined should be emitted when cancel button clicked', () => {
component.cancel.pipe(first()).subscribe((data: any) => { component.cancel.pipe(first()).subscribe((data: any) => {
expect(data).toBe(undefined); expect(data).not.toBeDefined();
}); });
component.cancelStartProcess(); component.cancelStartProcess();
}); });

View File

@ -16,25 +16,24 @@
*/ */
import { import {
Component, EventEmitter, Input, OnChanges, OnInit, Component, EventEmitter, HostListener, Input, OnChanges, OnDestroy, OnInit,
Output, SimpleChanges, ViewChild, ViewEncapsulation, OnDestroy, HostListener Output, SimpleChanges, ViewChild, ViewEncapsulation
} from '@angular/core'; } from '@angular/core';
import { ProcessInstanceCloud } from '../models/process-instance-cloud.model'; import { ContentLinkModel, FormModel } from '@alfresco/adf-core';
import { StartProcessCloudService } from '../services/start-process-cloud.service'; import { AbstractControl, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, ValidatorFn, Validators } from '@angular/forms';
import { UntypedFormControl, Validators, UntypedFormGroup, AbstractControl, UntypedFormBuilder, ValidatorFn } from '@angular/forms';
import { FormModel, ContentLinkModel } from '@alfresco/adf-core';
import { MatAutocompleteTrigger } from '@angular/material/autocomplete'; 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 { 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 { ProcessDefinitionCloud } from '../../../models/process-definition-cloud.model';
import { Subject, Observable, BehaviorSubject } from 'rxjs';
import { TaskVariableCloud } from '../../../form/models/task-variable-cloud.model'; import { TaskVariableCloud } from '../../../form/models/task-variable-cloud.model';
import { ProcessNameCloudPipe } from '../../../pipes/process-name-cloud.pipe'; import { ProcessNameCloudPipe } from '../../../pipes/process-name-cloud.pipe';
const MAX_NAME_LENGTH: number = 255; const MAX_NAME_LENGTH: number = 255;
const PROCESS_DEFINITION_DEBOUNCE: number = 300; const PROCESS_DEFINITION_DEBOUNCE: number = 300;
const PROCESS_FORM_DEBOUNCE: number = 400;
@Component({ @Component({
selector: 'adf-cloud-start-process', selector: 'adf-cloud-start-process',
@ -107,8 +106,6 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy
isLoading = false; isLoading = false;
isFormCloudLoaded = false; isFormCloudLoaded = false;
formCloud: FormModel; formCloud: FormModel;
currentCreatedProcess: ProcessInstanceCloud;
disableStartButton: boolean = true;
staticMappings: TaskVariableCloud[] = []; staticMappings: TaskVariableCloud[] = [];
resolvedValues: TaskVariableCloud[]; resolvedValues: TaskVariableCloud[];
@ -133,19 +130,6 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy
.subscribe((processDefinitionName) => { .subscribe((processDefinitionName) => {
this.selectProcessDefinitionByProcessDefinitionName(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) { ngOnChanges(changes: SimpleChanges) {
@ -176,27 +160,10 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy
this.formCloud = form; this.formCloud = form;
} }
private isProcessSelectionValid(): boolean {
return this.processForm.valid && this.isProcessPayloadValid();
}
private getMaxNameLength(): number { private getMaxNameLength(): number {
return this.maxNameLength > MAX_NAME_LENGTH ? MAX_NAME_LENGTH : this.maxNameLength; 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 { private selectProcessDefinitionByProcessDefinitionName(processDefinitionName: string): void {
this.filteredProcesses = this.getProcessDefinitionListByNameOrKey(processDefinitionName); 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 { private getProcessDefinition(processDefinitionCloud: ProcessDefinitionCloud, processDefinitionName: string): boolean {
return (this.isValidName(processDefinitionCloud.name) && processDefinitionCloud.name.toLowerCase().includes(processDefinitionName.toLowerCase())) || return (this.isValidName(processDefinitionCloud.name) && processDefinitionCloud.name.toLowerCase().includes(processDefinitionName.toLowerCase())) ||
(processDefinitionCloud.key && processDefinitionCloud.key.toLowerCase().includes(processDefinitionName.toLowerCase())); (processDefinitionCloud.key && processDefinitionCloud.key.toLowerCase().includes(processDefinitionName.toLowerCase()));
@ -317,10 +280,11 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy
startProcess() { startProcess() {
this.isLoading = true; this.isLoading = true;
this.buildProcessCloudPayload(); const createPayload: ProcessPayloadCloud = new ProcessPayloadCloud({
this.startProcessCloudService.startCreatedProcess(this.appName, name: this.processInstanceName.value,
this.currentCreatedProcess.id, processDefinitionKey: this.processPayloadCloud.processDefinitionKey
this.processPayloadCloud) });
this.startProcessCloudService.startProcess(this.appName, createPayload)
.subscribe( .subscribe(
(res) => { (res) => {
this.success.emit(res); this.success.emit(res);
@ -335,7 +299,6 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy
} }
cancelStartProcess() { cancelStartProcess() {
this.currentCreatedProcess = null;
this.cancel.emit(); this.cancel.emit();
} }
@ -419,4 +382,8 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy
this.onDestroy$.next(true); this.onDestroy$.next(true);
this.onDestroy$.complete(); this.onDestroy$.complete();
} }
disableStartButton() {
return !this.appName || !this.processDefinition.valid;
}
} }

View File

@ -31,8 +31,8 @@ import { TaskVariableCloud } from '../../../form/models/task-variable-cloud.mode
export class StartProcessCloudService extends BaseCloudService { export class StartProcessCloudService extends BaseCloudService {
constructor(apiService: AlfrescoApiService, constructor(apiService: AlfrescoApiService,
private logService: LogService, private logService: LogService,
appConfigService: AppConfigService) { appConfigService: AppConfigService) {
super(apiService, appConfigService); super(apiService, appConfigService);
} }
@ -96,7 +96,9 @@ export class StartProcessCloudService extends BaseCloudService {
const url = `${this.getBasePath(appName)}/rb/v1/process-instances`; const url = `${this.getBasePath(appName)}/rb/v1/process-instances`;
payload.payloadType = 'StartProcessPayload'; 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 * @param processDefinitionId ID of the target process definition
* @returns Static mappings for the start event * @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`; const apiUrl = `${this.getBasePath(appName)}/rb/v1/process-definitions/${processDefinitionId}/static-values`;
return this.get(apiUrl).pipe( return this.get(apiUrl).pipe(
map((res: { [key: string]: any }) => { map((res: { [key: string]: any }) => {