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 eb75d68587..b20dbcdbd0 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 @@ -217,10 +217,11 @@ *ngIf="isStartProcessMode()"> @@ -291,6 +292,24 @@
Show Process list Context menu
+
+ Show Application dropdown on start process + + + Filter Process definitions + + + + ApplicationId + + + + ProcessDefinitionName + + + + +

multiple 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 ebc8959b0e..7d79811a40 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 @@ -153,6 +153,9 @@ export class ProcessServiceComponent implements AfterViewInit, OnDestroy, OnInit showProcessFilterIcon: boolean; showTaskFilterIcon: boolean; + showApplications: boolean; + applicationId: number; + processDefinitionName: string; fieldValidators = [ ...FORM_FIELD_VALIDATORS, @@ -176,7 +179,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.defaultTaskName = this.appConfig.get('adf-start-task.name'); - + this.processDefinitionName = this.defaultProcessDefinitionName; // Uncomment this line to replace all 'text' field editors with custom component // formRenderingService.setComponentTypeResolver('text', () => CustomEditorComponent, true); @@ -251,6 +254,7 @@ export class ProcessServiceComponent implements AfterViewInit, OnDestroy, OnInit if (applicationId && applicationId !== '0') { this.appId = params['appId']; + this.applicationId = this.appId; } this.taskFilter = null; diff --git a/docs/docassets/images/start-process-with-selected-application.png b/docs/docassets/images/start-process-with-selected-application.png new file mode 100644 index 0000000000..af7a388b86 Binary files /dev/null and b/docs/docassets/images/start-process-with-selected-application.png differ diff --git a/docs/process-services/components/start-process.component.md b/docs/process-services/components/start-process.component.md index b789091c71..3a38edfc43 100644 --- a/docs/process-services/components/start-process.component.md +++ b/docs/process-services/components/start-process.component.md @@ -43,6 +43,7 @@ Starts a process. | processDefinitionName | `string` | | (optional) Definition name of the process to start. | | processFilterSelector | `boolean` | true | (optional) Parameter to enable selection of process when filtering. | | showSelectProcessDropdown | `boolean` | true | Hide or show the process selection dropdown. | +| showSelectApplicationDropdown | `boolean` | false | application selection dropdown. | | title | `string` | | (optional) Define the header of the component. | | values | [`FormValues`](../../../lib/core/form/components/widgets/core/form-values.ts) | | Parameter to pass form field values in the start form if one is associated. | | variables | [`ProcessInstanceVariable`](../../../lib/process-services/src/lib/process-list/models/process-instance-variable.model.ts)`[]` | | Variables in the input to the process [`RestVariable`](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/activiti-rest-api/docs/RestVariable.md). | @@ -175,6 +176,25 @@ The result will be the start form prefilled with the file data: ![Start process load file](../../docassets/images/start_process.png) +### Starting a process with a selected application + +Now you can start process based on selected application from the dropdown. The process definition dropdown will display based on the selected application. The application dropdown will be selected application based on the given `appId` otherwise first application will be selected as default. + +```html + + +``` + +You can use the `showSelectApplicationDropdown` property to Hide or show application drop down. + +![Start process with selected application](../../docassets/images/start-process-with-selected-application.png) + + ## See also - [Select Apps Dialog component](select-apps-dialog.component.md) diff --git a/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.scss b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.scss index 1db78e8ba1..e32cfbda50 100755 --- a/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.scss +++ b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.scss @@ -1,19 +1,11 @@ @mixin adf-cloud-start-service-theme($theme) { .adf { &-start-process { - .mat-select-trigger { - font-size: 14px !important; - } mat-form-field { width: 100%; } - mat-select { - width: 100%; - padding: 16px 0 0; - } - .mat-form-field-label { color: mat-color($mat-grey, A400); } diff --git a/lib/process-services/src/lib/form/start-form.component.spec.ts b/lib/process-services/src/lib/form/start-form.component.spec.ts index a168e9686d..5f6788a912 100644 --- a/lib/process-services/src/lib/form/start-form.component.spec.ts +++ b/lib/process-services/src/lib/form/start-form.component.spec.ts @@ -88,7 +88,7 @@ describe('StartFormComponent', () => { }); it('should not load start form when changes notified but no change to processDefinitionId', () => { - component.processDefinitionId = exampleId1; + component.processDefinitionId = undefined; component.ngOnChanges({ otherProp: new SimpleChange(exampleId1, exampleId2, true) }); expect(formService.getStartFormDefinition).not.toHaveBeenCalled(); }); diff --git a/lib/process-services/src/lib/form/start-form.component.ts b/lib/process-services/src/lib/form/start-form.component.ts index 7d98adf8b9..aeaed99beb 100644 --- a/lib/process-services/src/lib/form/start-form.component.ts +++ b/lib/process-services/src/lib/form/start-form.component.ts @@ -82,8 +82,12 @@ export class StartFormComponent extends FormComponent implements OnChanges, OnIn ngOnChanges(changes: SimpleChanges) { const processDefinitionId = changes['processDefinitionId']; if (processDefinitionId && processDefinitionId.currentValue) { + this.processDefinitionId = processDefinitionId.currentValue; + } + + if (this.processDefinitionId) { this.visibilityService.cleanProcessVariable(); - this.getStartFormDefinition(processDefinitionId.currentValue); + this.getStartFormDefinition(this.processDefinitionId); return; } diff --git a/lib/process-services/src/lib/i18n/en.json b/lib/process-services/src/lib/i18n/en.json index bb7c02bb46..069d57332f 100644 --- a/lib/process-services/src/lib/i18n/en.json +++ b/lib/process-services/src/lib/i18n/en.json @@ -276,6 +276,7 @@ "FORM": { "TITLE": "Start Process", "LABEL": { + "SELECT_APPLICATION": "Select Application", "TYPE": "Select Process", "NAME": "Process Name" }, diff --git a/lib/process-services/src/lib/process-list/components/start-process.component.html b/lib/process-services/src/lib/process-list/components/start-process.component.html index 0e0344751e..fc7cc0c1e4 100644 --- a/lib/process-services/src/lib/process-list/components/start-process.component.html +++ b/lib/process-services/src/lib/process-list/components/start-process.component.html @@ -4,35 +4,50 @@
{{errorMessageId|translate}}
- - {{'ADF_PROCESS_LIST.START_PROCESS.FORM.LABEL.TYPE' | translate}} - -
- - - {{ processDef.name }} +
+ + + + {{ application.name }} - - -
- - + + + + {{'ADF_PROCESS_LIST.START_PROCESS.FORM.LABEL.TYPE' | translate}} + +
+ + + {{ processDef.name }} + + + +
+
+
{{'ADF_PROCESS_LIST.START_PROCESS.FORM.LABEL.NAME' | translate}} { @@ -43,10 +44,12 @@ describe('StartFormComponent', () => { let fixture: ComponentFixture; let processService: ProcessService; let formService: FormService; + let appsProcessService: AppsProcessService; let getDefinitionsSpy: jasmine.Spy; let getStartFormDefinitionSpy: jasmine.Spy; let startProcessSpy: jasmine.Spy; let applyAlfrescoNodeSpy: jasmine.Spy; + let getDeployedApplicationsSpy: jasmine.Spy; setupTestBed({ imports: [ @@ -74,6 +77,7 @@ describe('StartFormComponent', () => { component = fixture.componentInstance; processService = TestBed.get(ProcessService); formService = TestBed.get(FormService); + appsProcessService = TestBed.get(AppsProcessService); getDefinitionsSpy = spyOn(processService, 'getProcessDefinitions').and.returnValue(of(testMultipleProcessDefs)); startProcessSpy = spyOn(processService, 'startProcess').and.returnValue(of(newProcess)); @@ -100,7 +104,7 @@ describe('StartFormComponent', () => { }); it('should enable start button when name and process filled out', async(() => { - spyOn(component, 'loadStartProcess').and.callThrough(); + spyOn(component, 'loadProcessDefinitions').and.callThrough(); component.processNameInput.setValue('My Process'); component.processDefinitionInput.setValue(testProcessDef.name); @@ -113,7 +117,7 @@ describe('StartFormComponent', () => { })); it('should have start button disabled when name not filled out', async(() => { - spyOn(component, 'loadStartProcess').and.callThrough(); + spyOn(component, 'loadProcessDefinitions').and.callThrough(); component.processNameInput.setValue(''); component.processDefinitionInput.setValue(testProcessDef.name); fixture.detectChanges(); @@ -305,7 +309,8 @@ describe('StartFormComponent', () => { it('should indicate an error to the user if process defs cannot be loaded', async(() => { getDefinitionsSpy = getDefinitionsSpy.and.returnValue(throwError({})); component.appId = 123; - component.ngOnChanges({}); + const change = new SimpleChange(null, 123, true); + component.ngOnChanges({ 'appId': change }); fixture.detectChanges(); fixture.whenStable().then(() => { @@ -318,7 +323,8 @@ describe('StartFormComponent', () => { it('should show no process available message when no process definition is loaded', async(() => { getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of([])); component.appId = 123; - component.ngOnChanges({}); + const change = new SimpleChange(null, 123, true); + component.ngOnChanges({ 'appId': change }); fixture.detectChanges(); fixture.whenStable().then(() => { @@ -342,7 +348,8 @@ describe('StartFormComponent', () => { it('should select automatically the processDefinition if the app contain only one', async(() => { getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of(testProcessDefinitions)); component.appId = 123; - component.ngOnChanges({}); + const change = new SimpleChange(null, 123, true); + component.ngOnChanges({ 'appId': change }); fixture.detectChanges(); fixture.whenStable().then(() => { expect(component.selectedProcessDef.name).toBe(JSON.parse(JSON.stringify(testProcessDefinitions[0])).name); @@ -352,7 +359,8 @@ describe('StartFormComponent', () => { it('should not select automatically any processDefinition if the app contain multiple process and does not have any processDefinition as input', async(() => { getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of(testMultipleProcessDefs)); component.appId = 123; - component.ngOnChanges({}); + const change = new SimpleChange(null, 123, true); + component.ngOnChanges({ 'appId': change }); fixture.detectChanges(); fixture.whenStable().then(() => { expect(component.selectedProcessDef.name).toBeNull(); @@ -585,4 +593,98 @@ describe('StartFormComponent', () => { }); })); }); + + describe('Select applications', () => { + + const mockAppId = 3; + + beforeEach(() => { + fixture.detectChanges(); + component.name = 'My new process'; + component.showSelectApplicationDropdown = true; + getDefinitionsSpy.and.returnValue(of(testMultipleProcessDefs)); + getDeployedApplicationsSpy = spyOn(appsProcessService, 'getDeployedApplications').and.returnValue(of(deployedApps)); + }); + + it('Should be able to show application drop-down and respective process definitions if showSelectApplicationDropdown set to true', () => { + const change = new SimpleChange(null, 3, true); + component.ngOnChanges({ 'appId': change }); + fixture.detectChanges(); + const appsSelector = fixture.nativeElement.querySelector('[data-automation-id="adf-start-process-apps-drop-down"]'); + const lableElement = fixture.nativeElement.querySelector('.adf-start-process-app-list .mat-form-field-label'); + + expect(appsSelector).not.toBeNull(); + expect(lableElement.innerText).toEqual('ADF_PROCESS_LIST.START_PROCESS.FORM.LABEL.SELECT_APPLICATION'); + + expect(getDeployedApplicationsSpy).toHaveBeenCalled(); + expect(component.applications.length).toBe(6); + + expect(component.selectedApplication).toEqual(deployedApps[2]); + expect(component.selectedApplication.id).toEqual(component.appId); + expect(component.selectedApplication.name).toEqual('App3'); + + expect(getDefinitionsSpy).toHaveBeenCalledWith(mockAppId); + expect(component.processDefinitions.length).toEqual(2); + expect(component.processDefinitions[0].name).toEqual('My Process 1'); + expect(component.processDefinitions[1].name).toEqual('My Process 2'); + }); + + it('Should be able to list process-definition based on selected application', () => { + const change = new SimpleChange(null, 3, true); + component.ngOnChanges({ 'appId': change }); + fixture.detectChanges(); + expect(component.appId).toBe(component.selectedApplication.id); + expect(component.selectedApplication).toEqual(deployedApps[2]); + expect(component.selectedApplication.name).toEqual('App3'); + + expect(getDefinitionsSpy).toHaveBeenCalledWith(mockAppId); + expect(component.processDefinitions.length).toEqual(2); + expect(component.processDefinitions[0].name).toEqual('My Process 1'); + expect(component.processDefinitions[1].name).toEqual('My Process 2'); + + const changedAppId = 2; + getDefinitionsSpy.and.returnValue(of([ { id: 'my:process 3', name: 'My Process 3', hasStartForm: true } ])); + fixture.detectChanges(); + + const newApplication = {value: deployedApps[1]}; + component.onAppSelectionChange(newApplication); + fixture.detectChanges(); + + expect(component.selectedApplication).toEqual(deployedApps[1]); + expect(component.selectedApplication.name).toEqual('App2'); + + expect(getDefinitionsSpy).toHaveBeenCalledWith(changedAppId); + expect(component.processDefinitions.length).toEqual(1); + expect(component.processDefinitions[0].name).toEqual('My Process 3'); + }); + + it('Should be able to select an application if the list has one application', () => { + getDeployedApplicationsSpy.and.returnValues(of([deployedApps[0]])); + const change = new SimpleChange(null, 123, true); + component.ngOnChanges({ 'appId': change }); + fixture.detectChanges(); + expect(getDeployedApplicationsSpy).toHaveBeenCalled(); + expect(component.applications.length).toEqual(1); + expect(component.selectedApplication.name).toEqual('App1'); + }); + + it('Should be able to select an application from the apps as default application when given appId is defined', () => { + component.appId = 2; + const change = new SimpleChange(null, 2, true); + component.ngOnChanges({ 'appId': change }); + fixture.detectChanges(); + expect(getDeployedApplicationsSpy).toHaveBeenCalled(); + expect(component.applications.length).toEqual(6); + expect(component.selectedApplication.id).toEqual(component.appId); + expect(component.selectedApplication.id).toEqual(2); + expect(component.selectedApplication.name).toEqual('App2'); + }); + + it('Should not be able to show application drop-down if showSelectApplicationDropdown set to false', () => { + component.showSelectApplicationDropdown = false; + fixture.detectChanges(); + const appsSelector = fixture.nativeElement.querySelector('[data-automation-id="adf-start-process-apps-drop-down"]'); + expect(appsSelector).toBeNull(); + }); + }); }); diff --git a/lib/process-services/src/lib/process-list/components/start-process.component.ts b/lib/process-services/src/lib/process-list/components/start-process.component.ts index 642541edfc..6af624e874 100644 --- a/lib/process-services/src/lib/process-list/components/start-process.component.ts +++ b/lib/process-services/src/lib/process-list/components/start-process.component.ts @@ -23,6 +23,7 @@ import { ActivitiContentService, AppConfigService, AppConfigValues, + AppsProcessService, FormValues } from '@alfresco/adf-core'; import { ProcessInstanceVariable } from '../models/process-instance-variable.model'; @@ -35,6 +36,7 @@ import { map, takeUntil } from 'rxjs/operators'; import { MatAutocompleteTrigger } from '@angular/material'; import { StartFormComponent } from '../../form'; import { MinimalNode, RelatedContentRepresentation } from '@alfresco/js-api'; +import { AppDefinitionRepresentationModel } from '../../task-list'; @Component({ selector: 'adf-start-process', @@ -77,6 +79,10 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr @Input() showSelectProcessDropdown: boolean = true; + /** (optional) Hide or show application selection dropdown. */ + @Input() + showSelectApplicationDropdown: boolean = false; + /** (optional) Parameter to enable selection of process when filtering. */ @Input() processFilterSelector: boolean = true; @@ -97,6 +103,10 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr @Output() processDefinitionSelection: EventEmitter = new EventEmitter(); + /** Emitted when application selection changes. */ + @Output() + applicationSelection: EventEmitter = new EventEmitter(); + @ViewChild('startForm') startForm: StartFormComponent; @@ -104,18 +114,20 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr inputAutocomplete: MatAutocompleteTrigger; processDefinitions: ProcessDefinitionRepresentation[] = []; - selectedProcessDef: ProcessDefinitionRepresentation = new ProcessDefinitionRepresentation(); + selectedProcessDef: ProcessDefinitionRepresentation; errorMessageId: string = ''; processNameInput: FormControl; processDefinitionInput: FormControl; - filteredProcesses: Observable; + filteredProcessesDefinitions$: Observable; maxProcessNameLength: number = this.MAX_LENGTH; alfrescoRepositoryName: string; + applications: AppDefinitionRepresentationModel[] = []; + selectedApplication: AppDefinitionRepresentationModel; private onDestroy$ = new Subject(); - constructor(private activitiProcess: ProcessService, private activitiContentService: ActivitiContentService, + private appsProcessService: AppsProcessService, private appConfig: AppConfigService) { } @@ -123,13 +135,13 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr this.processNameInput = new FormControl(this.name, [Validators.required, Validators.maxLength(this.maxProcessNameLength), Validators.pattern('^[^\\s]+(\\s+[^\\s]+)*$')]); this.processDefinitionInput = new FormControl(); - this.loadStartProcess(); + this.load(); this.processNameInput.valueChanges .pipe(takeUntil(this.onDestroy$)) .subscribe(name => this.name = name); - this.filteredProcesses = this.processDefinitionInput.valueChanges + this.filteredProcessesDefinitions$ = this.processDefinitionInput.valueChanges .pipe( map((value) => this._filter(value)), takeUntil(this.onDestroy$) @@ -153,11 +165,24 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr this.moveNodeFromCStoPS(); } - if (changes['appId'] && changes['appId'].currentValue) { + if (this.isAppIdChanged(changes)) { this.appId = changes['appId'].currentValue; + this.load(); } - this.loadStartProcess(); + if (this.isProcessDefinitionChanged(changes)) { + this.processDefinitionName = changes['processDefinitionName'].currentValue; + this.filterProcessDefinitionByName(); + } + } + + private isAppIdChanged(changes: SimpleChanges) { + return changes['appId'] && changes['appId'].currentValue && changes['appId'].currentValue !== changes['appId'].previousValue; + } + + private isProcessDefinitionChanged(changes: SimpleChanges) { + return changes['processDefinitionName'] && changes['processDefinitionName'].currentValue && + changes['processDefinitionName'].currentValue !== changes['processDefinitionName'].previousValue; } private _filter(value: string): ProcessDefinitionRepresentation[] { @@ -183,37 +208,106 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr return processSelected; } - loadStartProcess(): void { + load() { + if (this.showSelectApplicationDropdown) { + this.loadApps(); + } else { + this.loadProcessDefinitions(this.appId); + } + } + + loadProcessDefinitions(appId: any): void { this.resetSelectedProcessDefinition(); this.resetErrorMessage(); - this.activitiProcess.getProcessDefinitions(this.appId).subscribe( - (processDefinitionRepresentations: ProcessDefinitionRepresentation[]) => { - this.processDefinitions = processDefinitionRepresentations; + this.activitiProcess.getProcessDefinitions(appId).pipe( + map((processDefinitionRepresentations: ProcessDefinitionRepresentation[]) => { + let currentProcessDef: ProcessDefinitionRepresentation; - if (!this.isProcessDefinitionsEmpty()) { - - if (this.processDefinitions.length === 1) { - this.selectedProcessDef = this.processDefinitions[0]; - } - - if (this.processDefinitionName) { - const selectedProcess = this.processDefinitions.find((currentProcessDefinition) => { - return currentProcessDefinition.name === this.processDefinitionName; - }); - if (selectedProcess) { - this.selectedProcessDef = selectedProcess; - } - } - - this.processDefinitionInput.setValue(this.selectedProcessDef.name); + if (processDefinitionRepresentations.length === 1) { + currentProcessDef = processDefinitionRepresentations[0]; } + + if (this.processDefinitionName) { + const filteredProcessDefinition = processDefinitionRepresentations.find((processDefinition) => { + return processDefinition.name === this.processDefinitionName; + }); + if (filteredProcessDefinition) { + currentProcessDef = filteredProcessDefinition; + } + } + + return { currentProcessDef, processDefinitionRepresentations }; + }) + ).subscribe( + (filteredProcessDefinitions) => { + this.processDefinitions = filteredProcessDefinitions.processDefinitionRepresentations; + this.selectedProcessDef = filteredProcessDefinitions.currentProcessDef; + this.processDefinitionInput.setValue(this.selectedProcessDef ? this.selectedProcessDef.name : ''); + this.processDefinitionSelection.emit(this.selectedProcessDef); }, () => { this.errorMessageId = 'ADF_PROCESS_LIST.START_PROCESS.ERROR.LOAD_PROCESS_DEFS'; }); } + filterProcessDefinitionByName() { + if (this.processDefinitionName) { + const filteredProcessDef = this.processDefinitions.find((processDefinition) => { + return processDefinition.name === this.processDefinitionName; + }); + + if (filteredProcessDef) { + this.selectedProcessDef = filteredProcessDef; + this.processDefinitionInput.setValue(this.selectedProcessDef ? this.selectedProcessDef.name : ''); + } + } + } + + loadApps() { + this.appsProcessService + .getDeployedApplications() + .pipe(map((response: AppDefinitionRepresentationModel[]) => { + const applications = this.removeDefaultApps(response); + let currentApplication: AppDefinitionRepresentationModel; + + if (applications && applications.length === 1) { + currentApplication = applications[0]; + } + + const filteredApp = applications.find( app => app.id === +this.appId ); + + if (filteredApp) { + currentApplication = filteredApp; + } + + return { currentApplication, applications }; + }) + ) + .subscribe((filteredApps) => { + this.applications = filteredApps.applications; + this.selectedApplication = filteredApps.currentApplication; + this.applicationSelection.emit(this.selectedApplication); + this.loadProcessDefinitions(this.selectedApplication ? this.selectedApplication.id : null); + }, + err => { + this.error.emit(err); + } + ); + + } + + onAppSelectionChange(selectedApplication: any) { + this.selectedApplication = selectedApplication.value; + this.applicationSelection.emit(this.selectedApplication); + this.loadProcessDefinitions(this.selectedApplication.id); + } + + private removeDefaultApps(apps: AppDefinitionRepresentationModel []): AppDefinitionRepresentationModel[] { + return apps.filter((app) => app.id); + + } + isProcessDefinitionsEmpty(): boolean { return this.processDefinitions.length === 0; } @@ -332,6 +426,7 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr } processDefinitionSelectionChanged(processDefinition) { - this.processDefinitionSelection.emit(processDefinition); + this.selectedProcessDef = processDefinition; + this.processDefinitionSelection.emit(this.selectedProcessDef); } }