From 8e449829847c53299675cd823f35a3db4f7b5098 Mon Sep 17 00:00:00 2001 From: kurilova Date: Mon, 19 Aug 2024 12:18:52 +0000 Subject: [PATCH 1/2] Adds expired device callout --- modules/ui/src/app/app.component.html | 26 +++++++++++++++-- modules/ui/src/app/app.component.spec.ts | 28 +++++++++++++++++++ modules/ui/src/app/app.component.ts | 3 ++ modules/ui/src/app/app.store.spec.ts | 3 ++ modules/ui/src/app/app.store.ts | 3 ++ .../components/callout/callout.component.html | 5 +++- .../components/callout/callout.component.scss | 10 +++++++ .../callout/callout.component.spec.ts | 22 +++++++++++++++ .../components/callout/callout.component.ts | 12 ++++++-- .../device-item/device-item.component.spec.ts | 3 +- modules/ui/src/app/mocks/device.mock.ts | 20 ++++++++++++- modules/ui/src/app/mocks/reports.mock.ts | 9 ++++++ modules/ui/src/app/mocks/testrun.mock.ts | 2 ++ modules/ui/src/app/model/device.ts | 6 ++++ .../device-form/device-form.component.spec.ts | 5 +++- .../device-form/device-form.component.ts | 3 +- ...evice-qualification-from.component.spec.ts | 3 ++ .../testrun-initiate-form.component.spec.ts | 3 +- modules/ui/src/app/store/actions.ts | 5 ++++ modules/ui/src/app/store/effects.spec.ts | 13 ++++++++- modules/ui/src/app/store/effects.ts | 14 ++++++++++ modules/ui/src/app/store/reducers.spec.ts | 13 +++++++++ modules/ui/src/app/store/reducers.ts | 6 ++++ modules/ui/src/app/store/selectors.spec.ts | 7 +++++ modules/ui/src/app/store/selectors.ts | 5 ++++ modules/ui/src/app/store/state.ts | 2 ++ 26 files changed, 219 insertions(+), 12 deletions(-) diff --git a/modules/ui/src/app/app.component.html b/modules/ui/src/app/app.component.html index 122e8b129..9bb249ba4 100644 --- a/modules/ui/src/app/app.component.html +++ b/modules/ui/src/app/app.component.html @@ -171,9 +171,9 @@

Testrun

*ngIf="vm.hasConnectionSettings === true && vm.hasDevices === false"> Step 2: To perform a device test please Testrun >Risk Assessment questionnaire? + + Further information is required in your device configurations. Please + update your + Devices + to continue testing. + diff --git a/modules/ui/src/app/app.component.spec.ts b/modules/ui/src/app/app.component.spec.ts index b626123d7..6bc7505b8 100644 --- a/modules/ui/src/app/app.component.spec.ts +++ b/modules/ui/src/app/app.component.spec.ts @@ -54,6 +54,7 @@ import { selectError, selectHasConnectionSettings, selectHasDevices, + selectHasExpiredDevices, selectHasRiskProfiles, selectInterfaces, selectIsOpenStartTestrun, @@ -164,6 +165,7 @@ describe('AppComponent', () => { { selector: selectError, value: null }, { selector: selectMenuOpened, value: false }, { selector: selectHasDevices, value: false }, + { selector: selectHasExpiredDevices, value: false }, { selector: selectHasRiskProfiles, value: false }, { selector: selectStatus, value: null }, { selector: selectSystemStatus, value: null }, @@ -757,6 +759,32 @@ describe('AppComponent', () => { }); }); }); + + describe('with expired devices', () => { + beforeEach(() => { + store.overrideSelector(selectHasExpiredDevices, true); + fixture.detectChanges(); + }); + + it('should have callout component', () => { + const callouts = compiled.querySelectorAll('app-callout'); + //const calloutContent = callout?.innerHTML.trim(); + let hasExpiredDeviceCallout = false; + callouts.forEach(callout => { + if ( + callout?.innerHTML + .trim() + .includes( + 'Further information is required in your device configurations.' + ) + ) { + hasExpiredDeviceCallout = true; + } + }); + + expect(hasExpiredDeviceCallout).toBeTrue(); + }); + }); }); it('should not call toggleSettingsBtn focus on closeSetting when device length is 0', async () => { diff --git a/modules/ui/src/app/app.component.ts b/modules/ui/src/app/app.component.ts index 2214b8927..a3a28eb48 100644 --- a/modules/ui/src/app/app.component.ts +++ b/modules/ui/src/app/app.component.ts @@ -123,6 +123,9 @@ export class AppComponent { navigateToDeviceRepository(): void { this.route.navigate([Routes.Devices]); + } + navigateToAddDevice(): void { + this.route.navigate([Routes.Devices]); this.store.dispatch(setIsOpenAddDevice({ isOpenAddDevice: true })); } diff --git a/modules/ui/src/app/app.store.spec.ts b/modules/ui/src/app/app.store.spec.ts index 0130e9b40..db8f5cc3c 100644 --- a/modules/ui/src/app/app.store.spec.ts +++ b/modules/ui/src/app/app.store.spec.ts @@ -22,6 +22,7 @@ import { selectError, selectHasConnectionSettings, selectHasDevices, + selectHasExpiredDevices, selectHasRiskProfiles, selectInterfaces, selectIsOpenWaitSnackBar, @@ -109,6 +110,7 @@ describe('AppStore', () => { appStore = TestBed.inject(AppStore); store.overrideSelector(selectHasDevices, true); + store.overrideSelector(selectHasExpiredDevices, true); store.overrideSelector(selectHasRiskProfiles, false); store.overrideSelector(selectReports, []); store.overrideSelector(selectHasConnectionSettings, true); @@ -154,6 +156,7 @@ describe('AppStore', () => { expect(store).toEqual({ consentShown: false, hasDevices: true, + hasExpiredDevices: true, hasRiskProfiles: false, reports: [], isStatusLoaded: false, diff --git a/modules/ui/src/app/app.store.ts b/modules/ui/src/app/app.store.ts index 562d2618e..cd8c69474 100644 --- a/modules/ui/src/app/app.store.ts +++ b/modules/ui/src/app/app.store.ts @@ -21,6 +21,7 @@ import { selectError, selectHasConnectionSettings, selectHasDevices, + selectHasExpiredDevices, selectHasRiskProfiles, selectInterfaces, selectMenuOpened, @@ -62,6 +63,7 @@ export class AppStore extends ComponentStore { private consentShown$ = this.select(state => state.consentShown); private isStatusLoaded$ = this.select(state => state.isStatusLoaded); private hasDevices$ = this.store.select(selectHasDevices); + private hasExpiredDevices$ = this.store.select(selectHasExpiredDevices); private hasRiskProfiles$ = this.store.select(selectHasRiskProfiles); private reports$ = this.store.select(selectReports); private hasConnectionSetting$ = this.store.select( @@ -77,6 +79,7 @@ export class AppStore extends ComponentStore { viewModel$ = this.select({ consentShown: this.consentShown$, hasDevices: this.hasDevices$, + hasExpiredDevices: this.hasExpiredDevices$, hasRiskProfiles: this.hasRiskProfiles$, reports: this.reports$, isStatusLoaded: this.isStatusLoaded$, diff --git a/modules/ui/src/app/components/callout/callout.component.html b/modules/ui/src/app/components/callout/callout.component.html index a3fb7930c..6e7438c78 100644 --- a/modules/ui/src/app/components/callout/callout.component.html +++ b/modules/ui/src/app/components/callout/callout.component.html @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. --> -
+

+
diff --git a/modules/ui/src/app/components/callout/callout.component.scss b/modules/ui/src/app/components/callout/callout.component.scss index 8a9d77125..5a1abc842 100644 --- a/modules/ui/src/app/components/callout/callout.component.scss +++ b/modules/ui/src/app/components/callout/callout.component.scss @@ -31,6 +31,10 @@ top: 60px; } +:host.hidden { + display: none; +} + @media (width < 742px) { :host + ::ng-deep app-callout { top: 80px; @@ -117,3 +121,9 @@ line-height: 20px; letter-spacing: 0.2px; } + +.callout-close-button { + margin-left: auto; + margin-right: -20px; + color: $warn; +} diff --git a/modules/ui/src/app/components/callout/callout.component.spec.ts b/modules/ui/src/app/components/callout/callout.component.spec.ts index 28215ec7c..342cb19aa 100644 --- a/modules/ui/src/app/components/callout/callout.component.spec.ts +++ b/modules/ui/src/app/components/callout/callout.component.spec.ts @@ -42,4 +42,26 @@ describe('CalloutComponent', () => { expect(calloutContainerdEl?.classList).toContain('mockValue'); }); + + describe('closeable', () => { + beforeEach(() => { + component.closable = true; + fixture.detectChanges(); + }); + + it('should have close button', () => { + const closeButton = compiled.querySelector('.callout-close-button'); + + expect(closeButton).toBeTruthy(); + }); + + it('should have hidden class on close button clicked', () => { + const closeButton = compiled.querySelector( + '.callout-close-button' + ) as HTMLButtonElement; + closeButton?.click(); + + expect(component.closed).toBeTrue(); + }); + }); }); diff --git a/modules/ui/src/app/components/callout/callout.component.ts b/modules/ui/src/app/components/callout/callout.component.ts index bfb6ea9bf..ab517fa70 100644 --- a/modules/ui/src/app/components/callout/callout.component.ts +++ b/modules/ui/src/app/components/callout/callout.component.ts @@ -13,18 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + HostBinding, + Input, +} from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatIconModule } from '@angular/material/icon'; +import { MatButtonModule } from '@angular/material/button'; @Component({ selector: 'app-callout', standalone: true, - imports: [CommonModule, MatIconModule], + imports: [CommonModule, MatIconModule, MatButtonModule], templateUrl: './callout.component.html', styleUrls: ['./callout.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) export class CalloutComponent { + @HostBinding('class.hidden') closed: boolean = false; @Input() type = ''; + @Input() closable = false; } diff --git a/modules/ui/src/app/components/device-item/device-item.component.spec.ts b/modules/ui/src/app/components/device-item/device-item.component.spec.ts index 2fe79d8db..dce87cda3 100644 --- a/modules/ui/src/app/components/device-item/device-item.component.spec.ts +++ b/modules/ui/src/app/components/device-item/device-item.component.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { Device, DeviceView } from '../../model/device'; +import { Device, DeviceStatus, DeviceView } from '../../model/device'; import { DeviceItemComponent } from './device-item.component'; import { DevicesModule } from '../../pages/devices/devices.module'; @@ -39,6 +39,7 @@ describe('DeviceItemComponent', () => { component = fixture.componentInstance; compiled = fixture.nativeElement as HTMLElement; component.device = { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: 'O3-DIN-CPU', mac_addr: '00:1e:42:35:73:c4', diff --git a/modules/ui/src/app/mocks/device.mock.ts b/modules/ui/src/app/mocks/device.mock.ts index c8b12c523..33e1006a0 100644 --- a/modules/ui/src/app/mocks/device.mock.ts +++ b/modules/ui/src/app/mocks/device.mock.ts @@ -13,11 +13,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Device, DeviceQuestionnaireSection } from '../model/device'; +import { + Device, + DeviceStatus, + DeviceQuestionnaireSection, +} from '../model/device'; import { ProfileRisk } from '../model/profile'; import { FormControlType } from '../model/question'; export const device = { + status: DeviceStatus.VALID, + manufacturer: 'Delta', + model: 'O3-DIN-CPU', + mac_addr: '00:1e:42:35:73:c4', + test_modules: { + dns: { + enabled: true, + }, + }, +} as Device; + +export const expired_device = { + status: DeviceStatus.INVALID, manufacturer: 'Delta', model: 'O3-DIN-CPU', mac_addr: '00:1e:42:35:73:c4', @@ -28,6 +45,7 @@ export const device = { }, } as Device; export const updated_device = { + status: DeviceStatus.VALID, manufacturer: 'Alpha', model: 'O3-XYZ-CPU', mac_addr: '00:1e:42:35:73:11', diff --git a/modules/ui/src/app/mocks/reports.mock.ts b/modules/ui/src/app/mocks/reports.mock.ts index e1422a36c..82e0a48ef 100644 --- a/modules/ui/src/app/mocks/reports.mock.ts +++ b/modules/ui/src/app/mocks/reports.mock.ts @@ -1,11 +1,13 @@ import { HistoryTestrun, TestrunStatus } from '../model/testrun-status'; import { MatTableDataSource } from '@angular/material/table'; +import { DeviceStatus } from '../model/device'; export const HISTORY = [ { mac_addr: '01:02:03:04:05:06', status: 'compliant', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-SRC', mac_addr: '01:02:03:04:05:06', @@ -19,6 +21,7 @@ export const HISTORY = [ status: 'compliant', mac_addr: '01:02:03:04:05:07', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-SRC', mac_addr: '01:02:03:04:05:07', @@ -32,6 +35,7 @@ export const HISTORY = [ mac_addr: null, status: 'compliant', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-SRC', mac_addr: '01:02:03:04:05:08', @@ -48,6 +52,7 @@ export const HISTORY_AFTER_REMOVE = [ mac_addr: '01:02:03:04:05:06', status: 'compliant', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-SRC', mac_addr: '01:02:03:04:05:06', @@ -61,6 +66,7 @@ export const HISTORY_AFTER_REMOVE = [ mac_addr: null, status: 'compliant', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-SRC', mac_addr: '01:02:03:04:05:08', @@ -77,6 +83,7 @@ export const FORMATTED_HISTORY = [ status: 'compliant', mac_addr: '01:02:03:04:05:06', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-SRC', mac_addr: '01:02:03:04:05:06', @@ -93,6 +100,7 @@ export const FORMATTED_HISTORY = [ status: 'compliant', mac_addr: '01:02:03:04:05:07', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-SRC', mac_addr: '01:02:03:04:05:07', @@ -109,6 +117,7 @@ export const FORMATTED_HISTORY = [ mac_addr: null, status: 'compliant', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-SRC', mac_addr: '01:02:03:04:05:08', diff --git a/modules/ui/src/app/mocks/testrun.mock.ts b/modules/ui/src/app/mocks/testrun.mock.ts index bb588634c..f782c58f3 100644 --- a/modules/ui/src/app/mocks/testrun.mock.ts +++ b/modules/ui/src/app/mocks/testrun.mock.ts @@ -19,6 +19,7 @@ import { TestrunStatus, TestsData, } from '../model/testrun-status'; +import { DeviceStatus } from '../model/device'; export const TEST_DATA_RESULT: IResult[] = [ { @@ -71,6 +72,7 @@ const PROGRESS_DATA_RESPONSE = ( status, mac_addr: '01:02:03:04:05:06', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-CPU', mac_addr: '01:02:03:04:05:06', diff --git a/modules/ui/src/app/model/device.ts b/modules/ui/src/app/model/device.ts index 1fa187838..4a0d3d14d 100644 --- a/modules/ui/src/app/model/device.ts +++ b/modules/ui/src/app/model/device.ts @@ -21,6 +21,12 @@ export interface Device { mac_addr: string; test_modules?: TestModules; firmware?: string; + status: DeviceStatus; +} + +export enum DeviceStatus { + VALID = 'Valid', + INVALID = 'Invalid', } /** diff --git a/modules/ui/src/app/pages/devices/components/device-form/device-form.component.spec.ts b/modules/ui/src/app/pages/devices/components/device-form/device-form.component.spec.ts index 1911402a0..087adc37e 100644 --- a/modules/ui/src/app/pages/devices/components/device-form/device-form.component.spec.ts +++ b/modules/ui/src/app/pages/devices/components/device-form/device-form.component.spec.ts @@ -31,7 +31,7 @@ import { MatDialogRef, } from '@angular/material/dialog'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { Device } from '../../../../model/device'; +import { Device, DeviceStatus } from '../../../../model/device'; import { of } from 'rxjs'; import { DeviceTestsComponent } from '../../../../components/device-tests/device-tests.component'; import { SpinnerComponent } from '../../../../components/spinner/spinner.component'; @@ -167,6 +167,7 @@ describe('DeviceFormComponent', () => { it('should save data when form is valid', () => { const device: Device = { + status: DeviceStatus.VALID, manufacturer: 'manufacturer', model: 'model', mac_addr: '07:07:07:07:07:07', @@ -371,6 +372,7 @@ describe('DeviceFormComponent', () => { devices: [device], testModules: MOCK_TEST_MODULES, device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: 'O3-DIN-CPU', mac_addr: '00:1e:42:35:73:c4', @@ -412,6 +414,7 @@ describe('DeviceFormComponent', () => { const args = mockDevicesStore.editDevice.calls.argsFor(0); // @ts-expect-error config is in object expect(args[0].device).toEqual({ + status: DeviceStatus.VALID, manufacturer: 'Delta', model: 'O3-DIN-CPU', mac_addr: '00:1e:42:35:73:c4', diff --git a/modules/ui/src/app/pages/devices/components/device-form/device-form.component.ts b/modules/ui/src/app/pages/devices/components/device-form/device-form.component.ts index f7aa4f180..36743230c 100644 --- a/modules/ui/src/app/pages/devices/components/device-form/device-form.component.ts +++ b/modules/ui/src/app/pages/devices/components/device-form/device-form.component.ts @@ -23,7 +23,7 @@ import { } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { Device, TestModule } from '../../../../model/device'; +import { Device, DeviceStatus, TestModule } from '../../../../model/device'; import { DeviceValidators } from './device.validators'; import { Subject } from 'rxjs'; import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; @@ -159,6 +159,7 @@ export class DeviceFormComponent } ); return { + status: DeviceStatus.VALID, model: this.model.value.trim(), manufacturer: this.manufacturer.value.trim(), mac_addr: this.mac_addr.value.trim(), diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts index 2dd7263f2..ec7e4dd11 100644 --- a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts +++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts @@ -40,6 +40,8 @@ import { TestRunService } from '../../../../services/test-run.service'; import { DevicesStore } from '../../devices.store'; import { provideMockStore } from '@ngrx/store/testing'; import { FormAction } from '../../devices.component'; +import { DeviceStatus } from '../../../../model/device'; + describe('DeviceQualificationFromComponent', () => { let component: DeviceQualificationFromComponent; let fixture: ComponentFixture; @@ -351,6 +353,7 @@ describe('DeviceQualificationFromComponent', () => { devices: [device], testModules: MOCK_TEST_MODULES, device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: 'O3-DIN-CPU', mac_addr: '00:1e:42:35:73:c4', diff --git a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.spec.ts b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.spec.ts index 2a99f17b0..6fe08782b 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.spec.ts +++ b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.spec.ts @@ -23,7 +23,7 @@ import { } from '@angular/material/dialog'; import { TestRunService } from '../../../../services/test-run.service'; import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; -import { Device } from '../../../../model/device'; +import { Device, DeviceStatus } from '../../../../model/device'; import { DeviceItemComponent } from '../../../../components/device-item/device-item.component'; import { ReactiveFormsModule } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; @@ -196,6 +196,7 @@ describe('ProgressInitiateFormComponent', () => { component.startTestRun(); expect(testRunServiceMock.startTestrun).toHaveBeenCalledWith({ + status: DeviceStatus.VALID, manufacturer: 'Delta', model: 'O3-DIN-CPU', mac_addr: '00:1e:42:35:73:c4', diff --git a/modules/ui/src/app/store/actions.ts b/modules/ui/src/app/store/actions.ts index 9235f980f..cf9d3eea5 100644 --- a/modules/ui/src/app/store/actions.ts +++ b/modules/ui/src/app/store/actions.ts @@ -80,6 +80,11 @@ export const setHasDevices = createAction( props<{ hasDevices: boolean }>() ); +export const setHasExpiredDevices = createAction( + '[Shared] Set Has Expired Devices', + props<{ hasExpiredDevices: boolean }>() +); + export const setDevices = createAction( '[Shared] Set Devices', props<{ devices: Device[] }>() diff --git a/modules/ui/src/app/store/effects.spec.ts b/modules/ui/src/app/store/effects.spec.ts index 6f6fae967..b469f8752 100644 --- a/modules/ui/src/app/store/effects.spec.ts +++ b/modules/ui/src/app/store/effects.spec.ts @@ -33,7 +33,7 @@ import { selectMenuOpened, selectSystemStatus, } from './selectors'; -import { device } from '../mocks/device.mock'; +import { device, expired_device } from '../mocks/device.mock'; import { MOCK_PROGRESS_DATA_CANCELLING, MOCK_PROGRESS_DATA_COMPLIANT, @@ -141,6 +141,17 @@ describe('Effects', () => { }); }); + it('onSetExpiredDevices$ should call setHasExpiredDevices', done => { + actions$ = of(actions.setDevices({ devices: [device, expired_device] })); + + effects.onSetExpiredDevices$.subscribe(action => { + expect(action).toEqual( + actions.setHasExpiredDevices({ hasExpiredDevices: true }) + ); + done(); + }); + }); + it('onSetRiskProfiles$ should call setHasRiskProfiles', done => { actions$ = of(actions.setRiskProfiles({ riskProfiles: [PROFILE_MOCK] })); diff --git a/modules/ui/src/app/store/effects.ts b/modules/ui/src/app/store/effects.ts index b94eb55b1..3405c6ef2 100644 --- a/modules/ui/src/app/store/effects.ts +++ b/modules/ui/src/app/store/effects.ts @@ -54,6 +54,7 @@ import { import { takeUntil } from 'rxjs/internal/operators/takeUntil'; import { NotificationService } from '../services/notification.service'; import { Profile } from '../model/profile'; +import { DeviceStatus } from '../model/device'; const WAIT_TO_OPEN_SNACKBAR_MS = 60 * 1000; @@ -144,6 +145,19 @@ export class AppEffects { ); }); + onSetExpiredDevices$ = createEffect(() => { + return this.actions$.pipe( + ofType(AppActions.setDevices), + map(({ devices }) => + AppActions.setHasExpiredDevices({ + hasExpiredDevices: devices.some( + device => device.status === DeviceStatus.INVALID + ), + }) + ) + ); + }); + onSetRiskProfiles$ = createEffect(() => { return this.actions$.pipe( ofType(AppActions.setRiskProfiles), diff --git a/modules/ui/src/app/store/reducers.spec.ts b/modules/ui/src/app/store/reducers.spec.ts index 7d7d5219c..56d52d61d 100644 --- a/modules/ui/src/app/store/reducers.spec.ts +++ b/modules/ui/src/app/store/reducers.spec.ts @@ -21,6 +21,7 @@ import { setDevices, setHasConnectionSettings, setHasDevices, + setHasExpiredDevices, setHasRiskProfiles, setIsOpenAddDevice, setIsOpenStartTestrun, @@ -164,6 +165,18 @@ describe('Reducer', () => { }); }); + describe('setHasExpiredDevices action', () => { + it('should update state', () => { + const initialState = initialSharedState; + const action = setHasExpiredDevices({ hasExpiredDevices: true }); + const state = fromReducer.sharedReducer(initialState, action); + const newState = { ...initialState, ...{ hasExpiredDevices: true } }; + + expect(state).toEqual(newState); + expect(state).not.toBe(initialState); + }); + }); + describe('setDevices action', () => { it('should update state', () => { const initialState = initialSharedState; diff --git a/modules/ui/src/app/store/reducers.ts b/modules/ui/src/app/store/reducers.ts index 9d8bc7cde..05d1c6c61 100644 --- a/modules/ui/src/app/store/reducers.ts +++ b/modules/ui/src/app/store/reducers.ts @@ -65,6 +65,12 @@ export const sharedReducer = createReducer( hasDevices, }; }), + on(Actions.setHasExpiredDevices, (state, { hasExpiredDevices }) => { + return { + ...state, + hasExpiredDevices, + }; + }), on(Actions.setDevices, (state, { devices }) => { return { ...state, diff --git a/modules/ui/src/app/store/selectors.spec.ts b/modules/ui/src/app/store/selectors.spec.ts index b6c709c57..29b580351 100644 --- a/modules/ui/src/app/store/selectors.spec.ts +++ b/modules/ui/src/app/store/selectors.spec.ts @@ -33,6 +33,7 @@ import { selectStatus, selectSystemStatus, selectTestModules, + selectHasExpiredDevices, } from './selectors'; describe('Selectors', () => { @@ -49,6 +50,7 @@ describe('Selectors', () => { hasConnectionSettings: false, devices: [], hasDevices: false, + hasExpiredDevices: false, isOpenAddDevice: false, riskProfiles: [], hasRiskProfiles: false, @@ -94,6 +96,11 @@ describe('Selectors', () => { expect(result).toEqual(false); }); + it('should select hasExpiredDevices', () => { + const result = selectHasExpiredDevices.projector(initialState); + expect(result).toEqual(false); + }); + it('should select riskProfiles', () => { const result = selectRiskProfiles.projector(initialState); expect(result).toEqual([]); diff --git a/modules/ui/src/app/store/selectors.ts b/modules/ui/src/app/store/selectors.ts index 9671bcd10..451fa31d9 100644 --- a/modules/ui/src/app/store/selectors.ts +++ b/modules/ui/src/app/store/selectors.ts @@ -47,6 +47,11 @@ export const selectHasDevices = createSelector( (state: AppState) => state.shared.hasDevices ); +export const selectHasExpiredDevices = createSelector( + selectAppState, + (state: AppState) => state.shared.hasExpiredDevices +); + export const selectDevices = createSelector( selectAppState, (state: AppState) => state.shared.devices diff --git a/modules/ui/src/app/store/state.ts b/modules/ui/src/app/store/state.ts index 63e0c5eff..263f76cd5 100644 --- a/modules/ui/src/app/store/state.ts +++ b/modules/ui/src/app/store/state.ts @@ -43,6 +43,7 @@ export interface SharedState { devices: Device[]; //used in app, devices, testrun hasDevices: boolean; + hasExpiredDevices: boolean; //app, risk-assessment, testrun, reports riskProfiles: Profile[]; hasRiskProfiles: boolean; @@ -78,6 +79,7 @@ export const initialSharedState: SharedState = { isStopTestrun: false, isOpenWaitSnackBar: false, hasDevices: false, + hasExpiredDevices: false, devices: [], deviceInProgress: null, riskProfiles: [], From 0a00d320051fb660f328a5740083121aec5dbf0f Mon Sep 17 00:00:00 2001 From: kurilova Date: Tue, 20 Aug 2024 10:09:22 +0000 Subject: [PATCH 2/2] Save state of callout in session storage --- modules/ui/src/app/app.component.html | 3 ++ modules/ui/src/app/app.component.spec.ts | 1 - modules/ui/src/app/app.component.ts | 6 ++++ modules/ui/src/app/app.store.spec.ts | 26 ++++++++++++++++- modules/ui/src/app/app.store.ts | 29 +++++++++++++++++++ .../components/callout/callout.component.html | 5 +++- .../callout/callout.component.spec.ts | 5 ++-- .../components/callout/callout.component.ts | 6 +++- modules/ui/src/index.html | 16 ++++++++++ 9 files changed, 91 insertions(+), 6 deletions(-) diff --git a/modules/ui/src/app/app.component.html b/modules/ui/src/app/app.component.html index 9bb249ba4..7a4d0d1f7 100644 --- a/modules/ui/src/app/app.component.html +++ b/modules/ui/src/app/app.component.html @@ -228,10 +228,13 @@

Testrun

>? Further information is required in your device configurations. Please update your diff --git a/modules/ui/src/app/app.component.spec.ts b/modules/ui/src/app/app.component.spec.ts index 6bc7505b8..147303b97 100644 --- a/modules/ui/src/app/app.component.spec.ts +++ b/modules/ui/src/app/app.component.spec.ts @@ -768,7 +768,6 @@ describe('AppComponent', () => { it('should have callout component', () => { const callouts = compiled.querySelectorAll('app-callout'); - //const calloutContent = callout?.innerHTML.trim(); let hasExpiredDeviceCallout = false; callouts.forEach(callout => { if ( diff --git a/modules/ui/src/app/app.component.ts b/modules/ui/src/app/app.component.ts index a3a28eb48..d2559c926 100644 --- a/modules/ui/src/app/app.component.ts +++ b/modules/ui/src/app/app.component.ts @@ -210,4 +210,10 @@ export class AppComponent { this.appStore.setFocusOnPage(); }); } + + calloutClosed(id: string | null) { + if (id) { + this.appStore.setCloseCallout(id); + } + } } diff --git a/modules/ui/src/app/app.store.spec.ts b/modules/ui/src/app/app.store.spec.ts index db8f5cc3c..d90f1cac7 100644 --- a/modules/ui/src/app/app.store.spec.ts +++ b/modules/ui/src/app/app.store.spec.ts @@ -15,7 +15,7 @@ */ import { fakeAsync, TestBed, tick } from '@angular/core/testing'; import { of, skip, take } from 'rxjs'; -import { AppStore, CONSENT_SHOWN_KEY } from './app.store'; +import { AppStore, CALLOUT_STATE_KEY, CONSENT_SHOWN_KEY } from './app.store'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { AppState } from './store/state'; import { @@ -58,6 +58,12 @@ const mock = (() => { setItem: (key: string, value: string) => { store[key] = value + ''; }, + getObject: (key: string) => { + return store[key] || null; + }, + setObject: (key: string, value: object) => { + store[key] = JSON.stringify(value); + }, clear: () => { store = {}; }, @@ -165,6 +171,7 @@ describe('AppStore', () => { isMenuOpen: true, interfaces: {}, settingMissedError: null, + calloutState: new Map(), }); done(); }); @@ -306,5 +313,22 @@ describe('AppStore', () => { ); }); }); + + describe('setCloseCallout', () => { + it('should update store', done => { + appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { + expect(store.calloutState.get('test')).toEqual(true); + done(); + }); + + appStore.setCloseCallout('test'); + }); + + it('should update storage', () => { + appStore.setCloseCallout('test'); + + expect(mock.getObject(CALLOUT_STATE_KEY)).toBeTruthy(); + }); + }); }); }); diff --git a/modules/ui/src/app/app.store.ts b/modules/ui/src/app/app.store.ts index cd8c69474..12486d7db 100644 --- a/modules/ui/src/app/app.store.ts +++ b/modules/ui/src/app/app.store.ts @@ -53,14 +53,17 @@ import { TestRunMqttService } from './services/test-run-mqtt.service'; import { NotificationService } from './services/notification.service'; export const CONSENT_SHOWN_KEY = 'CONSENT_SHOWN'; +export const CALLOUT_STATE_KEY = 'CALLOUT_STATE'; export interface AppComponentState { consentShown: boolean; isStatusLoaded: boolean; systemStatus: TestrunStatus | null; + calloutState: Map; } @Injectable() export class AppStore extends ComponentStore { private consentShown$ = this.select(state => state.consentShown); + private calloutState$ = this.select(state => state.calloutState); private isStatusLoaded$ = this.select(state => state.isStatusLoaded); private hasDevices$ = this.store.select(selectHasDevices); private hasExpiredDevices$ = this.store.select(selectHasExpiredDevices); @@ -88,6 +91,7 @@ export class AppStore extends ComponentStore { isMenuOpen: this.isMenuOpen$, interfaces: this.interfaces$, settingMissedError: this.settingMissedError$, + calloutState: this.calloutState$, }); updateConsent = this.updater((state, consentShown: boolean) => ({ @@ -95,6 +99,17 @@ export class AppStore extends ComponentStore { consentShown, })); + updateCalloutState = this.updater((state, callout: string) => { + const calloutState = state.calloutState; + calloutState.set(callout, true); + // @ts-expect-error property is defined in index.html + sessionStorage.setObject(CALLOUT_STATE_KEY, calloutState); + return { + ...state, + calloutState: new Map(calloutState), + }; + }); + updateIsStatusLoaded = this.updater((state, isStatusLoaded: boolean) => ({ ...state, isStatusLoaded, @@ -217,6 +232,14 @@ export class AppStore extends ComponentStore { ); }); + setCloseCallout = this.effect(trigger$ => { + return trigger$.pipe( + tap((id: string) => { + this.updateCalloutState(id); + }) + ); + }); + constructor( private store: Store, private testRunService: TestRunService, @@ -224,10 +247,16 @@ export class AppStore extends ComponentStore { private focusManagerService: FocusManagerService, private notificationService: NotificationService ) { + // @ts-expect-error get object is defined in index.html + const calloutState = sessionStorage.getObject(CALLOUT_STATE_KEY); + super({ consentShown: sessionStorage.getItem(CONSENT_SHOWN_KEY) !== null, isStatusLoaded: false, systemStatus: null, + calloutState: calloutState + ? new Map(Object.entries(calloutState)) + : new Map(), }); } } diff --git a/modules/ui/src/app/components/callout/callout.component.html b/modules/ui/src/app/components/callout/callout.component.html index 6e7438c78..51e2e4441 100644 --- a/modules/ui/src/app/components/callout/callout.component.html +++ b/modules/ui/src/app/components/callout/callout.component.html @@ -23,7 +23,10 @@

-
diff --git a/modules/ui/src/app/components/callout/callout.component.spec.ts b/modules/ui/src/app/components/callout/callout.component.spec.ts index 342cb19aa..53e76a814 100644 --- a/modules/ui/src/app/components/callout/callout.component.spec.ts +++ b/modules/ui/src/app/components/callout/callout.component.spec.ts @@ -55,13 +55,14 @@ describe('CalloutComponent', () => { expect(closeButton).toBeTruthy(); }); - it('should have hidden class on close button clicked', () => { + it('should emit event', () => { + const calloutClosedSpy = spyOn(component.calloutClosed, 'emit'); const closeButton = compiled.querySelector( '.callout-close-button' ) as HTMLButtonElement; closeButton?.click(); - expect(component.closed).toBeTrue(); + expect(calloutClosedSpy).toHaveBeenCalled(); }); }); }); diff --git a/modules/ui/src/app/components/callout/callout.component.ts b/modules/ui/src/app/components/callout/callout.component.ts index ab517fa70..b986c6ccc 100644 --- a/modules/ui/src/app/components/callout/callout.component.ts +++ b/modules/ui/src/app/components/callout/callout.component.ts @@ -16,8 +16,10 @@ import { ChangeDetectionStrategy, Component, + EventEmitter, HostBinding, Input, + Output, } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatIconModule } from '@angular/material/icon'; @@ -32,7 +34,9 @@ import { MatButtonModule } from '@angular/material/button'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class CalloutComponent { - @HostBinding('class.hidden') closed: boolean = false; + @HostBinding('class.hidden') @Input() closed: boolean = false; + @Input() id: string | null = null; @Input() type = ''; @Input() closable = false; + @Output() calloutClosed = new EventEmitter(); } diff --git a/modules/ui/src/index.html b/modules/ui/src/index.html index 091591cac..375cf9243 100644 --- a/modules/ui/src/index.html +++ b/modules/ui/src/index.html @@ -28,6 +28,22 @@ j.src = 'https://www.googletagmanager.com/gtm.js?id=' + i + dl; f.parentNode.insertBefore(j, f); })(window, document, 'script', 'dataLayer', 'GTM-NDFZ7L89'); + + Storage.prototype.setObject = function (key, value) { + if (value instanceof Map) { + this.setItem( + key, + JSON.stringify(Object.fromEntries(value.entries())) + ); + } else { + this.setItem(key, JSON.stringify(value)); + } + }; + + Storage.prototype.getObject = function (key) { + var value = this.getItem(key); + return value && JSON.parse(value); + };