mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-09-10 14:11:42 +00:00
AAE-36869 Handles error messages with wrong JSON format (#11078)
* Handles error messages with wrong JSON format Handles cases where the error message from the backend is not a valid JSON. If the message is not a valid JSON, it displays the error as a string. This prevents the application from crashing when receiving error messages with wrong JSON format. Fixes AAE-36869 * SonarCloud solution * Displays the error message from the response The error message is now extracted from the response body, providing more context when process start fails. This change ensures that the user sees the specific error message returned by the service when a process instance cannot be started, improving the user experience. * Refactors start process template to use `@if` blocks Migrates the start process component template from `*ngIf` directives to the new Angular `@if` syntax for improved readability and performance. This change enhances the structure and efficiency of conditional rendering within the template. * prettier
This commit is contained in:
committed by
GitHub
parent
bfd29139a1
commit
8ef0aee768
@@ -1,147 +1,154 @@
|
||||
<mat-card appearance="outlined" class="adf-start-process" *ngIf="processDefinitionLoaded; else spinner">
|
||||
@if (processDefinitionLoaded) {
|
||||
<mat-card appearance="outlined" class="adf-start-process">
|
||||
<mat-card-content>
|
||||
@if (showTitle) {
|
||||
<mat-card-title class="adf-title">
|
||||
{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.FORM.TITLE' | translate }}
|
||||
</mat-card-title>
|
||||
}
|
||||
|
||||
<mat-card-content>
|
||||
<mat-card-title
|
||||
*ngIf="showTitle"
|
||||
class="adf-title">
|
||||
{{'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.FORM.TITLE' | translate}}
|
||||
</mat-card-title>
|
||||
@if (errorMessageId) {
|
||||
<mat-card-subtitle class="adf-error-message" id="error-message">
|
||||
{{ errorMessageId | translate }}
|
||||
</mat-card-subtitle>
|
||||
}
|
||||
|
||||
<mat-card-subtitle id="error-message" *ngIf="errorMessageId">
|
||||
{{ errorMessageId | translate }}
|
||||
</mat-card-subtitle>
|
||||
@if (!isProcessDefinitionsEmpty) {
|
||||
<div>
|
||||
<form [formGroup]="processForm" class="adf-select-process-form">
|
||||
@if (showSelectProcessDropdown) {
|
||||
<mat-form-field
|
||||
class="adf-process-input-container"
|
||||
floatLabel="always"
|
||||
data-automation-id="adf-select-cloud-process-dropdown"
|
||||
>
|
||||
<mat-label class="adf-start-process-input-label">{{
|
||||
'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.FORM.LABEL.TYPE' | translate
|
||||
}}</mat-label>
|
||||
<input matInput formControlName="processDefinition" [matAutocomplete]="auto" id="processDefinitionName" />
|
||||
|
||||
<div *ngIf="!isProcessDefinitionsEmpty; else emptyProcessDefinitionsList">
|
||||
<form [formGroup]="processForm" class="adf-select-process-form">
|
||||
<mat-form-field
|
||||
class="adf-process-input-container"
|
||||
floatLabel="always"
|
||||
*ngIf="showSelectProcessDropdown"
|
||||
data-automation-id="adf-select-cloud-process-dropdown"
|
||||
>
|
||||
<mat-label class="adf-start-process-input-label">{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.FORM.LABEL.TYPE' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
formControlName="processDefinition"
|
||||
[matAutocomplete]="auto"
|
||||
id="processDefinitionName"
|
||||
>
|
||||
|
||||
<div class="adf-process-input-autocomplete">
|
||||
<mat-autocomplete
|
||||
#auto="matAutocomplete"
|
||||
id="processDefinitionOptions"
|
||||
[displayWith]="displayProcessNameOnDropdown"
|
||||
(optionSelected)="setProcessDefinitionOnForm($event.option.value)" >
|
||||
<mat-option
|
||||
*ngFor="let processDef of filteredProcesses"
|
||||
[value]="getProcessDefinitionValue(processDef)"
|
||||
(click)="processDefinitionSelectionChanged(processDef)">
|
||||
<div class="adf-process-input-autocomplete">
|
||||
<mat-autocomplete
|
||||
#auto="matAutocomplete"
|
||||
id="processDefinitionOptions"
|
||||
[displayWith]="displayProcessNameOnDropdown"
|
||||
(optionSelected)="setProcessDefinitionOnForm($event.option.value)"
|
||||
>
|
||||
<mat-option
|
||||
*ngFor="let processDef of filteredProcesses"
|
||||
[value]="getProcessDefinitionValue(processDef)"
|
||||
(click)="processDefinitionSelectionChanged(processDef)"
|
||||
>
|
||||
{{ getProcessDefinitionValue(processDef) }}
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
|
||||
<button
|
||||
id="adf-select-process-dropdown"
|
||||
title="{{'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.FORM.SELECT_PROCESS_DROPDOWN' | translate}}"
|
||||
mat-icon-button
|
||||
(click)="displayDropdown($event)">
|
||||
<mat-icon>arrow_drop_down</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
id="adf-select-process-dropdown"
|
||||
title="{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.FORM.SELECT_PROCESS_DROPDOWN' | translate }}"
|
||||
mat-icon-button
|
||||
(click)="displayDropdown($event)"
|
||||
>
|
||||
<mat-icon>arrow_drop_down</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
@if (processDefinition.hasError('required')) {
|
||||
<mat-error class="adf-error-pb">
|
||||
{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.PROCESS_DEFINITION_REQUIRED' | translate }}
|
||||
</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
}
|
||||
|
||||
</div>
|
||||
<mat-error
|
||||
*ngIf="processDefinition.hasError('required')"
|
||||
class="adf-error-pb">
|
||||
{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.PROCESS_DEFINITION_REQUIRED' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<adf-inplace-form-input [control]="processInstanceName">
|
||||
<ng-container label>
|
||||
{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.FORM.LABEL.NAME' | translate }}
|
||||
</ng-container>
|
||||
|
||||
<adf-inplace-form-input [control]="processInstanceName">
|
||||
<ng-container label>
|
||||
{{'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.FORM.LABEL.NAME' | translate}}
|
||||
</ng-container>
|
||||
<ng-container error>
|
||||
@if (processInstanceName.hasError('required')) {
|
||||
<span>
|
||||
{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.PROCESS_NAME_REQUIRED' | translate }}
|
||||
</span>
|
||||
}
|
||||
@if (processInstanceName.hasError('maxlength')) {
|
||||
<span id="adf-start-process-maxlength-error">
|
||||
{{
|
||||
'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.MAXIMUM_LENGTH'
|
||||
| translate: { characters: maxNameLength }
|
||||
}}
|
||||
</span>
|
||||
}
|
||||
@if (processInstanceName.hasError('pattern')) {
|
||||
<span>
|
||||
{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.SPACE_VALIDATOR' | translate }}
|
||||
</span>
|
||||
}
|
||||
</ng-container>
|
||||
</adf-inplace-form-input>
|
||||
</form>
|
||||
|
||||
<ng-container error>
|
||||
<span *ngIf="processInstanceName.hasError('required')">
|
||||
{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.PROCESS_NAME_REQUIRED' | translate }}
|
||||
</span>
|
||||
<span *ngIf="processInstanceName.hasError('maxlength')" id="adf-start-process-maxlength-error">
|
||||
{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.MAXIMUM_LENGTH' | translate : { characters : maxNameLength } }}
|
||||
</span>
|
||||
<span *ngIf="processInstanceName.hasError('pattern')">
|
||||
{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.SPACE_VALIDATOR' | translate }}
|
||||
</span>
|
||||
</ng-container>
|
||||
</adf-inplace-form-input>
|
||||
</form>
|
||||
|
||||
<ng-container *ngIf="hasForm else taskFormCloudButtons">
|
||||
<adf-cloud-form
|
||||
#startForm
|
||||
[appName]="appName"
|
||||
[appVersion]="processDefinitionCurrent.appVersion"
|
||||
[data]="resolvedValues"
|
||||
[formId]="processDefinitionCurrent.formKey"
|
||||
[displayModeConfigurations]="displayModeConfigurations"
|
||||
[showSaveButton]="showSaveButton"
|
||||
[showCompleteButton]="showCompleteButton"
|
||||
[showRefreshButton]="false"
|
||||
[showValidationIcon]="false"
|
||||
[showTitle]="false"
|
||||
(formContentClicked)="onFormContentClicked($event)"
|
||||
(formLoaded)="onFormLoaded($event)"
|
||||
(executeOutcome)="onCustomOutcomeClicked($event.outcome.name)"
|
||||
>
|
||||
<adf-cloud-form-custom-outcomes>
|
||||
<ng-template [ngTemplateOutlet]="taskFormCloudButtons" />
|
||||
</adf-cloud-form-custom-outcomes>
|
||||
</adf-cloud-form>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
</mat-card-content>
|
||||
|
||||
</mat-card>
|
||||
@if (hasForm) {
|
||||
<adf-cloud-form
|
||||
#startForm
|
||||
[appName]="appName"
|
||||
[appVersion]="processDefinitionCurrent.appVersion"
|
||||
[data]="resolvedValues"
|
||||
[formId]="processDefinitionCurrent.formKey"
|
||||
[displayModeConfigurations]="displayModeConfigurations"
|
||||
[showSaveButton]="showSaveButton"
|
||||
[showCompleteButton]="showCompleteButton"
|
||||
[showRefreshButton]="false"
|
||||
[showValidationIcon]="false"
|
||||
[showTitle]="false"
|
||||
(formContentClicked)="onFormContentClicked($event)"
|
||||
(formLoaded)="onFormLoaded($event)"
|
||||
(executeOutcome)="onCustomOutcomeClicked($event.outcome.name)"
|
||||
>
|
||||
<adf-cloud-form-custom-outcomes>
|
||||
<ng-template [ngTemplateOutlet]="taskFormCloudButtons" />
|
||||
</adf-cloud-form-custom-outcomes>
|
||||
</adf-cloud-form>
|
||||
} @else {
|
||||
<ng-template [ngTemplateOutlet]="taskFormCloudButtons" />
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
@if (processDefinitionLoaded) {
|
||||
<mat-card-content>
|
||||
<mat-card-subtitle class="error-message" id="no-process-message">
|
||||
{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.NO_PROCESS_DEFINITIONS' | translate | uppercase }}
|
||||
</mat-card-subtitle>
|
||||
</mat-card-content>
|
||||
}
|
||||
}
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
} @else {
|
||||
<div class="adf-loading-container">
|
||||
<mat-progress-spinner class="adf-loading" color="primary" mode="indeterminate" />
|
||||
</div>
|
||||
}
|
||||
|
||||
<ng-template #taskFormCloudButtons>
|
||||
<div class="adf-start-process-cloud-actions">
|
||||
<button
|
||||
*ngIf="showCancelButton"
|
||||
mat-button
|
||||
(click)="cancelStartProcess()"
|
||||
id="cancel_process"
|
||||
>
|
||||
{{ cancelButtonLabel }}
|
||||
</button>
|
||||
<button
|
||||
*ngIf="showStartProcessButton$ | async"
|
||||
color="primary"
|
||||
mat-raised-button
|
||||
[disabled]="disableStartButton || !isProcessFormValid"
|
||||
(click)="startProcess()"
|
||||
data-automation-id="btn-start"
|
||||
id="button-start"
|
||||
class="adf-btn-start"
|
||||
>
|
||||
@if (showCancelButton) {
|
||||
<button mat-button (click)="cancelStartProcess()" id="cancel_process">
|
||||
{{ cancelButtonLabel }}
|
||||
</button>
|
||||
}
|
||||
@if (showStartProcessButton$ | async) {
|
||||
<button
|
||||
color="primary"
|
||||
mat-raised-button
|
||||
[disabled]="disableStartButton || !isProcessFormValid"
|
||||
(click)="startProcess()"
|
||||
data-automation-id="btn-start"
|
||||
id="button-start"
|
||||
class="adf-btn-start"
|
||||
>
|
||||
{{ startProcessButtonLabel }}
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #emptyProcessDefinitionsList>
|
||||
<mat-card-content *ngIf="processDefinitionLoaded">
|
||||
<mat-card-subtitle class="error-message" id="no-process-message">
|
||||
{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.NO_PROCESS_DEFINITIONS' | translate | uppercase}}
|
||||
</mat-card-subtitle>
|
||||
</mat-card-content>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #spinner>
|
||||
<div class="adf-loading-container">
|
||||
<mat-progress-spinner
|
||||
class="adf-loading"
|
||||
color="primary"
|
||||
mode="indeterminate" />
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</ng-template>
|
||||
|
@@ -19,6 +19,10 @@
|
||||
padding-bottom: 1.25em;
|
||||
}
|
||||
|
||||
&-error-message {
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
&-process-input-container {
|
||||
margin: 0 7px;
|
||||
}
|
||||
|
@@ -877,7 +877,13 @@ describe('StartProcessCloudComponent', () => {
|
||||
|
||||
it('should throw error event when process cannot be started', async () => {
|
||||
const errorSpy = spyOn(component.error, 'emit');
|
||||
const error = { message: 'My error' };
|
||||
const error = {
|
||||
response: {
|
||||
body: {
|
||||
message: 'My error'
|
||||
}
|
||||
}
|
||||
};
|
||||
startProcessSpy = startProcessSpy.and.returnValue(throwError(error));
|
||||
component.startProcess();
|
||||
await fixture.whenStable();
|
||||
@@ -888,14 +894,21 @@ describe('StartProcessCloudComponent', () => {
|
||||
getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
|
||||
const change = new SimpleChange('myApp', 'myApp1', true);
|
||||
component.ngOnChanges({ appName: change });
|
||||
startProcessSpy = startProcessSpy.and.returnValue(throwError({}));
|
||||
const error = {
|
||||
response: {
|
||||
body: {
|
||||
message: 'Process start failed'
|
||||
}
|
||||
}
|
||||
};
|
||||
startProcessSpy = startProcessSpy.and.returnValue(throwError(error));
|
||||
component.startProcess();
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const errorEl = fixture.nativeElement.querySelector('#error-message');
|
||||
expect(errorEl.innerText.trim()).toBe('ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.START');
|
||||
expect(errorEl.innerText.trim()).toBe('Process start failed');
|
||||
});
|
||||
|
||||
it('should emit start event when start select a process and add a name', (done) => {
|
||||
|
@@ -70,6 +70,7 @@ const PROCESS_DEFINITION_IDENTIFIER_REG_EXP = new RegExp('%{processdefinition}',
|
||||
|
||||
@Component({
|
||||
selector: 'adf-cloud-start-process',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
TranslatePipe,
|
||||
@@ -456,8 +457,7 @@ export class StartProcessCloudComponent implements OnChanges, OnInit {
|
||||
this.isProcessStarting = false;
|
||||
},
|
||||
error: (err) => {
|
||||
this.errorMessageId = 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.START';
|
||||
this.unifyErrorResponse(err);
|
||||
this.errorMessageId = err?.response?.body?.message || 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.START_PROCESS';
|
||||
this.error.emit(err);
|
||||
this.isProcessStarting = false;
|
||||
}
|
||||
@@ -483,14 +483,6 @@ export class StartProcessCloudComponent implements OnChanges, OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
private unifyErrorResponse(err: any) {
|
||||
if (!err?.response?.body?.entry && err?.response?.body?.message) {
|
||||
err.response.body = {
|
||||
entry: JSON.parse(err.response.body.message)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
cancelStartProcess() {
|
||||
this.cancel.emit();
|
||||
}
|
||||
|
Reference in New Issue
Block a user