From 651f85d9a2ced817d2cf55a33e573c33fb291c08 Mon Sep 17 00:00:00 2001 From: Sofia Kurilova Date: Mon, 29 Apr 2024 08:46:28 +0000 Subject: [PATCH 1/5] Adds certificates drawer component with list of certificates (#414) Adds certificates drawer component with list of certificates. Upload and delete is out of scope --- modules/ui/src/app/app.component.ts | 1 + modules/ui/src/app/app.store.spec.ts | 34 ++++++++++++++++++- modules/ui/src/app/app.store.ts | 21 ++++++++++++ .../ui/src/app/services/test-run.service.ts | 4 +++ 4 files changed, 59 insertions(+), 1 deletion(-) diff --git a/modules/ui/src/app/app.component.ts b/modules/ui/src/app/app.component.ts index ea315f68b..4334662b3 100644 --- a/modules/ui/src/app/app.component.ts +++ b/modules/ui/src/app/app.component.ts @@ -74,6 +74,7 @@ export class AppComponent { ) { this.appStore.getDevices(); this.appStore.getSystemStatus(); + this.appStore.getCertificates(); this.matIconRegistry.addSvgIcon( 'devices', this.domSanitizer.bypassSecurityTrustResourceUrl(DEVICES_LOGO_URL) diff --git a/modules/ui/src/app/app.store.spec.ts b/modules/ui/src/app/app.store.spec.ts index d53840850..a11f85943 100644 --- a/modules/ui/src/app/app.store.spec.ts +++ b/modules/ui/src/app/app.store.spec.ts @@ -32,6 +32,7 @@ import SpyObj = jasmine.SpyObj; import { device } from './mocks/device.mock'; import { setDevices, setTestrunStatus } from './store/actions'; import { MOCK_PROGRESS_DATA_IN_PROGRESS } from './mocks/progress.mock'; +import { certificate } from './mocks/certificate.mock'; const mock = (() => { let store: { [key: string]: string } = {}; @@ -57,7 +58,11 @@ describe('AppStore', () => { let mockService: SpyObj; beforeEach(() => { - mockService = jasmine.createSpyObj(['fetchDevices', 'fetchSystemStatus']); + mockService = jasmine.createSpyObj([ + 'fetchDevices', + 'fetchSystemStatus', + 'fetchCertificates', + ]); TestBed.configureTestingModule({ providers: [ @@ -107,6 +112,15 @@ describe('AppStore', () => { appStore.updateIsStatusLoaded(true); }); + + it('should update certificates', (done: DoneFn) => { + appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { + expect(store.certificates).toEqual([certificate]); + done(); + }); + + appStore.updateCertificates([certificate]); + }); }); describe('selectors', () => { @@ -122,6 +136,7 @@ describe('AppStore', () => { isMenuOpen: true, interfaces: {}, settingMissedError: null, + certificates: [], }); done(); }); @@ -184,5 +199,22 @@ describe('AppStore', () => { appStore.getSystemStatus(); }); }); + + describe('fetchCertificates', () => { + const certificates = [certificate]; + + beforeEach(() => { + mockService.fetchCertificates.and.returnValue(of(certificates)); + }); + + it('should update certificates', done => { + appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { + expect(store.certificates).toEqual(certificates); + done(); + }); + + appStore.getCertificates(); + }); + }); }); }); diff --git a/modules/ui/src/app/app.store.ts b/modules/ui/src/app/app.store.ts index d214d4848..ee5c952fe 100644 --- a/modules/ui/src/app/app.store.ts +++ b/modules/ui/src/app/app.store.ts @@ -38,6 +38,7 @@ import { } from './store/actions'; import { TestrunStatus } from './model/testrun-status'; import { SettingMissedError, SystemInterfaces } from './model/setting'; +import { Certificate } from './model/certificate'; export const CONSENT_SHOWN_KEY = 'CONSENT_SHOWN'; export interface AppComponentState { @@ -45,11 +46,13 @@ export interface AppComponentState { isStatusLoaded: boolean; isTestrunStarted: boolean; systemStatus: TestrunStatus | null; + certificates: Certificate[]; } @Injectable() export class AppStore extends ComponentStore { private consentShown$ = this.select(state => state.consentShown); private isStatusLoaded$ = this.select(state => state.isStatusLoaded); + private certificates$ = this.select(state => state.certificates); private hasDevices$ = this.store.select(selectHasDevices); private hasConnectionSetting$ = this.store.select( selectHasConnectionSettings @@ -72,6 +75,7 @@ export class AppStore extends ComponentStore { isMenuOpen: this.isMenuOpen$, interfaces: this.interfaces$, settingMissedError: this.settingMissedError$, + certificates: this.certificates$, }); updateConsent = this.updater((state, consentShown: boolean) => ({ @@ -84,6 +88,11 @@ export class AppStore extends ComponentStore { isStatusLoaded, })); + updateCertificates = this.updater((state, certificates: Certificate[]) => ({ + ...state, + certificates, + })); + setContent = this.effect(trigger$ => { return trigger$.pipe( tap(() => { @@ -128,6 +137,17 @@ export class AppStore extends ComponentStore { ); }); + getCertificates = this.effect(trigger$ => { + return trigger$.pipe( + exhaustMap(() => { + return this.testRunService.fetchCertificates().pipe( + tap((certificates: Certificate[]) => { + this.updateCertificates(certificates); + }) + ); + }) + ); + }); constructor( private store: Store, private testRunService: TestRunService @@ -137,6 +157,7 @@ export class AppStore extends ComponentStore { isStatusLoaded: false, isTestrunStarted: false, systemStatus: null, + certificates: [], }); } } diff --git a/modules/ui/src/app/services/test-run.service.ts b/modules/ui/src/app/services/test-run.service.ts index aa3585a9b..5e536bea1 100644 --- a/modules/ui/src/app/services/test-run.service.ts +++ b/modules/ui/src/app/services/test-run.service.ts @@ -245,4 +245,8 @@ export class TestRunService { .post(`${API_URL}/system/config/certs/upload`, formData) .pipe(map(() => true)); } + + fetchCertificates(): Observable { + return this.http.get(`${API_URL}/system/config/certs/list`); + } } From 00a97d743fe545c45292a57b3ae29245396be7bb Mon Sep 17 00:00:00 2001 From: Sofia Kurilova Date: Fri, 12 Apr 2024 15:54:59 +0000 Subject: [PATCH 2/5] Techdebt: adds state for testrun page (#392) --- modules/ui/src/app/pages/testrun/testrun.store.spec.ts | 4 ++++ modules/ui/src/app/services/test-run.service.ts | 4 +--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/ui/src/app/pages/testrun/testrun.store.spec.ts b/modules/ui/src/app/pages/testrun/testrun.store.spec.ts index 53f173e41..38cb9c5c3 100644 --- a/modules/ui/src/app/pages/testrun/testrun.store.spec.ts +++ b/modules/ui/src/app/pages/testrun/testrun.store.spec.ts @@ -88,6 +88,10 @@ describe('TestrunStore', () => { spyOn(store, 'dispatch').and.callFake(() => {}); }); + afterEach(() => { + mockService.fetchSystemStatus.calls.reset(); + }); + it('should be created', () => { expect(testrunStore).toBeTruthy(); }); diff --git a/modules/ui/src/app/services/test-run.service.ts b/modules/ui/src/app/services/test-run.service.ts index 5e536bea1..962462d1e 100644 --- a/modules/ui/src/app/services/test-run.service.ts +++ b/modules/ui/src/app/services/test-run.service.ts @@ -22,7 +22,6 @@ import { catchError, map, of, retry } from 'rxjs'; import { SystemConfig, SystemInterfaces } from '../model/setting'; import { StatusOfTestResult, - StatusOfTestrun, StatusResultClassName, TestrunStatus, } from '../model/testrun-status'; @@ -81,8 +80,7 @@ export class TestRunService { private version = new BehaviorSubject(null); constructor( - private http: HttpClient, - private store: Store + private http: HttpClient ) {} fetchDevices(): Observable { From a06581478f47bf0a6753466569490df621eaef36 Mon Sep 17 00:00:00 2001 From: Sofia Kurilova Date: Tue, 16 Apr 2024 11:13:59 +0000 Subject: [PATCH 3/5] Fix tests (#397) * Fix tests * Update node version --- modules/ui/src/app/pages/testrun/testrun.store.spec.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/ui/src/app/pages/testrun/testrun.store.spec.ts b/modules/ui/src/app/pages/testrun/testrun.store.spec.ts index 38cb9c5c3..53f173e41 100644 --- a/modules/ui/src/app/pages/testrun/testrun.store.spec.ts +++ b/modules/ui/src/app/pages/testrun/testrun.store.spec.ts @@ -88,10 +88,6 @@ describe('TestrunStore', () => { spyOn(store, 'dispatch').and.callFake(() => {}); }); - afterEach(() => { - mockService.fetchSystemStatus.calls.reset(); - }); - it('should be created', () => { expect(testrunStore).toBeTruthy(); }); From 0df86b467cc82a3086067a170600fedae7a01789 Mon Sep 17 00:00:00 2001 From: kurilova Date: Tue, 16 Apr 2024 13:48:05 +0000 Subject: [PATCH 4/5] Enable test modules for initiate test run dialog --- .../progress-initiate-form.component.html | 4 ++- .../progress-initiate-form.component.spec.ts | 22 ++++++++++++- .../progress-initiate-form.component.ts | 31 +++++++++++++++++++ .../ui/src/app/services/test-run.service.ts | 7 ++--- 4 files changed, 57 insertions(+), 7 deletions(-) diff --git a/modules/ui/src/app/pages/testrun/components/progress-initiate-form/progress-initiate-form.component.html b/modules/ui/src/app/pages/testrun/components/progress-initiate-form/progress-initiate-form.component.html index 93563201e..02fa4f2f8 100644 --- a/modules/ui/src/app/pages/testrun/components/progress-initiate-form/progress-initiate-form.component.html +++ b/modules/ui/src/app/pages/testrun/components/progress-initiate-form/progress-initiate-form.component.html @@ -60,10 +60,12 @@ + + {{ error$ | async }} + diff --git a/modules/ui/src/app/pages/testrun/components/progress-initiate-form/progress-initiate-form.component.spec.ts b/modules/ui/src/app/pages/testrun/components/progress-initiate-form/progress-initiate-form.component.spec.ts index 9e5b71bcd..4f24ecaaa 100644 --- a/modules/ui/src/app/pages/testrun/components/progress-initiate-form/progress-initiate-form.component.spec.ts +++ b/modules/ui/src/app/pages/testrun/components/progress-initiate-form/progress-initiate-form.component.spec.ts @@ -178,10 +178,27 @@ describe('ProgressInitiateFormComponent', () => { }); }); + it('should not start if no test selected', () => { + component.firmware.setValue('firmware'); + component.selectedDevice = device; + fixture.detectChanges(); + component.test_modules.setValue([false, false]); + + component.startTestRun(); + fixture.detectChanges(); + + const error = compiled.querySelector('mat-error'); + expect(error?.innerHTML).toContain( + 'At least one test has to be selected to start test run.' + ); + }); + describe('when selectedDevice is present and firmware is filled', () => { beforeEach(() => { component.firmware.setValue('firmware'); component.selectedDevice = device; + fixture.detectChanges(); + component.test_modules.setValue([true, true]); }); it('should call startTestRun with device', () => { @@ -193,6 +210,9 @@ describe('ProgressInitiateFormComponent', () => { mac_addr: '00:1e:42:35:73:c4', firmware: 'firmware', test_modules: { + connection: { + enabled: true, + }, dns: { enabled: true, }, @@ -309,7 +329,7 @@ describe('ProgressInitiateFormComponent', () => { const tests = compiled.querySelectorAll('.device-form-test-modules p'); expect(testsForm).toBeTruthy(); - expect(tests[0].classList.contains('disabled')).toEqual(true); + expect(tests[0].classList.contains('disabled')).toEqual(false); expect(tests.length).toEqual(2); }); diff --git a/modules/ui/src/app/pages/testrun/components/progress-initiate-form/progress-initiate-form.component.ts b/modules/ui/src/app/pages/testrun/components/progress-initiate-form/progress-initiate-form.component.ts index 3cd304b6a..505df7271 100644 --- a/modules/ui/src/app/pages/testrun/components/progress-initiate-form/progress-initiate-form.component.ts +++ b/modules/ui/src/app/pages/testrun/components/progress-initiate-form/progress-initiate-form.component.ts @@ -37,6 +37,7 @@ import { take } from 'rxjs'; import { Store } from '@ngrx/store'; import { AppState } from '../../../../store/state'; import { selectDevices } from '../../../../store/selectors'; +import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; interface DialogData { device?: Device; @@ -59,6 +60,9 @@ export class ProgressInitiateFormComponent prevDevice: Device | null = null; setFirmwareFocus = false; readonly DeviceView = DeviceView; + error$: BehaviorSubject = new BehaviorSubject( + null + ); constructor( public override dialogRef: MatDialogRef, @@ -76,6 +80,10 @@ export class ProgressInitiateFormComponent return this.initiateForm.get('firmware') as AbstractControl; } + get test_modules() { + return this.initiateForm.controls['test_modules'] as FormArray; + } + cancel(startTestrun: boolean): void { this.dialogRef.close(startTestrun); } @@ -131,12 +139,29 @@ export class ProgressInitiateFormComponent return; } + if (this.isAllTestsDisabled()) { + this.error$.next( + 'At least one test has to be selected to start test run.' + ); + return; + } + + const testModules: { [key: string]: { enabled: boolean } } = {}; + this.initiateForm.value.test_modules.forEach( + (enabled: boolean, i: number) => { + testModules[this.testModules[i]?.name] = { + enabled: enabled, + }; + } + ); + if (this.selectedDevice) { this.testRunService.fetchVersion(); this.testRunService .startTestrun({ ...this.selectedDevice, firmware: this.firmware.value.trim(), + test_modules: testModules, }) .pipe(take(1)) .subscribe(() => { @@ -151,4 +176,10 @@ export class ProgressInitiateFormComponent test_modules: new FormArray([]), }); } + + private isAllTestsDisabled(): boolean { + return this.initiateForm.value.test_modules.every((enabled: boolean) => { + return !enabled; + }); + } } diff --git a/modules/ui/src/app/services/test-run.service.ts b/modules/ui/src/app/services/test-run.service.ts index 962462d1e..18f788f04 100644 --- a/modules/ui/src/app/services/test-run.service.ts +++ b/modules/ui/src/app/services/test-run.service.ts @@ -22,12 +22,11 @@ import { catchError, map, of, retry } from 'rxjs'; import { SystemConfig, SystemInterfaces } from '../model/setting'; import { StatusOfTestResult, + StatusOfTestrun, StatusResultClassName, TestrunStatus, } from '../model/testrun-status'; import { Version } from '../model/version'; -import { Store } from '@ngrx/store'; -import { AppState } from '../store/state'; import { Certificate } from '../model/certificate'; const API_URL = `http://${window.location.hostname}:8000`; @@ -79,9 +78,7 @@ export class TestRunService { private version = new BehaviorSubject(null); - constructor( - private http: HttpClient - ) {} + constructor(private http: HttpClient) {} fetchDevices(): Observable { return this.http.get(`${API_URL}/devices`); From 6a4c7a02286ce0ed74c9ec39a5c1d3b5105b3914 Mon Sep 17 00:00:00 2001 From: kurilova Date: Wed, 8 May 2024 19:28:02 +0000 Subject: [PATCH 5/5] remove duplicate; remove old code --- modules/ui/src/app/app.component.ts | 1 - modules/ui/src/app/app.store.spec.ts | 34 +------------------ modules/ui/src/app/app.store.ts | 21 ------------ .../ui/src/app/services/test-run.service.ts | 4 --- 4 files changed, 1 insertion(+), 59 deletions(-) diff --git a/modules/ui/src/app/app.component.ts b/modules/ui/src/app/app.component.ts index 4334662b3..ea315f68b 100644 --- a/modules/ui/src/app/app.component.ts +++ b/modules/ui/src/app/app.component.ts @@ -74,7 +74,6 @@ export class AppComponent { ) { this.appStore.getDevices(); this.appStore.getSystemStatus(); - this.appStore.getCertificates(); this.matIconRegistry.addSvgIcon( 'devices', this.domSanitizer.bypassSecurityTrustResourceUrl(DEVICES_LOGO_URL) diff --git a/modules/ui/src/app/app.store.spec.ts b/modules/ui/src/app/app.store.spec.ts index a11f85943..d53840850 100644 --- a/modules/ui/src/app/app.store.spec.ts +++ b/modules/ui/src/app/app.store.spec.ts @@ -32,7 +32,6 @@ import SpyObj = jasmine.SpyObj; import { device } from './mocks/device.mock'; import { setDevices, setTestrunStatus } from './store/actions'; import { MOCK_PROGRESS_DATA_IN_PROGRESS } from './mocks/progress.mock'; -import { certificate } from './mocks/certificate.mock'; const mock = (() => { let store: { [key: string]: string } = {}; @@ -58,11 +57,7 @@ describe('AppStore', () => { let mockService: SpyObj; beforeEach(() => { - mockService = jasmine.createSpyObj([ - 'fetchDevices', - 'fetchSystemStatus', - 'fetchCertificates', - ]); + mockService = jasmine.createSpyObj(['fetchDevices', 'fetchSystemStatus']); TestBed.configureTestingModule({ providers: [ @@ -112,15 +107,6 @@ describe('AppStore', () => { appStore.updateIsStatusLoaded(true); }); - - it('should update certificates', (done: DoneFn) => { - appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { - expect(store.certificates).toEqual([certificate]); - done(); - }); - - appStore.updateCertificates([certificate]); - }); }); describe('selectors', () => { @@ -136,7 +122,6 @@ describe('AppStore', () => { isMenuOpen: true, interfaces: {}, settingMissedError: null, - certificates: [], }); done(); }); @@ -199,22 +184,5 @@ describe('AppStore', () => { appStore.getSystemStatus(); }); }); - - describe('fetchCertificates', () => { - const certificates = [certificate]; - - beforeEach(() => { - mockService.fetchCertificates.and.returnValue(of(certificates)); - }); - - it('should update certificates', done => { - appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { - expect(store.certificates).toEqual(certificates); - done(); - }); - - appStore.getCertificates(); - }); - }); }); }); diff --git a/modules/ui/src/app/app.store.ts b/modules/ui/src/app/app.store.ts index ee5c952fe..d214d4848 100644 --- a/modules/ui/src/app/app.store.ts +++ b/modules/ui/src/app/app.store.ts @@ -38,7 +38,6 @@ import { } from './store/actions'; import { TestrunStatus } from './model/testrun-status'; import { SettingMissedError, SystemInterfaces } from './model/setting'; -import { Certificate } from './model/certificate'; export const CONSENT_SHOWN_KEY = 'CONSENT_SHOWN'; export interface AppComponentState { @@ -46,13 +45,11 @@ export interface AppComponentState { isStatusLoaded: boolean; isTestrunStarted: boolean; systemStatus: TestrunStatus | null; - certificates: Certificate[]; } @Injectable() export class AppStore extends ComponentStore { private consentShown$ = this.select(state => state.consentShown); private isStatusLoaded$ = this.select(state => state.isStatusLoaded); - private certificates$ = this.select(state => state.certificates); private hasDevices$ = this.store.select(selectHasDevices); private hasConnectionSetting$ = this.store.select( selectHasConnectionSettings @@ -75,7 +72,6 @@ export class AppStore extends ComponentStore { isMenuOpen: this.isMenuOpen$, interfaces: this.interfaces$, settingMissedError: this.settingMissedError$, - certificates: this.certificates$, }); updateConsent = this.updater((state, consentShown: boolean) => ({ @@ -88,11 +84,6 @@ export class AppStore extends ComponentStore { isStatusLoaded, })); - updateCertificates = this.updater((state, certificates: Certificate[]) => ({ - ...state, - certificates, - })); - setContent = this.effect(trigger$ => { return trigger$.pipe( tap(() => { @@ -137,17 +128,6 @@ export class AppStore extends ComponentStore { ); }); - getCertificates = this.effect(trigger$ => { - return trigger$.pipe( - exhaustMap(() => { - return this.testRunService.fetchCertificates().pipe( - tap((certificates: Certificate[]) => { - this.updateCertificates(certificates); - }) - ); - }) - ); - }); constructor( private store: Store, private testRunService: TestRunService @@ -157,7 +137,6 @@ export class AppStore extends ComponentStore { isStatusLoaded: false, isTestrunStarted: false, systemStatus: null, - certificates: [], }); } } diff --git a/modules/ui/src/app/services/test-run.service.ts b/modules/ui/src/app/services/test-run.service.ts index 18f788f04..be09c77dc 100644 --- a/modules/ui/src/app/services/test-run.service.ts +++ b/modules/ui/src/app/services/test-run.service.ts @@ -240,8 +240,4 @@ export class TestRunService { .post(`${API_URL}/system/config/certs/upload`, formData) .pipe(map(() => true)); } - - fetchCertificates(): Observable { - return this.http.get(`${API_URL}/system/config/certs/list`); - } }