[AAE-40028] Added group/section validation skipping to cloud forms for workspace (#11466)

This commit is contained in:
Alex Molodyh
2025-12-16 20:02:07 -08:00
committed by GitHub
parent 8c1118984d
commit fe39b3c1bd
15 changed files with 287 additions and 2 deletions

View File

@@ -96,6 +96,7 @@ The template defined inside `empty-form` will be shown when no form definition i
| showValidationIcon | `boolean` | true | Toggle rendering of the validation icon next to the form title. |
| taskId | `string` | | Task id to fetch corresponding form and values. |
| displayModeConfigurations | [`FormCloudDisplayModeConfiguration`](../../../lib/process-services-cloud/src/lib/services/form-fields.interfaces.ts)`[]` | | The available display configurations for the form |
| enableParentVisibilityCheck | `boolean` | false | Toggle to enable parent visibility check for validation. When enabled, fields inside hidden groups/sections will skip validation. |
### Events

View File

@@ -42,6 +42,9 @@ Starts a process.
| processDefinitionName | `string` | | Name of the process definition. |
| showSelectProcessDropdown | `boolean` | true | Show/hide the process dropdown list. |
| showTitle | `boolean` | true | Show/hide title. |
| showCancelButton | `boolean` | true | Show/hide cancel button. |
| displayModeConfigurations | [`FormCloudDisplayModeConfiguration`](../../../lib/process-services-cloud/src/lib/services/form-fields.interfaces.ts)`[]` | | The available display configurations for the form (start process event can have assigned form). |
| enableParentVisibilityCheck | `boolean` | false | Toggle to enable parent visibility check for validation. When enabled, fields inside hidden groups/sections will skip validation. |
| values | [`TaskVariableCloud`](../../../lib/process-services-cloud/src/lib/form/models/task-variable-cloud.model.ts)`[]` | | Parameter to pass form field values in the start form if one is associated. |
| variables | `any` | | Variables to attach to the payload. |

View File

@@ -44,6 +44,7 @@ Save and Complete buttons get disabled when at least one of the form's inputs ar
| showValidationIcon | `boolean` | true | Toggle rendering of the `Validation` icon. |
| taskId | `string` | | Task id to fetch corresponding form and values. |
| displayModeConfigurations | `FormCloudDisplayModeConfiguration[]` | | The available display configurations for the form |
| enableParentVisibilityCheck | `boolean` | false | Toggle to enable parent visibility check for validation. When enabled, fields inside hidden groups/sections will skip validation. |
### Events

View File

@@ -45,6 +45,7 @@ Based on property taskDetails: TaskDetailsCloudModel shows a form or a screen.
| showValidationIcon | `boolean` | true | Toggle rendering of the `Validation` icon. |
| taskId | `string` | | Task id to fetch corresponding form and values. |
| displayModeConfigurations | `FormCloudDisplayModeConfiguration[]` | | The available display configurations for the form |
| enableParentVisibilityCheck | `boolean` | false | Toggle to enable parent visibility check for validation. When enabled, fields inside hidden groups/sections will skip validation. |
### Events

View File

@@ -461,6 +461,138 @@ describe('FormCloudComponent', () => {
expect(formComponent.getFormById).not.toHaveBeenCalled();
});
describe('enableParentVisibilityCheck', () => {
let formModel: FormModel;
let field1: FormFieldModel;
let field2: FormFieldModel;
beforeEach(() => {
const formJson = {
id: 'test-form',
name: 'Test Form',
fields: [
{
id: 'group1',
type: FormFieldTypes.GROUP,
numberOfColumns: 1,
fields: {
1: [
{ id: 'field1', type: FormFieldTypes.TEXT, required: true },
{ id: 'field2', type: FormFieldTypes.TEXT, required: true }
]
}
}
]
};
formModel = new FormModel(formJson);
formComponent.form = formModel;
field1 = formModel.getFieldById('field1');
field2 = formModel.getFieldById('field2');
});
it('should set flags on form and fields when enableParentVisibilityCheck changes from false to true', () => {
formComponent.enableParentVisibilityCheck = false;
expect(formModel.enableParentVisibilityCheck).toBe(false);
expect(field1.checkParentVisibilityForValidation).toBe(false);
expect(field2.checkParentVisibilityForValidation).toBe(false);
const change = new SimpleChange(false, true, false);
formComponent.enableParentVisibilityCheck = true;
spyOn(formModel, 'validateForm').and.stub();
formComponent.ngOnChanges({ enableParentVisibilityCheck: change });
expect(formModel.enableParentVisibilityCheck).toBe(true);
expect(field1.checkParentVisibilityForValidation).toBe(true);
expect(field2.checkParentVisibilityForValidation).toBe(true);
expect(formModel.validateForm).toHaveBeenCalled();
});
it('should not set flags when enableParentVisibilityCheck changes but form is null', () => {
formComponent.form = null;
formComponent.enableParentVisibilityCheck = false;
const change = new SimpleChange(false, true, false);
formComponent.ngOnChanges({ enableParentVisibilityCheck: change });
expect(formComponent.form).toBeNull();
});
it('should reset flags when enableParentVisibilityCheck changes from true to false', () => {
formModel.enableParentVisibilityCheck = true;
field1.checkParentVisibilityForValidation = true;
field2.checkParentVisibilityForValidation = true;
formComponent.enableParentVisibilityCheck = false;
const change = new SimpleChange(true, false, false);
spyOn(formModel, 'validateForm').and.stub();
formComponent.ngOnChanges({ enableParentVisibilityCheck: change });
expect(formModel.enableParentVisibilityCheck).toBe(false);
expect(field1.checkParentVisibilityForValidation).toBe(false);
expect(field2.checkParentVisibilityForValidation).toBe(false);
expect(formModel.validateForm).toHaveBeenCalled();
});
it('should set flags on all fields including nested fields', () => {
const formJsonWithNestedFields = {
id: 'test-form',
name: 'Test Form',
fields: [
{
id: 'group1',
type: FormFieldTypes.GROUP,
numberOfColumns: 1,
fields: {
1: [{ id: 'field1', type: FormFieldTypes.TEXT }]
}
},
{
id: 'section1',
type: FormFieldTypes.SECTION,
numberOfColumns: 1,
fields: {
1: [
{
id: 'group2',
type: FormFieldTypes.GROUP,
numberOfColumns: 1,
fields: {
1: [{ id: 'field2', type: FormFieldTypes.TEXT }]
}
}
]
}
},
{ id: 'field3', type: FormFieldTypes.TEXT }
]
};
const nestedFormModel = new FormModel(formJsonWithNestedFields);
formComponent.form = nestedFormModel;
formComponent.enableParentVisibilityCheck = true;
const change = new SimpleChange(false, true, false);
formComponent.ngOnChanges({ enableParentVisibilityCheck: change });
const allFields = nestedFormModel.getFormFields();
allFields.forEach((field) => {
expect(field.checkParentVisibilityForValidation).toBe(true);
});
expect(nestedFormModel.enableParentVisibilityCheck).toBe(true);
});
it('should re-validate form after setting flags', () => {
formComponent.enableParentVisibilityCheck = true;
spyOn(formModel, 'validateForm').and.stub();
const change = new SimpleChange(false, true, false);
formComponent.ngOnChanges({ enableParentVisibilityCheck: change });
expect(formModel.validateForm).toHaveBeenCalled();
});
});
it('should complete form on custom outcome click', () => {
const formModel = new FormModel();
const outcomeName = 'Custom Action';

View File

@@ -135,6 +135,13 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
@Input()
customCompleteButtonText: string = '';
/**
* Toggle to enable parent visibility check for validation.
* When enabled, fields inside hidden groups/sections will skip validation.
*/
@Input()
enableParentVisibilityCheck: boolean = false;
/** Emitted when the form is submitted with the `Save` or custom outcomes. */
@Output()
formSaved = new EventEmitter<FormModel>();
@@ -254,6 +261,12 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
this.onFormLoaded(this.form);
return;
}
const enableParentVisibilityCheckChange = changes['enableParentVisibilityCheck'];
if (enableParentVisibilityCheckChange && this.form) {
this.setCheckParentVisibilityForValidationOnFields();
this.form.validateForm();
}
}
ngOnInit(): void {
@@ -338,8 +351,9 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
const parsedForm = this.parseForm(this.formCloudRepresentationJSON);
this.visibilityService.refreshVisibility(parsedForm, this.data);
parsedForm.validateForm();
this.form = parsedForm;
this.setCheckParentVisibilityForValidationOnFields();
parsedForm.validateForm();
this.form.nodeId = '-my-';
this.onFormLoaded(this.form);
resolve(this.form);
@@ -369,8 +383,9 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
this.formCloudRepresentationJSON.processVariables = this.data || [];
const parsedForm = this.parseForm(form);
this.visibilityService.refreshVisibility(parsedForm);
parsedForm?.validateForm();
this.form = parsedForm;
this.setCheckParentVisibilityForValidationOnFields();
parsedForm?.validateForm();
this.form.nodeId = '-my-';
this.onFormLoaded(this.form);
},
@@ -467,11 +482,29 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
private refreshFormData() {
this.form = this.parseForm(this.formCloudRepresentationJSON);
if (this.form) {
this.setCheckParentVisibilityForValidationOnFields();
this.onFormLoaded(this.form);
this.onFormDataRefreshed(this.form);
}
}
/**
* Sets the parent visibility check flag on all form fields.
* When enabled, fields inside hidden groups/sections will skip validation.
* When disabled, resets flags to false to revert to default behavior.
*/
private setCheckParentVisibilityForValidationOnFields(): void {
if (!this.form) {
return;
}
this.form.enableParentVisibilityCheck = this.enableParentVisibilityCheck;
const allFields = this.form.getFormFields();
allFields.forEach((field) => {
field.checkParentVisibilityForValidation = this.enableParentVisibilityCheck;
});
}
protected onFormLoaded(form: FormModel) {
if (form) {
this.displayModeConfigurations = this.displayModeService.getDisplayModeConfigurations(this.displayModeConfigurations);

View File

@@ -96,6 +96,7 @@
[data]="resolvedValues"
[formId]="processDefinitionCurrent.formKey"
[displayModeConfigurations]="displayModeConfigurations"
[enableParentVisibilityCheck]="enableParentVisibilityCheck"
[showSaveButton]="showSaveButton"
[showCompleteButton]="showCompleteButton"
[showRefreshButton]="false"

View File

@@ -21,6 +21,7 @@ import { FormModel, FormOutcomeEvent, FormOutcomeModel } from '@alfresco/adf-cor
import { of, throwError } from 'rxjs';
import { StartProcessCloudService } from '../services/start-process-cloud.service';
import { FormCloudService } from '../../../form/services/form-cloud.service';
import { FormCloudComponent } from '../../../form/components/form-cloud.component';
import { StartProcessCloudComponent } from './start-process-cloud.component';
import {
fakeProcessDefinitions,
@@ -629,6 +630,51 @@ describe('StartProcessCloudComponent', () => {
expect(processForm).not.toBeNull();
});
it('should pass enableParentVisibilityCheck to adf-cloud-form', async () => {
getProcessDefinitionsSpy.and.returnValue(of([fakeProcessDefinitions[2]]));
formDefinitionSpy.and.returnValue(of(fakeStartForm));
component.enableParentVisibilityCheck = true;
const change = new SimpleChange('myApp', 'myApp1', true);
component.ngOnChanges({ appName: change });
fixture.detectChanges();
await fixture.whenStable();
await selectOptionByName('processwithform');
component.processDefinitionName = fakeProcessDefinitions[2].name;
component.setProcessDefinitionOnForm(fakeProcessDefinitions[2].name);
fixture.detectChanges();
await fixture.whenStable();
const cloudFormElement = fixture.debugElement.query(By.css('adf-cloud-form'));
expect(cloudFormElement).toBeDefined();
const cloudFormComponent = cloudFormElement.componentInstance as FormCloudComponent;
expect(cloudFormComponent).toBeDefined();
expect(cloudFormComponent.enableParentVisibilityCheck).toBe(true);
});
it('should pass enableParentVisibilityCheck as false by default to adf-cloud-form', async () => {
getProcessDefinitionsSpy.and.returnValue(of([fakeProcessDefinitions[2]]));
formDefinitionSpy.and.returnValue(of(fakeStartForm));
const change = new SimpleChange('myApp', 'myApp1', true);
component.ngOnChanges({ appName: change });
fixture.detectChanges();
await fixture.whenStable();
await selectOptionByName('processwithform');
component.processDefinitionName = fakeProcessDefinitions[2].name;
component.setProcessDefinitionOnForm(fakeProcessDefinitions[2].name);
fixture.detectChanges();
await fixture.whenStable();
const cloudFormElement = fixture.debugElement.query(By.css('adf-cloud-form'));
expect(cloudFormElement).toBeDefined();
const cloudFormComponent = cloudFormElement.componentInstance as FormCloudComponent;
expect(cloudFormComponent).toBeDefined();
expect(cloudFormComponent.enableParentVisibilityCheck).toBe(false);
});
it('should not select automatically any processDefinition if the app contain multiple process and does not have any processDefinition as input', async () => {
getProcessDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
component.appName = 'myApp';

View File

@@ -142,6 +142,13 @@ export class StartProcessCloudComponent implements OnChanges, OnInit {
@Input()
displayModeConfigurations: FormCloudDisplayModeConfiguration[];
/**
* Toggle to enable parent visibility check for validation.
* When enabled, fields inside hidden groups/sections will skip validation.
*/
@Input()
enableParentVisibilityCheck: boolean = false;
/** Emitted when the process is successfully started. */
@Output()
success = new EventEmitter<ProcessInstanceCloud>();

View File

@@ -14,6 +14,7 @@
[customSaveButtonText]="customSaveButtonText"
[customCompleteButtonText]="customCompleteButtonText"
[displayModeConfigurations]="displayModeConfigurations"
[enableParentVisibilityCheck]="enableParentVisibilityCheck"
(formLoaded)="onFormLoaded($event)"
(formSaved)="onFormSaved($event)"
(formCompleted)="onFormCompleted($event)"

View File

@@ -276,6 +276,31 @@ describe('TaskFormCloudComponent', () => {
expect(cloudFormComponent).toBeDefined();
expect(cloudFormComponent.customCompleteButtonText).toBe(customText);
});
it('should pass enableParentVisibilityCheck to adf-cloud-form', () => {
component.appName = 'app1';
component.taskId = 'task1';
component.enableParentVisibilityCheck = true;
fixture.detectChanges();
const cloudFormElement = fixture.debugElement.query(By.css('adf-cloud-form'));
expect(cloudFormElement).toBeDefined();
const cloudFormComponent = cloudFormElement.componentInstance as FormCloudComponent;
expect(cloudFormComponent).toBeDefined();
expect(cloudFormComponent.enableParentVisibilityCheck).toBe(true);
});
it('should pass enableParentVisibilityCheck as false by default to adf-cloud-form', () => {
component.appName = 'app1';
component.taskId = 'task1';
fixture.detectChanges();
const cloudFormElement = fixture.debugElement.query(By.css('adf-cloud-form'));
expect(cloudFormElement).toBeDefined();
const cloudFormComponent = cloudFormElement.componentInstance as FormCloudComponent;
expect(cloudFormComponent).toBeDefined();
expect(cloudFormComponent.enableParentVisibilityCheck).toBe(false);
});
});
describe('Events', () => {

View File

@@ -107,6 +107,13 @@ export class TaskFormCloudComponent {
@Input()
displayModeConfigurations: FormCloudDisplayModeConfiguration[];
/**
* Toggle to enable parent visibility check for validation.
* When enabled, fields inside hidden groups/sections will skip validation.
*/
@Input()
enableParentVisibilityCheck: boolean = false;
/** Task details. */
@Input()
taskDetails: TaskDetailsCloudModel;

View File

@@ -8,6 +8,7 @@
[candidateUsers]="candidateUsers"
[candidateGroups]="candidateGroups"
[displayModeConfigurations]="displayModeConfigurations"
[enableParentVisibilityCheck]="enableParentVisibilityCheck"
[showValidationIcon]="showValidationIcon"
[showTitle]="showTitle"
[taskId]="taskId"

View File

@@ -736,6 +736,25 @@ describe('UserTaskCloudComponent', () => {
expect(taskFormComponent.customCompleteButtonText).toBe(customText);
});
it('should pass enableParentVisibilityCheck to task form when task type is Form', () => {
fixture.componentRef.setInput('enableParentVisibilityCheck', true);
fixture.detectChanges();
const taskFormElement = fixture.debugElement.query(By.css('adf-cloud-task-form'));
const taskFormComponent = taskFormElement?.componentInstance as TaskFormCloudComponent;
expect(taskFormComponent.enableParentVisibilityCheck).toBe(true);
});
it('should pass enableParentVisibilityCheck as false by default to task form', () => {
fixture.detectChanges();
const taskFormElement = fixture.debugElement.query(By.css('adf-cloud-task-form'));
const taskFormComponent = taskFormElement?.componentInstance as TaskFormCloudComponent;
expect(taskFormComponent.enableParentVisibilityCheck).toBe(false);
});
});
});

View File

@@ -67,6 +67,13 @@ export class UserTaskCloudComponent implements OnInit, OnChanges {
@Input()
displayModeConfigurations: FormCloudDisplayModeConfiguration[];
/**
* Toggle to enable parent visibility check for validation.
* When enabled, fields inside hidden groups/sections will skip validation.
*/
@Input()
enableParentVisibilityCheck: boolean = false;
/** Toggle readonly state of the task. */
@Input()
readOnly = false;