mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
AAE-44435 Fix incorrect Start process button on user task forms (#11837)
This commit is contained in:
@@ -89,7 +89,7 @@ The template defined inside `empty-form` will be shown when no form definition i
|
|||||||
| path | `string` | | Path of the folder where the metadata will be stored. |
|
| path | `string` | | Path of the folder where the metadata will be stored. |
|
||||||
| processInstanceId | `string` | | ProcessInstanceId id to fetch corresponding form and values. |
|
| processInstanceId | `string` | | ProcessInstanceId id to fetch corresponding form and values. |
|
||||||
| readOnly | `boolean` | false | Toggle readonly state of the form. Forces all form widgets to render as readonly if enabled. |
|
| readOnly | `boolean` | false | Toggle readonly state of the form. Forces all form widgets to render as readonly if enabled. |
|
||||||
| showCompleteButton | `boolean` | true | Toggle rendering of the `Complete` outcome button. |
|
| showCompleteButton | `boolean` | false | Toggle rendering of the `Complete` outcome button. |
|
||||||
| showRefreshButton | `boolean` | true | Toggle rendering of the `Refresh` button. |
|
| showRefreshButton | `boolean` | true | Toggle rendering of the `Refresh` button. |
|
||||||
| showSaveButton | `boolean` | true | Toggle rendering of the `Save` outcome button. |
|
| showSaveButton | `boolean` | true | Toggle rendering of the `Save` outcome button. |
|
||||||
| showTitle | `boolean` | true | Toggle rendering of the form title. |
|
| showTitle | `boolean` | true | Toggle rendering of the form title. |
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { FormOutcomeModel } from '../widgets';
|
||||||
|
import { isOutcomeButtonVisible } from './buttons-visibility';
|
||||||
|
|
||||||
|
describe('isOutcomeButtonVisible', () => {
|
||||||
|
const defaultProps = {
|
||||||
|
isFormReadOnly: false,
|
||||||
|
showCompleteButton: true,
|
||||||
|
showSaveButton: true
|
||||||
|
};
|
||||||
|
|
||||||
|
const outcome = (overrides: Partial<FormOutcomeModel>): FormOutcomeModel => ({ name: '', isSelected: false, ...overrides }) as FormOutcomeModel;
|
||||||
|
|
||||||
|
it('should return false when outcome has no name', () => {
|
||||||
|
expect(isOutcomeButtonVisible(outcome({ name: '' }), defaultProps)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respect showCompleteButton for COMPLETE action', () => {
|
||||||
|
const o = outcome({ name: FormOutcomeModel.COMPLETE_ACTION });
|
||||||
|
expect(isOutcomeButtonVisible(o, { ...defaultProps, showCompleteButton: false })).toBe(false);
|
||||||
|
expect(isOutcomeButtonVisible(o, { ...defaultProps, showCompleteButton: true })).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should always hide START_PROCESS action regardless of readOnly state', () => {
|
||||||
|
const o = outcome({ name: FormOutcomeModel.START_PROCESS_ACTION });
|
||||||
|
expect(isOutcomeButtonVisible(o, { ...defaultProps, isFormReadOnly: false })).toBe(false);
|
||||||
|
expect(isOutcomeButtonVisible(o, { ...defaultProps, isFormReadOnly: true })).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show only selected outcome when form is read-only', () => {
|
||||||
|
const selected = outcome({ name: 'custom-1', isSelected: true });
|
||||||
|
const notSelected = outcome({ name: 'custom-2', isSelected: false });
|
||||||
|
expect(isOutcomeButtonVisible(selected, { ...defaultProps, isFormReadOnly: true })).toBe(true);
|
||||||
|
expect(isOutcomeButtonVisible(notSelected, { ...defaultProps, isFormReadOnly: true })).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respect showSaveButton for SAVE action on writable forms', () => {
|
||||||
|
const o = outcome({ name: FormOutcomeModel.SAVE_ACTION });
|
||||||
|
expect(isOutcomeButtonVisible(o, { ...defaultProps, showSaveButton: true })).toBe(true);
|
||||||
|
expect(isOutcomeButtonVisible(o, { ...defaultProps, showSaveButton: false })).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show custom outcomes on writable forms', () => {
|
||||||
|
const o = outcome({ name: 'custom-outcome' });
|
||||||
|
expect(isOutcomeButtonVisible(o, defaultProps)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -30,15 +30,15 @@ export const isOutcomeButtonVisible = (outcome: FormOutcomeModel, props: IsOutco
|
|||||||
if (outcome.name === FormOutcomeModel.COMPLETE_ACTION) {
|
if (outcome.name === FormOutcomeModel.COMPLETE_ACTION) {
|
||||||
return showCompleteButton;
|
return showCompleteButton;
|
||||||
}
|
}
|
||||||
|
if (outcome.name === FormOutcomeModel.START_PROCESS_ACTION) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (isFormReadOnly) {
|
if (isFormReadOnly) {
|
||||||
return outcome.isSelected;
|
return outcome.isSelected;
|
||||||
}
|
}
|
||||||
if (outcome.name === FormOutcomeModel.SAVE_ACTION) {
|
if (outcome.name === FormOutcomeModel.SAVE_ACTION) {
|
||||||
return showSaveButton;
|
return showSaveButton;
|
||||||
}
|
}
|
||||||
if (outcome.name === FormOutcomeModel.START_PROCESS_ACTION) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -121,20 +121,17 @@
|
|||||||
}
|
}
|
||||||
<div class="adf-cloud-form-outcome-buttons">
|
<div class="adf-cloud-form-outcome-buttons">
|
||||||
<ng-content select="adf-cloud-form-custom-outcomes" />
|
<ng-content select="adf-cloud-form-custom-outcomes" />
|
||||||
@for (outcome of form.outcomes; track outcome.name) {
|
@for (outcome of visibleOutcomes; track outcome.name) {
|
||||||
@if (outcome.isVisible) {
|
<button
|
||||||
<button
|
[id]="'adf-form-' + outcome.name | formatSpace"
|
||||||
[id]="'adf-form-' + outcome.name | formatSpace"
|
[color]="getColorForOutcome(outcome.name)"
|
||||||
[color]="getColorForOutcome(outcome.name)"
|
mat-button
|
||||||
mat-button
|
[disabled]="!isOutcomeButtonEnabled(outcome)"
|
||||||
[disabled]="!isOutcomeButtonEnabled(outcome)"
|
class="adf-cloud-form-custom-outcome-button"
|
||||||
[class.adf-form-hide-button]="!isOutcomeButtonVisible(outcome, form.readOnly)"
|
(click)="onOutcomeClicked(outcome)"
|
||||||
class="adf-cloud-form-custom-outcome-button"
|
>
|
||||||
(click)="onOutcomeClicked(outcome)"
|
{{ getCustomOutcomeButtonText(outcome) || (outcome.name | translate | uppercase) }}
|
||||||
>
|
</button>
|
||||||
{{ getCustomOutcomeButtonText(outcome) || (outcome.name | translate | uppercase) }}
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</mat-card-actions>
|
</mat-card-actions>
|
||||||
|
|||||||
@@ -1210,6 +1210,33 @@ describe('FormCloudComponent', () => {
|
|||||||
expect(formComponent.isOutcomeButtonEnabled(startProcessOutcome)).toBeTruthy();
|
expect(formComponent.isOutcomeButtonEnabled(startProcessOutcome)).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not include START_PROCESS outcome when form is loaded for a task', () => {
|
||||||
|
formComponent.taskId = 'mock-task-id';
|
||||||
|
formComponent.appName = 'mock-app';
|
||||||
|
|
||||||
|
const form = formComponent.parseForm(cloudFormMock);
|
||||||
|
|
||||||
|
const startProcessOutcome = form.outcomes.find((outcome) => outcome.id === FormModel.START_PROCESS_OUTCOME);
|
||||||
|
expect(startProcessOutcome).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should populate visibleOutcomes when the form is set', () => {
|
||||||
|
formComponent.showCompleteButton = true;
|
||||||
|
formComponent.form = new FormModel(cloudFormMock);
|
||||||
|
|
||||||
|
expect(formComponent.visibleOutcomes.length).toBeGreaterThan(0);
|
||||||
|
expect(formComponent.visibleOutcomes.every((outcome) => outcome.name !== FormOutcomeModel.START_PROCESS_ACTION)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear visibleOutcomes when the form is cleared', () => {
|
||||||
|
formComponent.showCompleteButton = true;
|
||||||
|
formComponent.form = new FormModel(cloudFormMock);
|
||||||
|
expect(formComponent.visibleOutcomes.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
formComponent.form = null;
|
||||||
|
expect(formComponent.visibleOutcomes).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
it('should raise [executeOutcome] event for formService', async () => {
|
it('should raise [executeOutcome] event for formService', async () => {
|
||||||
spyOn(formComponent.executeOutcome, 'emit');
|
spyOn(formComponent.executeOutcome, 'emit');
|
||||||
|
|
||||||
@@ -1656,6 +1683,7 @@ describe('FormCloudComponent', () => {
|
|||||||
|
|
||||||
describe('Custom outcome button text for default outcomes', () => {
|
describe('Custom outcome button text for default outcomes', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
formComponent.showCompleteButton = true;
|
||||||
formComponent.form = formComponent.parseForm(emptyFormRepresentationJSON);
|
formComponent.form = formComponent.parseForm(emptyFormRepresentationJSON);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -175,6 +175,19 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
|
|||||||
formCloudRepresentationJSON: any;
|
formCloudRepresentationJSON: any;
|
||||||
fieldValidators: FormFieldValidator[] = [];
|
fieldValidators: FormFieldValidator[] = [];
|
||||||
|
|
||||||
|
/** Pre-computed list of outcome buttons to render, filtered by visibility rules. */
|
||||||
|
visibleOutcomes: FormOutcomeModel[] = [];
|
||||||
|
|
||||||
|
override get form(): FormModel {
|
||||||
|
return super.form;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
override set form(form: FormModel) {
|
||||||
|
super.form = form;
|
||||||
|
this.recomputeVisibleOutcomes();
|
||||||
|
}
|
||||||
|
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
displayMode: string;
|
displayMode: string;
|
||||||
displayConfiguration: FormCloudDisplayModeConfiguration = DisplayModeService.DEFAULT_DISPLAY_MODE_CONFIGURATIONS[0];
|
displayConfiguration: FormCloudDisplayModeConfiguration = DisplayModeService.DEFAULT_DISPLAY_MODE_CONFIGURATIONS[0];
|
||||||
@@ -268,6 +281,13 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
|
|||||||
this.disableSaveButton = false;
|
this.disableSaveButton = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.formService.formRulesEvent
|
||||||
|
.pipe(
|
||||||
|
filter((event) => event?.type === 'fieldValueChanged' && event.form?.id === this.form?.id),
|
||||||
|
takeUntilDestroyed()
|
||||||
|
)
|
||||||
|
.subscribe(() => this.recomputeVisibleOutcomes());
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('keydown', ['$event'])
|
@HostListener('keydown', ['$event'])
|
||||||
@@ -318,6 +338,10 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
|
|||||||
this.setCheckParentVisibilityForValidationOnFields();
|
this.setCheckParentVisibilityForValidationOnFields();
|
||||||
this.form.validateForm();
|
this.form.validateForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (changes['readOnly'] || changes['showCompleteButton'] || changes['showSaveButton']) {
|
||||||
|
this.recomputeVisibleOutcomes();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@@ -511,9 +535,11 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
|
|||||||
});
|
});
|
||||||
|
|
||||||
const form = new FormModel(formCloudRepresentationJSON, formValues, this.readOnly, this.formService, undefined, this.fieldValidators);
|
const form = new FormModel(formCloudRepresentationJSON, formValues, this.readOnly, this.formService, undefined, this.fieldValidators);
|
||||||
if (!form) {
|
|
||||||
form.outcomes = this.getFormDefinitionOutcomes(form);
|
if (this.taskId) {
|
||||||
|
form.outcomes = (form.outcomes ?? []).filter((outcome) => outcome.id !== FormModel.START_PROCESS_OUTCOME);
|
||||||
}
|
}
|
||||||
|
|
||||||
return form;
|
return form;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -533,6 +559,7 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
|
|||||||
checkVisibility(field: FormFieldModel) {
|
checkVisibility(field: FormFieldModel) {
|
||||||
if (field?.form) {
|
if (field?.form) {
|
||||||
this.visibilityService.refreshVisibility(field.form);
|
this.visibilityService.refreshVisibility(field.form);
|
||||||
|
this.recomputeVisibleOutcomes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,6 +572,16 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private recomputeVisibleOutcomes(): void {
|
||||||
|
const outcomes = this.form?.outcomes;
|
||||||
|
if (!outcomes) {
|
||||||
|
this.visibleOutcomes = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.visibleOutcomes = outcomes.filter((outcome) => outcome.isVisible && this.isOutcomeButtonVisible(outcome, this.form.readOnly));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the parent visibility check flag on all form fields.
|
* Sets the parent visibility check flag on all form fields.
|
||||||
* When enabled, fields inside hidden groups/sections will skip validation.
|
* When enabled, fields inside hidden groups/sections will skip validation.
|
||||||
|
|||||||
Reference in New Issue
Block a user