+
+
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..53e76a814 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,27 @@ 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 emit event', () => {
+ const calloutClosedSpy = spyOn(component.calloutClosed, 'emit');
+ const closeButton = compiled.querySelector(
+ '.callout-close-button'
+ ) as HTMLButtonElement;
+ closeButton?.click();
+
+ 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 64487236d..e4be32f45 100644
--- a/modules/ui/src/app/components/callout/callout.component.ts
+++ b/modules/ui/src/app/components/callout/callout.component.ts
@@ -13,20 +13,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
+import {
+ ChangeDetectionStrategy,
+ Component,
+ EventEmitter,
+ HostBinding,
+ Input,
+ Output,
+} from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatIconModule } from '@angular/material/icon';
+import { MatButtonModule } from '@angular/material/button';
import { CalloutType } from '../../model/callout-type';
@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 {
public readonly CalloutType = CalloutType;
+ @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/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: [],
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);
+ };