diff --git a/lib/core/src/lib/form/components/widgets/core/form.model.spec.ts b/lib/core/src/lib/form/components/widgets/core/form.model.spec.ts index b3e34e1307..feb9652efe 100644 --- a/lib/core/src/lib/form/components/widgets/core/form.model.spec.ts +++ b/lib/core/src/lib/form/components/widgets/core/form.model.spec.ts @@ -555,11 +555,21 @@ describe('FormModel', () => { expect(value).toBeUndefined(); }); - it('should find a process variable by form variable name', () => { + it('should find a process variable by full form variable name', () => { const value = form.getProcessVariableValue('variables.name1'); expect(value).toBe('hello'); }); + it('should find a process variable by form variable name', () => { + const value = form.getProcessVariableValue('name1'); + expect(value).toBe('hello'); + }); + + it('should find default form variable by form variable name', () => { + const value = form.getProcessVariableValue('name2'); + expect(value).toBe('29.09.2019T00:00:00.000Z'); + }); + it('should find a process variable by name', () => { const value = form.getProcessVariableValue('booleanVar'); expect(value).toEqual(true); diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.spec.ts b/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.spec.ts index 1f76645c0b..61562861c5 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.spec.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.spec.ts @@ -37,11 +37,13 @@ import { HarnessLoader } from '@angular/cdk/testing'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { MatSelectHarness } from '@angular/material/select/testing'; import { DebugElement } from '@angular/core'; +import { FormUtilsService } from '../../../services/form-utils.service'; describe('DropdownCloudWidgetComponent', () => { let formService: FormService; let widget: DropdownCloudWidgetComponent; let formCloudService: FormCloudService; + let formUtilsService: FormUtilsService; let fixture: ComponentFixture; let element: HTMLElement; let loader: HarnessLoader; @@ -56,6 +58,7 @@ describe('DropdownCloudWidgetComponent', () => { formService = TestBed.inject(FormService); formCloudService = TestBed.inject(FormCloudService); + formUtilsService = TestBed.inject(FormUtilsService); loader = TestbedHarnessEnvironment.loader(fixture); }); @@ -63,7 +66,7 @@ describe('DropdownCloudWidgetComponent', () => { describe('Simple Dropdown', () => { beforeEach(() => { - widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id', readOnly: false }), { + widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id', readOnly: false, id: 'form-id' }), { id: 'dropdown-id', name: 'date-name', type: 'dropdown', @@ -91,6 +94,26 @@ describe('DropdownCloudWidgetComponent', () => { expect(formCloudService.getRestWidgetData).toHaveBeenCalled(); }); + it('should call getRestWidgetData with correct body parameters when body is empty', () => { + spyOn(formCloudService, 'getRestWidgetData').and.returnValue(of(fakeOptionList)); + widget.field.optionType = 'rest'; + + widget.ngOnInit(); + expect(formCloudService.getRestWidgetData).toHaveBeenCalledWith('form-id', 'dropdown-id', {}); + }); + + it('should call getRestWidgetData with correct body parameters when variables are mapped', () => { + spyOn(formCloudService, 'getRestWidgetData').and.returnValue(of(fakeOptionList)); + widget.field.optionType = 'rest'; + const body = { var1: 'value1', var2: 'value2' }; + spyOn(formUtilsService, 'getRestUrlVariablesMap').and.returnValue(body); + + widget.ngOnInit(); + + expect(formUtilsService.getRestUrlVariablesMap).toHaveBeenCalledWith(widget.field.form, widget.field.restUrl, {}); + expect(formCloudService.getRestWidgetData).toHaveBeenCalledWith('form-id', 'dropdown-id', body); + }); + it('should select the default value when an option is chosen as default', async () => { widget.field.value = 'option_2'; @@ -623,6 +646,9 @@ describe('DropdownCloudWidgetComponent', () => { await dropdown.open(); const allOptions = await dropdown.getOptions(); + expect(formCloudService.getRestWidgetData).toHaveBeenCalledWith('fake-form-id', 'child-dropdown-id', { + parentDropdown: 'mock-value' + }); expect(jsonDataSpy).toHaveBeenCalledWith('fake-form-id', 'child-dropdown-id', { parentDropdown: 'mock-value' }); expect(await (await allOptions[0].host()).getAttribute('id')).toEqual('LO'); expect(await allOptions[0].getText()).toEqual('LONDON'); diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.ts b/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.ts index 7b1fe79ce3..727b0e477d 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.ts @@ -40,6 +40,7 @@ import { filter, map } from 'rxjs/operators'; import { TaskVariableCloud } from '../../../models/task-variable-cloud.model'; import { FormCloudService } from '../../../services/form-cloud.service'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { FormUtilsService } from '../../../services/form-utils.service'; export const DEFAULT_OPTION = { id: 'empty', @@ -69,8 +70,9 @@ export const HIDE_FILTER_LIMIT = 5; }) export class DropdownCloudWidgetComponent extends WidgetComponent implements OnInit, ReactiveFormWidget { public formService = inject(FormService); - private formCloudService = inject(FormCloudService); - private appConfig = inject(AppConfigService); + private readonly formCloudService = inject(FormCloudService); + private readonly appConfig = inject(AppConfigService); + private readonly formUtilsService = inject(FormUtilsService); private destroyRef = inject(DestroyRef); typeId = 'DropdownCloudWidgetComponent'; @@ -364,7 +366,8 @@ export class DropdownCloudWidgetComponent extends WidgetComponent implements OnI const parentWidgetId = this.linkedWidgetId; bodyParam[parentWidgetId] = parentWidgetValue; } - return bodyParam; + + return this.formUtilsService.getRestUrlVariablesMap(this.field.form, this.field.restUrl, bodyParam); } private loadFieldOptionsForLinkedWidget() { diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/radio-buttons/radio-buttons-cloud.widget.spec.ts b/lib/process-services-cloud/src/lib/form/components/widgets/radio-buttons/radio-buttons-cloud.widget.spec.ts index 5db542518d..a28bce7480 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/radio-buttons/radio-buttons-cloud.widget.spec.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/radio-buttons/radio-buttons-cloud.widget.spec.ts @@ -24,11 +24,13 @@ import { of, throwError } from 'rxjs'; import { HarnessLoader } from '@angular/cdk/testing'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { MatRadioButtonHarness, MatRadioGroupHarness } from '@angular/material/radio/testing'; +import { FormUtilsService } from '../../../services/form-utils.service'; describe('RadioButtonsCloudWidgetComponent', () => { let fixture: ComponentFixture; let widget: RadioButtonsCloudWidgetComponent; let formCloudService: FormCloudService; + let formUtilsService: FormUtilsService; let element: HTMLElement; let loader: HarnessLoader; const restOption: FormFieldOption[] = [ @@ -47,6 +49,7 @@ describe('RadioButtonsCloudWidgetComponent', () => { imports: [ProcessServiceCloudTestingModule] }); formCloudService = TestBed.inject(FormCloudService); + formUtilsService = TestBed.inject(FormUtilsService); fixture = TestBed.createComponent(RadioButtonsCloudWidgetComponent); widget = fixture.componentInstance; element = fixture.nativeElement; @@ -134,6 +137,21 @@ describe('RadioButtonsCloudWidgetComponent', () => { spyOn(formCloudService, 'getRestWidgetData').and.returnValue(of(restOption)); }); + it('should call getRestWidgetData with correct parameters and update field options on success', () => { + const formId = 'form-id'; + const fieldId = 'field-id'; + widget.field = new FormFieldModel(new FormModel({ id: formId }), { id: fieldId, restUrl: '' }); + const body = { var1: 'value1', var2: 'value2' }; + spyOn(formUtilsService, 'getRestUrlVariablesMap').and.returnValue(body); + spyOn(widget.field, 'updateForm'); + + widget.getValuesFromRestApi(); + + expect(formCloudService.getRestWidgetData).toHaveBeenCalledWith(formId, fieldId, body); + expect(widget.field.options).toEqual(restOption); + expect(widget.field.updateForm).toHaveBeenCalled(); + }); + describe('when widget is readonly', () => { it('should call rest api when form is NOT readonly', () => { widget.field = new FormFieldModel(new FormModel({}, undefined, false), getRadioButtonsWidgetConfig(true)); diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/radio-buttons/radio-buttons-cloud.widget.ts b/lib/process-services-cloud/src/lib/form/components/widgets/radio-buttons/radio-buttons-cloud.widget.ts index 67e65118e4..25757598a2 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/radio-buttons/radio-buttons-cloud.widget.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/radio-buttons/radio-buttons-cloud.widget.ts @@ -22,6 +22,7 @@ import { ErrorMessageModel, FormFieldOption, FormService, WidgetComponent } from import { FormCloudService } from '../../../services/form-cloud.service'; import { TranslateService } from '@ngx-translate/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { FormUtilsService } from '../../../services/form-utils.service'; @Component({ selector: 'radio-buttons-cloud-widget', @@ -46,7 +47,12 @@ export class RadioButtonsCloudWidgetComponent extends WidgetComponent implements private readonly destroyRef = inject(DestroyRef); - constructor(public formService: FormService, private formCloudService: FormCloudService, private translateService: TranslateService) { + constructor( + public formService: FormService, + private readonly formCloudService: FormCloudService, + private readonly translateService: TranslateService, + private readonly formUtilsService: FormUtilsService + ) { super(formService); } @@ -57,8 +63,10 @@ export class RadioButtonsCloudWidgetComponent extends WidgetComponent implements } getValuesFromRestApi() { + const body = this.formUtilsService.getRestUrlVariablesMap(this.field.form, this.field.restUrl, {}); + this.formCloudService - .getRestWidgetData(this.field.form.id, this.field.id) + .getRestWidgetData(this.field.form.id, this.field.id, body) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe( (result: FormFieldOption[]) => { diff --git a/lib/process-services-cloud/src/lib/form/services/form-utils.service.spec.ts b/lib/process-services-cloud/src/lib/form/services/form-utils.service.spec.ts new file mode 100644 index 0000000000..4b0d89d3fd --- /dev/null +++ b/lib/process-services-cloud/src/lib/form/services/form-utils.service.spec.ts @@ -0,0 +1,75 @@ +/*! + * @license + * Copyright © 2005-2024 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 { TestBed } from '@angular/core/testing'; +import { FormUtilsService } from './form-utils.service'; +import { FormModel, FormVariableModel } from '@alfresco/adf-core'; + +describe('FormUtilsService', () => { + let service: FormUtilsService; + const variables: FormVariableModel[] = [ + { id: '1', name: 'var1', type: 'string', value: 'value1' }, + { id: '2', name: 'var2', type: 'string', value: 'value2' } + ]; + + /** + * Test the getRestUrlVariablesMap method + * + * @param restUrl The rest URL for getRestUrlVariablesMap + * @param inputBody The input body for getRestUrlVariablesMap + * @param expected The expected result of getRestUrlVariablesMap + */ + function testRestUrlVariablesMap(restUrl: string, inputBody: { [key: string]: any }, expected: { [key: string]: any }) { + const formModel = new FormModel({ variables }); + spyOn(formModel, 'getProcessVariableValue').and.callFake((name) => { + return variables.find((variable) => variable.name === name)?.value; + }); + const result = service.getRestUrlVariablesMap(formModel, restUrl, inputBody); + expect(result).toEqual(expected); + } + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(FormUtilsService); + }); + + it('should return an empty map if there are no variables', () => { + const formModel = new FormModel({ variables: [] }); + const restUrl = 'https://example.com/api'; + const inputBody = {}; + + const result = service.getRestUrlVariablesMap(formModel, restUrl, inputBody); + + expect(result).toEqual({}); + }); + + it('should map variable values to the input body if they are present in the restUrl', () => { + testRestUrlVariablesMap('https://example.com/api?var1=${var1}&var2=${var2}', {}, { var1: 'value1', var2: 'value2' }); + }); + + it('should not map variable values if they are not present in the restUrl', () => { + testRestUrlVariablesMap('https://example.com/api', {}, {}); + }); + + it('should merge the mapped variables with the input body', () => { + testRestUrlVariablesMap( + 'https://example.com/api?var1=${var1}', + { existingKey: 'existingValue' }, + { existingKey: 'existingValue', var1: 'value1' } + ); + }); +}); diff --git a/lib/process-services-cloud/src/lib/form/services/form-utils.service.ts b/lib/process-services-cloud/src/lib/form/services/form-utils.service.ts new file mode 100644 index 0000000000..627a9599b9 --- /dev/null +++ b/lib/process-services-cloud/src/lib/form/services/form-utils.service.ts @@ -0,0 +1,32 @@ +/*! + * @license + * Copyright © 2005-2024 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 { Injectable } from '@angular/core'; +import { FormModel, FormVariableModel } from '@alfresco/adf-core'; + +@Injectable({ + providedIn: 'root' +}) +export class FormUtilsService { + getRestUrlVariablesMap(formModel: FormModel, restUrl: string, inputBody: { [key: string]: any }) { + return formModel.variables.reduce((map: { [key: string]: any }, variable: FormVariableModel) => { + const variablePattern = new RegExp(`\\$\\{${variable.name}\\}`); + if (variablePattern.test(restUrl)) map[variable.name] = formModel.getProcessVariableValue(variable.name); + return map; + }, inputBody); + } +}