diff --git a/modules/ui/src/app/mocks/profile.mock.ts b/modules/ui/src/app/mocks/profile.mock.ts
index f5a9120b0..000be4337 100644
--- a/modules/ui/src/app/mocks/profile.mock.ts
+++ b/modules/ui/src/app/mocks/profile.mock.ts
@@ -15,6 +15,7 @@
*/
import { Profile } from '../model/profile';
+import { FormControlType, ProfileFormat } from '../model/profile';
export const PROFILE_MOCK: Profile = {
name: 'Profile name',
@@ -25,3 +26,46 @@ export const PROFILE_MOCK_2: Profile = {
name: 'Second profile name',
sections: [],
};
+
+export const PROFILE_FORM: ProfileFormat[] = [
+ {
+ question: 'Email',
+ type: FormControlType.EMAIL_MULTIPLE,
+ validation: {
+ required: true,
+ },
+ },
+ {
+ question: 'What type of device do you need reviewed?',
+ type: FormControlType.TEXTAREA,
+ validation: {
+ required: true,
+ },
+ description: 'This tells us about the device',
+ },
+ {
+ question:
+ 'Has this device already been through a criticality assessment with testrun?',
+ type: FormControlType.SELECT,
+ options: [],
+ validation: {
+ max: '128',
+ required: true,
+ },
+ },
+ {
+ question: 'What features does the device have?',
+ description:
+ 'This tells us about the data your device will collectThis tells us about the data your device will collect',
+ type: FormControlType.SELECT_MULTIPLE,
+ options: ['Wi-fi', 'Bluetooth', 'ZigBee / Z-Wave / Thread / Matter'],
+ validation: {
+ required: true,
+ },
+ },
+ {
+ question: 'Comments',
+ type: FormControlType.TEXT,
+ description: 'Please enter any comments here',
+ },
+];
diff --git a/modules/ui/src/app/model/profile.ts b/modules/ui/src/app/model/profile.ts
index 28bf4bd1c..9d62b5eb6 100644
--- a/modules/ui/src/app/model/profile.ts
+++ b/modules/ui/src/app/model/profile.ts
@@ -28,3 +28,35 @@ export interface Profile {
name: string;
sections: ProfileSection[];
}
+
+export interface ProfileRequestBody {
+ name: string;
+ questions: Question[];
+}
+
+export interface Question {
+ question?: string;
+ answer?: string | number[];
+}
+
+export enum FormControlType {
+ SELECT = 'select',
+ TEXTAREA = 'text-long',
+ EMAIL_MULTIPLE = 'email-multiple',
+ SELECT_MULTIPLE = 'select-multiple',
+ TEXT = 'text',
+}
+
+export interface Validation {
+ required?: boolean;
+ max?: string;
+}
+
+export interface ProfileFormat {
+ question: string;
+ type: FormControlType;
+ description?: string;
+ options?: string[];
+ default?: string;
+ validation?: Validation;
+}
diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html
index 33c70f809..1738d5ef0 100644
--- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html
+++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html
@@ -15,8 +15,12 @@
-->
@@ -52,3 +72,185 @@
Discard
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ description }}
+
+
+
+
+
+
+ {{ description }}
+
+
+
+
+
+
+ {{ description }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ option }}
+
+
+ {{
+ description
+ }}
+
+
diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.scss b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.scss
index 003ed84a8..f65dc04c0 100644
--- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.scss
+++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.scss
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+@use '@angular/material' as mat;
@import 'src/theming/colors';
@import 'src/theming/variables';
@@ -21,7 +22,6 @@
display: flex;
flex-direction: column;
align-items: flex-start;
- gap: 16px;
padding: 8px 16px 8px 24px;
}
.field-label {
@@ -29,19 +29,49 @@
color: $grey-800;
font-size: 18px;
line-height: 24px;
+ padding-top: 24px;
+ padding-bottom: 16px;
+ &:first-child {
+ padding-top: 0;
+ }
}
mat-form-field {
width: 100%;
}
+ .field-hint {
+ font-family: $font-secondary;
+ font-size: 12px;
+ font-weight: 400;
+ line-height: 16px;
+ text-align: left;
+ padding-top: 8px;
+ }
}
.form-actions {
display: flex;
gap: 16px;
- padding: 0 24px;
+ padding: 8px 24px 24px 24px;
}
.save-draft-button,
.discard-button {
color: $primary;
}
+
+.field-select-multiple {
+ .field-select-checkbox {
+ &:has(::ng-deep .mat-mdc-checkbox-checked) {
+ background: mat.get-color-from-palette($color-primary, 50);
+ }
+ ::ng-deep .mdc-checkbox__ripple {
+ display: none;
+ }
+ &:first-of-type {
+ margin-top: 0;
+ }
+ &:last-of-type {
+ margin-bottom: 8px;
+ }
+ }
+}
diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts
index f72f41735..cc2a328cb 100644
--- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts
+++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts
@@ -17,10 +17,17 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ProfileFormComponent } from './profile-form.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import {
+ PROFILE_FORM,
+ PROFILE_MOCK,
+ PROFILE_MOCK_2,
+} from '../../../mocks/profile.mock';
+import { FormControlType } from '../../../model/profile';
describe('ProfileFormComponent', () => {
let component: ProfileFormComponent;
let fixture: ComponentFixture;
+ let compiled: HTMLElement;
beforeEach(async () => {
await TestBed.configureTestingModule({
@@ -29,6 +36,10 @@ describe('ProfileFormComponent', () => {
fixture = TestBed.createComponent(ProfileFormComponent);
component = fixture.componentInstance;
+ component.profileFormat = PROFILE_FORM;
+ component.profiles = [PROFILE_MOCK, PROFILE_MOCK_2];
+ compiled = fixture.nativeElement as HTMLElement;
+
fixture.detectChanges();
});
@@ -36,5 +47,138 @@ describe('ProfileFormComponent', () => {
expect(component).toBeTruthy();
});
- // TODO: Add more unit tests
+ describe('DOM tests', () => {
+ describe('Profile name input', () => {
+ it('should be present', () => {
+ const name: HTMLInputElement = compiled.querySelector(
+ '.form-name'
+ ) as HTMLInputElement;
+
+ expect(name).toBeTruthy();
+ });
+
+ it('should not contain errors when input is correct', () => {
+ const name: HTMLInputElement = compiled.querySelector(
+ '.form-name'
+ ) as HTMLInputElement;
+ ['name', 'Gebäude', 'jardín'].forEach(value => {
+ name.value = value;
+ name.dispatchEvent(new Event('input'));
+
+ const errors = component.nameControl.errors;
+ const uiValue = name.value;
+ const formValue = component.nameControl.value;
+
+ expect(uiValue).toEqual(formValue);
+ expect(errors).toBeNull();
+ });
+ });
+
+ it('should have "invalid_format" error when field does not satisfy validation rules', () => {
+ [
+ 'very long value very long value very long value very long value very long value very long value very long value',
+ 'as&@3$',
+ ].forEach(value => {
+ const name: HTMLInputElement = compiled.querySelector(
+ '.form-name'
+ ) as HTMLInputElement;
+ name.value = value;
+ name.dispatchEvent(new Event('input'));
+ component.nameControl.markAsTouched();
+ fixture.detectChanges();
+
+ const nameError = compiled.querySelector('mat-error')?.innerHTML;
+ const error = component.nameControl.hasError('invalid_format');
+
+ expect(error).toBeTruthy();
+ expect(nameError).toContain(
+ 'Please, check. The Profile name must be a maximum of 28 characters. Only letters, numbers, and accented letters are permitted.'
+ );
+ });
+ });
+
+ it('should have "required" error when field is not filled', () => {
+ const name: HTMLInputElement = compiled.querySelector(
+ '.form-name'
+ ) as HTMLInputElement;
+ name.value = '';
+ name.dispatchEvent(new Event('input'));
+ component.nameControl.markAsTouched();
+ fixture.detectChanges();
+
+ const nameError = compiled.querySelector('mat-error')?.innerHTML;
+ const error = component.nameControl.hasError('required');
+
+ expect(error).toBeTruthy();
+ expect(nameError).toContain('The Profile name is required');
+ });
+
+ it('should have "required" error when field is not filled', () => {
+ const name: HTMLInputElement = compiled.querySelector(
+ '.form-name'
+ ) as HTMLInputElement;
+ name.value = 'Profile name';
+ name.dispatchEvent(new Event('input'));
+ component.nameControl.markAsTouched();
+ fixture.detectChanges();
+
+ const nameError = compiled.querySelector('mat-error')?.innerHTML;
+ const error = component.nameControl.hasError('has_same_profile_name');
+
+ expect(error).toBeTruthy();
+ expect(nameError).toContain(
+ 'This Profile name is already used for another Risk Assessment profile'
+ );
+ });
+ });
+
+ PROFILE_FORM.forEach((item, index) => {
+ const uiIndex = index + 1; // as Profile name is at 0 position, the json items start from 1 i
+
+ it(`should have form field with specific type"`, () => {
+ const fields = compiled.querySelectorAll('.profile-form-field');
+
+ if (item.type === FormControlType.SELECT) {
+ const select = fields[uiIndex].querySelector('mat-select');
+ expect(select).toBeTruthy();
+ } else if (item.type === FormControlType.SELECT_MULTIPLE) {
+ const select = fields[uiIndex].querySelector('mat-checkbox');
+ expect(select).toBeTruthy();
+ } else {
+ const input = fields[uiIndex]?.querySelector('input');
+ expect(input).toBeTruthy();
+ }
+ });
+
+ it('should have label', () => {
+ const labels = compiled.querySelectorAll('.field-label');
+ const uiIndex = index + 1; // as Profile name is at 0 position, the json items start from 1 i
+
+ const label = item?.validation?.required
+ ? item.question + ' *'
+ : item.question;
+ expect(labels[uiIndex].textContent?.trim()).toEqual(label);
+ });
+
+ it('should have hint', () => {
+ const fields = compiled.querySelectorAll('.profile-form-field');
+ const uiIndex = index + 1; // as Profile name is at 0 position, the json items start from 1 i
+ const hint = fields[uiIndex].querySelector('mat-hint');
+
+ if (item.description) {
+ expect(hint?.textContent?.trim()).toEqual(item.description);
+ } else {
+ expect(hint).toBeNull();
+ }
+ });
+
+ if (item.type === FormControlType.SELECT) {
+ it(`should have default value if provided`, () => {
+ const fields = compiled.querySelectorAll('.profile-form-field');
+ const select = fields[uiIndex].querySelector('mat-select');
+ expect(select?.textContent?.trim()).toEqual(item.default || '');
+ });
+ }
+ });
+ });
});
diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts
index bb8e5b12e..85f93f546 100644
--- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts
+++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts
@@ -20,9 +20,13 @@ import {
OnInit,
} from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatSelectModule } from '@angular/material/select';
+import { MatCheckboxModule } from '@angular/material/checkbox';
import { CommonModule } from '@angular/common';
import {
AbstractControl,
+ FormControl,
FormBuilder,
FormGroup,
ReactiveFormsModule,
@@ -34,6 +38,8 @@ import { Profile } from '../../../model/profile';
import { ProfileValidators } from './profile.validators';
import { MatError } from '@angular/material/form-field';
+import { FormControlType, ProfileFormat } from '../../../model/profile';
+
@Component({
selector: 'app-profile-form',
standalone: true,
@@ -43,13 +49,18 @@ import { MatError } from '@angular/material/form-field';
ReactiveFormsModule,
MatInputModule,
MatError,
+ MatFormFieldModule,
+ MatSelectModule,
+ MatCheckboxModule,
],
templateUrl: './profile-form.component.html',
styleUrl: './profile-form.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProfileFormComponent implements OnInit {
- profileForm!: FormGroup;
+ public readonly FormControlType = FormControlType;
+ @Input() profileFormat!: ProfileFormat[];
+ profileForm: FormGroup = this.fb.group({});
@Input() profiles!: Profile[];
constructor(
private deviceValidators: DeviceValidators,
@@ -57,24 +68,44 @@ export class ProfileFormComponent implements OnInit {
private fb: FormBuilder
) {}
+ ngOnInit() {
+ this.profileForm = this.createProfileForm(this.profileFormat);
+ }
+
get nameControl() {
return this.profileForm.get('name') as AbstractControl;
}
- ngOnInit() {
- this.createProfileForm();
+ createProfileForm(questions: ProfileFormat[]): FormGroup {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const group: any = {};
+
+ group['name'] = new FormControl('', [
+ Validators.required,
+ this.deviceValidators.deviceStringFormat(),
+ this.profileValidators.differentProfileName(this.profiles),
+ ]);
+
+ questions.forEach((question, index) => {
+ if (question.type === FormControlType.SELECT_MULTIPLE) {
+ group[index] = this.getMultiSelectGroup(question);
+ } else {
+ group[index] = new FormControl(question.default || '');
+ }
+ });
+ return new FormGroup(group);
}
- createProfileForm() {
- this.profileForm = this.fb.group({
- name: [
- '',
- [
- Validators.required,
- this.deviceValidators.deviceStringFormat(),
- this.profileValidators.differentProfileName(this.profiles),
- ],
- ],
+ getMultiSelectGroup(question: ProfileFormat): FormGroup {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const group: any = {};
+ question.options?.forEach((option, index) => {
+ group[index] = false;
});
+ return this.fb.group(group);
+ }
+
+ getFormGroup(name: string): FormGroup {
+ return this.profileForm?.controls[name] as FormGroup;
}
}
diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html
index 8975b819b..146168e9a 100644
--- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html
+++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html
@@ -21,7 +21,9 @@
Risk assessment
diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss
index 0fc2c3784..1f8926b2e 100644
--- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss
+++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss
@@ -16,6 +16,11 @@
@import 'src/theming/colors';
@import 'src/theming/variables';
+:host {
+ overflow: hidden;
+ display: flex;
+}
+
.risk-assessment-content-empty {
height: 100%;
width: calc(100%);
@@ -32,17 +37,20 @@
.risk-assessment-container,
.risk-assessment-content {
- height: 100%;
background-color: $white;
}
+.risk-assessment-container {
+ flex: 1;
+}
+
.risk-assessment-content {
display: flex;
flex-direction: column;
gap: 14px;
- width: calc(100% - $profiles-drawer-width);
box-sizing: border-box;
padding-right: 94px;
+ overflow: hidden;
}
.risk-assessment-toolbar {
@@ -53,6 +61,8 @@
.main-content {
padding: 16px 32px;
+ overflow: scroll;
+ width: calc(100% - $profiles-drawer-width);
}
.profiles-drawer {
diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts
index 059f9888e..4dda189b7 100644
--- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts
+++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts
@@ -29,7 +29,7 @@ import { MatSidenavModule } from '@angular/material/sidenav';
import { PROFILE_MOCK } from '../../mocks/profile.mock';
import { of } from 'rxjs';
import { Component, Input } from '@angular/core';
-import { Profile } from '../../model/profile';
+import { Profile, ProfileFormat } from '../../model/profile';
import { MatDialogRef } from '@angular/material/dialog';
import { DeleteFormComponent } from '../../components/delete-form/delete-form.component';
import { FocusManagerService } from '../../services/focus-manager.service';
@@ -54,6 +54,7 @@ describe('RiskAssessmentComponent', () => {
mockRiskAssessmentStore = jasmine.createSpyObj('RiskAssessmentStore', [
'deleteProfile',
'setFocus',
+ 'getProfilesFormat',
]);
await TestBed.configureTestingModule({
@@ -87,6 +88,7 @@ describe('RiskAssessmentComponent', () => {
beforeEach(() => {
component.viewModel$ = of({
profiles: [] as Profile[],
+ profileFormat: [],
});
mockRiskAssessmentStore.profiles$ = of([]);
fixture.detectChanges();
@@ -130,6 +132,7 @@ describe('RiskAssessmentComponent', () => {
beforeEach(() => {
component.viewModel$ = of({
profiles: [PROFILE_MOCK, PROFILE_MOCK],
+ profileFormat: [],
});
fixture.detectChanges();
});
@@ -188,4 +191,5 @@ class FakeProfileItemComponent {
})
class FakeProfileFormComponent {
@Input() profiles!: Profile[];
+ @Input() profileFormat!: ProfileFormat[];
}
diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts
index ca5725a59..69c6aa4f9 100644
--- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts
+++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts
@@ -13,7 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { ChangeDetectionStrategy, Component, OnDestroy } from '@angular/core';
+import {
+ ChangeDetectionStrategy,
+ Component,
+ OnDestroy,
+ OnInit,
+} from '@angular/core';
import { RiskAssessmentStore } from './risk-assessment.store';
import { DeleteFormComponent } from '../../components/delete-form/delete-form.component';
import { Subject, takeUntil } from 'rxjs';
@@ -26,7 +31,7 @@ import { MatDialog } from '@angular/material/dialog';
providers: [RiskAssessmentStore],
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class RiskAssessmentComponent implements OnDestroy {
+export class RiskAssessmentComponent implements OnInit, OnDestroy {
viewModel$ = this.store.viewModel$;
isOpenProfileForm = false;
private destroy$: Subject = new Subject();
@@ -35,6 +40,10 @@ export class RiskAssessmentComponent implements OnDestroy {
public dialog: MatDialog
) {}
+ ngOnInit() {
+ this.store.getProfilesFormat();
+ }
+
ngOnDestroy() {
this.destroy$.next(true);
this.destroy$.unsubscribe();
diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts
index a9add3bc4..e04a3121a 100644
--- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts
+++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts
@@ -14,13 +14,17 @@
* limitations under the License.
*/
import { TestBed } from '@angular/core/testing';
-import { of, take } from 'rxjs';
+import { of, skip, take } from 'rxjs';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import { TestRunService } from '../../services/test-run.service';
import SpyObj = jasmine.SpyObj;
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { RiskAssessmentStore } from './risk-assessment.store';
-import { PROFILE_MOCK, PROFILE_MOCK_2 } from '../../mocks/profile.mock';
+import {
+ PROFILE_FORM,
+ PROFILE_MOCK,
+ PROFILE_MOCK_2,
+} from '../../mocks/profile.mock';
import { FocusManagerService } from '../../services/focus-manager.service';
import { AppState } from '../../store/state';
import { selectRiskProfiles } from '../../store/selectors';
@@ -33,7 +37,11 @@ describe('RiskAssessmentStore', () => {
let mockFocusManagerService: SpyObj;
beforeEach(() => {
- mockService = jasmine.createSpyObj(['fetchProfiles', 'deleteProfile']);
+ mockService = jasmine.createSpyObj([
+ 'fetchProfiles',
+ 'deleteProfile',
+ 'fetchProfilesFormat',
+ ]);
mockFocusManagerService = jasmine.createSpyObj([
'focusFirstElementInContainer',
]);
@@ -65,11 +73,23 @@ describe('RiskAssessmentStore', () => {
expect(riskAssessmentStore).toBeTruthy();
});
+ describe('updaters', () => {
+ it('should update activeFiler', (done: DoneFn) => {
+ riskAssessmentStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => {
+ expect(store.profileFormat).toEqual(PROFILE_FORM);
+ done();
+ });
+
+ riskAssessmentStore.updateProfileFormat(PROFILE_FORM);
+ });
+ });
+
describe('selectors', () => {
it('should select state', done => {
riskAssessmentStore.viewModel$.pipe(take(1)).subscribe(store => {
expect(store).toEqual({
profiles: [PROFILE_MOCK, PROFILE_MOCK_2],
+ profileFormat: [],
});
done();
});
@@ -136,5 +156,20 @@ describe('RiskAssessmentStore', () => {
).toHaveBeenCalledWith();
});
});
+
+ describe('getProfilesFormat', () => {
+ it('should update store', done => {
+ mockService.fetchProfilesFormat.and.returnValue(of(PROFILE_FORM));
+
+ riskAssessmentStore.viewModel$
+ .pipe(skip(1), take(1))
+ .subscribe(store => {
+ expect(store.profileFormat).toEqual(PROFILE_FORM);
+ done();
+ });
+
+ riskAssessmentStore.getProfilesFormat();
+ });
+ });
});
});
diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts
index 737d8f024..3a47abf53 100644
--- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts
+++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts
@@ -19,7 +19,7 @@ import { ComponentStore } from '@ngrx/component-store';
import { tap, withLatestFrom } from 'rxjs/operators';
import { exhaustMap } from 'rxjs';
import { TestRunService } from '../../services/test-run.service';
-import { Profile } from '../../model/profile';
+import { Profile, ProfileFormat } from '../../model/profile';
import { FocusManagerService } from '../../services/focus-manager.service';
import { Store } from '@ngrx/store';
import { AppState } from '../../store/state';
@@ -28,15 +28,25 @@ import { setRiskProfiles } from '../../store/actions';
export interface AppComponentState {
profiles: Profile[];
+ profileFormat: ProfileFormat[];
}
@Injectable()
export class RiskAssessmentStore extends ComponentStore {
profiles$ = this.store.select(selectRiskProfiles);
+ profileFormat$ = this.select(state => state.profileFormat);
viewModel$ = this.select({
profiles: this.profiles$,
+ profileFormat: this.profileFormat$,
});
+ updateProfileFormat = this.updater(
+ (state, profileFormat: ProfileFormat[]) => ({
+ ...state,
+ profileFormat,
+ })
+ );
+
deleteProfile = this.effect(trigger$ => {
return trigger$.pipe(
exhaustMap((name: string) => {
@@ -69,6 +79,18 @@ export class RiskAssessmentStore extends ComponentStore {
}
);
+ getProfilesFormat = this.effect(trigger$ => {
+ return trigger$.pipe(
+ exhaustMap(() => {
+ return this.testRunService.fetchProfilesFormat().pipe(
+ tap((profileFormat: ProfileFormat[]) => {
+ this.updateProfileFormat(profileFormat);
+ })
+ );
+ })
+ );
+ });
+
private removeProfile(name: string, current: Profile[]): void {
const profiles = current.filter(profile => profile.name !== name);
this.updateProfiles(profiles);
@@ -85,6 +107,7 @@ export class RiskAssessmentStore extends ComponentStore {
) {
super({
profiles: [],
+ profileFormat: [],
});
}
}
diff --git a/modules/ui/src/app/services/test-run.service.spec.ts b/modules/ui/src/app/services/test-run.service.spec.ts
index fcf00f02b..c8c5a74b7 100644
--- a/modules/ui/src/app/services/test-run.service.spec.ts
+++ b/modules/ui/src/app/services/test-run.service.spec.ts
@@ -34,7 +34,7 @@ import { MockStore, provideMockStore } from '@ngrx/store/testing';
import { AppState } from '../store/state';
import { Certificate } from '../model/certificate';
import { certificate } from '../mocks/certificate.mock';
-import { PROFILE_MOCK } from '../mocks/profile.mock';
+import { PROFILE_FORM, PROFILE_MOCK } from '../mocks/profile.mock';
const MOCK_SYSTEM_CONFIG: SystemConfig = {
network: {
@@ -590,4 +590,20 @@ describe('TestRunService', () => {
req.flush(true);
});
+
+ describe('fetchProfilesFormat', () => {
+ it('should get system status data with no changes', () => {
+ const result = { ...PROFILE_FORM };
+
+ service.fetchProfilesFormat().subscribe(res => {
+ expect(res).toEqual(result);
+ });
+
+ const req = httpTestingController.expectOne(
+ 'http://localhost:8000/profiles/format'
+ );
+ expect(req.request.method).toBe('GET');
+ req.flush(result);
+ });
+ });
});
diff --git a/modules/ui/src/app/services/test-run.service.ts b/modules/ui/src/app/services/test-run.service.ts
index 4cf4a140d..9aa3ae367 100644
--- a/modules/ui/src/app/services/test-run.service.ts
+++ b/modules/ui/src/app/services/test-run.service.ts
@@ -28,7 +28,7 @@ import {
} from '../model/testrun-status';
import { Version } from '../model/version';
import { Certificate } from '../model/certificate';
-import { Profile } from '../model/profile';
+import { Profile, ProfileFormat } from '../model/profile';
const API_URL = `http://${window.location.hostname}:8000`;
export const SYSTEM_STOP = '/system/stop';
@@ -260,4 +260,8 @@ export class TestRunService {
downloadZip(url: string, profile: string) {
return this.http.post(url, JSON.stringify({ profile }));
}
+
+ fetchProfilesFormat(): Observable {
+ return this.http.get(`${API_URL}/profiles/format`);
+ }
}