diff --git a/modules/ui/src/app/app.component.ts b/modules/ui/src/app/app.component.ts index 10f70f977..803dd2731 100644 --- a/modules/ui/src/app/app.component.ts +++ b/modules/ui/src/app/app.component.ts @@ -119,6 +119,8 @@ export class AppComponent implements AfterViewInit { this.appStore.getReports(); this.appStore.getTestModules(); this.appStore.getNetworkAdapters(); + this.appStore.getInterfaces(); + this.appStore.getSystemConfig(); this.matIconRegistry.addSvgIcon( 'device_run', this.domSanitizer.bypassSecurityTrustResourceUrl(DEVICES_RUN_URL) diff --git a/modules/ui/src/app/app.store.spec.ts b/modules/ui/src/app/app.store.spec.ts index 3e5d1897f..7df01953a 100644 --- a/modules/ui/src/app/app.store.spec.ts +++ b/modules/ui/src/app/app.store.spec.ts @@ -45,6 +45,8 @@ import { setDevices, updateAdapters, setTestModules, + fetchSystemConfig, + fetchInterfaces, } from './store/actions'; import { MOCK_PROGRESS_DATA_IN_PROGRESS } from './mocks/testrun.mock'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; @@ -518,5 +520,21 @@ describe('AppStore', () => { store.refreshState(); }); }); + + describe('getInterfaces', () => { + it('should dispatch action fetchInterfaces', () => { + appStore.getInterfaces(); + + expect(store.dispatch).toHaveBeenCalledWith(fetchInterfaces()); + }); + }); + + describe('getSystemConfig', () => { + it('should dispatch action fetchSystemConfig', () => { + appStore.getSystemConfig(); + + expect(store.dispatch).toHaveBeenCalledWith(fetchSystemConfig()); + }); + }); }); }); diff --git a/modules/ui/src/app/app.store.ts b/modules/ui/src/app/app.store.ts index 326c5cf40..11df3f571 100644 --- a/modules/ui/src/app/app.store.ts +++ b/modules/ui/src/app/app.store.ts @@ -52,6 +52,8 @@ import { fetchReports, setTestModules, updateAdapters, + fetchInterfaces, + fetchSystemConfig, } from './store/actions'; import { StatusOfTestrun, TestrunStatus } from './model/testrun-status'; import { @@ -320,6 +322,22 @@ export class AppStore extends ComponentStore { ); }); + getInterfaces = this.effect(trigger$ => { + return trigger$.pipe( + tap(() => { + this.store.dispatch(fetchInterfaces()); + }) + ); + }); + + getSystemConfig = this.effect(trigger$ => { + return trigger$.pipe( + tap(() => { + this.store.dispatch(fetchSystemConfig()); + }) + ); + }); + constructor() { // @ts-expect-error get object is defined in index.html const calloutState = sessionStorage.getObject(CALLOUT_STATE_KEY); diff --git a/modules/ui/src/app/components/device-item/device-item.component.scss b/modules/ui/src/app/components/device-item/device-item.component.scss index 5c078860a..3c1ca8c0a 100644 --- a/modules/ui/src/app/components/device-item/device-item.component.scss +++ b/modules/ui/src/app/components/device-item/device-item.component.scss @@ -70,7 +70,6 @@ $border-radius: 12px; width: 230px; box-sizing: border-box; text-align: start; - margin: 0; } .item-mac-address { @@ -163,6 +162,7 @@ $border-radius: 12px; max-width: -webkit-fill-available; max-width: -moz-available; text-align: left; + margin: 0; } app-program-type-icon { diff --git a/modules/ui/src/app/pages/general-settings/general-settings.store.spec.ts b/modules/ui/src/app/pages/general-settings/general-settings.store.spec.ts index 451a967df..562a06ee5 100644 --- a/modules/ui/src/app/pages/general-settings/general-settings.store.spec.ts +++ b/modules/ui/src/app/pages/general-settings/general-settings.store.spec.ts @@ -27,12 +27,18 @@ import { skip, take } from 'rxjs'; import { selectAdapters, selectHasConnectionSettings, + selectInterfaces, + selectSystemConfig, } from '../../store/selectors'; import { of } from 'rxjs/internal/observable/of'; -import { fetchSystemConfigSuccess } from '../../store/actions'; +import { + fetchInterfaces, + fetchSystemConfig, + fetchSystemConfigSuccess, +} from '../../store/actions'; import { fetchInterfacesSuccess } from '../../store/actions'; import { FormBuilder, FormControl } from '@angular/forms'; -import { FormKey, SystemConfig } from '../../model/setting'; +import { FormKey } from '../../model/setting'; import { MOCK_ADAPTERS, MOCK_DEVICE_VALUE, @@ -52,11 +58,7 @@ describe('GeneralSettingsStore', () => { let fb: FormBuilder; beforeEach(() => { - mockService = jasmine.createSpyObj([ - 'getSystemInterfaces', - 'createSystemConfig', - 'getSystemConfig', - ]); + mockService = jasmine.createSpyObj(['createSystemConfig']); TestBed.configureTestingModule({ providers: [ @@ -66,6 +68,8 @@ describe('GeneralSettingsStore', () => { selectors: [ { selector: selectHasConnectionSettings, value: true }, { selector: selectAdapters, value: {} }, + { selector: selectInterfaces, value: {} }, + { selector: selectSystemConfig, value: { network: {} } }, ], }), FormBuilder, @@ -83,28 +87,6 @@ describe('GeneralSettingsStore', () => { }); describe('updaters', () => { - describe('setSystemConfig', () => { - it('should update systemConfig', (done: DoneFn) => { - const config = { - network: { - device_intf: 'enx207bd2620617', - internet_intf: 'enx207bd2620618', - }, - log_level: 'INFO', - startup_timeout: 60, - monitor_period: 60, - runtime: 120, - } as SystemConfig; - - settingsStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { - expect(store.systemConfig).toEqual(config); - done(); - }); - - settingsStore.setSystemConfig(config); - }); - }); - it('should update isSubmitting', (done: DoneFn) => { settingsStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { expect(store.isSubmitting).toEqual(true); @@ -115,8 +97,7 @@ describe('GeneralSettingsStore', () => { }); it('should update interfaces', (done: DoneFn) => { - settingsStore.viewModel$.pipe(skip(3), take(1)).subscribe(store => { - expect(store.interfaces).toEqual(MOCK_INTERFACES); + settingsStore.viewModel$.pipe(skip(2), take(1)).subscribe(store => { expect(store.deviceOptions).toEqual(MOCK_INTERFACES); expect(store.internetOptions).toEqual({ mockDeviceKey: 'mockDeviceValue', @@ -150,52 +131,18 @@ describe('GeneralSettingsStore', () => { describe('effects', () => { describe('getSystemConfig', () => { - beforeEach(() => { - mockService.getSystemConfig.and.returnValue(of({ network: {} })); - }); - - it('should dispatch action fetchSystemConfigSuccess', () => { + it('should dispatch action fetchSystemConfig', () => { settingsStore.getSystemConfig(); - expect(store.dispatch).toHaveBeenCalledWith( - fetchSystemConfigSuccess({ systemConfig: { network: {} } }) - ); - }); - - it('should update store', done => { - settingsStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { - expect(store.systemConfig).toEqual({ network: {} }); - done(); - }); - - settingsStore.getSystemConfig(); + expect(store.dispatch).toHaveBeenCalledWith(fetchSystemConfig()); }); }); describe('getInterfaces', () => { - const interfaces = MOCK_INTERFACES; - - beforeEach(() => { - mockService.getSystemInterfaces.and.returnValue(of(interfaces)); - }); - it('should dispatch action fetchInterfacesSuccess', () => { settingsStore.getInterfaces(); - expect(store.dispatch).toHaveBeenCalledWith( - fetchInterfacesSuccess({ interfaces }) - ); - }); - - it('should update store', done => { - settingsStore.viewModel$.pipe(skip(3), take(1)).subscribe(store => { - expect(store.interfaces).toEqual(interfaces); - expect(store.deviceOptions).toEqual(interfaces); - expect(store.internetOptions).toEqual(interfaces); - done(); - }); - - settingsStore.getInterfaces(); + expect(store.dispatch).toHaveBeenCalledWith(fetchInterfaces()); }); }); @@ -217,20 +164,6 @@ describe('GeneralSettingsStore', () => { ); }); - it('should update store', done => { - settingsStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { - expect(store.systemConfig).toEqual({ network: {} }); - done(); - }); - - settingsStore.updateSystemConfig( - of({ - onSystemConfigUpdate: () => {}, - config: { network: {} }, - }) - ); - }); - it('should call onSystemConfigUpdate', () => { const effectParams = { onSystemConfigUpdate: () => {}, @@ -249,8 +182,12 @@ describe('GeneralSettingsStore', () => { describe('setDefaultFormValues', () => { describe('when values are present', () => { beforeEach(() => { - settingsStore.setSystemConfig(MOCK_SYSTEM_CONFIG_WITH_DATA); - settingsStore.setInterfaces(MOCK_INTERFACES); + store.overrideSelector(selectInterfaces, MOCK_INTERFACES); + store.overrideSelector( + selectSystemConfig, + MOCK_SYSTEM_CONFIG_WITH_DATA + ); + store.refreshState(); }); it('should set default form values', () => { @@ -279,8 +216,12 @@ describe('GeneralSettingsStore', () => { describe('with single port mode', () => { beforeEach(() => { - settingsStore.setSystemConfig(MOCK_SYSTEM_CONFIG_WITH_SINGLE_PORT); - settingsStore.setInterfaces(MOCK_INTERFACES); + store.overrideSelector(selectInterfaces, MOCK_INTERFACES); + store.overrideSelector( + selectSystemConfig, + MOCK_SYSTEM_CONFIG_WITH_SINGLE_PORT + ); + store.refreshState(); }); it('should disable internet control', () => { @@ -300,8 +241,12 @@ describe('GeneralSettingsStore', () => { describe('when values are empty', () => { beforeEach(() => { - settingsStore.setSystemConfig(MOCK_SYSTEM_CONFIG_WITH_NO_DATA); - settingsStore.setInterfaces(MOCK_INTERFACES); + store.overrideSelector(selectInterfaces, MOCK_INTERFACES); + store.overrideSelector( + selectSystemConfig, + MOCK_SYSTEM_CONFIG_WITH_NO_DATA + ); + store.refreshState(); }); it('should set default form values', () => { @@ -347,13 +292,14 @@ describe('GeneralSettingsStore', () => { beforeEach(() => { settingsStore.setInterfaces(MOCK_INTERFACES); + store.overrideSelector(selectInterfaces, MOCK_INTERFACES); + store.refreshState(); }); it('should update store', done => { settingsStore.viewModel$ - .pipe(skip(3), take(1)) + .pipe(skip(2), take(1)) .subscribe(storeValue => { - expect(storeValue.interfaces).toEqual(updateInterfaces); expect(storeValue.deviceOptions).toEqual(updateInterfaces); expect(storeValue.internetOptions).toEqual(updateInternetOptions); diff --git a/modules/ui/src/app/pages/general-settings/general-settings.store.ts b/modules/ui/src/app/pages/general-settings/general-settings.store.ts index 2256eee1f..870262452 100644 --- a/modules/ui/src/app/pages/general-settings/general-settings.store.ts +++ b/modules/ui/src/app/pages/general-settings/general-settings.store.ts @@ -31,16 +31,17 @@ import { AppState } from '../../store/state'; import { selectAdapters, selectHasConnectionSettings, + selectInterfaces, + selectSystemConfig, selectSystemStatus, } from '../../store/selectors'; import { FormControl, FormGroup } from '@angular/forms'; +import { fetchInterfaces, fetchSystemConfig } from '../../store/actions'; export interface SettingsComponentState { hasConnectionSettings: boolean; isSubmitting: boolean; - systemConfig: SystemConfig; isLessThanOneInterface: boolean; - interfaces: SystemInterfaces; deviceOptions: SystemInterfaces; internetOptions: SystemInterfaces; logLevelOptions: { [key: string]: string }; @@ -74,7 +75,6 @@ export class GeneralSettingsStore extends ComponentStore private static readonly DEFAULT_LOG_LEVEL = 'INFO'; private static readonly DEFAULT_MONITORING_PERIOD = '300'; - private systemConfig$ = this.select(state => state.systemConfig); private hasConnectionSettings$ = this.store.select( selectHasConnectionSettings ); @@ -85,7 +85,10 @@ export class GeneralSettingsStore extends ComponentStore private isLessThanOneInterfaces$ = this.select( state => state.isLessThanOneInterface ); - private interfaces$ = this.select(state => state.interfaces); + private interfaces$: Observable = + this.store.select(selectInterfaces); + private systemConfig$: Observable = + this.store.select(selectSystemConfig); private deviceOptions$ = this.select(state => state.deviceOptions); private internetOptions$ = this.select(state => state.internetOptions); private logLevelOptions$ = this.select(state => state.logLevelOptions); @@ -104,11 +107,6 @@ export class GeneralSettingsStore extends ComponentStore monitoringPeriodOptions: this.monitoringPeriodOptions$, }); - setSystemConfig = this.updater((state, systemConfig: SystemConfig) => ({ - ...state, - systemConfig, - })); - setIsSubmitting = this.updater((state, isSubmitting: boolean) => ({ ...state, isSubmitting, @@ -117,36 +115,33 @@ export class GeneralSettingsStore extends ComponentStore setInterfaces = this.updater((state, interfaces: SystemInterfaces) => { return { ...state, - interfaces, deviceOptions: interfaces, internetOptions: interfaces, isLessThanOneInterface: Object.keys(interfaces).length < 1, }; }); + statusLoaded = this.effect(() => { + return this.interfaces$.pipe( + skip(1), + tap(interfaces => { + this.setInterfaces(interfaces); + }) + ); + }); + getInterfaces = this.effect(trigger$ => { return trigger$.pipe( - exhaustMap(() => { - return this.testRunService.getSystemInterfaces().pipe( - tap((interfaces: SystemInterfaces) => { - this.updateInterfaces(interfaces); - }) - ); + tap(() => { + this.store.dispatch(fetchInterfaces()); }) ); }); getSystemConfig = this.effect(trigger$ => { return trigger$.pipe( - exhaustMap(() => { - return this.testRunService.getSystemConfig().pipe( - tap((systemConfig: SystemConfig) => { - this.store.dispatch( - AppActions.fetchSystemConfigSuccess({ systemConfig }) - ); - this.setSystemConfig(systemConfig); - }) - ); + tap(() => { + this.store.dispatch(fetchSystemConfig()); }) ); }); @@ -164,7 +159,6 @@ export class GeneralSettingsStore extends ComponentStore systemConfig: trigger.config, }) ); - this.setSystemConfig(trigger.config); trigger.onSystemConfigUpdate(); }) ); @@ -335,11 +329,9 @@ export class GeneralSettingsStore extends ComponentStore constructor() { super({ - systemConfig: { network: {} }, hasConnectionSettings: false, isSubmitting: false, isLessThanOneInterface: false, - interfaces: {}, deviceOptions: {}, internetOptions: {}, logLevelOptions: LOG_LEVELS, diff --git a/modules/ui/src/app/store/actions.ts b/modules/ui/src/app/store/actions.ts index 58153ad9a..5a290109b 100644 --- a/modules/ui/src/app/store/actions.ts +++ b/modules/ui/src/app/store/actions.ts @@ -142,3 +142,7 @@ export const updateInternetConnection = createAction( '[Shared] Fetch internet connection', props<{ internetConnection: boolean | null }>() ); + +export const fetchInterfaces = createAction('[Shared] Fetch interfaces'); + +export const fetchSystemConfig = createAction('[Shared] Fetch system config'); diff --git a/modules/ui/src/app/store/effects.spec.ts b/modules/ui/src/app/store/effects.spec.ts index c0a06352d..7ca894c27 100644 --- a/modules/ui/src/app/store/effects.spec.ts +++ b/modules/ui/src/app/store/effects.spec.ts @@ -431,4 +431,30 @@ describe('Effects', () => { }); }); }); + + it('onFetchInterfaces$ should call fetchInterfacesSuccess on success', done => { + actions$ = of(actions.fetchInterfaces()); + + effects.onFetchInterfaces$.subscribe(action => { + expect(action).toEqual( + actions.fetchInterfacesSuccess({ + interfaces: {}, + }) + ); + done(); + }); + }); + + it('onFetchSystemConfig$ should call fetchSystemConfigSuccess on success', done => { + actions$ = of(actions.fetchSystemConfig()); + + effects.onFetchSystemConfig$.subscribe(action => { + expect(action).toEqual( + actions.fetchSystemConfigSuccess({ + systemConfig: { network: {} }, + }) + ); + done(); + }); + }); }); diff --git a/modules/ui/src/app/store/effects.ts b/modules/ui/src/app/store/effects.ts index 7be67f1ee..1da1cfc00 100644 --- a/modules/ui/src/app/store/effects.ts +++ b/modules/ui/src/app/store/effects.ts @@ -53,6 +53,7 @@ import { Profile } from '../model/profile'; import { DeviceStatus } from '../model/device'; import { TestRunMqttService } from '../services/test-run-mqtt.service'; import { InternetConnection } from '../model/topic'; +import { SystemConfig, SystemInterfaces } from '../model/setting'; const WAIT_TO_OPEN_SNACKBAR_MS = 60 * 1000; @@ -293,6 +294,32 @@ export class AppEffects { ); }); + onFetchInterfaces$ = createEffect(() => { + return this.actions$.pipe( + ofType(AppActions.fetchInterfaces), + switchMap(() => + this.testrunService.getSystemInterfaces().pipe( + map((interfaces: SystemInterfaces) => { + return AppActions.fetchInterfacesSuccess({ interfaces }); + }) + ) + ) + ); + }); + + onFetchSystemConfig$ = createEffect(() => { + return this.actions$.pipe( + ofType(AppActions.fetchSystemConfig), + switchMap(() => + this.testrunService.getSystemConfig().pipe( + map((systemConfig: SystemConfig) => { + return AppActions.fetchSystemConfigSuccess({ systemConfig }); + }) + ) + ) + ); + }); + private isTestrunFinished(status: string) { return ( status === StatusOfTestrun.Compliant ||