From 4ffa338585a5dad90ac375e6720df429446e2ffb Mon Sep 17 00:00:00 2001 From: siva kumar Date: Mon, 24 Jan 2022 14:47:44 +0530 Subject: [PATCH] [AAE-6660] FE - [ADF] Linked dropdown still contains old options when the rest api does not return results (#7454) * [AAE-6660] FE - [ADF] Linked dropdown still contains old options when the rest api does not return results * * Added unit tests to the recent changes done on dropdown. * * Improved error message * My empty commit to kick start travis * * Fixed lint errors * * Fixed css lint error * * Fixed failing unit test --- lib/core/i18n/en.json | 1 + .../dropdown/dropdown-cloud.widget.html | 2 + .../dropdown/dropdown-cloud.widget.scss | 4 + .../dropdown/dropdown-cloud.widget.spec.ts | 73 +++++++++++++++++-- .../widgets/dropdown/dropdown-cloud.widget.ts | 31 +++++++- .../src/lib/form/mocks/dropdown.mock.ts | 5 ++ 6 files changed, 109 insertions(+), 7 deletions(-) diff --git a/lib/core/i18n/en.json b/lib/core/i18n/en.json index 3617ef42b2..6798f8606b 100644 --- a/lib/core/i18n/en.json +++ b/lib/core/i18n/en.json @@ -42,6 +42,7 @@ "REMOVE_FILE": "Remove", "UPLOAD": "UPLOAD", "REQUIRED": "*Required", + "REST_API_FAILED": "The server `{{ hostname }}` is not reachable", "FILE_NAME": "File Name", "NO_FILE_ATTACHED": "No file attached", "VALIDATOR": { diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.html b/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.html index 35a78ae2a2..7b04b5666e 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.html +++ b/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.html @@ -29,4 +29,6 @@ + diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.scss b/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.scss index e770d674b6..9a675ba27a 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.scss +++ b/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.scss @@ -19,5 +19,9 @@ &-dropdown-required-message .adf-error-text-container { margin-top: 1px !important; } + + &-dropdown-failed-message .adf-error-text-container { + margin-top: 1px !important; + } } } 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 aed7b27ddf..408d8411ec 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 @@ -17,7 +17,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { of } from 'rxjs'; +import { of, throwError } from 'rxjs'; import { DropdownCloudWidgetComponent } from './dropdown-cloud.widget'; import { FormFieldModel, FormModel, FormService, setupTestBed } from '@alfresco/adf-core'; import { FormCloudService } from '../../../services/form-cloud.service'; @@ -27,7 +27,8 @@ import { fakeOptionList, filterOptionList, mockConditionalEntries, - mockRestDropdownOptions + mockRestDropdownOptions, + mockSecondRestDropdownOptions } from '../../../mocks/dropdown.mock'; import { OverlayContainer } from '@angular/cdk/overlay'; @@ -78,7 +79,7 @@ describe('DropdownCloudWidgetComponent', () => { name: 'date-name', type: 'dropdown', readOnly: false, - restUrl: 'fake-rest-url' + restUrl: 'https://fake-rest-url' }); widget.field.emptyOption = { id: 'empty', name: 'Choose one...' }; widget.field.isVisible = true; @@ -134,7 +135,7 @@ describe('DropdownCloudWidgetComponent', () => { it('should load data from restUrl and populate options', async () => { const jsonDataSpy = spyOn(formCloudService, 'getRestWidgetData').and.returnValue(of(fakeOptionList)); - widget.field.restUrl = 'fake-rest-url'; + widget.field.restUrl = 'https://fake-rest-url'; widget.field.optionType = 'rest'; widget.field.restIdProperty = 'name'; @@ -159,8 +160,30 @@ describe('DropdownCloudWidgetComponent', () => { expect(optThree.nativeElement.innerText).toEqual('option_3'); }); + it('should show error message if the restUrl failed to fetch options', async () => { + const jsonDataSpy = spyOn(formCloudService, 'getRestWidgetData').and.returnValue(throwError('Failed to fetch options')); + widget.field.restUrl = 'https://fake-rest-url'; + widget.field.optionType = 'rest'; + widget.field.restIdProperty = 'name'; + + widget.ngOnInit(); + fixture.detectChanges(); + await fixture.whenStable(); + + const dropdown = fixture.debugElement.query(By.css('mat-select')); + dropdown.nativeElement.click(); + fixture.detectChanges(); + await fixture.whenStable(); + const failedErrorMsgElement = fixture.debugElement.query(By.css('.adf-dropdown-failed-message')); + + expect(jsonDataSpy).toHaveBeenCalled(); + expect(widget.isRestApiFailed).toBe(true); + expect(widget.field.options.length).toEqual(0); + expect(failedErrorMsgElement.nativeElement.innerText.trim()).toBe('FORM.FIELD.REST_API_FAILED'); + }); + it('should preselect dropdown widget value when Json (rest call) passed', async () => { - widget.field.restUrl = 'fake-rest-url'; + widget.field.restUrl = 'https://fake-rest-url'; widget.field.optionType = 'rest'; widget.field.value = { id: 'opt1', @@ -188,7 +211,7 @@ describe('DropdownCloudWidgetComponent', () => { }); it('should preselect dropdown widget value when String (defined value) passed ', async () => { - widget.field.restUrl = 'fake-rest-url'; + widget.field.restUrl = 'https://fake-rest-url'; widget.field.optionType = 'rest'; widget.field.value = 'opt1'; @@ -367,6 +390,44 @@ describe('DropdownCloudWidgetComponent', () => { expect(optTwo.context.value).toBe('MA'); expect(optTwo.context.viewValue).toBe('MANCHESTER'); }); + + it('should reset previous child options if the rest url failed for a linked dropdown', async () => { + const jsonDataSpy = spyOn(formCloudService, 'getRestWidgetData').and.returnValue(of(mockRestDropdownOptions)); + const mockParentDropdown = { id: 'parentDropdown', value: 'mock-value', validate: () => true }; + spyOn(widget.field.form, 'getFormFields').and.returnValue([mockParentDropdown]); + + function selectParentOption(parentOptionName: string) { + parentDropdown.value = parentOptionName; + widget.selectionChangedForField(parentDropdown); + fixture.detectChanges(); + } + + selectParentOption('UK'); + await openSelect('child-dropdown-id'); + const failedErrorMsgElement1 = fixture.debugElement.query(By.css('.adf-dropdown-failed-message')); + + expect(widget.isRestApiFailed).toBe(false); + expect(widget.field.options.length).toBe(2); + expect(failedErrorMsgElement1).toBeNull(); + + jsonDataSpy.and.returnValue(throwError('Failed to fetch options')); + selectParentOption('GR'); + await openSelect('child-dropdown-id'); + const failedErrorMsgElement2 = fixture.debugElement.query(By.css('.adf-dropdown-failed-message')); + + expect(widget.isRestApiFailed).toBe(true); + expect(widget.field.options.length).toBe(0); + expect(failedErrorMsgElement2.nativeElement.innerText.trim()).toBe('FORM.FIELD.REST_API_FAILED'); + + jsonDataSpy.and.returnValue(of(mockSecondRestDropdownOptions)); + selectParentOption('IT'); + await openSelect('child-dropdown-id'); + const failedErrorMsgElement3 = fixture.debugElement.query(By.css('.adf-dropdown-failed-message')); + + expect(widget.isRestApiFailed).toBe(false); + expect(widget.field.options.length).toBe(2); + expect(failedErrorMsgElement3).toBeNull(); + }); }); describe('Manual options', () => { 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 6dd75aa277..d28b7c4ac5 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 @@ -59,6 +59,8 @@ export class DropdownCloudWidgetComponent extends WidgetComponent implements OnI typeId = 'DropdownCloudWidgetComponent'; HIDE_FILTER_LIMIT = 5; showInputFilter = false; + isRestApiFailed = false; + restApiHostName: string; list$: Observable; filter$ = new BehaviorSubject(''); @@ -93,14 +95,19 @@ export class DropdownCloudWidgetComponent extends WidgetComponent implements OnI private persistFieldOptionsFromRestApi() { if (this.isValidRestType()) { + this.resetRestApiErrorMessage(); const bodyParam = this.buildBodyParam(); this.formCloudService.getRestWidgetData(this.field.form.id, this.field.id, bodyParam) .pipe(takeUntil(this.onDestroy$)) .subscribe((result: FormFieldOption[]) => { + this.resetRestApiErrorMessage(); this.field.options = result; this.updateOptions(); this.field.updateForm(); - }, (err) => this.handleError(err)); + }, (err) => { + this.resetRestApiOptions(); + this.handleError(err); + }); } } @@ -129,6 +136,7 @@ export class DropdownCloudWidgetComponent extends WidgetComponent implements OnI if (this.isValidValue(value)) { this.isValidRestType() ? this.persistFieldOptionsFromRestApi() : this.persistFieldOptionsFromManualList(value); } else if (this.isDefaultValue(value)) { + this.resetRestApiErrorMessage(); this.addDefaultOption(); } } @@ -253,4 +261,25 @@ export class DropdownCloudWidgetComponent extends WidgetComponent implements OnI takeUntil(this.onDestroy$) ); } + + resetRestApiErrorMessage() { + this.isRestApiFailed = false; + this.restApiHostName = ''; + } + + resetRestApiOptions() { + this.field.options = []; + this.isRestApiFailed = true; + this.restApiHostName = this.getRestUrlHostName(); + this.updateOptions(); + this.field.updateForm(); + } + + private getRestUrlHostName(): string { + try { + return new URL(this.field?.restUrl).hostname; + } catch { + return this.field?.restUrl; + } + } } diff --git a/lib/process-services-cloud/src/lib/form/mocks/dropdown.mock.ts b/lib/process-services-cloud/src/lib/form/mocks/dropdown.mock.ts index 38b2ab2c2b..9cb69c0917 100644 --- a/lib/process-services-cloud/src/lib/form/mocks/dropdown.mock.ts +++ b/lib/process-services-cloud/src/lib/form/mocks/dropdown.mock.ts @@ -84,6 +84,11 @@ export const mockRestDropdownOptions: FormFieldOption[] = [ { id: 'MA', name: 'MANCHESTER' } ]; +export const mockSecondRestDropdownOptions: FormFieldOption[] = [ + { id: 'MI', name: 'MILAN' }, + { id: 'RM', name: 'ROME' } +]; + export const fakeOptionList: FormFieldOption[] = [ { id: 'opt_1', name: 'option_1' }, { id: 'opt_2', name: 'option_2' },