[DW-1669] [ADF-5107]- Added new api call for creating and starting a process in… (#5602)

* [DW-1669] - Added new api call for creating and starting a process instance

* [DW-1669] - fixed linting

* [DW-1669] - fixed process default issue

* [DW-1669] - fixed problem on start process cloud

* [DW-1669] - removed check on valid form as it might interact with the correct behaviour

* [DW-1669] - added a small delay before pressing start as a debounce time was added and the click on start is too fast

* [DW-1669] - added a small delay before pressing start as a debounce time was added and the click on start is too fast

* [DW-1669] - removed wrong fdescribe

* [DW-1669] - added a small delay before pressing start as a debounce time was added and the click on start is too fast
This commit is contained in:
Vito 2020-04-24 14:27:31 +01:00 committed by GitHub
parent 6fea3b8cdd
commit d0f58600bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 247 additions and 49 deletions

View File

@ -103,6 +103,7 @@ describe('Task cloud visibility', async () => {
await startProcessPage.selectFromProcessDropdown(browser.params.resources.ACTIVITI_CLOUD_APPS.SIMPLE_APP.processes.numbervisibilityprocess);
await startProcessPage.enterProcessName(processName);
await browser.sleep(400);
await startProcessPage.clickStartProcessButton();
await processCloudDemoPage.editProcessFilterCloudComponent().setFilter({ processName });
@ -134,6 +135,7 @@ describe('Task cloud visibility', async () => {
await startProcessPage.clearField(startProcessPage.processNameInput);
await startProcessPage.selectFromProcessDropdown(browser.params.resources.ACTIVITI_CLOUD_APPS.SIMPLE_APP.processes.booleanvisibilityprocess);
await startProcessPage.enterProcessName(processName);
await browser.sleep(400);
await startProcessPage.clickStartProcessButton();
await processCloudDemoPage.editProcessFilterCloudComponent().setFilter({ processName });

View File

@ -103,6 +103,7 @@ describe('Start Process', () => {
await startProcessPage.clearField(startProcessPage.processNameInput);
await startProcessPage.enterProcessName(processName);
await expect(await startProcessPage.checkStartProcessButtonIsEnabled()).toBe(true);
await browser.sleep(400);
await startProcessPage.clickStartProcessButton();
await processCloudDemoPage.processFilterCloudComponent.clickOnProcessFilters();

View File

@ -18,7 +18,7 @@
[matAutocomplete]="auto"
id="processDefinitionName">
<div class="adf-process-input-autocomplete">
<mat-autocomplete #auto="matAutocomplete" id="processDefinitionOptions" [displayWith]="displayProcessNameOnDropdown" (optionSelected)="setProcessDefinitionOnForm($event.option.value)">
<mat-autocomplete #auto="matAutocomplete" id="processDefinitionOptions" [displayWith]="displayProcessNameOnDropdown" (optionSelected)="setProcessDefinitionOnForm($event.option.value)" >
<mat-option *ngFor="let processDef of filteredProcesses" [value]="getProcessDefinitionValue(processDef)"
(click)="processDefinitionSelectionChanged(processDef)">
{{ getProcessDefinitionValue(processDef) }}

View File

@ -17,16 +17,21 @@
import { SimpleChange, DebugElement } from '@angular/core';
import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { setupTestBed } from '@alfresco/adf-core';
import { setupTestBed, StorageService, LogService, TranslationService, TranslationMock, FormService } from '@alfresco/adf-core';
import { of, throwError } from 'rxjs';
import { StartProcessCloudService } from '../services/start-process-cloud.service';
import { FormCloudService } from '../../../form/services/form-cloud.service';
import { StartProcessCloudComponent } from './start-process-cloud.component';
import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module';
import { ProcessCloudModule } from '../../process-cloud.module';
import { HttpClientModule } from '@angular/common/http';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateModule, TranslateStore } from '@ngx-translate/core';
import { MatCardModule, MatOptionModule, MatAutocompleteModule, MatIconModule, MatButtonModule, MatFormFieldModule, MatInputModule, MatRippleModule, MatCommonModule } from '@angular/material';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { FormCloudModule } from 'process-services-cloud/src/lib/form/form-cloud.module';
import { fakeProcessDefinitions, fakeStartForm, fakeStartFormNotValid,
fakeProcessInstance, fakeProcessPayload, fakeNoNameProcessDefinitions, fakeSingleProcessDefinition } from '../mock/start-process.component.mock';
fakeProcessInstance, fakeNoNameProcessDefinitions,
fakeSingleProcessDefinition, fakeCreatedProcessInstance } from '../mock/start-process.component.mock';
import { By } from '@angular/platform-browser';
describe('StartProcessCloudComponent', () => {
@ -37,6 +42,7 @@ describe('StartProcessCloudComponent', () => {
let formCloudService: FormCloudService;
let getDefinitionsSpy: jasmine.Spy;
let startProcessSpy: jasmine.Spy;
let createProcessSpy: jasmine.Spy;
let formDefinitionSpy: jasmine.Spy;
const selectOptionByName = (name: string) => {
@ -52,10 +58,37 @@ describe('StartProcessCloudComponent', () => {
}
};
function typeValueInto(selector: any, value: string ) {
const inputElement = fixture.debugElement.query(By.css(`${selector}`));
inputElement.nativeElement.value = value;
inputElement.nativeElement.dispatchEvent(new Event('input'));
}
setupTestBed({
imports: [
ProcessServiceCloudTestingModule,
ProcessCloudModule
HttpClientModule,
NoopAnimationsModule,
FormsModule,
MatCommonModule,
ReactiveFormsModule,
MatCardModule,
MatIconModule,
MatAutocompleteModule,
MatOptionModule,
MatButtonModule,
MatFormFieldModule,
MatInputModule,
MatRippleModule,
FormCloudModule,
TranslateModule.forChild()
],
declarations: [StartProcessCloudComponent],
providers: [
{ provide: TranslationService, useClass: TranslationMock },
FormService,
TranslateStore,
StorageService,
LogService
]
});
@ -66,7 +99,8 @@ describe('StartProcessCloudComponent', () => {
component = fixture.componentInstance;
getDefinitionsSpy = spyOn(processService, 'getProcessDefinitions').and.returnValue(of(fakeProcessDefinitions));
startProcessSpy = spyOn(processService, 'startProcess').and.returnValue(of(fakeProcessInstance));
startProcessSpy = spyOn(processService, 'startCreatedProcess').and.returnValue(of(fakeProcessInstance));
createProcessSpy = spyOn(processService, 'createProcess').and.returnValue(of(fakeCreatedProcessInstance));
});
afterEach(() => {
@ -76,6 +110,29 @@ describe('StartProcessCloudComponent', () => {
describe('start a process without start form', () => {
it('should create a process instance if the selection is valid', fakeAsync(() => {
component.name = '';
component.processDefinitionName = '';
fixture.detectChanges();
const change = new SimpleChange(null, 'MyApp', true);
component.ngOnChanges({ 'appName': change });
fixture.detectChanges();
tick();
typeValueInto('#processName', 'OLE');
typeValueInto('#processDefinitionName', 'processwithoutform2');
fixture.detectChanges();
fixture.whenStable();
tick(450);
const startBtn = fixture.nativeElement.querySelector('#button-start');
expect(startBtn.disabled).toBe(false);
expect(component.isProcessFormValid()).toBe(true);
expect(createProcessSpy).toHaveBeenCalledWith('MyApp', component.processPayloadCloud);
expect(component.currentCreatedProcess.status).toBe('CREATED');
expect(component.currentCreatedProcess.startDate).toBeNull();
}));
it('should be able to start a process with a valid process name and process definition', async(() => {
component.name = 'My new process';
component.processDefinitionName = 'processwithoutform2';
@ -88,21 +145,7 @@ describe('StartProcessCloudComponent', () => {
fixture.whenStable().then(() => {
const startBtn = fixture.nativeElement.querySelector('#button-start');
expect(startBtn.disabled).toBe(false);
});
}));
it('should have start button disabled when name not filled out', async(() => {
component.name = '';
component.processDefinitionName = 'processwithoutform2';
fixture.detectChanges();
const change = new SimpleChange(null, 'MyApp', true);
component.ngOnChanges({ 'appName': change });
fixture.detectChanges();
fixture.whenStable().then(() => {
const startBtn = fixture.nativeElement.querySelector('#button-start');
expect(startBtn.disabled).toBe(true);
expect(component.isProcessFormValid()).toBe(true);
});
}));
@ -118,6 +161,23 @@ describe('StartProcessCloudComponent', () => {
fixture.whenStable().then(() => {
const startBtn = fixture.nativeElement.querySelector('#button-start');
expect(startBtn.disabled).toBe(true);
expect(component.isProcessFormValid()).toBe(false);
});
}));
it('should have start button disabled when name not filled out', async(() => {
component.name = '';
component.processDefinitionName = 'processwithoutform2';
fixture.detectChanges();
const change = new SimpleChange(null, 'MyApp', true);
component.ngOnChanges({ 'appName': change });
fixture.detectChanges();
fixture.whenStable().then(() => {
const startBtn = fixture.nativeElement.querySelector('#button-start');
expect(startBtn.disabled).toBe(true);
expect(component.isProcessFormValid()).toBe(false);
});
}));
});
@ -218,6 +278,29 @@ describe('StartProcessCloudComponent', () => {
expect(startBtn.disabled).toBe(true);
});
}));
it('should create a process instance if the selection is valid', fakeAsync(() => {
component.values = [{'name': 'firstName', 'value': 'FakeName'}, {'name': 'lastName', 'value': 'FakeLastName'}];
component.name = 'testFormWithProcess';
component.processDefinitionName = 'processwithoutform2';
getDefinitionsSpy.and.returnValue(of(fakeSingleProcessDefinition(component.processDefinitionName)));
fixture.detectChanges();
formDefinitionSpy = spyOn(formCloudService, 'getForm').and.returnValue(of(fakeStartForm));
const change = new SimpleChange(null, 'MyApp', true);
component.ngOnChanges({ 'appName': change });
fixture.detectChanges();
fixture.whenStable();
tick(450);
const startBtn = fixture.nativeElement.querySelector('#button-start');
expect(startBtn.disabled).toBe(false);
expect(component.formCloud.isValid).toBe(true);
expect(component.isProcessFormValid()).toBe(true);
expect(createProcessSpy).toHaveBeenCalledWith('MyApp', component.processPayloadCloud);
expect(component.currentCreatedProcess.status).toBe('CREATED');
expect(component.currentCreatedProcess.startDate).toBeNull();
}));
});
describe('process definitions list', () => {
@ -459,7 +542,7 @@ describe('StartProcessCloudComponent', () => {
});
it('should call service to start process if required fields provided', async(() => {
component.processPayloadCloud = fakeProcessPayload;
component.currentCreatedProcess = fakeProcessInstance;
component.startProcess();
fixture.whenStable().then(() => {
expect(startProcessSpy).toHaveBeenCalled();
@ -467,10 +550,10 @@ describe('StartProcessCloudComponent', () => {
}));
it('should call service to start process with the correct parameters', async(() => {
component.processPayloadCloud = fakeProcessPayload;
component.currentCreatedProcess = fakeProcessInstance;
component.startProcess();
fixture.whenStable().then(() => {
expect(startProcessSpy).toHaveBeenCalledWith('myApp', fakeProcessPayload);
expect(startProcessSpy).toHaveBeenCalledWith(component.appName, fakeProcessInstance.id);
});
}));
@ -479,7 +562,7 @@ describe('StartProcessCloudComponent', () => {
inputProcessVariable['name'] = {value: 'Josh'};
component.variables = inputProcessVariable;
component.processPayloadCloud = fakeProcessPayload;
component.currentCreatedProcess = fakeProcessInstance;
component.startProcess();
fixture.whenStable().then(() => {
@ -489,7 +572,7 @@ describe('StartProcessCloudComponent', () => {
it('should output start event when process started successfully', async(() => {
const emitSpy = spyOn(component.success, 'emit');
component.processPayloadCloud = fakeProcessPayload;
component.currentCreatedProcess = fakeProcessInstance;
component.startProcess();
fixture.whenStable().then(() => {
expect(emitSpy).toHaveBeenCalledWith(fakeProcessInstance);
@ -500,7 +583,7 @@ describe('StartProcessCloudComponent', () => {
const errorSpy = spyOn(component.error, 'emit');
const error = { message: 'My error' };
startProcessSpy = startProcessSpy.and.returnValue(throwError(error));
component.processPayloadCloud = fakeProcessPayload;
component.currentCreatedProcess = fakeProcessInstance;
component.startProcess();
fixture.whenStable().then(() => {
expect(errorSpy).toHaveBeenCalledWith(error);
@ -510,6 +593,7 @@ 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();
@ -526,7 +610,7 @@ describe('StartProcessCloudComponent', () => {
done();
});
component.processPayloadCloud = fakeProcessPayload;
component.currentCreatedProcess = fakeProcessInstance;
component.name = 'NewProcess 1';
component.startProcess();
fixture.detectChanges();
@ -540,7 +624,7 @@ describe('StartProcessCloudComponent', () => {
disposableStart.unsubscribe();
done();
});
component.currentCreatedProcess = fakeProcessInstance;
component.startProcess();
});

View File

@ -26,9 +26,9 @@ import { FormControl, Validators, FormGroup, AbstractControl, FormBuilder, Valid
import { FormModel, ContentLinkModel } from '@alfresco/adf-core';
import { MatAutocompleteTrigger } from '@angular/material';
import { ProcessPayloadCloud } from '../models/process-payload-cloud.model';
import { debounceTime, takeUntil } from 'rxjs/operators';
import { debounceTime, takeUntil, switchMap, filter, distinctUntilChanged } from 'rxjs/operators';
import { ProcessDefinitionCloud } from '../models/process-definition-cloud.model';
import { Subject } from 'rxjs';
import { Subject, Observable } from 'rxjs';
import { TaskVariableCloud } from '../../../form/models/task-variable-cloud.model';
@Component({
@ -100,6 +100,7 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy
isLoading = false;
isFormCloudLoaded = false;
formCloud: FormModel;
currentCreatedProcess: any;
protected onDestroy$ = new Subject<boolean>();
constructor(private startProcessCloudService: StartProcessCloudService,
@ -109,15 +110,32 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy
ngOnInit() {
this.processForm = this.formBuilder.group({
processInstanceName: new FormControl(this.name, [Validators.required, Validators.maxLength(this.getMaxNameLength()), this.whitespaceValidator]),
processDefinition: new FormControl(this.processDefinitionName, [Validators.required, this.processDefinitionNameValidator()])
processDefinition: new FormControl('', [Validators.required, this.processDefinitionNameValidator()])
});
this.processDefinition.valueChanges
.pipe(debounceTime(300))
.pipe(takeUntil(this.onDestroy$))
.subscribe((processDefinitionName) => {
this.filteredProcesses = this.getProcessDefinitionList(processDefinitionName);
this.selectProcessDefinitionByProcesDefinitionName(processDefinitionName);
});
this.processForm.valueChanges
.pipe(
debounceTime(400),
distinctUntilChanged(),
filter(() => this.isProcessSelectionValid()),
switchMap(() => this.generateProcessInstance())
).pipe(takeUntil(this.onDestroy$))
.subscribe((res) => {
this.currentCreatedProcess = res;
});
if (this.processDefinitionName) {
this.processDefinition.setValue(this.processDefinitionName);
this.processDefinition.markAsDirty();
this.processDefinition.markAsTouched();
}
}
ngOnChanges(changes: SimpleChanges) {
@ -139,11 +157,28 @@ 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 > StartProcessCloudComponent.MAX_NAME_LENGTH ?
StartProcessCloudComponent.MAX_NAME_LENGTH : this.maxNameLength;
}
private generateProcessInstance(): Observable <ProcessInstanceCloud> {
this.buildProcessCloudPayload();
return this.startProcessCloudService.createProcess(this.appName, this.processPayloadCloud);
}
private selectProcessDefinitionByProcesDefinitionName(processDefinitionName: string): void {
this.filteredProcesses = this.getProcessDefinitionList(processDefinitionName);
if (this.isProcessFormValid() &&
this.filteredProcesses && this.filteredProcesses.length === 1) {
this.setProcessDefinitionOnForm(this.filteredProcesses[0].name);
}
}
setProcessDefinitionOnForm(selectedProcessDefinitionName: string) {
this.processDefinitionCurrent = this.filteredProcesses.find( (process: ProcessDefinitionCloud) =>
process.name === selectedProcessDefinitionName || process.key === selectedProcessDefinitionName );
@ -210,6 +245,10 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy
}
}
private isProcessPayloadValid(): boolean {
return !!this.processPayloadCloud.processDefinitionKey;
}
private getProcessDefinition(option: ProcessDefinitionCloud, processDefinition: string): boolean {
return (this.isValidName(option.name) && option.name.toLowerCase().includes(processDefinition.toLowerCase())) ||
(option.key && option.key.toLowerCase().includes(processDefinition.toLowerCase()));
@ -219,9 +258,7 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy
return this.processDefinitionList.length === 0;
}
startProcess() {
this.isLoading = true;
buildProcessCloudPayload() {
this.processPayloadCloud.name = this.processInstanceName.value;
if (this.variables) {
this.processPayloadCloud.variables = this.variables;
@ -230,8 +267,14 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy
if (this.hasForm()) {
this.processPayloadCloud.variables = Object.assign(this.processPayloadCloud.variables, this.formCloud.values);
}
}
this.startProcessCloudService.startProcess(this.appName, this.processPayloadCloud).subscribe(
startProcess() {
this.isLoading = true;
this.buildProcessCloudPayload();
this.startProcessCloudService.startCreatedProcess(this.appName, this.currentCreatedProcess.id).subscribe(
(res) => {
this.success.emit(res);
this.isLoading = false;
@ -245,6 +288,7 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy
}
cancelStartProcess() {
this.currentCreatedProcess = null;
this.cancel.emit();
}

View File

@ -31,6 +31,18 @@ export let fakeProcessInstance = new ProcessInstanceCloud({
processDefinitionKey: 'BasicProcess'
});
export let fakeCreatedProcessInstance = new ProcessInstanceCloud({
appName: 'simple-app',
appVersion: '',
id: 'd0b30377-dc5a-11e8-ae24-0a58646001fa',
name: 'My Process Name',
startDate: null,
initiator: 'usermock',
status: 'CREATED',
processDefinitionId: 'BasicProcess:1:d05062f1-c6fb-11e8-ae24-0a58646001fa',
processDefinitionKey: 'BasicProcess'
});
export let fakeProcessDefinitions: ProcessDefinitionCloud[] = [
new ProcessDefinitionCloud({
appName: 'myApp',

View File

@ -18,7 +18,7 @@
import { TestBed } from '@angular/core/testing';
import { of, throwError } from 'rxjs';
import {
setupTestBed, CoreModule,
setupTestBed,
AlfrescoApiService,
AppConfigService,
LogService,
@ -27,16 +27,15 @@ import {
import { StartProcessCloudService } from './start-process-cloud.service';
import { fakeProcessPayload } from '../mock/start-process.component.mock';
import { ProcessInstanceCloud } from '../models/process-instance-cloud.model';
import { HttpErrorResponse } from '@angular/common/http';
import { HttpErrorResponse, HttpClientModule } from '@angular/common/http';
import { ProcessDefinitionCloud } from '../models/process-definition-cloud.model';
import { ProcessCloudModule } from '../../process-cloud.module';
describe('StartProcessCloudService', () => {
let service: StartProcessCloudService;
setupTestBed({
imports: [CoreModule.forRoot(), ProcessCloudModule],
imports: [HttpClientModule],
providers: [StartProcessCloudService, AlfrescoApiService, AppConfigService, LogService, StorageService]
});
@ -108,4 +107,32 @@ describe('StartProcessCloudService', () => {
}
);
});
it('should be able to create a new process instance without starting it', (done) => {
spyOn(service, 'createProcess').and.returnValue(of({ id: 'fake-id', name: 'fake-name', status: 'CREATED' }));
service.createProcess('appName1', fakeProcessPayload)
.subscribe(
(res: ProcessInstanceCloud) => {
expect(res).toBeDefined();
expect(res.id).toEqual('fake-id');
expect(res.name).toEqual('fake-name');
expect(res.status).toEqual('CREATED');
done();
}
);
});
it('should be able to start a created new process instance', (done) => {
spyOn(service, 'startCreatedProcess').and.returnValue(of({ id: 'fake-id', name: 'fake-name', status: 'RUNNING' }));
service.startCreatedProcess('appName1', 'fake-id')
.subscribe(
(res: ProcessInstanceCloud) => {
expect(res).toBeDefined();
expect(res.id).toEqual('fake-id');
expect(res.name).toEqual('fake-name');
expect(res.status).toEqual('RUNNING');
done();
}
);
});
});

View File

@ -55,6 +55,35 @@ export class StartProcessCloudService extends BaseCloudService {
}
}
/**
* Create a process based on a process definition, name, form values or variables.
* @param appName name of the Application
* @param payload Details of the process (definition key, name, variables, etc)
* @returns Details of the process instance just created
*/
createProcess(appName: string, payload: ProcessPayloadCloud): Observable<ProcessInstanceCloud> {
const url = `${this.getBasePath(appName)}/rb/v1/process-instances/create`;
return this.post(url, payload).pipe(
map((result: any) => result.entry),
map(processInstance => new ProcessInstanceCloud(processInstance))
);
}
/**
* Starts an already created process using the process instance id.
* @param createdProcessInstanceId process instance id of the process previously created
* @returns Details of the process instance just started
*/
startCreatedProcess(appName: string, createdProcessInstanceId: string): Observable<ProcessInstanceCloud> {
const url = `${this.getBasePath(appName)}/rb/v1/process-instances/${createdProcessInstanceId}/start`;
return this.post(url).pipe(
map((result: any) => result.entry),
map(processInstance => new ProcessInstanceCloud(processInstance.entry))
);
}
/**
* Starts a process based on a process definition, name, form values or variables.
* @param appName name of the Application

View File

@ -69,10 +69,6 @@ describe('ProcessFiltersComponent', () => {
})
]);
mockErrorFilterPromise = Promise.reject({
error: 'wrong request'
});
processFilterService = TestBed.get(ProcessFilterService);
appsProcessService = TestBed.get(AppsProcessService);
});
@ -153,6 +149,9 @@ describe('ProcessFiltersComponent', () => {
});
it('should emit an error with a bad response', (done) => {
mockErrorFilterPromise = Promise.reject({
error: 'wrong request'
});
spyOn(processFilterService, 'getProcessFilters').and.returnValue(from(mockErrorFilterPromise));
const appId = '1';