diff --git a/demo-shell/src/app.config.json b/demo-shell/src/app.config.json index 1972bfc2e4..04b2026b37 100644 --- a/demo-shell/src/app.config.json +++ b/demo-shell/src/app.config.json @@ -744,6 +744,9 @@ "name": "My Default Name", "processDefinitionName": "My default process def name" }, + "adf-cloud-start-process": { + "name": "My Default Cloud Name" + }, "adf-process-list": { "presets": { "default": [ diff --git a/demo-shell/src/app/components/cloud/start-process-cloud-demo.component.html b/demo-shell/src/app/components/cloud/start-process-cloud-demo.component.html index 981810289d..6e1e8728e5 100644 --- a/demo-shell/src/app/components/cloud/start-process-cloud-demo.component.html +++ b/demo-shell/src/app/components/cloud/start-process-cloud-demo.component.html @@ -1,6 +1,8 @@ diff --git a/demo-shell/src/app/components/cloud/start-process-cloud-demo.component.ts b/demo-shell/src/app/components/cloud/start-process-cloud-demo.component.ts index 884169eb99..2d6470ca29 100644 --- a/demo-shell/src/app/components/cloud/start-process-cloud-demo.component.ts +++ b/demo-shell/src/app/components/cloud/start-process-cloud-demo.component.ts @@ -28,6 +28,8 @@ export class StartProcessCloudDemoComponent implements OnInit { appName; processName: string; + formValues: string; + variables: any; constructor(private appConfig: AppConfigService, private cloudLayoutService: CloudLayoutService, @@ -41,7 +43,9 @@ export class StartProcessCloudDemoComponent implements OnInit { this.appName = params.appName; }); - this.processName = this.appConfig.get('adf-start-process.name'); + this.processName = this.appConfig.get('adf-cloud-start-process.name'); + this.formValues = this.appConfig.get('adf-cloud-start-process.values'); + this.variables = this.appConfig.get('adf-cloud-start-process.variables'); } onStartProcessSuccess() { diff --git a/docs/process-services-cloud/components/start-process-cloud.component.md b/docs/process-services-cloud/components/start-process-cloud.component.md index a0fd558b06..6d2c8e0086 100644 --- a/docs/process-services-cloud/components/start-process-cloud.component.md +++ b/docs/process-services-cloud/components/start-process-cloud.component.md @@ -30,7 +30,8 @@ Starts a process. | name | `string` | "" | Name of the process. | | processDefinitionName | `string` | | Name of the process definition. | | showSelectProcessDropdown | `boolean` | true | Show/hide the process dropdown list. | -| variables | `Map[]` | | Variables to attach to the payload. | +| variables | `Object` | | Variables to attach to the payload. | +| values | `TaskVariableCloud` | | Values to attach to a form. | ### Events @@ -55,3 +56,22 @@ Starts a process. You can use the `processDefinitionName` property to select which process will be selected by default on the dropdown (when there is more than one process to choose from). Use the `name` property to set the name shown on the dropdown item. If the app contains only one process definition, this process definition will be selected by default + + +### Starting a process with variables + +```html + + +``` + +### Starting a process with values passed to the form + +```html + + +``` 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 e72293bff7..625a7be648 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 @@ -46,8 +46,30 @@ + + + + + + + + + + + + + + + @@ -55,19 +77,4 @@ - - - - - - \ No newline at end of file + 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 5a05d7eb80..d58830fc57 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 @@ -20,18 +20,19 @@ import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core import { setupTestBed } 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 { fakeProcessDefinitions, fakeProcessInstance, fakeProcessPayload, fakeNoNameProcessDefinitions } from '../mock/start-process.component.mock'; -import { By } from '@angular/platform-browser'; +import { fakeProcessDefinitions, fakeStartForm, fakeProcessInstance, fakeProcessPayload, fakeNoNameProcessDefinitions } from '../mock/start-process.component.mock'; describe('StartProcessCloudComponent', () => { let component: StartProcessCloudComponent; let fixture: ComponentFixture; let processService: StartProcessCloudService; + let formCloudService: FormCloudService; let getDefinitionsSpy: jasmine.Spy; let startProcessSpy: jasmine.Spy; @@ -44,6 +45,7 @@ describe('StartProcessCloudComponent', () => { beforeEach(() => { processService = TestBed.get(StartProcessCloudService); + formCloudService = TestBed.get(FormCloudService); fixture = TestBed.createComponent(StartProcessCloudComponent); component = fixture.componentInstance; @@ -60,51 +62,100 @@ describe('StartProcessCloudComponent', () => { expect(fixture.componentInstance instanceof StartProcessCloudComponent).toBe(true, 'should create StartProcessInstanceComponent'); }); - describe('first step', () => { + describe('start a process without start form', () => { - describe('without start form', () => { + it('should enable start button when name and process filled out', async(() => { + component.name = 'My new process'; + component.processDefinitionName = 'processwithoutform2'; + fixture.detectChanges(); - beforeEach(() => { - fixture.detectChanges(); - component.name = 'My new process'; - component.appName = 'myApp'; - 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(false); }); + })); - it('should enable start button when name and process filled out', async(() => { - spyOn(component, 'loadProcessDefinitions').and.callThrough(); - component.processDefinitionList = fakeProcessDefinitions; - component.processForm.controls['processInstanceName'].setValue('My Process 1'); - component.processForm.controls['processDefinition'].setValue('NewProcess 1'); + it('should have start button disabled when name not filled out', async(() => { + component.name = ''; + component.processDefinitionName = 'processwithoutform2'; + fixture.detectChanges(); - 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(false); - }); - })); + fixture.whenStable().then(() => { + const startBtn = fixture.nativeElement.querySelector('#button-start'); + expect(startBtn.disabled).toBe(true); + }); + })); - it('should have start button disabled when name not filled out', async(() => { - spyOn(component, 'loadProcessDefinitions').and.callThrough(); - component.processForm.controls['processInstanceName'].setValue(''); - component.processForm.controls['processDefinition'].setValue(fakeProcessInstance.name); - fixture.detectChanges(); - fixture.whenStable().then(() => { - const startBtn = fixture.nativeElement.querySelector('#button-start'); - expect(startBtn.disabled).toBe(true); - }); - })); + it('should have start button disabled when no process is selected', async(() => { + component.name = ''; + component.processDefinitionName = ''; + fixture.detectChanges(); - it('should have start button disabled when no process is selected', async(() => { - component.processPayloadCloud.processDefinitionKey = null; - fixture.detectChanges(); - fixture.whenStable().then(() => { - const startBtn = fixture.nativeElement.querySelector('#button-start'); - expect(startBtn.disabled).toBe(true); - }); - })); + 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); + }); + })); + }); + + describe('start a process with start form', () => { + + beforeEach(() => { + component.name = 'My new process with form'; }); + + it('should show a form if the process definition has one', async(() => { + component.processDefinitionName = 'processwithform'; + fixture.detectChanges(); + getDefinitionsSpy = spyOn(formCloudService, 'getForm').and.returnValue(of(fakeStartForm)); + + const change = new SimpleChange(null, 'MyApp', true); + component.ngOnChanges({ 'appName': change }); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + 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(startBtn.disabled).toBe(false); + }); + })); + + it('should show a prefilled form if the values are passed as input', async(() => { + component.processDefinitionName = 'processwithform'; + component.values = [{'name': 'firstName', 'value': 'FakeName'}, {'name': 'lastName', 'value': 'FakeLastName'}]; + fixture.detectChanges(); + getDefinitionsSpy = spyOn(formCloudService, 'getForm').and.returnValue(of(fakeStartForm)); + + const change = new SimpleChange(null, 'MyApp', true); + component.ngOnChanges({ 'appName': change }); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + const firstNameEl = fixture.nativeElement.querySelector('#firstName'); + expect(firstNameEl).toBeDefined(); + expect(firstNameEl.value).toEqual('FakeName'); + const lastNameEl = fixture.nativeElement.querySelector('#lastName'); + expect(lastNameEl).toBeDefined(); + expect(lastNameEl.value).toEqual('FakeLastName'); + const startBtn = fixture.nativeElement.querySelector('#button-start'); + expect(startBtn.disabled).toBe(false); + }); + })); }); describe('process definitions list', () => { @@ -185,12 +236,16 @@ describe('StartProcessCloudComponent', () => { })); it('should select processDefinition based on processDefinition input', async(() => { - getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions)); - component.processForm.controls['processInstanceName'].setValue('NewProcess 1'); - component.processForm.controls['processDefinition'].setValue('NewProcess 1'); + component.name = 'My new process'; + component.processDefinitionName = 'processwithoutform1'; fixture.detectChanges(); + + const change = new SimpleChange(null, 'MyApp', true); + component.ngOnChanges({ 'appName': change }); + fixture.detectChanges(); + fixture.whenStable().then(() => { - expect(component.processPayloadCloud.processDefinitionKey).toBe(JSON.parse(JSON.stringify(fakeProcessDefinitions[0])).name); + expect(component.processPayloadCloud.processDefinitionKey).toBe(JSON.parse(JSON.stringify(fakeProcessDefinitions[0])).key); }); })); @@ -320,15 +375,6 @@ describe('StartProcessCloudComponent', () => { }); })); - it('should avoid calling service to start process if required fields NOT provided', async(() => { - component.processForm.controls['processInstanceName'].setValue(''); - component.processForm.controls['processDefinition'].setValue(''); - fixture.whenStable().then(() => { - const startProcessButton = fixture.debugElement.query(By.css('[data-automation-id="btn-start"]')); - expect(startProcessButton.nativeElement.disabled).toBeTruthy(); - }); - })); - it('should call service to start process with the correct parameters', async(() => { component.processPayloadCloud = fakeProcessPayload; component.startProcess(); 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 eaa25606db..78052068c5 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,6 +28,8 @@ import { ProcessPayloadCloud } from '../models/process-payload-cloud.model'; import { debounceTime, takeUntil } from 'rxjs/operators'; import { ProcessDefinitionCloud } from '../models/process-definition-cloud.model'; import { Subject } from 'rxjs'; +import { FormCloudComponent } from '../../../form/components/form-cloud.component'; +import { TaskVariableCloud } from '../../../form/models/task-variable-cloud.model'; @Component({ selector: 'adf-cloud-start-process', @@ -42,6 +44,9 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy @ViewChild(MatAutocompleteTrigger) inputAutocomplete: MatAutocompleteTrigger; + @ViewChild(FormCloudComponent) + formCloud: FormCloudComponent; + /** (required) Name of the app. */ @Input() appName: string; @@ -60,7 +65,11 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy /** Variables to attach to the payload. */ @Input() - variables: Map[]; + variables: {}; + + /** Parameter to pass form field values in the start form if one is associated. */ + @Input() + values: TaskVariableCloud[]; /** Show/hide the process dropdown list. */ @Input() @@ -79,6 +88,7 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy error: EventEmitter = new EventEmitter(); processDefinitionList: ProcessDefinitionCloud[] = []; + processDefinitionCurrent: ProcessDefinitionCloud; errorMessageId: string = ''; processForm: FormGroup; processPayloadCloud = new ProcessPayloadCloud(); @@ -92,7 +102,7 @@ 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('', [Validators.required, this.processDefinitionNameValidator()]) + processDefinition: new FormControl(this.processDefinitionName, [Validators.required, this.processDefinitionNameValidator()]) }); this.processDefinition.valueChanges @@ -115,6 +125,10 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy } } + hasForm(): boolean { + return this.processDefinitionCurrent && !!this.processDefinitionCurrent.formKey; + } + private getMaxNameLength(): number { return this.maxNameLength > StartProcessCloudComponent.MAX_NAME_LENGTH ? StartProcessCloudComponent.MAX_NAME_LENGTH : this.maxNameLength; @@ -149,7 +163,8 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy private selectDefaultProcessDefinition() { const selectedProcess = this.getProcessDefinitionByName(this.processDefinitionName); if (selectedProcess) { - this.processForm.controls['processDefinition'].setValue(selectedProcess.name); + this.processDefinitionCurrent = selectedProcess; + this.processDefinition.setValue(selectedProcess.name); this.processPayloadCloud.processDefinitionKey = selectedProcess.key; } } @@ -176,7 +191,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.toLowerCase().includes(processDefinition.toLowerCase())); + (option.key && option.key.toLowerCase().includes(processDefinition.toLowerCase())); } isProcessDefinitionsEmpty(): boolean { @@ -191,6 +206,10 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy this.processPayloadCloud.variables = this.variables; } + if (this.hasForm()) { + this.processPayloadCloud.variables = Object.assign(this.processPayloadCloud.variables, this.formCloud.form.values); + } + this.startProcessCloudService.startProcess(this.appName, this.processPayloadCloud).subscribe( (res) => { this.success.emit(res); @@ -213,7 +232,7 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy } private resetProcessDefinitionList() { - this.processForm.controls['processDefinition'].setValue(''); + this.processDefinition.setValue(''); this.filteredProcesses = this.processDefinitionList; } diff --git a/lib/process-services-cloud/src/lib/process/start-process/mock/start-process.component.mock.ts b/lib/process-services-cloud/src/lib/process/start-process/mock/start-process.component.mock.ts index ab6674e4cd..7840d8138d 100755 --- a/lib/process-services-cloud/src/lib/process/start-process/mock/start-process.component.mock.ts +++ b/lib/process-services-cloud/src/lib/process/start-process/mock/start-process.component.mock.ts @@ -36,15 +36,24 @@ export let fakeProcessDefinitions: ProcessDefinitionCloud[] = [ appName: 'myApp', appVersion: 0, id: 'NewProcess:1', - key: 'NewProcess 1', - name: 'NewProcess 1' + name: 'processwithoutform1', + key: 'process-12345-f992-4ee6-9742-3a04617469fe', + formKey: '' }), new ProcessDefinitionCloud({ appName: 'myApp', appVersion: 0, id: 'NewProcess:2', - key: 'NewProcess 2', - name: 'NewProcess 2' + name: 'processwithoutform2', + key: 'process-5151ad1d-f992-4ee6-9742-3a04617469fe', + formKey: '' + }), + new ProcessDefinitionCloud({ + appName: 'startformwithoutupload', + formKey: 'form-a5d50817-5183-4850-802d-17af54b2632f', + id: 'd00c0237-8772-11e9-859a-428f83d5904f', + key: 'process-5151ad1d-f992-4ee6-9742-3a04617469fe', + name: 'processwithform' }) ]; @@ -70,3 +79,67 @@ export let fakeProcessPayload = new ProcessPayloadCloud({ name: 'NewProcess 1', payloadType: 'string' }); + +export let fakeStartForm = { + 'formRepresentation': { + 'id': 'form-a5d50817-5183-4850-802d-17af54b2632f', + 'name': 'simpleform', + 'description': '', + 'version': 0, + 'formDefinition': { + 'tabs': [], + 'fields': [ + { + 'type': 'container', + 'id': '5a6b24c1-db2b-45e9-9aff-142395433d23', + 'name': 'Label', + 'tab': null, + 'fields': { + '1': [ + { + 'type': 'text', + 'id': 'firstName', + 'name': 'firstName', + 'colspan': 1, + 'params': { + 'existingColspan': 1, + 'maxColspan': 2 + }, + 'visibilityCondition': null, + 'placeholder': null, + 'value': null, + 'required': false, + 'minLength': 0, + 'maxLength': 0, + 'regexPattern': null + } + ], + '2': [ + { + 'type': 'text', + 'id': 'lastName', + 'name': 'lastName', + 'colspan': 1, + 'params': { + 'existingColspan': 1, + 'maxColspan': 2 + }, + 'visibilityCondition': null, + 'placeholder': null, + 'value': null, + 'required': false, + 'minLength': 0, + 'maxLength': 0, + 'regexPattern': null + } + ] + }, + 'numberOfColumns': 2 + } + ], + 'outcomes': [], + 'metadata': {}, + 'variables': [] + } + } + }; diff --git a/lib/process-services-cloud/src/lib/process/start-process/models/process-definition-cloud.model.ts b/lib/process-services-cloud/src/lib/process/start-process/models/process-definition-cloud.model.ts index c837a941c6..51fba34597 100755 --- a/lib/process-services-cloud/src/lib/process/start-process/models/process-definition-cloud.model.ts +++ b/lib/process-services-cloud/src/lib/process/start-process/models/process-definition-cloud.model.ts @@ -19,6 +19,7 @@ export class ProcessDefinitionCloud { id: string; appName: string; key: string; + formKey?: string; appVersion: number; version: number; name: string; @@ -28,6 +29,7 @@ export class ProcessDefinitionCloud { this.name = obj && obj.name || null; this.appName = obj && obj.appName || null; this.key = obj && obj.key || null; + this.formKey = obj && obj.formKey || null; this.version = obj && obj.version || 0; this.appVersion = obj && obj.appVersion || 0; } diff --git a/lib/process-services-cloud/src/lib/process/start-process/models/process-payload-cloud.model.ts b/lib/process-services-cloud/src/lib/process/start-process/models/process-payload-cloud.model.ts index 43be1e041f..44f481f1b5 100755 --- a/lib/process-services-cloud/src/lib/process/start-process/models/process-payload-cloud.model.ts +++ b/lib/process-services-cloud/src/lib/process/start-process/models/process-payload-cloud.model.ts @@ -19,7 +19,7 @@ export class ProcessPayloadCloud { processDefinitionKey: string; name: string; businessKey: string; - variables: Map[]; + variables: {}; payloadType: string = 'StartProcessPayload'; constructor(obj?: any) { diff --git a/lib/process-services-cloud/src/lib/process/start-process/start-process-cloud.module.ts b/lib/process-services-cloud/src/lib/process/start-process/start-process-cloud.module.ts index 5b648214eb..1cfbec4dcb 100644 --- a/lib/process-services-cloud/src/lib/process/start-process/start-process-cloud.module.ts +++ b/lib/process-services-cloud/src/lib/process/start-process/start-process-cloud.module.ts @@ -20,12 +20,14 @@ import { CommonModule } from '@angular/common'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FlexLayoutModule } from '@angular/flex-layout'; import { MaterialModule } from '../../material.module'; +import { FormCloudModule } from '../../form/form-cloud.module'; import { StartProcessCloudComponent } from './components/start-process-cloud.component'; import { CoreModule } from '@alfresco/adf-core'; @NgModule({ imports: [ FormsModule, CommonModule, + FormCloudModule, MaterialModule, FlexLayoutModule, ReactiveFormsModule,