AAE-32255 Start Process button in start event is showing and failing for form with custom outcomes (#10697)

* [AAE-32255] check if form has visible outcomes

* [AAE-32255] readded ?

* [AAE-32255] use pipe and observable

* [AAE-32255] check if exists
This commit is contained in:
Michaela
2025-03-11 17:21:55 +01:00
committed by GitHub
parent 08fd1f3d12
commit a74388513e
5 changed files with 69 additions and 19 deletions

View File

@@ -103,6 +103,10 @@ export abstract class FormBaseComponent {
*/ */
formStyle: string = ''; formStyle: string = '';
get hasVisibleOutcomes(): boolean {
return this.form?.outcomes?.some((outcome) => this.isOutcomeButtonVisible(outcome, this.form.readOnly));
}
get form(): FormModel { get form(): FormModel {
return this._form; return this._form;
} }

View File

@@ -73,6 +73,7 @@
<ng-container *ngIf="hasForm else taskFormCloudButtons"> <ng-container *ngIf="hasForm else taskFormCloudButtons">
<adf-cloud-form <adf-cloud-form
#startForm
[appName]="appName" [appName]="appName"
[appVersion]="processDefinitionCurrent.appVersion" [appVersion]="processDefinitionCurrent.appVersion"
[data]="resolvedValues" [data]="resolvedValues"
@@ -108,7 +109,7 @@
{{ cancelButtonLabel }} {{ cancelButtonLabel }}
</button> </button>
<button <button
*ngIf="showStartProcessButton" *ngIf="showStartProcessButton$ | async"
color="primary" color="primary"
mat-raised-button mat-raised-button
[disabled]="disableStartButton || !isProcessFormValid" [disabled]="disableStartButton || !isProcessFormValid"

View File

@@ -31,7 +31,8 @@ import {
fakeNoNameProcessDefinitions, fakeNoNameProcessDefinitions,
fakeSingleProcessDefinition, fakeSingleProcessDefinition,
fakeSingleProcessDefinitionWithoutForm, fakeSingleProcessDefinitionWithoutForm,
fakeFormModelJson fakeFormModelJson,
fakeStartFormWithOutcomes
} from '../mock/start-process.component.mock'; } from '../mock/start-process.component.mock';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { ProcessPayloadCloud } from '../models/process-payload-cloud.model'; import { ProcessPayloadCloud } from '../models/process-payload-cloud.model';
@@ -69,6 +70,8 @@ describe('StartProcessCloudComponent', () => {
const panel = await loader.getHarness(MatAutocompleteHarness); const panel = await loader.getHarness(MatAutocompleteHarness);
await panel.selectOption({ text: name }); await panel.selectOption({ text: name });
fixture.detectChanges();
await fixture.whenStable();
}; };
const typeValueInto = (selector: any, value: string) => { const typeValueInto = (selector: any, value: string) => {
@@ -185,8 +188,6 @@ describe('StartProcessCloudComponent', () => {
component.name = 'My new process'; component.name = 'My new process';
component.processDefinitionName = 'process'; component.processDefinitionName = 'process';
await selectOptionByName('processwithoutform2'); await selectOptionByName('processwithoutform2');
fixture.detectChanges();
await fixture.whenStable();
expect(component.processDefinitionCurrent.name).toBe(JSON.parse(JSON.stringify(fakeProcessDefinitions[1])).name); expect(component.processDefinitionCurrent.name).toBe(JSON.parse(JSON.stringify(fakeProcessDefinitions[1])).name);
const startBtn = fixture.nativeElement.querySelector('#button-start'); const startBtn = fixture.nativeElement.querySelector('#button-start');
@@ -537,8 +538,6 @@ describe('StartProcessCloudComponent', () => {
await fixture.whenStable(); await fixture.whenStable();
await selectOptionByName('processwithform'); await selectOptionByName('processwithform');
fixture.detectChanges();
await fixture.whenStable();
component.processDefinitionName = fakeProcessDefinitions[2].name; component.processDefinitionName = fakeProcessDefinitions[2].name;
component.setProcessDefinitionOnForm(fakeProcessDefinitions[2].name); component.setProcessDefinitionOnForm(fakeProcessDefinitions[2].name);
@@ -570,9 +569,6 @@ describe('StartProcessCloudComponent', () => {
component.ngOnChanges({}); component.ngOnChanges({});
await selectOptionByName('process'); await selectOptionByName('process');
fixture.detectChanges();
await fixture.whenStable();
expect(component.processDefinitionCurrent.name).toBe(JSON.parse(JSON.stringify(fakeProcessDefinitions[3])).name); expect(component.processDefinitionCurrent.name).toBe(JSON.parse(JSON.stringify(fakeProcessDefinitions[3])).name);
expect(component.processDefinitionCurrent.key).toBe(JSON.parse(JSON.stringify(fakeProcessDefinitions[3])).key); expect(component.processDefinitionCurrent.key).toBe(JSON.parse(JSON.stringify(fakeProcessDefinitions[3])).key);
}); });
@@ -701,16 +697,38 @@ describe('StartProcessCloudComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
}); });
it('should see start button', async () => { it('should show start button', () => {
component.ngOnChanges({ appName: firstChange }); component.ngOnChanges({ appName: firstChange });
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable();
const startButton = fixture.debugElement.query(By.css('#button-start')); const startButton = fixture.debugElement.query(By.css('#button-start'));
expect(startButton).toBeDefined(); expect(startButton).toBeDefined();
expect(startButton).not.toBeNull(); expect(startButton).not.toBeNull();
}); });
it('should show start button when start process has form without outcomes', async () => {
formDefinitionSpy.and.returnValue(of(fakeStartForm));
component.ngOnChanges({ appName: firstChange });
fixture.detectChanges();
await selectOptionByName('processwithform');
const startButton = fixture.debugElement.query(By.css('#button-start'));
expect(startButton).toBeDefined();
expect(startButton).not.toBeNull();
});
it('should NOT see start button when start process has form with outcomes', async () => {
formDefinitionSpy.and.returnValue(of(fakeStartFormWithOutcomes));
component.ngOnChanges({ appName: firstChange });
fixture.detectChanges();
await selectOptionByName('processwithform');
const startButton = fixture.debugElement.query(By.css('#button-start'));
expect(startButton).toBeNull();
});
it('should call service with the correct parameters when button is clicked and variables are defined and formCloud is undefined', async () => { it('should call service with the correct parameters when button is clicked and variables are defined and formCloud is undefined', async () => {
component.ngOnChanges({ appName: firstChange }); component.ngOnChanges({ appName: firstChange });
component.processForm.controls['processInstanceName'].setValue('My Process 1'); component.processForm.controls['processInstanceName'].setValue('My Process 1');
@@ -820,8 +838,6 @@ describe('StartProcessCloudComponent', () => {
await fixture.whenStable(); await fixture.whenStable();
await selectOptionByName('processwithform'); await selectOptionByName('processwithform');
fixture.detectChanges();
await fixture.whenStable();
component.processDefinitionName = fakeProcessDefinitions[2].name; component.processDefinitionName = fakeProcessDefinitions[2].name;
component.setProcessDefinitionOnForm(fakeProcessDefinitions[2].name); component.setProcessDefinitionOnForm(fakeProcessDefinitions[2].name);
@@ -941,8 +957,6 @@ describe('StartProcessCloudComponent', () => {
await fixture.whenStable(); await fixture.whenStable();
component.processDefinitionName = 'processwithoutform1'; component.processDefinitionName = 'processwithoutform1';
await selectOptionByName(fakeProcessDefinitions[0].name); await selectOptionByName(fakeProcessDefinitions[0].name);
fixture.detectChanges();
await fixture.whenStable();
expect(emitSpy).toHaveBeenCalledOnceWith(fakeProcessDefinitions[0]); expect(emitSpy).toHaveBeenCalledOnceWith(fakeProcessDefinitions[0]);
}); });

View File

@@ -32,12 +32,12 @@ import {
import { ContentLinkModel, FormModel, InplaceFormInputComponent, LocalizedDatePipe, TranslationService } from '@alfresco/adf-core'; import { ContentLinkModel, FormModel, InplaceFormInputComponent, LocalizedDatePipe, TranslationService } from '@alfresco/adf-core';
import { AbstractControl, FormControl, FormGroup, ReactiveFormsModule, ValidatorFn, Validators } from '@angular/forms'; import { AbstractControl, FormControl, FormGroup, ReactiveFormsModule, ValidatorFn, Validators } from '@angular/forms';
import { MatAutocompleteModule, MatAutocompleteTrigger } from '@angular/material/autocomplete'; import { MatAutocompleteModule, MatAutocompleteTrigger } from '@angular/material/autocomplete';
import { catchError, debounceTime } from 'rxjs/operators'; import { catchError, debounceTime, map } from 'rxjs/operators';
import { ProcessInstanceCloud } from '../models/process-instance-cloud.model'; import { ProcessInstanceCloud } from '../models/process-instance-cloud.model';
import { ProcessPayloadCloud } from '../models/process-payload-cloud.model'; import { ProcessPayloadCloud } from '../models/process-payload-cloud.model';
import { ProcessWithFormPayloadCloud } from '../models/process-with-form-payload-cloud.model'; import { ProcessWithFormPayloadCloud } from '../models/process-with-form-payload-cloud.model';
import { StartProcessCloudService } from '../services/start-process-cloud.service'; import { StartProcessCloudService } from '../services/start-process-cloud.service';
import { forkJoin, of } from 'rxjs'; import { BehaviorSubject, forkJoin, Observable, of, combineLatest } from 'rxjs';
import { ProcessDefinitionCloud } from '../../../models/process-definition-cloud.model'; import { ProcessDefinitionCloud } from '../../../models/process-definition-cloud.model';
import { TaskVariableCloud } from '../../../form/models/task-variable-cloud.model'; import { TaskVariableCloud } from '../../../form/models/task-variable-cloud.model';
import { FormCloudDisplayModeConfiguration } from '../../../services/form-fields.interfaces'; import { FormCloudDisplayModeConfiguration } from '../../../services/form-fields.interfaces';
@@ -86,6 +86,8 @@ export class StartProcessCloudComponent implements OnChanges, OnInit {
@ViewChild(MatAutocompleteTrigger) @ViewChild(MatAutocompleteTrigger)
inputAutocomplete: MatAutocompleteTrigger; inputAutocomplete: MatAutocompleteTrigger;
@ViewChild('startForm') startForm: FormCloudComponent;
/** (required) Name of the app. */ /** (required) Name of the app. */
@Input() @Input()
appName: string = ''; appName: string = '';
@@ -163,7 +165,7 @@ export class StartProcessCloudComponent implements OnChanges, OnInit {
isFormCloudLoading = false; isFormCloudLoading = false;
processDefinitionLoaded = false; processDefinitionLoaded = false;
showStartProcessButton = true; showStartProcessButton$: Observable<boolean>;
startProcessButtonLabel: string; startProcessButtonLabel: string;
cancelButtonLabel: string; cancelButtonLabel: string;
@@ -180,6 +182,8 @@ export class StartProcessCloudComponent implements OnChanges, OnInit {
private readonly destroyRef = inject(DestroyRef); private readonly destroyRef = inject(DestroyRef);
private readonly startProcessCloudService = inject(StartProcessCloudService); private readonly startProcessCloudService = inject(StartProcessCloudService);
private readonly localizedDatePipe = inject(LocalizedDatePipe); private readonly localizedDatePipe = inject(LocalizedDatePipe);
private readonly displayStartSubject = new BehaviorSubject<string>(null);
private readonly hasVisibleOutcomesSubject = new BehaviorSubject<boolean>(false);
get isProcessFormValid(): boolean { get isProcessFormValid(): boolean {
if (this.hasForm && this.isFormCloudLoaded) { if (this.hasForm && this.isFormCloudLoaded) {
@@ -230,6 +234,9 @@ export class StartProcessCloudComponent implements OnChanges, OnInit {
.subscribe((processDefinitionName) => { .subscribe((processDefinitionName) => {
this.selectProcessDefinitionByProcessDefinitionName(processDefinitionName); this.selectProcessDefinitionByProcessDefinitionName(processDefinitionName);
}); });
this.showStartProcessButton$ = combineLatest([this.displayStartSubject, this.hasVisibleOutcomesSubject]).pipe(
map(([displayStart, hasVisibleOutcomes]) => (displayStart !== null ? displayStart === 'true' : !hasVisibleOutcomes))
);
} }
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
@@ -254,6 +261,10 @@ export class StartProcessCloudComponent implements OnChanges, OnInit {
onFormLoaded(form: FormModel) { onFormLoaded(form: FormModel) {
this.isFormCloudLoaded = true; this.isFormCloudLoaded = true;
this.formCloud = form; this.formCloud = form;
if (this.startForm) {
this.hasVisibleOutcomesSubject.next(this.startForm.hasVisibleOutcomes);
}
} }
private getMaxNameLength(): number { private getMaxNameLength(): number {
@@ -294,7 +305,7 @@ export class StartProcessCloudComponent implements OnChanges, OnInit {
const cancelLabel = constants?.find((constant) => constant.name === 'cancelLabel'); const cancelLabel = constants?.find((constant) => constant.name === 'cancelLabel');
if (displayStart) { if (displayStart) {
this.showStartProcessButton = displayStart?.value === 'true'; this.displayStartSubject.next(displayStart?.value);
} }
if (startLabel) { if (startLabel) {
this.startProcessButtonLabel = startLabel?.value?.trim()?.length > 0 ? startLabel.value.trim() : this.defaultStartProcessButtonLabel; this.startProcessButtonLabel = startLabel?.value?.trim()?.length > 0 ? startLabel.value.trim() : this.defaultStartProcessButtonLabel;

View File

@@ -199,6 +199,26 @@ export const fakeStartForm = {
} }
}; };
export const fakeStartFormWithOutcomes = {
formRepresentation: {
...fakeStartForm.formRepresentation,
formDefinition: {
...fakeStartForm.formRepresentation.formDefinition,
outcomes: [
{
id: 'c5676ca7-8ad4-421c-9538-aaf8560bd5fc',
name: 'Option 1',
visibilityCondition: null
},
{
id: '48e9c1f8-50b9-4d2f-998c-7836c132986f',
name: 'Option 2',
visibilityCondition: null
}
]
}
}
};
export const fakeStartFormNotValid = { export const fakeStartFormNotValid = {
formRepresentation: { formRepresentation: {
id: 'form-a5d50817-5183-4850-802d-17af54b2632f', id: 'form-a5d50817-5183-4850-802d-17af54b2632f',