From: Stephan Müller Date: Wed, 1 Jul 2020 14:30:55 +0000 (+0200) Subject: mgr/dashboard: Fixes regression in device selection modal X-Git-Tag: v17.0.0~1879^2 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=10c14613eaaf3703bb1e19384456798706a0d027;p=ceph.git mgr/dashboard: Fixes regression in device selection modal The 'ExpressionChangedAfterItHasBeenCheckedError' has recently shown up in device selection modal in OSD creation form. It looks like it's a regression due to the modal switch (PR #35370). Which was merged yesterday and it had worked on Monday. Fixes: https://tracker.ceph.com/issues/46303 Signed-off-by: Stephan Müller --- diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-groups/osd-devices-selection-groups.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-groups/osd-devices-selection-groups.component.spec.ts index 2d7d516145335..893f981498782 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-groups/osd-devices-selection-groups.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-groups/osd-devices-selection-groups.component.spec.ts @@ -8,7 +8,8 @@ import { ToastrModule } from 'ngx-toastr'; import { configureTestBed, FixtureHelper, - i18nProviders + i18nProviders, + Mocks } from '../../../../../testing/unit-test-helper'; import { SharedModule } from '../../../../shared/shared.module'; import { InventoryDevice } from '../../inventory/inventory-devices/inventory-device.model'; @@ -19,25 +20,7 @@ describe('OsdDevicesSelectionGroupsComponent', () => { let component: OsdDevicesSelectionGroupsComponent; let fixture: ComponentFixture; let fixtureHelper: FixtureHelper; - const devices: InventoryDevice[] = [ - { - hostname: 'node0', - uid: '1', - path: 'sda', - sys_api: { - vendor: 'AAA', - model: 'aaa', - size: 1024, - rotational: 'false', - human_readable_size: '1 KB' - }, - available: false, - rejected_reasons: [''], - device_id: 'AAA-aaa-id0', - human_readable_type: 'nvme/ssd', - osd_ids: [] - } - ]; + const devices: InventoryDevice[] = [Mocks.getInventoryDevice('node0', '1')]; const buttonSelector = '.cd-col-form-input button'; const getButton = () => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-modal/osd-devices-selection-modal.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-modal/osd-devices-selection-modal.component.spec.ts index 91573a80649d9..6847104c67d72 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-modal/osd-devices-selection-modal.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-modal/osd-devices-selection-modal.component.spec.ts @@ -7,7 +7,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { ToastrModule } from 'ngx-toastr'; -import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper'; +import { configureTestBed, i18nProviders, Mocks } from '../../../../../testing/unit-test-helper'; import { CdTableColumnFiltersChange } from '../../../../shared/models/cd-table-column-filters-change'; import { SharedModule } from '../../../../shared/shared.module'; import { InventoryDevice } from '../../inventory/inventory-devices/inventory-device.model'; @@ -17,25 +17,9 @@ import { OsdDevicesSelectionModalComponent } from './osd-devices-selection-modal describe('OsdDevicesSelectionModalComponent', () => { let component: OsdDevicesSelectionModalComponent; let fixture: ComponentFixture; - const devices: InventoryDevice[] = [ - { - hostname: 'node0', - uid: '1', - path: 'sda', - sys_api: { - vendor: 'AAA', - model: 'aaa', - size: 1024, - rotational: 'false', - human_readable_size: '1 KB' - }, - available: false, - rejected_reasons: [''], - device_id: 'AAA-aaa-id0', - human_readable_type: 'nvme/ssd', - osd_ids: [] - } - ]; + let timeoutFn: Function; + + const devices: InventoryDevice[] = [Mocks.getInventoryDevice('node0', '1')]; const expectSubmitButton = (enabled: boolean) => { const nativeElement = fixture.debugElement.nativeElement; @@ -58,9 +42,28 @@ describe('OsdDevicesSelectionModalComponent', () => { }); beforeEach(() => { + spyOn(window, 'setTimeout').and.callFake((fn) => (timeoutFn = fn)); + fixture = TestBed.createComponent(OsdDevicesSelectionModalComponent); component = fixture.componentInstance; component.devices = devices; + + // Mocks InventoryDeviceComponent + component.inventoryDevices = { + columns: [ + { name: 'Device path', prop: 'path' }, + { + name: 'Type', + prop: 'human_readable_type' + }, + { + name: 'Available', + prop: 'available' + } + ] + } as InventoryDevicesComponent; + // Mocks the update from the above component + component.filterColumns = ['path', 'human_readable_type']; fixture.detectChanges(); }); @@ -72,6 +75,16 @@ describe('OsdDevicesSelectionModalComponent', () => { expectSubmitButton(false); }); + it( + 'should update requiredFilters after ngAfterViewInit is called to prevent ' + + 'ExpressionChangedAfterItHasBeenCheckedError', + () => { + expect(component.requiredFilters).toEqual([]); + timeoutFn(); + expect(component.requiredFilters).toEqual(['Device path', 'Type']); + } + ); + it('should enable submit button after filtering some devices', () => { const event: CdTableColumnFiltersChange = { filters: [ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-modal/osd-devices-selection-modal.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-modal/osd-devices-selection-modal.component.ts index deb613a3f0cce..4f677ac147dcd 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-modal/osd-devices-selection-modal.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-modal/osd-devices-selection-modal.component.ts @@ -54,7 +54,10 @@ export class OsdDevicesSelectionModalComponent implements AfterViewInit { const cols = _.filter(this.inventoryDevices.columns, (col) => { return this.filterColumns.includes(col.prop) && col.prop !== 'hostname'; }); - this.requiredFilters = _.map(cols, 'name'); + // Fixes 'ExpressionChangedAfterItHasBeenCheckedError' + setTimeout(() => { + this.requiredFilters = _.map(cols, 'name'); + }, 0); } createForm() { diff --git a/src/pybind/mgr/dashboard/frontend/src/testing/unit-test-helper.ts b/src/pybind/mgr/dashboard/frontend/src/testing/unit-test-helper.ts index 8b12c377a39b9..da024dc1990fc 100644 --- a/src/pybind/mgr/dashboard/frontend/src/testing/unit-test-helper.ts +++ b/src/pybind/mgr/dashboard/frontend/src/testing/unit-test-helper.ts @@ -8,6 +8,7 @@ import { NgbModal, NgbNav, NgbNavItem } from '@ng-bootstrap/ng-bootstrap'; import { I18n } from '@ngx-translate/i18n-polyfill'; import { configureTestSuite } from 'ng-bullet'; +import { InventoryDevice } from '../app/ceph/cluster/inventory/inventory-devices/inventory-device.model'; import { TableActionsComponent } from '../app/shared/datatable/table-actions/table-actions.component'; import { Icons } from '../app/shared/enum/icons.enum'; import { CdFormGroup } from '../app/shared/forms/cd-form-group'; @@ -549,6 +550,31 @@ export class Mocks { ]; return rule; } + + static getInventoryDevice( + hostname: string, + uid: string, + path = 'sda', + available = false + ): InventoryDevice { + return { + hostname, + uid, + path, + available, + sys_api: { + vendor: 'AAA', + model: 'aaa', + size: 1024, + rotational: 'false', + human_readable_size: '1 KB' + }, + rejected_reasons: [''], + device_id: 'AAA-aaa-id0', + human_readable_type: 'nvme/ssd', + osd_ids: [] + }; + } } export class TabHelper {