mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[AAE-15082][AAE-15081] Resolve the options coming from a json variabl… (#8673)
* [AAE-15082][AAE-15081] Resolve the options coming from a json variable for a dropdown * split method to smaller parts and remove duplications in units * fix unit tests * get variables from API call * [AAE-15082] Add handle form variable * replace variableId by variableName * improve code
This commit is contained in:
@@ -37,5 +37,7 @@
|
||||
required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
||||
<error-widget class="adf-dropdown-failed-message" *ngIf="isRestApiFailed"
|
||||
required="{{ 'FORM.FIELD.REST_API_FAILED' | translate: { hostname: restApiHostName } }}"></error-widget>
|
||||
<error-widget class="adf-dropdown-failed-message" *ngIf="variableOptionsFailed"
|
||||
required="{{ 'FORM.FIELD.VARIABLE_DROPDOWN_OPTIONS_FAILED' | translate }}"></error-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -19,7 +19,15 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { DropdownCloudWidgetComponent } from './dropdown-cloud.widget';
|
||||
import { FormFieldModel, FormModel, FormService, setupTestBed, FormFieldEvent, FormFieldTypes } from '@alfresco/adf-core';
|
||||
import {
|
||||
FormFieldModel,
|
||||
FormModel,
|
||||
FormService,
|
||||
setupTestBed,
|
||||
FormFieldEvent,
|
||||
FormFieldTypes,
|
||||
LogService
|
||||
} from '@alfresco/adf-core';
|
||||
import { FormCloudService } from '../../../services/form-cloud.service';
|
||||
import { ProcessServiceCloudTestingModule } from '../../../../testing/process-service-cloud.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
@@ -27,16 +35,22 @@ import {
|
||||
fakeOptionList,
|
||||
filterOptionList,
|
||||
mockConditionalEntries,
|
||||
mockFormVariableWithJson,
|
||||
mockPlayersResponse,
|
||||
mockRestDropdownOptions,
|
||||
mockSecondRestDropdownOptions
|
||||
mockSecondRestDropdownOptions,
|
||||
mockVariablesWithDefaultJson,
|
||||
mockProcessVariablesWithJson
|
||||
} from '../../../mocks/dropdown.mock';
|
||||
import { OverlayContainer } from '@angular/cdk/overlay';
|
||||
import { TaskVariableCloud } from '../../../models/task-variable-cloud.model';
|
||||
|
||||
describe('DropdownCloudWidgetComponent', () => {
|
||||
|
||||
let formService: FormService;
|
||||
let widget: DropdownCloudWidgetComponent;
|
||||
let formCloudService: FormCloudService;
|
||||
let logService: LogService;
|
||||
let overlayContainer: OverlayContainer;
|
||||
let fixture: ComponentFixture<DropdownCloudWidgetComponent>;
|
||||
let element: HTMLElement;
|
||||
@@ -64,6 +78,7 @@ describe('DropdownCloudWidgetComponent', () => {
|
||||
formService = TestBed.inject(FormService);
|
||||
formCloudService = TestBed.inject(FormCloudService);
|
||||
overlayContainer = TestBed.inject(OverlayContainer);
|
||||
logService = TestBed.inject(LogService);
|
||||
});
|
||||
|
||||
afterEach(() => fixture.destroy());
|
||||
@@ -876,4 +891,136 @@ describe('DropdownCloudWidgetComponent', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('variable options', () => {
|
||||
let logServiceSpy: jasmine.Spy;
|
||||
const errorIcon: string = 'error_outline';
|
||||
|
||||
const getVariableDropdownWidget = (
|
||||
variableName: string,
|
||||
optionsPath: string,
|
||||
optionsId: string,
|
||||
optionsLabel: string,
|
||||
processVariables?: TaskVariableCloud[],
|
||||
variables?: TaskVariableCloud[]
|
||||
) => new FormFieldModel(
|
||||
new FormModel({ taskId: 'fake-task-id', processVariables, variables }), {
|
||||
id: 'variable-dropdown-id',
|
||||
name: 'variable-options-dropdown',
|
||||
type: 'dropdown',
|
||||
readOnly: 'false',
|
||||
optionType: 'variable',
|
||||
variableConfig: {
|
||||
variableName,
|
||||
optionsPath,
|
||||
optionsId,
|
||||
optionsLabel
|
||||
}
|
||||
});
|
||||
|
||||
const checkDropdownVariableOptionsFailed = () => {
|
||||
const failedErrorMsgElement = fixture.debugElement.query(By.css('.adf-dropdown-failed-message'));
|
||||
expect(failedErrorMsgElement.nativeElement.textContent.trim()).toBe(errorIcon.concat('FORM.FIELD.VARIABLE_DROPDOWN_OPTIONS_FAILED'));
|
||||
|
||||
expect(widget.field.options.length).toEqual(0);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
logServiceSpy = spyOn(logService, 'error');
|
||||
});
|
||||
|
||||
it('should display options persisted from process variable', async () => {
|
||||
widget.field = getVariableDropdownWidget('variables.json-variable', 'response.people.players', 'playerId', 'playerFullName', mockProcessVariablesWithJson);
|
||||
fixture.detectChanges();
|
||||
await openSelect('variable-dropdown-id');
|
||||
|
||||
const optOne: any = fixture.debugElement.query(By.css('[id="player-1"]'));
|
||||
const optTwo: any = fixture.debugElement.query(By.css('[id="player-2"]'));
|
||||
const optThree: any = fixture.debugElement.query(By.css('[id="player-3"]'));
|
||||
|
||||
expect(widget.field.options.length).toEqual(3);
|
||||
expect(optOne.context.value).toBe('player-1');
|
||||
expect(optOne.context.viewValue).toBe('Lionel Messi');
|
||||
expect(optTwo.context.value).toBe('player-2');
|
||||
expect(optTwo.context.viewValue).toBe('Cristiano Ronaldo');
|
||||
expect(optThree.context.value).toBe('player-3');
|
||||
expect(optThree.context.viewValue).toBe('Robert Lewandowski');
|
||||
});
|
||||
|
||||
it('should display options persisted from form variable if there are NO process variables', async () => {
|
||||
widget.field = getVariableDropdownWidget('json-form-variable', 'countries', 'id', 'name', [], mockFormVariableWithJson);
|
||||
fixture.detectChanges();
|
||||
await openSelect('variable-dropdown-id');
|
||||
|
||||
const optOne: any = fixture.debugElement.query(By.css('[id="PL"]'));
|
||||
const optTwo: any = fixture.debugElement.query(By.css('[id="UK"]'));
|
||||
const optThree: any = fixture.debugElement.query(By.css('[id="GR"]'));
|
||||
|
||||
expect(widget.field.options.length).toEqual(3);
|
||||
expect(optOne.context.value).toBe('PL');
|
||||
expect(optOne.context.viewValue).toBe('Poland');
|
||||
expect(optTwo.context.value).toBe('UK');
|
||||
expect(optTwo.context.viewValue).toBe('United Kingdom');
|
||||
expect(optThree.context.value).toBe('GR');
|
||||
expect(optThree.context.viewValue).toBe('Greece');
|
||||
});
|
||||
|
||||
it('should display default options if config options are NOT provided', async () => {
|
||||
widget.field = getVariableDropdownWidget('variables.json-default-variable', null, null, null, mockVariablesWithDefaultJson);
|
||||
fixture.detectChanges();
|
||||
await openSelect('variable-dropdown-id');
|
||||
|
||||
const optOne: any = fixture.debugElement.query(By.css('[id="default-pet-1"]'));
|
||||
const optTwo: any = fixture.debugElement.query(By.css('[id="default-pet-2"]'));
|
||||
const optThree: any = fixture.debugElement.query(By.css('[id="default-pet-3"]'));
|
||||
|
||||
expect(widget.field.options.length).toEqual(3);
|
||||
expect(optOne.context.value).toBe('default-pet-1');
|
||||
expect(optOne.context.viewValue).toBe('Dog');
|
||||
expect(optTwo.context.value).toBe('default-pet-2');
|
||||
expect(optTwo.context.viewValue).toBe('Cat');
|
||||
expect(optThree.context.value).toBe('default-pet-3');
|
||||
expect(optThree.context.viewValue).toBe('Parrot');
|
||||
});
|
||||
|
||||
it('should return empty array and display error when path is incorrect', () => {
|
||||
widget.field = getVariableDropdownWidget('variables.json-variable', 'response.wrongPath.players', 'playerId', 'playerFullName', mockProcessVariablesWithJson);
|
||||
fixture.detectChanges();
|
||||
|
||||
checkDropdownVariableOptionsFailed();
|
||||
expect(logServiceSpy).toHaveBeenCalledWith(`wrongPath not found in ${JSON.stringify(mockPlayersResponse.response)}`);
|
||||
});
|
||||
|
||||
it('should return empty array and display error when id is incorrect', () => {
|
||||
widget.field = getVariableDropdownWidget('variables.json-variable', 'response.people.players', 'wrongId', 'playerFullName', mockProcessVariablesWithJson);
|
||||
fixture.detectChanges();
|
||||
|
||||
checkDropdownVariableOptionsFailed();
|
||||
expect(logServiceSpy).toHaveBeenCalledWith(`'id' or 'label' is not properly defined`);
|
||||
});
|
||||
|
||||
it('should return empty array and display error when label is incorrect', () => {
|
||||
widget.field = getVariableDropdownWidget('variables.json-variable', 'response.people.players', 'playerId', 'wrongFullName', mockProcessVariablesWithJson);
|
||||
fixture.detectChanges();
|
||||
|
||||
checkDropdownVariableOptionsFailed();
|
||||
expect(logServiceSpy).toHaveBeenCalledWith(`'id' or 'label' is not properly defined`);
|
||||
});
|
||||
|
||||
it('should return empty array and display error when variable is NOT found', () => {
|
||||
widget.field = getVariableDropdownWidget('variables.wrong-variable-id', 'response.people.players', 'playerId', 'playerFullName', mockProcessVariablesWithJson);
|
||||
fixture.detectChanges();
|
||||
|
||||
checkDropdownVariableOptionsFailed();
|
||||
expect(logServiceSpy).toHaveBeenCalledWith(`variables.wrong-variable-id not found`);
|
||||
});
|
||||
|
||||
it('should return empty array and display error if there are NO process and form variables', () => {
|
||||
widget.field = getVariableDropdownWidget('variables.json-variable', 'response.people.players', 'playerId', 'playerFullName', [], []);
|
||||
fixture.detectChanges();
|
||||
|
||||
checkDropdownVariableOptionsFailed();
|
||||
expect(logServiceSpy).toHaveBeenCalledWith('variables.json-variable not found');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -30,6 +30,7 @@ import {
|
||||
import { FormCloudService } from '../../../services/form-cloud.service';
|
||||
import { BehaviorSubject, combineLatest, Observable, of, Subject } from 'rxjs';
|
||||
import { filter, map, takeUntil } from 'rxjs/operators';
|
||||
import { TaskVariableCloud } from '../../../models/task-variable-cloud.model';
|
||||
|
||||
export const DEFAULT_OPTION = {
|
||||
id: 'empty',
|
||||
@@ -60,10 +61,15 @@ export class DropdownCloudWidgetComponent extends WidgetComponent implements OnI
|
||||
typeId = 'DropdownCloudWidgetComponent';
|
||||
showInputFilter = false;
|
||||
isRestApiFailed = false;
|
||||
variableOptionsFailed = false;
|
||||
restApiHostName: string;
|
||||
list$: Observable<FormFieldOption[]>;
|
||||
filter$ = new BehaviorSubject<string>('');
|
||||
|
||||
private readonly defaultVariableOptionId = 'id';
|
||||
private readonly defaultVariableOptionLabel = 'name';
|
||||
private readonly defaultVariableOptionPath = 'data';
|
||||
|
||||
protected onDestroy$ = new Subject<boolean>();
|
||||
|
||||
constructor(public formService: FormService,
|
||||
@@ -74,25 +80,113 @@ export class DropdownCloudWidgetComponent extends WidgetComponent implements OnI
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.field.restUrl && !this.isLinkedWidget()) {
|
||||
this.persistFieldOptionsFromRestApi();
|
||||
}
|
||||
|
||||
if (this.isLinkedWidget()) {
|
||||
this.loadFieldOptionsForLinkedWidget();
|
||||
|
||||
this.formService.formFieldValueChanged
|
||||
.pipe(
|
||||
filter((event: FormFieldEvent) => this.isFormFieldEventOfTypeDropdown(event) && this.isParentFormFieldEvent(event)),
|
||||
takeUntil(this.onDestroy$))
|
||||
.subscribe((event: FormFieldEvent) => {
|
||||
const valueOfParentWidget = event.field.value;
|
||||
this.parentValueChanged(valueOfParentWidget);
|
||||
});
|
||||
}
|
||||
this.checkFieldOptionsSource();
|
||||
this.updateOptions();
|
||||
}
|
||||
|
||||
private checkFieldOptionsSource(): void {
|
||||
switch (true) {
|
||||
case this.field.restUrl && !this.isLinkedWidget():
|
||||
this.persistFieldOptionsFromRestApi();
|
||||
break;
|
||||
|
||||
case this.isLinkedWidget():
|
||||
this.loadFieldOptionsForLinkedWidget();
|
||||
break;
|
||||
|
||||
case this.isVariableOptionType():
|
||||
this.persistFieldOptionsFromVariable();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private persistFieldOptionsFromVariable(): void {
|
||||
const optionsPath = this.field?.variableConfig?.optionsPath ?? this.defaultVariableOptionPath;
|
||||
const variableName = this.field?.variableConfig?.variableName;
|
||||
const processVariables = this.field?.form?.processVariables;
|
||||
const formVariables = this.field?.form?.variables;
|
||||
|
||||
const dropdownOptions = this.getOptionsFromVariable(processVariables, formVariables, variableName);
|
||||
|
||||
if (dropdownOptions) {
|
||||
const formVariableOptions: FormFieldOption[] = this.getOptionsFromPath(dropdownOptions, optionsPath);
|
||||
this.field.options = formVariableOptions;
|
||||
this.resetInvalidValue();
|
||||
this.field.updateForm();
|
||||
} else {
|
||||
this.handleError(`${variableName} not found`);
|
||||
this.resetOptions();
|
||||
this.variableOptionsFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private getOptionsFromPath(data: any, path: string): FormFieldOption[] {
|
||||
const optionsId = this.field?.variableConfig?.optionsId ?? this.defaultVariableOptionId;
|
||||
const optionsLabel = this.field?.variableConfig?.optionsLabel ?? this.defaultVariableOptionLabel;
|
||||
|
||||
const properties = path.split('.');
|
||||
const currentProperty = properties.shift();
|
||||
|
||||
if (!data.hasOwnProperty(currentProperty)) {
|
||||
this.handleError(`${currentProperty} not found in ${JSON.stringify(data)}`);
|
||||
this.variableOptionsFailed = true;
|
||||
return [];
|
||||
}
|
||||
|
||||
const nestedData = data[currentProperty];
|
||||
|
||||
if (Array.isArray(nestedData)) {
|
||||
return this.getOptionsFromArray(nestedData, optionsId, optionsLabel);
|
||||
}
|
||||
|
||||
return this.getOptionsFromPath(nestedData, properties.join('.'));
|
||||
}
|
||||
|
||||
private getOptionsFromArray(nestedData: any[], id: string, label: string): FormFieldOption[] {
|
||||
const options = nestedData.map(item => this.createOption(item, id, label));
|
||||
const hasInvalidOption = options.some(option => !option);
|
||||
|
||||
if (hasInvalidOption) {
|
||||
this.variableOptionsFailed = true;
|
||||
return [];
|
||||
}
|
||||
|
||||
this.variableOptionsFailed = false;
|
||||
return options;
|
||||
}
|
||||
|
||||
private createOption(item: any, id: string, label: string): FormFieldOption {
|
||||
const option: FormFieldOption = {
|
||||
id: item[id],
|
||||
name: item[label]
|
||||
};
|
||||
|
||||
if (!option.id || !option.name) {
|
||||
this.handleError(`'id' or 'label' is not properly defined`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return option;
|
||||
}
|
||||
|
||||
private getOptionsFromVariable(processVariables: TaskVariableCloud[], formVariables: TaskVariableCloud[], variableName: string): TaskVariableCloud {
|
||||
const processVariableDropdownOptions: TaskVariableCloud = this.getVariableValueByName(processVariables, variableName);
|
||||
const formVariableDropdownOptions: TaskVariableCloud = this.getVariableValueByName(formVariables, variableName);
|
||||
|
||||
return processVariableDropdownOptions ?? formVariableDropdownOptions;
|
||||
}
|
||||
|
||||
private getVariableValueByName(variables: TaskVariableCloud[], variableName: string): any {
|
||||
return variables?.find((variable: TaskVariableCloud) => variable?.name === `variables.${variableName}` || variable?.name === variableName)?.value;
|
||||
}
|
||||
|
||||
private isVariableOptionType(): boolean {
|
||||
return this.field?.optionType === 'variable';
|
||||
}
|
||||
|
||||
private persistFieldOptionsFromRestApi() {
|
||||
if (this.isValidRestType()) {
|
||||
this.resetRestApiErrorMessage();
|
||||
@@ -125,6 +219,15 @@ export class DropdownCloudWidgetComponent extends WidgetComponent implements OnI
|
||||
private loadFieldOptionsForLinkedWidget() {
|
||||
const parentWidgetValue = this.getParentWidgetValue();
|
||||
this.parentValueChanged(parentWidgetValue);
|
||||
|
||||
this.formService.formFieldValueChanged
|
||||
.pipe(
|
||||
filter((event: FormFieldEvent) => this.isFormFieldEventOfTypeDropdown(event) && this.isParentFormFieldEvent(event)),
|
||||
takeUntil(this.onDestroy$))
|
||||
.subscribe((event: FormFieldEvent) => {
|
||||
const valueOfParentWidget = event.field.value;
|
||||
this.parentValueChanged(valueOfParentWidget);
|
||||
});
|
||||
}
|
||||
|
||||
private getParentWidgetValue(): string {
|
||||
@@ -317,7 +420,10 @@ export class DropdownCloudWidgetComponent extends WidgetComponent implements OnI
|
||||
}
|
||||
|
||||
showRequiredMessage(): boolean {
|
||||
return (this.isInvalidFieldRequired() || (this.isNoneValueSelected(this.field.value) && this.isRequired())) && this.isTouched() && !this.isRestApiFailed;
|
||||
return (this.isInvalidFieldRequired() || (this.isNoneValueSelected(this.field.value) && this.isRequired())) &&
|
||||
this.isTouched() &&
|
||||
!this.isRestApiFailed &&
|
||||
!this.variableOptionsFailed;
|
||||
}
|
||||
|
||||
getDefaultOption(options: FormFieldOption[]): FormFieldOption {
|
||||
|
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
|
||||
import { FormFieldOption } from '@alfresco/adf-core';
|
||||
import { TaskVariableCloud } from '../models/task-variable-cloud.model';
|
||||
|
||||
export const mockConditionalEntries = [
|
||||
{
|
||||
@@ -103,3 +104,79 @@ export const filterOptionList = [
|
||||
{ id: 'opt_5', name: 'option_5' },
|
||||
{ id: 'opt_6', name: 'option_6' }
|
||||
];
|
||||
|
||||
export const mockPlayersResponse = {
|
||||
response: {
|
||||
people: {
|
||||
players:
|
||||
[
|
||||
{
|
||||
playerId: 'player-1',
|
||||
playerFullName: 'Lionel Messi',
|
||||
totalGoals: 999,
|
||||
shirtNumber: 10
|
||||
},
|
||||
{
|
||||
playerId: 'player-2',
|
||||
playerFullName: 'Cristiano Ronaldo',
|
||||
totalGoals: 15,
|
||||
shirtNumber: 7
|
||||
},
|
||||
{
|
||||
playerId: 'player-3',
|
||||
playerFullName: 'Robert Lewandowski',
|
||||
totalGoals: 500,
|
||||
shirtNumber: 9
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const mockDefaultResponse = {
|
||||
data:
|
||||
[
|
||||
{
|
||||
id: 'default-pet-1',
|
||||
name: 'Dog'
|
||||
},
|
||||
{
|
||||
id: 'default-pet-2',
|
||||
name: 'Cat'
|
||||
},
|
||||
{
|
||||
id: 'default-pet-3',
|
||||
name: 'Parrot'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const mockCountriesResponse = {
|
||||
countries: [
|
||||
{
|
||||
id: 'PL',
|
||||
name: 'Poland'
|
||||
},
|
||||
{
|
||||
id: 'UK',
|
||||
name: 'United Kingdom'
|
||||
},
|
||||
{
|
||||
id: 'GR',
|
||||
name: 'Greece'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const mockFormVariableWithJson = [
|
||||
new TaskVariableCloud({ name: 'json-form-variable', value: mockCountriesResponse, type: 'json', id: 'fake-id-1' })
|
||||
];
|
||||
|
||||
export const mockProcessVariablesWithJson = [
|
||||
new TaskVariableCloud({ name: 'variables.json-variable', value: mockPlayersResponse, type: 'json', id: 'fake-id-1' }),
|
||||
new TaskVariableCloud({ name: 'variables.different-variable', value: 'fake-value', type: 'json', id: 'fake-id-2' })
|
||||
];
|
||||
|
||||
export const mockVariablesWithDefaultJson = [
|
||||
new TaskVariableCloud({ name: 'variables.json-default-variable', value: mockDefaultResponse, type: 'json', id: 'fake-id-1' })
|
||||
];
|
||||
|
Reference in New Issue
Block a user