diff --git a/demo-shell/src/app.config.json b/demo-shell/src/app.config.json index e00e9a7d66..6752616d60 100644 --- a/demo-shell/src/app.config.json +++ b/demo-shell/src/app.config.json @@ -494,7 +494,8 @@ }, "adf-start-process": { "name": "My Default Name", - "processDefinitionName": "My default process def name" + "processDefinitionName": "My default process def name", + "defaultProcessSelected": true }, "adf-process-list": { "presets": { diff --git a/demo-shell/src/app/components/process-service/process-service.component.html b/demo-shell/src/app/components/process-service/process-service.component.html index 67ebe4737a..9eddd8ff43 100644 --- a/demo-shell/src/app/components/process-service/process-service.component.html +++ b/demo-shell/src/app/components/process-service/process-service.component.html @@ -204,6 +204,7 @@ [appId]="appId" [name]="defaultProcessName" [processDefinitionName]="defaultProcessDefinitionName" + [defaultProcessSelected]="defaultProcessSelected" (formContentClicked)="onContentClick($event)" (start)="onStartProcessInstance($event)" (cancel)="onCancelProcessInstance()"> diff --git a/demo-shell/src/app/components/process-service/process-service.component.ts b/demo-shell/src/app/components/process-service/process-service.component.ts index ffe13e9a5c..068ef8009b 100644 --- a/demo-shell/src/app/components/process-service/process-service.component.ts +++ b/demo-shell/src/app/components/process-service/process-service.component.ts @@ -134,6 +134,7 @@ export class ProcessServiceComponent implements AfterViewInit, OnDestroy, OnInit defaultProcessDefinitionName: string; defaultProcessName: string; + defaultProcessSelected: boolean; activeTab: number = this.tabs.tasks; // tasks|processes|reports @@ -172,6 +173,7 @@ export class ProcessServiceComponent implements AfterViewInit, OnDestroy, OnInit this.defaultProcessName = this.appConfig.get('adf-start-process.name'); this.defaultProcessDefinitionName = this.appConfig.get('adf-start-process.processDefinitionName'); + this.defaultProcessSelected = this.appConfig.get('adf-start-process.defaultProcessSelected'); // Uncomment this line to replace all 'text' field editors with custom component // formRenderingService.setComponentTypeResolver('text', () => CustomEditorComponent, true); diff --git a/docs/process-services/start-process.component.md b/docs/process-services/start-process.component.md index 8840f6bcb8..fcc39df78e 100644 --- a/docs/process-services/start-process.component.md +++ b/docs/process-services/start-process.component.md @@ -40,6 +40,8 @@ Starts a process. | appId | `number` | (optional): Limit the list of processes which can be started to those contained in the specified app | | name | `string` | (optional) name to assign to the current process | | processDefinitionName | `string` | (optional) definition name of the process to start | +| defaultProcessSelected | `boolean` | (optional) make selected process the default one in process fields, true by default | +| processFilterSelector | `boolean` | (optional) Enables automatic selection of process when typing dowm in process field, true by default | | variables | [`ProcessInstanceVariable[]`](../../lib/process-services/process-list/models/process-instance-variable.model.ts) | Variables in input to the process [RestVariable](https://github.com/Alfresco/alfresco-js-api/tree/master/src/alfresco-activiti-rest-api/docs/RestVariable.md) | | values | [`FormValues`](../../lib/core/form/components/widgets/core/form-values.ts) | Parameter to pass form field values in the start form if is associated | | showSelectProcessDropdown | `boolean` | hide or show the process selection drodown, true by default | diff --git a/e2e/pages/adf/process_services/startProcessPage.js b/e2e/pages/adf/process_services/startProcessPage.js index 3ce6436279..667fee8489 100644 --- a/e2e/pages/adf/process_services/startProcessPage.js +++ b/e2e/pages/adf/process_services/startProcessPage.js @@ -21,7 +21,7 @@ var StartProcessPage = function () { var defaultProcessName = element(by.css("input[id='processName']")); var processNameInput = element(by.id('processName')); - var selectProcessDropdownArrow = element(by.css("div[class='mat-select-arrow-wrapper'] div")); + var selectProcessDropdownArrow = element(by.css("button[id='adf-select-process-dropdown']")); var cancelProcessButton = element(by.id('cancel_process')); var formStartProcessButton = element(by.css('button[data-automation-id="adf-form-start process"]')); var startProcessButton = element(by.css("button[data-automation-id='btn-start']")); diff --git a/e2e/process-services/start_process_component.e2e.ts b/e2e/process-services/start_process_component.e2e.ts index 8e620f33a6..a65fb8735b 100644 --- a/e2e/process-services/start_process_component.e2e.ts +++ b/e2e/process-services/start_process_component.e2e.ts @@ -148,7 +148,7 @@ describe('Start Process Component', () => { appNavigationBarPage.clickProcessButton(); processFiltersPage.clickCreateProcessButton(); processFiltersPage.clickNewProcessDropdown(); - startProcessPage.selectFromProcessDropdown('Choose one...'); + startProcessPage.enterProcessName(''); startProcessPage.checkStartProcessButtonIsDisabled(); startProcessPage.clickCancelProcessButton(); processFiltersPage.checkNoContentMessage(); diff --git a/lib/process-services/process-list/components/start-process.component.html b/lib/process-services/process-list/components/start-process.component.html index fb72ab21e1..24a58cf055 100644 --- a/lib/process-services/process-list/components/start-process.component.html +++ b/lib/process-services/process-list/components/start-process.component.html @@ -6,19 +6,39 @@ {{errorMessageId|translate}} - + - -
- - - {{'ADF_PROCESS_LIST.START_PROCESS.FORM.TYPE_PLACEHOLDER' | translate}} - + + +
+ + {{ processDef.name }} - - -
+ + +
+ - + @@ -37,7 +63,23 @@ - - + + diff --git a/lib/process-services/process-list/components/start-process.component.scss b/lib/process-services/process-list/components/start-process.component.scss index b95df69d10..649aaabab0 100644 --- a/lib/process-services/process-list/components/start-process.component.scss +++ b/lib/process-services/process-list/components/start-process.component.scss @@ -26,6 +26,14 @@ width: 100%; } } + &-process-input-autocomplete { + display: flex; + button { + position: absolute; + right: 0; + top: 0; + } + } &-start-form-container { .mat-card { box-shadow: none !important; diff --git a/lib/process-services/process-list/components/start-process.component.spec.ts b/lib/process-services/process-list/components/start-process.component.spec.ts index 3730f3cdac..dc7ecdcb4b 100644 --- a/lib/process-services/process-list/components/start-process.component.spec.ts +++ b/lib/process-services/process-list/components/start-process.component.spec.ts @@ -79,18 +79,17 @@ describe('StartFormComponent', () => { describe('without start form', () => { beforeEach(() => { + fixture.detectChanges(); component.name = 'My new process'; let change = new SimpleChange(null, 123, true); component.ngOnChanges({ 'appId': change }); + fixture.detectChanges(); }); it('should enable start button when name and process filled out', async(() => { spyOn(component, 'loadStartProcess').and.callThrough(); - - component.processDefinitionName = 'My Process 1'; - - let change = new SimpleChange(null, 123, true); - component.ngOnChanges({ 'appId': change }); + component.processNameInput.setValue('My Process'); + component.processDefinitionInput.setValue(testProcessDefRepr.name); fixture.detectChanges(); @@ -101,7 +100,9 @@ describe('StartFormComponent', () => { })); it('should have start button disabled when name not filled out', async(() => { - component.name = ''; + spyOn(component, 'loadStartProcess').and.callThrough(); + component.processNameInput.setValue(''); + component.processDefinitionInput.setValue(testProcessDefRepr.name); fixture.detectChanges(); fixture.whenStable().then(() => { let startBtn = fixture.nativeElement.querySelector('#button-start'); @@ -122,6 +123,7 @@ describe('StartFormComponent', () => { describe('with start form', () => { beforeEach(() => { + fixture.detectChanges(); getDefinitionsSpy.and.returnValue(of(testProcessDefWithForm)); let change = new SimpleChange(null, 123, true); component.ngOnChanges({ 'appId': change }); @@ -143,7 +145,7 @@ describe('StartFormComponent', () => { }); })); - it('should have start button disabled if the process is not seleted', async(() => { + it('should have start button disabled if the process is not selected', async(() => { component.name = 'My new process'; fixture.detectChanges(); fixture.whenStable().then(() => { @@ -205,25 +207,19 @@ describe('StartFormComponent', () => { component.appId = 123; component.ngOnChanges({}); fixture.detectChanges(); - - expect(getDefinitionsSpy).toHaveBeenCalledWith(123); - }); - - it('should call service to fetch process definitions with appId when provided', () => { - component.appId = 123; - component.ngOnChanges({}); - fixture.detectChanges(); - - expect(getDefinitionsSpy).toHaveBeenCalledWith(123); + fixture.whenStable().then(() => { + expect(getDefinitionsSpy).toHaveBeenCalledWith(123); + }); }); it('should display the correct number of processes in the select list', () => { component.appId = 123; component.ngOnChanges({}); fixture.detectChanges(); - - let selectElement = fixture.nativeElement.querySelector('mat-select'); - expect(selectElement.children.length).toBe(1); + fixture.whenStable().then(() => { + let selectElement = fixture.nativeElement.querySelector('mat-select'); + expect(selectElement.children.length).toBe(1); + }); }); it('should display the option def details', () => { @@ -269,17 +265,19 @@ describe('StartFormComponent', () => { })); it('should select processDefinition based on processDefinition input', async(() => { + fixture.detectChanges(); getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of(testMultipleProcessDefs)); component.appId = 123; - component.processDefinitionName = 'My Process 2'; - component.ngOnChanges({}); + component.processNameInput.setValue('My Process 2'); + component.processDefinitionInput.setValue('My Process 2'); fixture.detectChanges(); fixture.whenStable().then(() => { expect(component.selectedProcessDef.name).toBe(JSON.parse(JSON.stringify(testMultipleProcessDefs[1])).name); }); })); - it('should select automatically the processDefinition if the app contain oly one', async(() => { + it('should select automatically the processDefinition if the app contain only one', async(() => { + fixture.detectChanges(); getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of(testProcessDefinitions)); component.appId = 123; component.ngOnChanges({}); @@ -291,19 +289,20 @@ describe('StartFormComponent', () => { describe('dropdown', () => { - it('should hide the process dropdown if showSelectProcessDropdown is false', async(() => { + it('should hide the process dropdown button if showSelectProcessDropdown is false', async(() => { + fixture.detectChanges(); getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of([testProcessDefRepr])); component.appId = 123; component.showSelectProcessDropdown = false; component.ngOnChanges({}); fixture.detectChanges(); fixture.whenStable().then(() => { - let selectElement = fixture.nativeElement.querySelector('mat-select > .mat-select-trigger'); + let selectElement = fixture.nativeElement.querySelector('button#adf-select-process-dropdown'); expect(selectElement).toBeNull(); }); })); - it('should show the process dropdown if showSelectProcessDropdown is false', async(() => { + it('should show the process dropdown button if showSelectProcessDropdown is false', async(() => { getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of(testMultipleProcessDefs)); component.appId = 123; component.processDefinitionName = 'My Process 2'; @@ -311,19 +310,19 @@ describe('StartFormComponent', () => { component.ngOnChanges({}); fixture.detectChanges(); fixture.whenStable().then(() => { - let selectElement = fixture.nativeElement.querySelector('mat-select > .mat-select-trigger'); + let selectElement = fixture.nativeElement.querySelector('button#adf-select-process-dropdown'); expect(selectElement).not.toBeNull(); }); })); - it('should show the process dropdown by default', async(() => { + it('should show the process dropdown button by default', async(() => { getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of(testMultipleProcessDefs)); component.appId = 123; component.processDefinitionName = 'My Process 2'; component.ngOnChanges({}); fixture.detectChanges(); fixture.whenStable().then(() => { - let selectElement = fixture.nativeElement.querySelector('mat-select > .mat-select-trigger'); + let selectElement = fixture.nativeElement.querySelector('button#adf-select-process-dropdown'); expect(selectElement).not.toBeNull(); }); })); @@ -346,12 +345,13 @@ describe('StartFormComponent', () => { it('should reload processes when appId input changed', async(() => { component.appId = 456; component.ngOnChanges({ appId: change }); + fixture.detectChanges(); fixture.whenStable().then(() => { expect(getDefinitionsSpy).toHaveBeenCalledWith(456); }); })); - it('should get current processDeff', () => { + it('should get current processDef', () => { component.appId = 456; component.ngOnChanges({ appId: change }); fixture.detectChanges(); @@ -430,11 +430,12 @@ describe('StartFormComponent', () => { })); it('should indicate an error to the user if process cannot be started', async(() => { + fixture.detectChanges(); startProcessSpy = startProcessSpy.and.returnValue(throwError({})); component.selectedProcessDef = testProcessDefRepr; component.startProcess(); + fixture.detectChanges(); fixture.whenStable().then(() => { - fixture.detectChanges(); let errorEl = fixture.nativeElement.querySelector('#error-message'); expect(errorEl).not.toBeNull(); expect(errorEl.innerText.trim()).toBe('ADF_PROCESS_LIST.START_PROCESS.ERROR.START'); diff --git a/lib/process-services/process-list/components/start-process.component.ts b/lib/process-services/process-list/components/start-process.component.ts index 27f7965b3e..86f7f67298 100644 --- a/lib/process-services/process-list/components/start-process.component.ts +++ b/lib/process-services/process-list/components/start-process.component.ts @@ -15,10 +15,12 @@ * limitations under the License. */ -import { Component, EventEmitter, Input, OnChanges, +import { + Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core'; -import { ActivitiContentService, AppConfigService, AppConfigValues, +import { + ActivitiContentService, AppConfigService, AppConfigValues, StartFormComponent, FormRenderingService, FormValues } from '@alfresco/adf-core'; import { ProcessInstanceVariable } from '../models/process-instance-variable.model'; @@ -26,6 +28,10 @@ import { ProcessDefinitionRepresentation } from './../models/process-definition. import { ProcessInstance } from './../models/process-instance.model'; import { ProcessService } from './../services/process.service'; import { AttachFileWidgetComponent, AttachFolderWidgetComponent } from '../../content-widget'; +import { FormControl, Validators } from '@angular/forms'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { MatAutocompleteTrigger } from '@angular/material'; @Component({ selector: 'adf-start-process', @@ -33,7 +39,7 @@ import { AttachFileWidgetComponent, AttachFolderWidgetComponent } from '../../co styleUrls: ['./start-process.component.scss'], encapsulation: ViewEncapsulation.None }) -export class StartProcessInstanceComponent implements OnChanges { +export class StartProcessInstanceComponent implements OnChanges, OnInit { /** (optional) Limit the list of processes that can be started to those * contained in the specified app. @@ -45,6 +51,10 @@ export class StartProcessInstanceComponent implements OnChanges { @Input() processDefinitionName: string; + /** (optional) Definition name of the process to start. */ + @Input() + defaultProcessSelected: boolean; + /** Variables in input to the process * [RestVariable](https://github.com/Alfresco/alfresco-js-api/tree/master/src/alfresco-activiti-rest-api/docs/RestVariable.md). */ @@ -57,12 +67,16 @@ export class StartProcessInstanceComponent implements OnChanges { /** (optional) Name to assign to the current process. */ @Input() - name: string; + name: string = ''; - /** Hide or show the process selection drodown. */ + /** Hide or show the process selection dropdown. */ @Input() showSelectProcessDropdown: boolean = true; + /** (optional) Parameter to enable selection of process when filtering. */ + @Input() + processFilterSelector: boolean = true; + /** Emitted when the process starts. */ @Output() start: EventEmitter = new EventEmitter(); @@ -78,12 +92,19 @@ export class StartProcessInstanceComponent implements OnChanges { @ViewChild('startForm') startForm: StartFormComponent; + @ViewChild(MatAutocompleteTrigger) + inputAutocomplete: MatAutocompleteTrigger; + processDefinitions: ProcessDefinitionRepresentation[] = []; selectedProcessDef: ProcessDefinitionRepresentation = new ProcessDefinitionRepresentation(); errorMessageId: string = ''; + processNameInput: FormControl; + processDefinitionInput: FormControl; + filteredProcesses: Observable; + constructor(private activitiProcess: ProcessService, private formRenderingService: FormRenderingService, private activitiContentService: ActivitiContentService, @@ -92,14 +113,50 @@ export class StartProcessInstanceComponent implements OnChanges { this.formRenderingService.setComponentTypeResolver('select-folder', () => AttachFolderWidgetComponent, true); } + ngOnInit() { + this.processNameInput = new FormControl(this.name, Validators.required); + this.processDefinitionInput = new FormControl(); + + this.loadStartProcess(); + + this.processNameInput.valueChanges.subscribe(name => this.name = name); + this.filteredProcesses = this.processDefinitionInput.valueChanges + .pipe( + map(value => this._filter(value)) + ); + } + ngOnChanges(changes: SimpleChanges) { if (changes['values'] && changes['values'].currentValue) { this.moveNodeFromCStoPS(); } + if (changes['appId'] && changes['appId'].currentValue) { + this.appId = changes['appId'].currentValue; + } + this.loadStartProcess(); } + private _filter(value: string): ProcessDefinitionRepresentation[] { + const filterValue = value.toLowerCase(); + let filteredProcess = this.processDefinitions.filter(option => option.name.toLowerCase().includes(filterValue)); + + if (this.processFilterSelector) { + this.selectedProcessDef = this.getSelectedProcess(filterValue); + } + return filteredProcess; + } + + getSelectedProcess(selectedProcess) { + let processSelected = this.processDefinitions.find(process => process.name.toLowerCase() === selectedProcess); + + if (!processSelected) { + processSelected = new ProcessDefinitionRepresentation(); + } + return processSelected; + } + public loadStartProcess() { this.resetSelectedProcessDefinition(); this.resetErrorMessage(); @@ -109,17 +166,22 @@ export class StartProcessInstanceComponent implements OnChanges { this.processDefinitions = processDefinitionRepresentations; if (this.hasSingleProcessDefinition()) { - this.selectedProcessDef = this.processDefinitions[0]; - } else { - this.selectedProcessDef = this.processDefinitions.find((currentProcessDefinition) => { - return currentProcessDefinition.name === this.processDefinitionName; - }); + if (this.processDefinitionName && this.defaultProcessSelected) { + this.selectedProcessDef = this.getSelectedProcess(this.processDefinitionName); + + if (!this.selectedProcessDef.id) { + this.selectedProcessDef = this.processDefinitions[0]; + } + } else { + this.selectedProcessDef = this.processDefinitions[0]; + } + + this.processDefinitionInput.setValue(this.selectedProcessDef.name); } }, () => { this.errorMessageId = 'ADF_PROCESS_LIST.START_PROCESS.ERROR.LOAD_PROCESS_DEFS'; }); - } hasSingleProcessDefinition(): boolean { @@ -219,4 +281,24 @@ export class StartProcessInstanceComponent implements OnChanges { hasProcessName(): boolean { return this.name ? true : false; } + + displayFn(process: any) { + if (process) { + let processName = process; + if (typeof process !== 'string') { + processName = process.name; + } + return processName; + } + } + + displayDropdown(event) { + event.stopPropagation(); + if (!this.inputAutocomplete.panelOpen) { + this.processDefinitionInput.setValue(''); + this.inputAutocomplete.openPanel(); + } else { + this.inputAutocomplete.closePanel(); + } + } }