diff --git a/README.md b/README.md index 456f644e13..239d705fe0 100644 --- a/README.md +++ b/README.md @@ -66,3 +66,4 @@ All components are supported in the following browsers: * Due to a [known issue](https://bugzilla.mozilla.org/show_bug.cgi?id=1188880) in Firefox, the Alfresco Upload Component does not currently support folder upload functionality on Firefox. See the [Browser Support](BROWSER-SUPPORT.md) article for more details. + diff --git a/e2e/process-services-cloud/form-field/task-visibility-condition.e2e.ts b/e2e/process-services-cloud/form-field/task-visibility-condition.e2e.ts index 1f64fc1cd9..a7570d23c9 100644 --- a/e2e/process-services-cloud/form-field/task-visibility-condition.e2e.ts +++ b/e2e/process-services-cloud/form-field/task-visibility-condition.e2e.ts @@ -71,7 +71,6 @@ describe('Task cloud visibility', async () => { await navigationBarPage.navigateToProcessServicesCloudPage(); await appListCloudComponent.checkApsContainer(); await appListCloudComponent.goToApp(simpleApp); - await tasksCloudDemoPage.taskListCloudComponent().checkTaskListIsLoaded(); }); it('[C315170] Should be able to complete a task with a form with required number widgets', async () => { diff --git a/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.ts b/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.ts index 98bce1c6e8..396f1631c9 100644 --- a/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.ts @@ -66,9 +66,17 @@ export class ProcessListCloudService extends BaseCloudService { } } + if (!queryParam['status']) { + queryParam['status'] = this.buildFilterForAllStatus(); + } + return queryParam; } + private buildFilterForAllStatus() { + return ['RUNNING', 'SUSPENDED', 'CANCELLED', 'COMPLETED']; + } + private isExcludedField(property: string): boolean { return property === 'appName' || property === 'sorting'; } 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 5c78f2b76f..6c7464dcaf 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 @@ -78,7 +78,7 @@ - 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 7dab7f524d..cbf7c288b2 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 @@ -17,7 +17,14 @@ import { SimpleChange, DebugElement } from '@angular/core'; import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; -import { setupTestBed, StorageService, LogService, TranslationService, TranslationMock, FormService } 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'; @@ -25,13 +32,25 @@ import { StartProcessCloudComponent } from './start-process-cloud.component'; 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 { + MatCardModule, + MatOptionModule, + MatAutocompleteModule, + MatIconModule, + MatButtonModule, + MatFormFieldModule, + MatInputModule, + MatRippleModule, + MatCommonModule +} from '@angular/material'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormCloudModule } from '../../../form/form-cloud.module'; -import { fakeProcessDefinitions, fakeStartForm, fakeStartFormNotValid, +import { + fakeProcessDefinitions, fakeStartForm, fakeStartFormNotValid, fakeProcessInstance, fakeNoNameProcessDefinitions, - fakeSingleProcessDefinition, fakeCreatedProcessInstance } from '../mock/start-process.component.mock'; + fakeSingleProcessDefinition, fakeCreatedProcessInstance +} from '../mock/start-process.component.mock'; import { By } from '@angular/platform-browser'; import { ProcessPayloadCloud } from '../models/process-payload-cloud.model'; @@ -52,14 +71,14 @@ describe('StartProcessCloudComponent', () => { selectElement.click(); fixture.detectChanges(); const options: any = fixture.debugElement.queryAll(By.css('.mat-option-text')); - const currentOption = options.find( (option: DebugElement) => option.nativeElement.innerHTML.trim() === name ); + const currentOption = options.find((option: DebugElement) => option.nativeElement.innerHTML.trim() === name); if (currentOption) { currentOption.nativeElement.click(); } }; - function typeValueInto(selector: any, value: string ) { + function typeValueInto(selector: any, value: string) { const inputElement = fixture.debugElement.query(By.css(`${selector}`)); inputElement.nativeElement.value = value; inputElement.nativeElement.dispatchEvent(new Event('input')); @@ -113,28 +132,26 @@ 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 = ''; + 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(); - tick(); - typeValueInto('#processName', 'OLE'); - typeValueInto('#processDefinitionName', 'processwithoutform2'); - fixture.detectChanges(); tick(450); fixture.whenStable().then(() => { fixture.detectChanges(); + const firstNameEl = fixture.nativeElement.querySelector('#firstName'); + expect(firstNameEl).toBeDefined(); + const lastNameEl = fixture.nativeElement.querySelector('#lastName'); + expect(lastNameEl).toBeDefined(); const startBtn = fixture.nativeElement.querySelector('#button-start'); + expect(component.formCloud.isValid).toBe(true); expect(startBtn.disabled).toBe(false); - expect(component.isProcessFormValid()).toBe(true); - expect(createProcessSpy).toHaveBeenCalledWith('MyApp', new ProcessPayloadCloud({name: 'OLE', - processDefinitionKey: fakeProcessDefinitions[1].key})); - expect(component.currentCreatedProcess.status).toBe('CREATED'); - expect(component.currentCreatedProcess.startDate).toBeNull(); }); })); @@ -272,7 +289,10 @@ describe('StartProcessCloudComponent', () => { it('should be able to start a process with a prefilled valid form', fakeAsync(() => { component.processDefinitionName = 'processwithform'; getDefinitionsSpy.and.returnValue(of(fakeSingleProcessDefinition(component.processDefinitionName))); - component.values = [{'name': 'firstName', 'value': 'FakeName'}, {'name': 'lastName', 'value': 'FakeLastName'}]; + component.values = [{ 'name': 'firstName', 'value': 'FakeName' }, { + 'name': 'lastName', + 'value': 'FakeLastName' + }]; fixture.detectChanges(); formDefinitionSpy = spyOn(formCloudService, 'getForm').and.returnValue(of(fakeStartForm)); @@ -303,7 +323,10 @@ describe('StartProcessCloudComponent', () => { it('should NOT be able to start a process with a prefilled NOT valid form', async(() => { component.processDefinitionName = 'processwithform'; getDefinitionsSpy.and.returnValue(of(fakeSingleProcessDefinition(component.processDefinitionName))); - component.values = [{'name': 'firstName', 'value': 'FakeName'}, {'name': 'lastName', 'value': 'FakeLastName'}]; + component.values = [{ 'name': 'firstName', 'value': 'FakeName' }, { + 'name': 'lastName', + 'value': 'FakeLastName' + }]; fixture.detectChanges(); formDefinitionSpy = spyOn(formCloudService, 'getForm').and.returnValue(of(fakeStartFormNotValid)); @@ -326,7 +349,10 @@ describe('StartProcessCloudComponent', () => { })); it('should create a process instance if the selection is valid', fakeAsync(() => { - component.values = [{'name': 'firstName', 'value': 'FakeName'}, {'name': 'lastName', 'value': 'FakeLastName'}]; + component.values = [{ 'name': 'firstName', 'value': 'FakeName' }, { + 'name': 'lastName', + 'value': 'FakeLastName' + }]; component.name = 'testFormWithProcess'; component.processDefinitionName = 'processwithoutform2'; getDefinitionsSpy.and.returnValue(of(fakeSingleProcessDefinition(component.processDefinitionName))); @@ -344,8 +370,10 @@ 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(createProcessSpy).toHaveBeenCalledWith('MyApp', new ProcessPayloadCloud({ + name: 'testFormWithProcess', + processDefinitionKey: fakeProcessDefinitions[1].key + })); expect(component.currentCreatedProcess.status).toBe('CREATED'); expect(component.currentCreatedProcess.startDate).toBeNull(); }); @@ -602,13 +630,13 @@ describe('StartProcessCloudComponent', () => { component.currentCreatedProcess = fakeProcessInstance; component.startProcess(); fixture.whenStable().then(() => { - expect(startProcessSpy).toHaveBeenCalledWith(component.appName, fakeProcessInstance.id); + expect(startProcessSpy).toHaveBeenCalledWith(component.appName, fakeProcessInstance.id, component.processPayloadCloud); }); })); it('should call service to start process with the variables setted', async(() => { const inputProcessVariable: Map[] = []; - inputProcessVariable['name'] = {value: 'Josh'}; + inputProcessVariable['name'] = { value: 'Josh' }; component.variables = inputProcessVariable; component.currentCreatedProcess = fakeProcessInstance; 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 41bda8fab4..7499a370bc 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 @@ -28,7 +28,7 @@ import { MatAutocompleteTrigger } from '@angular/material'; import { ProcessPayloadCloud } from '../models/process-payload-cloud.model'; import { debounceTime, takeUntil, switchMap, filter, distinctUntilChanged, tap } from 'rxjs/operators'; import { ProcessDefinitionCloud } from '../models/process-definition-cloud.model'; -import { Subject, Observable, concat } from 'rxjs'; +import { Subject, Observable } from 'rxjs'; import { TaskVariableCloud } from '../../../form/models/task-variable-cloud.model'; @Component({ @@ -100,7 +100,9 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy isLoading = false; isFormCloudLoaded = false; formCloud: FormModel; - currentCreatedProcess: any; + currentCreatedProcess: ProcessInstanceCloud; + disableStartButton: boolean = true; + protected onDestroy$ = new Subject(); constructor(private startProcessCloudService: StartProcessCloudService, @@ -118,19 +120,20 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy .pipe(takeUntil(this.onDestroy$)) .subscribe((processDefinitionName) => { this.selectProcessDefinitionByProcesDefinitionName(processDefinitionName); - }); + }); this.processForm.valueChanges - .pipe( - tap(() => this.currentCreatedProcess = undefined), - debounceTime(400), - distinctUntilChanged(), - filter(() => this.isProcessSelectionValid()), - switchMap(() => this.generateProcessInstance()) - ).pipe(takeUntil(this.onDestroy$)) - .subscribe((res) => { - this.currentCreatedProcess = res; - }); + .pipe( + debounceTime(200), + tap(() => this.disableStartButton = true), + distinctUntilChanged(), + filter(() => this.isProcessSelectionValid()), + switchMap(() => this.generateProcessInstance()) + ).pipe(takeUntil(this.onDestroy$)) + .subscribe((res) => { + this.currentCreatedProcess = res; + this.disableStartButton = false; + }); if (this.processDefinitionName) { this.processDefinition.setValue(this.processDefinitionName); @@ -167,12 +170,17 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy StartProcessCloudComponent.MAX_NAME_LENGTH : this.maxNameLength; } - private generateProcessInstance(): Observable { + private generateProcessInstance(): Observable { const createPayload: ProcessPayloadCloud = new ProcessPayloadCloud({ name: this.processInstanceName.value, processDefinitionKey: this.processPayloadCloud.processDefinitionKey }); - return this.startProcessCloudService.createProcess(this.appName, createPayload); + + 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 selectProcessDefinitionByProcesDefinitionName(processDefinitionName: string): void { @@ -184,8 +192,8 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy } setProcessDefinitionOnForm(selectedProcessDefinitionName: string) { - this.processDefinitionCurrent = this.filteredProcesses.find( (process: ProcessDefinitionCloud) => - process.name === selectedProcessDefinitionName || process.key === selectedProcessDefinitionName ); + this.processDefinitionCurrent = this.filteredProcesses.find((process: ProcessDefinitionCloud) => + process.name === selectedProcessDefinitionName || process.key === selectedProcessDefinitionName); this.isFormCloudLoaded = false; this.processPayloadCloud.processDefinitionKey = this.processDefinitionCurrent.key; @@ -198,7 +206,7 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy } private getProcessIfExists(processDefinition: string): ProcessDefinitionCloud { - let matchedProcess = this.processDefinitionList.find((option) => this.getProcessDefinition(option, processDefinition) ); + let matchedProcess = this.processDefinitionList.find((option) => this.getProcessDefinition(option, processDefinition)); if (!matchedProcess) { matchedProcess = new ProcessDefinitionCloud(); } @@ -227,14 +235,14 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy this.startProcessCloudService.getProcessDefinitions(this.appName) .pipe(takeUntil(this.onDestroy$)) .subscribe((processDefinitionRepresentations: ProcessDefinitionCloud[]) => { - this.processDefinitionList = processDefinitionRepresentations; - if (processDefinitionRepresentations.length === 1) { - this.selectDefaultProcessDefinition(); - } - }, - () => { - this.errorMessageId = 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.LOAD_PROCESS_DEFS'; - }); + this.processDefinitionList = processDefinitionRepresentations; + if (processDefinitionRepresentations.length === 1) { + this.selectDefaultProcessDefinition(); + } + }, + () => { + this.errorMessageId = 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.LOAD_PROCESS_DEFS'; + }); } private isValidName(name: string): boolean { @@ -255,7 +263,7 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy 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())); + (option.key && option.key.toLowerCase().includes(processDefinition.toLowerCase())); } isProcessDefinitionsEmpty(): boolean { @@ -276,23 +284,27 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy startProcess() { this.isLoading = true; this.buildProcessCloudPayload(); - concat( - this.startProcessCloudService.updateProcess(this.appName, this.currentCreatedProcess.id, this.processPayloadCloud), - this.startProcessCloudService.startCreatedProcess(this.appName, this.currentCreatedProcess.id) - ).subscribe( - (res) => { - this.success.emit(res); - this.isLoading = false; - }, - (err) => { - this.errorMessageId = 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.START'; - this.error.emit(err); - this.isLoading = false; - } - ); + this.startProcessCloudService.startCreatedProcess(this.appName, + this.currentCreatedProcess.id, + this.processPayloadCloud) + .subscribe( + (res) => { + this.success.emit(res); + this.isLoading = false; + }, + (err) => { + this.errorMessageId = 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.START'; + this.error.emit(err); + this.isLoading = false; + } + ); } - cancelStartProcess() { + async cancelStartProcess() { + if (this.currentCreatedProcess) { + await this.startProcessCloudService.deleteProcess(this.appName, this.currentCreatedProcess.id); + } + this.currentCreatedProcess = null; this.cancel.emit(); } diff --git a/lib/process-services-cloud/src/lib/process/start-process/services/start-process-cloud.service.spec.ts b/lib/process-services-cloud/src/lib/process/start-process/services/start-process-cloud.service.spec.ts index 70ec5eedda..49661de320 100755 --- a/lib/process-services-cloud/src/lib/process/start-process/services/start-process-cloud.service.spec.ts +++ b/lib/process-services-cloud/src/lib/process/start-process/services/start-process-cloud.service.spec.ts @@ -124,7 +124,7 @@ describe('StartProcessCloudService', () => { 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') + service.startCreatedProcess('appName1', 'fake-id', fakeProcessPayload) .subscribe( (res: ProcessInstanceCloud) => { expect(res).toBeDefined(); 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 7e4d4148f3..a7a4da7906 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 @@ -63,7 +63,7 @@ export class StartProcessCloudService extends BaseCloudService { */ createProcess(appName: string, payload: ProcessPayloadCloud): Observable { const url = `${this.getBasePath(appName)}/rb/v1/process-instances/create`; - payload.payloadType = 'StartProcessPayload'; + payload.payloadType = 'CreateProcessInstancePayload'; return this.post(url, payload).pipe( map((result: any) => result.entry), @@ -76,10 +76,10 @@ export class StartProcessCloudService extends BaseCloudService { * @param createdProcessInstanceId process instance id of the process previously created * @returns Details of the process instance just started */ - startCreatedProcess(appName: string, createdProcessInstanceId: string): Observable { + startCreatedProcess(appName: string, createdProcessInstanceId: string, payload: ProcessPayloadCloud): Observable { const url = `${this.getBasePath(appName)}/rb/v1/process-instances/${createdProcessInstanceId}/start`; - return this.post(url).pipe( + return this.post(url, payload).pipe( map((result: any) => result.entry), map(processInstance => new ProcessInstanceCloud(processInstance.entry)) ); @@ -112,7 +112,20 @@ export class StartProcessCloudService extends BaseCloudService { payload.payloadType = 'UpdateProcessPayload'; return this.put(url, payload).pipe( - map(processInstance => new ProcessInstanceCloud(processInstance)) + map((processInstance: any) => { + return new ProcessInstanceCloud(processInstance.entry); + }) ); } + + /** + * Delete an existing process instance + * @param appName name of the Application + * @param processInstanceId process instance to update + */ + deleteProcess(appName: string, processInstanceId: string): Observable { + const url = `${this.getBasePath(appName)}/rb/v1/process-instances/${processInstanceId}`; + + return this.delete(url); + } } diff --git a/lib/testing/src/lib/process-services-cloud/pages/start-process-cloud-component.page.ts b/lib/testing/src/lib/process-services-cloud/pages/start-process-cloud-component.page.ts index 65cb3ed39b..8a99af906f 100644 --- a/lib/testing/src/lib/process-services-cloud/pages/start-process-cloud-component.page.ts +++ b/lib/testing/src/lib/process-services-cloud/pages/start-process-cloud-component.page.ts @@ -87,7 +87,7 @@ export class StartProcessCloudPage { } async checkStartProcessButtonIsEnabled(): Promise { - await browser.sleep(1000); // waiting for API response + await browser.sleep(2000); // waiting for API response await BrowserVisibility.waitUntilElementIsVisible(this.startProcessButton); return this.startProcessButton.isEnabled(); }