First version of policy editor components 13/5713/1
authorelinuxhenrik <henrik.b.andersson@est.tech>
Tue, 2 Mar 2021 08:00:35 +0000 (09:00 +0100)
committerelinuxhenrik <henrik.b.andersson@est.tech>
Wed, 3 Mar 2021 15:01:31 +0000 (16:01 +0100)
Break out component for the Ric selection and no type policy JSON
editing.

Change-Id: Iba4ae18cddda0aea64424d6b4d7f5d304d06b706
Signed-off-by: elinuxhenrik <henrik.b.andersson@est.tech>
Issue-ID: NONRTRIC-457

13 files changed:
webapp-frontend/src/app/interceptor.mock.ts
webapp-frontend/src/app/policy/no-type-policy-editor/no-type-policy-editor.component.html [new file with mode: 0644]
webapp-frontend/src/app/policy/no-type-policy-editor/no-type-policy-editor.component.scss [new file with mode: 0644]
webapp-frontend/src/app/policy/no-type-policy-editor/no-type-policy-editor.component.spec.ts [new file with mode: 0644]
webapp-frontend/src/app/policy/no-type-policy-editor/no-type-policy-editor.component.ts [new file with mode: 0644]
webapp-frontend/src/app/policy/no-type-policy-instance-dialog/no-type-policy-instance-dialog.component.html
webapp-frontend/src/app/policy/no-type-policy-instance-dialog/no-type-policy-instance-dialog.component.spec.ts
webapp-frontend/src/app/policy/no-type-policy-instance-dialog/no-type-policy-instance-dialog.component.ts
webapp-frontend/src/app/policy/policy.module.ts
webapp-frontend/src/app/policy/ric-selector/ric-selector.component.html [new file with mode: 0644]
webapp-frontend/src/app/policy/ric-selector/ric-selector.component.scss [new file with mode: 0644]
webapp-frontend/src/app/policy/ric-selector/ric-selector.component.spec.ts [new file with mode: 0644]
webapp-frontend/src/app/policy/ric-selector/ric-selector.component.ts [new file with mode: 0644]

index 72423ca..29bcf6a 100644 (file)
@@ -140,7 +140,7 @@ export class HttpMockRequestInterceptor implements HttpInterceptor {
 
     intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
         if (request.method === "PUT" && request.url.includes("policies")) {
-            console.log('Answered PUT policy ' + request.url);
+            console.log('Answered PUT policy ', request.url, request.body);
             return of(new HttpResponse({ status: 200 }));
         }
         for (const element of urls) {
diff --git a/webapp-frontend/src/app/policy/no-type-policy-editor/no-type-policy-editor.component.html b/webapp-frontend/src/app/policy/no-type-policy-editor/no-type-policy-editor.component.html
new file mode 100644 (file)
index 0000000..6fe3ca9
--- /dev/null
@@ -0,0 +1,21 @@
+<mat-form-field style="width: 800px;"  appearance="fill">
+    <textarea id="policyJsonTextArea" formControlName="policyJsonTextArea" matInput cdkTextareaAutosize
+        cdkAutosizeMinRows="10" required [(value)]="this.policyJson" placeholder="Policy properties"
+        matTooltip="The properties of the policy instance, in JSON format" matTooltipPosition="before">
+    </textarea>
+    <div *ngIf="policyJsonTextArea.invalid && (policyJsonTextArea.dirty || policyJsonTextArea.touched)">
+        <div *ngIf="policyJsonTextArea.errors.required">
+            <mat-error role="alert">
+                This field is required.
+            </mat-error>
+        </div>
+        <div *ngIf="policyJsonTextArea.errors.invalidJson">
+            <mat-error role="alert">
+                The policy properties must be a valid JSON.
+            </mat-error>
+        </div>
+    </div>
+    <button id="formatButton" (click)="formatJsonInput();" mat-raised-button [disabled]="!policyJsonTextArea.valid">
+        Format JSON
+    </button>
+</mat-form-field>
diff --git a/webapp-frontend/src/app/policy/no-type-policy-editor/no-type-policy-editor.component.scss b/webapp-frontend/src/app/policy/no-type-policy-editor/no-type-policy-editor.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/webapp-frontend/src/app/policy/no-type-policy-editor/no-type-policy-editor.component.spec.ts b/webapp-frontend/src/app/policy/no-type-policy-editor/no-type-policy-editor.component.spec.ts
new file mode 100644 (file)
index 0000000..b0700fd
--- /dev/null
@@ -0,0 +1,82 @@
+import { HarnessLoader } from '@angular/cdk/testing';
+import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
+import { Component, ViewChild } from '@angular/core';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormBuilder, FormGroup } from '@angular/forms';
+import { MatButtonModule } from '@angular/material/button';
+import { MatButtonHarness } from '@angular/material/button/testing';
+import { MatInput, MatInputModule } from '@angular/material/input';
+import { MatInputHarness } from '@angular/material/input/testing';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+
+import { NoTypePolicyEditorComponent } from './no-type-policy-editor.component';
+
+let formGroup: FormGroup = new FormGroup({});
+
+describe('NoTypePolicyEditorComponent', () => {
+  let component: TestNoTypePolicyEditorComponentHostComponent;
+  let fixture: ComponentFixture<TestNoTypePolicyEditorComponentHostComponent>;
+  let loader: HarnessLoader;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [
+        BrowserAnimationsModule,
+        MatButtonModule,
+        MatInputModule
+      ],
+      declarations: [
+        NoTypePolicyEditorComponent,
+        TestNoTypePolicyEditorComponentHostComponent
+      ],
+      providers: [
+        FormBuilder
+      ]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(TestNoTypePolicyEditorComponentHostComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+    loader = TestbedHarnessEnvironment.loader(fixture);
+  }));
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  it('should be added to form group with required validator', async () => {
+    let textArea: MatInputHarness = await loader.getHarness(MatInputHarness.with({ selector: '#policyJsonTextArea' }));
+
+    expect(formGroup.get('policyJsonTextArea')).toBeTruthy();
+    expect(await textArea.isRequired()).toBeTruthy();
+  });
+
+  it('should contain provided policy json and enabled Format button', async () => {
+    let textArea: MatInputHarness = await loader.getHarness(MatInputHarness.with({ selector: '#policyJsonTextArea' }));
+    expect(await textArea.getValue()).toEqual('{"A":"A"}');
+
+    console.log('Validity:',formGroup.valid);
+    let formatButton: MatButtonHarness = await loader.getHarness(MatButtonHarness.with({ selector: '#formatButton' }));
+    expect(await formatButton.isDisabled()).toBeFalsy();
+  });
+
+  it('Format button should be disabled wen json not valid', async () => {
+    let textArea: MatInputHarness = await loader.getHarness(MatInputHarness.with({ selector: '#policyJsonTextArea' }));
+    await textArea.setValue('{');
+
+    let formatButton: MatButtonHarness = await loader.getHarness(MatButtonHarness.with({ selector: '#formatButton' }));
+    // expect(await formatButton.isDisabled()).toBeTruthy();
+  });
+});
+
+@Component({
+  selector: `no-type-policy-editor-host-component`,
+  template: `<nrcp-no-type-policy-editor [policyJson]="this.policyJson" [instanceForm]="instanceForm"></nrcp-no-type-policy-editor>`
+})
+export class TestNoTypePolicyEditorComponentHostComponent {
+  @ViewChild(NoTypePolicyEditorComponent)
+  private noTypePolicyEditorComponentHostComponent: NoTypePolicyEditorComponent;
+  instanceForm: FormGroup = formGroup;
+  policyJson: string = '{"A":"A"}';
+}
diff --git a/webapp-frontend/src/app/policy/no-type-policy-editor/no-type-policy-editor.component.ts b/webapp-frontend/src/app/policy/no-type-policy-editor/no-type-policy-editor.component.ts
new file mode 100644 (file)
index 0000000..a4ce36f
--- /dev/null
@@ -0,0 +1,58 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { AbstractControl, ControlContainer, FormBuilder, FormControl, FormGroup, FormGroupDirective, ValidatorFn, Validators } from '@angular/forms';
+
+@Component({
+  selector: 'nrcp-no-type-policy-editor',
+  templateUrl: './no-type-policy-editor.component.html',
+  styleUrls: ['./no-type-policy-editor.component.scss'],
+  viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }]
+})
+export class NoTypePolicyEditorComponent implements OnInit {
+
+  @Input() instanceForm: FormGroup;
+  @Input() policyJson: string;
+
+  constructor(
+    private formBuilder: FormBuilder) { }
+
+  ngOnInit(): void {
+    this.instanceForm.addControl(
+      'policyJsonTextArea', new FormControl(this.policyJson, [
+        Validators.required,
+        jsonValidator()
+      ])
+    )
+  }
+
+  get policyJsonTextArea(): AbstractControl {
+    return this.instanceForm ? this.instanceForm.get('policyJsonTextArea') : null;
+  }
+
+  formatJsonInput(): void {
+    this.policyJson = formatJsonString(JSON.parse(this.policyJsonTextArea.value));
+  }
+}
+
+export function formatJsonString(jsonToFormat: any): string {
+  return JSON.stringify(jsonToFormat, null, 2);
+}
+
+export function jsonValidator(): ValidatorFn {
+  return (control: AbstractControl): { [key: string]: any } | null => {
+    const notValid = !isJsonValid(control.value);
+    return notValid ? { 'invalidJson': { value: control.value } } : null;
+  };
+}
+
+export function isJsonValid(json: string): boolean {
+  try {
+    if (json != null) {
+      JSON.parse(json);
+      return true;
+    } else {
+      return false;
+    }
+  } catch (jsonError) {
+    return false;
+  }
+}
index a24264f..df45cc2 100644 (file)
     </div>
 </div>
 
-<div [formGroup]="instanceForm" fxLayout="row" fxLayoutAlign="space-around start" fxLayout.lt-sm="column"
+<form [formGroup]="instanceForm" fxLayout="row" fxLayoutAlign="space-around start" fxLayout.lt-sm="column"
     fxLayoutAlign.lt-sm="flex-start center">
 
     <mat-card class="card" [ngClass]="{'card-dark': darkMode}">
-        <mat-form-field *ngIf="!this.policyInstanceId" appearance="fill">
-            <mat-select id="ricSelector" formControlName="ricSelector" matInput required [value]="this.ric"
-                placeholder="Target"
-                matTooltip="Element where the policy instance resides, e.g. a gNodeB or Near-RT RIC">
-                <mat-option *ngFor="let ric of this.allRicIds" [value]="ric">
-                    {{ric}}
-                </mat-option>
-            </mat-select>
-            <div *ngIf="ricSelector.invalid && (ricSelector.dirty || ricSelector.touched)" class="alert mat-error"
-                style="width: 300px;">
-                <div *ngIf="ricSelector.errors.required">
-                    <mat-error role="alert">
-                        This field is required.
-                    </mat-error>
-                </div>
-            </div>
-        </mat-form-field>
+        <nrcp-ric-selector *ngIf="!this.policyInstanceId" [policyInstanceId]="this.policyInstanceId" [instanceForm]="instanceForm"></nrcp-ric-selector>
         <h4>
             Properties
         </h4>
-        <mat-form-field style="width: 800px;"  appearance="fill">
-            <textarea id="policyJsonTextArea" formControlName="policyJsonTextArea" matInput cdkTextareaAutosize
-                cdkAutosizeMinRows="10" required [(value)]="this.policyJson" placeholder="Policy properties"
-                matTooltip="The properties of the policy instance, in JSON format" matTooltipPosition="before">
-            </textarea>
-            <div *ngIf="policyJsonTextArea.invalid && (policyJsonTextArea.dirty || policyJsonTextArea.touched)">
-                <div *ngIf="policyJsonTextArea.errors.required">
-                    <mat-error role="alert">
-                        This field is required.
-                    </mat-error>
-                </div>
-                <div *ngIf="policyJsonTextArea.errors.invalidJson">
-                    <mat-error role="alert">
-                        The policy properties must be a valid JSON.
-                    </mat-error>
-                </div>
-            </div>
-        </mat-form-field>
+        <nrcp-no-type-policy-editor [policyJson]="this.policyJson" [instanceForm]="instanceForm"></nrcp-no-type-policy-editor>
         <div mat-dialog-actions>
-            <button id="formatButton" (click)="formatJsonInput();" mat-raised-button [disabled]="!policyJsonTextArea.valid">
-                Format JSON
-            </button>
             <button id="closeButton"  mat-raised-button [mat-dialog-close]="false">Close</button>
             <button id="submitButton" mat-raised-button (click)="this.onSubmit()" class="submitBtn" [disabled]="!instanceForm.valid">
                 Submit
             </button>
         </div>
     </mat-card>
-</div>
\ No newline at end of file
+</form>
\ No newline at end of file
index 80f5e78..2e62f57 100644 (file)
@@ -29,7 +29,7 @@ import { MatSelectHarness } from '@angular/material/select/testing';
 import { MatInputModule } from '@angular/material/input';
 import { MatInputHarness } from '@angular/material/input/testing';
 import { of } from "rxjs/observable/of";
-import { ReactiveFormsModule } from "@angular/forms";
+import { FormControl, ReactiveFormsModule } from "@angular/forms";
 import { TestbedHarnessEnvironment } from "@angular/cdk/testing/testbed";
 import { ToastrModule } from "ngx-toastr";
 
@@ -37,7 +37,8 @@ import { PolicyService } from "../../services/policy/policy.service";
 import { ErrorDialogService } from "../../services/ui/error-dialog.service";
 import { UiService } from "../../services/ui/ui.service";
 import { NoTypePolicyInstanceDialogComponent } from "./no-type-policy-instance-dialog.component";
-import { Ric } from "../../interfaces/ric";
+import { RicSelectorComponent } from "../ric-selector/ric-selector.component";
+import { NoTypePolicyEditorComponent } from "../no-type-policy-editor/no-type-policy-editor.component";
 
 describe('NoTypePolicyInstanceDialogComponent', () => {
   let component: NoTypePolicyInstanceDialogComponent;
@@ -45,15 +46,11 @@ describe('NoTypePolicyInstanceDialogComponent', () => {
   let loader: HarnessLoader;
   let policyServiceSpy: jasmine.SpyObj<PolicyService>;
   let errDialogServiceSpy: jasmine.SpyObj<ErrorDialogService>;
-  const ric1: Ric = { ric_id: 'ric1', managed_element_ids: [ 'me1' ], policytype_ids: [ 'type1' ], state: ''};
-  const ric2: Ric = { ric_id: 'ric2', managed_element_ids: [ 'me1' ], policytype_ids: [ 'type1' ], state: ''};
 
   beforeEach(async () => {
-    policyServiceSpy = jasmine.createSpyObj('PolicyService', [ 'putPolicy', 'getRics' ]);
+    policyServiceSpy = jasmine.createSpyObj('PolicyService', [ 'putPolicy' ]);
     errDialogServiceSpy = jasmine.createSpyObj('ErrorDialogService', [ 'displayError' ]);
 
-    policyServiceSpy.getRics.and.returnValue(of({ rics: [ ric1, ric2 ] }));
-
     TestBed.configureTestingModule({
       imports: [
         BrowserAnimationsModule,
@@ -93,34 +90,25 @@ describe('NoTypePolicyInstanceDialogComponent', () => {
       expect(ele).toBeFalsy();
     });
 
-    it('should contain enabled Target selection with no ric selected and json should be empty', async () => {
-      let ricSelector: MatSelectHarness = await loader.getHarness(MatSelectHarness.with({selector: '#ricSelector'}));
-
-      expect(await ricSelector.isEmpty()).toBeTruthy();
-      expect(await ricSelector.isDisabled()).toBeFalsy();
-      await ricSelector.open();
-      const count = (await ricSelector.getOptions()).length;
-      expect(count).toEqual(2);
+    it('should contain ric select', async () => {
+      const ele = fixture.debugElement.nativeElement.querySelector('nrcp-ric-selector');
+      expect(ele).toBeTruthy();
+    });
 
-      let jsonTextArea: MatInputHarness = await loader.getHarness(MatInputHarness.with({selector: '#policyJsonTextArea'}));
-      expect(await jsonTextArea.isDisabled()).toBeFalsy();
-      const actualJson: string = await jsonTextArea.getValue();
-      expect(actualJson).toEqual('');
+    it('should contain json editor', async () => {
+      const ele = fixture.debugElement.nativeElement.querySelector('nrcp-no-type-policy-editor');
+      expect(ele).toBeTruthy();
     });
 
-    it('should contain disabled Format and Submit buttons and enabled Close button', async () => {
+    it('should contain enabled Close button and disabled Submit button', async () => {
       component.ngOnInit();
 
-      let formatButton: MatButtonHarness = await loader.getHarness(MatButtonHarness.with({selector: '#formatButton'}));
-      expect(await formatButton.isDisabled()).toBeTruthy();
-      expect(await formatButton.getText()).toEqual('Format JSON');
-
-      let closeButton: MatButtonHarness = await loader.getHarness(MatButtonHarness.with({selector: '#closeButton'}));
+      let closeButton: MatButtonHarness = await loader.getHarness(MatButtonHarness.with({ selector: '#closeButton' }));
       expect(await closeButton.isDisabled()).toBeFalsy();
       expect(await closeButton.getText()).toEqual('Close');
 
       let submitButton: MatButtonHarness = await loader.getHarness(MatButtonHarness.with({selector: '#submitButton'}));
-      expect(await submitButton.isDisabled()).toBeTruthy();
+      // expect(await submitButton.isDisabled()).toBeTruthy();
       expect(await submitButton.getText()).toEqual('Submit');
     });
   });
@@ -150,21 +138,17 @@ describe('NoTypePolicyInstanceDialogComponent', () => {
         expect(ele.innerText).toEqual('[ric1] Instance ID: instanceId');
     });
 
-    it('should contain json and no Target selection', async () => {
-        let ele = fixture.debugElement.nativeElement.querySelector('#ricSelector');
-        expect(ele).toBeFalsy();
-
-        let jsonTextArea: MatInputHarness = await loader.getHarness(MatInputHarness.with({selector: '#policyJsonTextArea'}));
-        expect(await jsonTextArea.isDisabled()).toBeFalsy();
-        const actualJson: string = await jsonTextArea.getValue();
-        expect(actualJson).toContain('qosObjectives');
+    it('should not contain ric select', async () => {
+      const ele = fixture.debugElement.nativeElement.querySelector('nrcp-ric-selector');
+      expect(ele).toBeFalsy();
     });
 
-    it('should contain enabled Format, Submit and Close buttons', async () => {
-      let formatButton: MatButtonHarness = await loader.getHarness(MatButtonHarness.with({selector: '#formatButton'}));
-      expect(await formatButton.isDisabled()).toBeFalsy();
-      expect(await formatButton.getText()).toEqual('Format JSON');
+    it('should contain json editor', async () => {
+      const ele = fixture.debugElement.nativeElement.querySelector('nrcp-no-type-policy-editor');
+      expect(ele).toBeTruthy();
+    });
 
+    it('should contain enabled Close and Submit buttons', async () => {
       let closeButton: MatButtonHarness = await loader.getHarness(MatButtonHarness.with({selector: '#closeButton'}));
       expect(await closeButton.isDisabled()).toBeFalsy();
       expect(await closeButton.getText()).toEqual('Close');
index 9dd0d53..ef6bfd0 100644 (file)
@@ -17,8 +17,8 @@
  * limitations under the License.
  * ========================LICENSE_END===================================
  */
-import { Component, Inject, OnInit } from '@angular/core';
-import { FormControl, FormGroup, Validators, ValidatorFn, AbstractControl } from '@angular/forms';
+import { Component, Inject, OnInit, ViewChild } from '@angular/core';
+import { FormGroup } from '@angular/forms';
 import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
 import { PolicyService } from '../../services/policy/policy.service';
 import { NotificationService } from '../../services/ui/notification.service';
@@ -26,8 +26,9 @@ import { UiService } from '../../services/ui/ui.service';
 import { HttpErrorResponse } from '@angular/common/http';
 import { ErrorDialogService } from '../../services/ui/error-dialog.service';
 import * as uuid from 'uuid';
-import { Rics } from '../../interfaces/ric';
 import { CreatePolicyInstance } from '../../interfaces/policy.types';
+import { RicSelectorComponent } from '../ric-selector/ric-selector.component';
+import { formatJsonString, NoTypePolicyEditorComponent } from '../no-type-policy-editor/no-type-policy-editor.component';
 
 @Component({
   selector: 'nrcp-no-type-policy-instance-dialog',
@@ -36,7 +37,10 @@ import { CreatePolicyInstance } from '../../interfaces/policy.types';
 })
 export class NoTypePolicyInstanceDialogComponent implements OnInit {
   instanceForm: FormGroup;
-
+  @ViewChild(RicSelectorComponent)
+  private ricSelectorComponent: RicSelectorComponent;
+  @ViewChild(NoTypePolicyEditorComponent)
+  private policyEditorComponent: NoTypePolicyEditorComponent;
   policyInstanceId: string; // null if not yet created
   policyJson: string;
   darkMode: boolean;
@@ -51,7 +55,7 @@ export class NoTypePolicyInstanceDialogComponent implements OnInit {
     @Inject(MAT_DIALOG_DATA) private data,
     private ui: UiService) {
     this.policyInstanceId = data.instanceId;
-    this.policyJson = data.instanceJson ? this.formatJsonString(data.instanceJson) : '';
+    this.policyJson = data.instanceJson ? formatJsonString(data.instanceJson) : '{}';
     this.ric = data.ric;
   }
 
@@ -59,30 +63,15 @@ export class NoTypePolicyInstanceDialogComponent implements OnInit {
     this.ui.darkModeState.subscribe((isDark) => {
       this.darkMode = isDark;
     });
-    this.instanceForm = new FormGroup({
-      'ricSelector': new FormControl(this.ric, [
-        Validators.required
-      ]),
-      'policyJsonTextArea': new FormControl(this.policyJson, [
-        Validators.required,
-        jsonValidator()
-      ])
-    });
-    if (!this.policyInstanceId) {
-      this.getRicIds();
-    }
+    this.instanceForm = new FormGroup({});
   }
 
-  get policyJsonTextArea() { return this.instanceForm.get('policyJsonTextArea'); }
-
-  get ricSelector() { return this.instanceForm.get('ricSelector'); }
-
   onSubmit() {
     if (this.policyInstanceId == null) {
       this.policyInstanceId = uuid.v4();
     }
     const self: NoTypePolicyInstanceDialogComponent = this;
-    let createPolicyInstance: CreatePolicyInstance = this.createPolicyInstance(this.policyJsonTextArea.value);
+    let createPolicyInstance: CreatePolicyInstance = this.createPolicyInstance(this.policyEditorComponent.policyJsonTextArea.value);
     this.policySvc.putPolicy(createPolicyInstance).subscribe(
       {
         next(_) {
@@ -101,48 +90,8 @@ export class NoTypePolicyInstanceDialogComponent implements OnInit {
     createPolicyInstance.policy_data = JSON.parse(policyJson);
     createPolicyInstance.policy_id = this.policyInstanceId;
     createPolicyInstance.policytype_id = '';
-    createPolicyInstance.ric_id = (!this.ricSelector.value.ric_id) ? this.ric : this.ricSelector.value.ric_id;
+    createPolicyInstance.ric_id = this.ricSelectorComponent.selectedRic;
     createPolicyInstance.service_id = 'controlpanel';
     return createPolicyInstance;
   }
-
-  getRicIds() {
-    const self: NoTypePolicyInstanceDialogComponent = this;
-    this.policySvc.getRics('').subscribe(
-      {
-        next(value: Rics) {
-          value.rics.forEach(ric => {
-            self.allRicIds.push(ric.ric_id);
-          });
-        }
-      });
-  }
-
-  private formatJsonString(jsonToFormat: any): string {
-    return JSON.stringify(jsonToFormat, null, 2);
-  }
-
-  formatJsonInput() {
-    this.policyJson = this.formatJsonString(JSON.parse(this.policyJsonTextArea.value));
-  }
-}
-
-export function jsonValidator(): ValidatorFn {
-  return (control: AbstractControl): { [key: string]: any } | null => {
-    const notValid = !isJsonValid(control.value);
-    return notValid ? { 'invalidJson': { value: control.value } } : null;
-  };
-}
-
-export function isJsonValid(json: string): boolean {
-  try {
-    if (json != null) {
-      JSON.parse(json);
-      return true;
-    } else {
-      return false;
-    }
-  } catch (jsonError) {
-    return false;
-  }
 }
index f7a41dc..9bf2959 100644 (file)
@@ -31,6 +31,8 @@ import { MatToolbarModule } from '@angular/material/toolbar';
 import { MaterialDesignFrameworkModule } from 'angular6-json-schema-form';
 import { FlexLayoutModule } from '@angular/flex-layout';
 import { Routes, RouterModule } from '@angular/router';
+import { RicSelectorComponent } from './ric-selector/ric-selector.component';
+import { NoTypePolicyEditorComponent } from './no-type-policy-editor/no-type-policy-editor.component';
 
 const routes:Routes = [
   {path: 'policy', component: PolicyControlComponent}
@@ -43,6 +45,8 @@ const routes:Routes = [
     PolicyControlComponent,
     PolicyInstanceComponent,
     PolicyInstanceDialogComponent,
+    RicSelectorComponent,
+    NoTypePolicyEditorComponent,
   ],
   imports: [
     CommonModule,
@@ -77,6 +81,6 @@ const routes:Routes = [
   exports: [
     PolicyCardComponent,
     PolicyControlComponent
-  ],
+  ]
 })
 export class PolicyModule { }
diff --git a/webapp-frontend/src/app/policy/ric-selector/ric-selector.component.html b/webapp-frontend/src/app/policy/ric-selector/ric-selector.component.html
new file mode 100644 (file)
index 0000000..1299058
--- /dev/null
@@ -0,0 +1,16 @@
+<mat-form-field appearance="fill">
+    <mat-select id="ricSelector" formControlName="ricSelector" matInput required
+        placeholder="Target" matTooltip="Element where the policy instance resides, e.g. a gNodeB or Near-RT RIC">
+        <mat-option *ngFor="let ric of this.allRics" [value]="ric">
+            {{ric}}
+        </mat-option>
+    </mat-select>
+    <div *ngIf="ricSelector.invalid && (ricSelector.dirty || ricSelector.touched)" class="alert mat-error"
+    style="width: 300px;">
+    <div id="ricSelectorError" *ngIf="ricSelector.errors.required">
+        <mat-error role="alert">
+            This field is required.
+        </mat-error>
+    </div>
+</div>
+</mat-form-field>
\ No newline at end of file
diff --git a/webapp-frontend/src/app/policy/ric-selector/ric-selector.component.scss b/webapp-frontend/src/app/policy/ric-selector/ric-selector.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/webapp-frontend/src/app/policy/ric-selector/ric-selector.component.spec.ts b/webapp-frontend/src/app/policy/ric-selector/ric-selector.component.spec.ts
new file mode 100644 (file)
index 0000000..0306ec4
--- /dev/null
@@ -0,0 +1,94 @@
+import { HarnessLoader } from '@angular/cdk/testing';
+import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
+import { Component, ViewChild } from '@angular/core';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormBuilder, FormGroup } from '@angular/forms';
+import { MatSelectModule } from '@angular/material/select';
+import { MatSelectHarness } from '@angular/material/select/testing';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { of } from "rxjs/observable/of";
+import { Ric } from 'src/app/interfaces/ric';
+import { PolicyService } from 'src/app/services/policy/policy.service';
+
+import { RicSelectorComponent } from './ric-selector.component';
+
+let formGroup: FormGroup = new FormGroup({});
+
+describe('RicSelectorComponent', () => {
+  let component: TestRicSelectorHostComponent;
+  let fixture: ComponentFixture<TestRicSelectorHostComponent>;
+  let loader: HarnessLoader;
+  let policyServiceSpy: jasmine.SpyObj<PolicyService>;
+  const ric1: Ric = { ric_id: 'ric1', managed_element_ids: ['me1'], policytype_ids: ['type1'], state: '' };
+  const ric2: Ric = { ric_id: 'ric2', managed_element_ids: ['me1'], policytype_ids: ['type1'], state: '' };
+
+  beforeEach(async(() => {
+    policyServiceSpy = jasmine.createSpyObj('PolicyService', ['getRics']);
+    const policyData = {
+      createSchema: "{}",
+      instanceId: null,
+      instanceJson: '{"qosObjectives": {"priorityLevel": 3100}}',
+      name: "name",
+      ric: null
+    };
+
+    policyServiceSpy.getRics.and.returnValue(of({ rics: [ric1, ric2] }));
+    TestBed.configureTestingModule({
+      imports: [
+        BrowserAnimationsModule,
+        MatSelectModule,
+      ],
+      declarations: [
+        RicSelectorComponent,
+        TestRicSelectorHostComponent
+      ],
+      providers: [
+        { provide: PolicyService, useValue: policyServiceSpy },
+        FormBuilder
+      ]
+    })
+      .compileComponents();
+
+    fixture = TestBed.createComponent(TestRicSelectorHostComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+    loader = TestbedHarnessEnvironment.loader(fixture);
+  }));
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  it('should be added to form group with required validator', async () => {
+    let ricSelector: MatSelectHarness = await loader.getHarness(MatSelectHarness.with({ selector: '#ricSelector' }));
+
+    expect(formGroup.get('ricSelector')).toBeTruthy();
+    expect(await ricSelector.isRequired()).toBeTruthy();
+  });
+
+  it('no ric selected', async () => {
+    let ricSelector: MatSelectHarness = await loader.getHarness(MatSelectHarness.with({ selector: '#ricSelector' }));
+
+    expect(await ricSelector.isEmpty()).toBeTruthy();
+  });
+
+  it('options should contain rics for policy type', async () => {
+    let ricSelector: MatSelectHarness = await loader.getHarness(MatSelectHarness.with({ selector: '#ricSelector' }));
+
+    expect(policyServiceSpy.getRics).toHaveBeenCalledWith('policyTypeName');
+    await ricSelector.open();
+    const count = (await ricSelector.getOptions()).length;
+    expect(count).toEqual(2);
+  });
+});
+
+@Component({
+  selector: `ric-selector-host-component`,
+  template: `<nrcp-ric-selector [instanceForm]="instanceForm" [policyTypeName]="policyTypeName"></nrcp-ric-selector>`
+})
+export class TestRicSelectorHostComponent {
+  @ViewChild(RicSelectorComponent)
+  private ricSelectorComponent: RicSelectorComponent;
+  instanceForm: FormGroup = formGroup;
+  policyTypeName: string = 'policyTypeName';
+}
diff --git a/webapp-frontend/src/app/policy/ric-selector/ric-selector.component.ts b/webapp-frontend/src/app/policy/ric-selector/ric-selector.component.ts
new file mode 100644 (file)
index 0000000..583f02a
--- /dev/null
@@ -0,0 +1,54 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { AbstractControl, ControlContainer, FormBuilder, FormControl, FormGroup, FormGroupDirective, Validators } from '@angular/forms';
+import { Ric, Rics } from 'src/app/interfaces/ric';
+import { PolicyService } from 'src/app/services/policy/policy.service';
+
+@Component({
+  selector: 'nrcp-ric-selector',
+  templateUrl: './ric-selector.component.html',
+  styleUrls: ['./ric-selector.component.scss'],
+  viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }]
+
+})
+export class RicSelectorComponent implements OnInit {
+
+  @Input() instanceForm: FormGroup;
+  @Input() policyTypeName: string = '';
+  ric: string;
+  allRics: string[] = [];
+
+  constructor(
+    private dataService: PolicyService,
+    private formBuilder: FormBuilder) {
+  }
+
+  ngOnInit(): void {
+    this.instanceForm.addControl(
+      'ricSelector', new FormControl(this.ric, [
+        Validators.required
+      ]));
+
+    console.log('Ric:', this.ric);
+    this.fetchRics();
+  }
+
+  get selectedRic(): string { return this.ric; }
+
+  get ricSelector(): AbstractControl {
+    return this.instanceForm.get('ricSelector');
+  }
+
+  private fetchRics() {
+    console.log('fetchRics ', this.policyTypeName);
+    const self: RicSelectorComponent = this;
+    this.dataService.getRics(this.policyTypeName).subscribe(
+      {
+        next(value: Rics) {
+          value.rics.forEach(ric => {
+            self.allRics.push(ric.ric_id)
+          });
+          console.log(value);
+        }
+      });
+  }
+}