Possibility to create and edit typeless policies 14/3714/1
authorelinuxhenrik <henrik.b.andersson@est.tech>
Fri, 15 May 2020 15:15:04 +0000 (17:15 +0200)
committerelinuxhenrik <henrik.b.andersson@est.tech>
Fri, 15 May 2020 15:36:45 +0000 (17:36 +0200)
Change-Id: I3ad0345c134705e7d60c0f09fee7f7874f963847
Issue-ID: NONRTRIC-212
Signed-off-by: elinuxhenrik <henrik.b.andersson@est.tech>
webapp-backend/src/test/java/org/oransc/portal/nonrtric/controlpanel/config/PolicyControllerMockConfiguration.java
webapp-frontend/src/app/controlpanel.module.ts
webapp-frontend/src/app/policy-control/no-type-policy-instance-dialog.component.html [new file with mode: 0644]
webapp-frontend/src/app/policy-control/no-type-policy-instance-dialog.component.scss [new file with mode: 0644]
webapp-frontend/src/app/policy-control/no-type-policy-instance-dialog.component.ts [new file with mode: 0644]
webapp-frontend/src/app/policy-control/policy-control.component.html
webapp-frontend/src/app/policy-control/policy-control.component.ts
webapp-frontend/src/app/policy-control/policy-instance-dialog.component.html
webapp-frontend/src/app/policy-control/policy-instance-dialog.component.ts
webapp-frontend/src/app/policy-control/policy-instance.component.ts

index ff17d06..89a3ede 100644 (file)
@@ -21,7 +21,6 @@
 package org.oransc.portal.nonrtric.controlpanel.config;
 
 import com.google.gson.GsonBuilder;
-
 import java.io.BufferedReader;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -34,7 +33,6 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.Vector;
 import java.util.stream.Collectors;
-
 import org.oransc.portal.nonrtric.controlpanel.model.ImmutablePolicyInfo;
 import org.oransc.portal.nonrtric.controlpanel.model.PolicyInfo;
 import org.oransc.portal.nonrtric.controlpanel.model.PolicyInstances;
@@ -116,8 +114,11 @@ public class PolicyControllerMockConfiguration {
     class Database {
 
         Database() {
+            PolicyType policyType = new PolicyType("", "{}");
+            types.put("", policyType);
+
             String schema = getStringFromFile("demo-policy-schema-1.json");
-            PolicyType policyType = new PolicyType("type2", schema);
+            policyType = new PolicyType("type2", schema);
             types.put("type2", policyType);
 
             schema = getStringFromFile("demo-policy-schema-2.json");
@@ -127,6 +128,8 @@ public class PolicyControllerMockConfiguration {
             schema = getStringFromFile("demo-policy-schema-3.json");
             policyType = new PolicyType("type4", schema);
             types.put("type4", policyType);
+
+            putInstance("", "123", "{\"data\":\"data\"}", "ric_1");
         }
 
         private String getStringFromFile(String path) {
index 2540edd..4abd69f 100644 (file)
@@ -46,6 +46,7 @@ import { FlexLayoutModule } from '@angular/flex-layout';
 import { FooterComponent } from './footer/footer.component';
 import { MainComponent } from './main/main.component';
 import { MaterialDesignFrameworkModule } from 'angular6-json-schema-form';
+import { NoTypePolicyInstanceDialogComponent } from './policy-control/no-type-policy-instance-dialog.component';
 import { PolicyCardComponent } from './ui/policy-card/policy-card.component';
 import { PolicyControlComponent } from './policy-control/policy-control.component';
 import { PolicyInstanceComponent } from './policy-control/policy-instance.component';
@@ -62,6 +63,7 @@ import { CookieModule } from 'ngx-cookie';
     ErrorDialogComponent,
     FooterComponent,
     MainComponent,
+    NoTypePolicyInstanceDialogComponent,
     PolicyCardComponent,
     PolicyControlComponent,
     PolicyInstanceComponent,
@@ -129,6 +131,7 @@ import { CookieModule } from 'ngx-cookie';
   entryComponents: [
     ConfirmDialogComponent,
     ErrorDialogComponent,
+    NoTypePolicyInstanceDialogComponent,
     PolicyInstanceDialogComponent
   ],
   providers: [
diff --git a/webapp-frontend/src/app/policy-control/no-type-policy-instance-dialog.component.html b/webapp-frontend/src/app/policy-control/no-type-policy-instance-dialog.component.html
new file mode 100644 (file)
index 0000000..393d40b
--- /dev/null
@@ -0,0 +1,75 @@
+<!--
+  ========================LICENSE_START=================================
+  O-RAN-SC
+  %%
+  Copyright (C) 2020 Nordix Foundation
+  %%
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ========================LICENSE_END===================================
+  -->
+
+<div class="mat-elevation-z8 header row" [ngClass]="{'header-dark': darkMode}">
+    <div class="logo">
+        <img src="../../assets/oran-logo.png" width="30px" height="30px" style="position: relative; z-index: 50" />
+        <svg class="logo__icon" viewBox="150.3 22.2 1000 50">
+            <text class="logo__text" [ngClass]="{'logo__text-dark': darkMode}" font-size="30" font-weight="600"
+                letter-spacing=".1em" transform="translate(149 56)">
+                <tspan>Policy with no type</tspan>
+            </text>
+        </svg>
+    </div>
+</div>
+
+<div class="text-muted">Since this is a policy type without a schema, the user will have to make sure that the content
+    of the policy has the necessary content. This dialog will only validate that it is a valid JSON string.</div>
+
+<form [formGroup]="instanceForm" novalidate autocomplete="off">
+    <div mat-dialog-content>
+        <mat-form-field *ngIf="!this.policyInstanceId">
+            <mat-label [class.text-danger]="!this.ric">Select RIC</mat-label>
+            <mat-select id="ricSelector" formControlName="ricSelector" matInput required [(value)]="this.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 alert-danger">
+                <div *ngIf="ricSelector.errors.required">
+                    A Ric must be selected.
+                </div>
+            </div>
+        </mat-form-field>
+
+        <mat-form-field style="width: 800px;">
+            <mat-label>Policy content</mat-label>
+            <textarea id="policyJsonTextArea" formControlName="policyJsonTextArea" matInput cdkTextareaAutosize
+                cdkAutosizeMinRows="10" required>
+            </textarea>
+            <div *ngIf="policyJsonTextArea.invalid && (policyJsonTextArea.dirty || policyJsonTextArea.touched)"
+                class="alert alert-danger">
+                <div *ngIf="policyJsonTextArea.errors.required">
+                    The policy body is required.
+                </div>
+                <div *ngIf="policyJsonTextArea.errors.invalidJson">
+                    The policy body must be a valid JSON.
+                </div>
+            </div>
+        </mat-form-field>
+    </div>
+    <div mat-dialog-actions class="modal-footer justify-content-center">
+        <button mat-raised-button (click)="this.onSubmit()" class="submitBtn" [disabled]="!instanceForm.valid">
+            Submit
+        </button>
+        <button mat-raised-button class="mat-raised-button" [mat-dialog-close]="false">Close</button>
+    </div>
+</form>
\ No newline at end of file
diff --git a/webapp-frontend/src/app/policy-control/no-type-policy-instance-dialog.component.scss b/webapp-frontend/src/app/policy-control/no-type-policy-instance-dialog.component.scss
new file mode 100644 (file)
index 0000000..39787cd
--- /dev/null
@@ -0,0 +1,47 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+
+.logo__text {
+    fill: #2B244D;
+}
+
+.logo__text-dark {
+    fill: #ffffff;
+}
+
+.header-dark {
+    background: #2B244D;
+}
+
+.logo {
+    margin-left: 10px;
+}
+
+.logo__icon {
+    height: 2rem;
+    margin-left: 1rem;
+}
+
+.submitBtn {
+    background-color: #4CAF50;
+    /* Green */
+    margin-right: 10px;
+}
diff --git a/webapp-frontend/src/app/policy-control/no-type-policy-instance-dialog.component.ts b/webapp-frontend/src/app/policy-control/no-type-policy-instance-dialog.component.ts
new file mode 100644 (file)
index 0000000..da05ebb
--- /dev/null
@@ -0,0 +1,128 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2020 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { Component, Inject, OnInit } from '@angular/core';
+import { FormControl, FormGroup, Validators, ValidatorFn, AbstractControl } from '@angular/forms';
+import { MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { PolicyService } from '../services/policy/policy.service';
+import { NotificationService } from '../services/ui/notification.service';
+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';
+
+@Component({
+  selector: 'rd-no-type-policy-instance-dialog',
+  templateUrl: './no-type-policy-instance-dialog.component.html',
+  styleUrls: ['./no-type-policy-instance-dialog.component.scss']
+})
+export class NoTypePolicyInstanceDialogComponent implements OnInit {
+
+  // Declare following variables as Public variable. Private variables should not be used in template HTML
+  instanceForm: FormGroup;
+
+  policyInstanceId: string; // null if not yet created
+  policyJson: string;
+  darkMode: boolean;
+  ric: string;
+  allRics: string[];
+
+  constructor(
+    private policySvc: PolicyService,
+    private errorService: ErrorDialogService,
+    private notificationService: NotificationService,
+    @Inject(MAT_DIALOG_DATA) private data,
+    private ui: UiService) {
+    this.policyInstanceId = data.instanceId;
+    this.policyJson = data.instanceJson ? JSON.stringify(JSON.parse(data.instanceJson), null, 2) : '';
+    this.ric = data.ric;
+  }
+
+  ngOnInit() {
+    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.fetchRics();
+    }
+  }
+
+  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;
+    this.policySvc.putPolicy('', this.policyInstanceId, this.policyJsonTextArea.value, this.ric).subscribe(
+      {
+        next(_) {
+          self.notificationService.success('Policy without type:' + self.policyInstanceId + ' submitted');
+        },
+        error(error: HttpErrorResponse) {
+          self.errorService.displayError('Submit failed: ' + error.error);
+        },
+        complete() { }
+      });
+  }
+
+  private fetchRics() {
+    const self: NoTypePolicyInstanceDialogComponent = this;
+    this.policySvc.getRics('').subscribe(
+      {
+        next(value) {
+          self.allRics = value;
+          console.log(value);
+        },
+        error(error: HttpErrorResponse) {
+          self.errorService.displayError('Fetching of rics failed: ' + error.message);
+        },
+        complete() { }
+      });
+  }
+}
+
+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) {
+  try {
+    if (json != null) {
+      JSON.parse(json);
+      return true;
+    }
+  } catch (jsonError) {
+    return false;
+  }
+}
index 997defd..fc74800 100644 (file)
@@ -41,8 +41,7 @@
     <ng-container matColumnDef="action">
         <mat-header-cell class="action-cell" *matHeaderCellDef>Action </mat-header-cell>
         <mat-cell class="action-cell" *matCellDef="let policyType" (click)="$event.stopPropagation()">
-            <button [disabled]="this.isSchemaEmpty(policyType)" mat-icon-button
-                (click)="createPolicyInstance(policyType)">
+            <button mat-icon-button (click)="createPolicyInstance(policyType)">
                 <mat-icon matTooltip="Create instance">add_box</mat-icon>
             </button>
         </mat-cell>
index 4b9d85a..9cf2438 100644 (file)
@@ -30,6 +30,7 @@ import { PolicyInstanceDialogComponent } from './policy-instance-dialog.componen
 import { NotificationService } from '../services/ui/notification.service';
 import { BehaviorSubject, Observable } from 'rxjs';
 import { UiService } from '../services/ui/ui.service';
+import { NoTypePolicyInstanceDialogComponent } from './no-type-policy-instance-dialog.component';
 
 class PolicyTypeInfo {
     constructor(public type: PolicyType) { }
@@ -74,10 +75,17 @@ export class PolicyControlComponent implements OnInit {
     }
 
     createPolicyInstance(policyType: PolicyType): void {
-        const dialogRef = this.dialog.open(PolicyInstanceDialogComponent, getPolicyDialogProperties(policyType, null, this.darkMode));
+        let dialogRef;
+        if (this.isSchemaEmpty(policyType)) {
+            dialogRef = this.dialog.open(NoTypePolicyInstanceDialogComponent,
+                getPolicyDialogProperties(policyType, null, this.darkMode));
+        } else {
+            dialogRef = this.dialog.open(PolicyInstanceDialogComponent,
+                getPolicyDialogProperties(policyType, null, this.darkMode));
+        }
         const info: PolicyTypeInfo = this.getPolicyTypeInfo(policyType);
         dialogRef.afterClosed().subscribe(
-            (result: any) => {
+            (_) => {
                 info.isExpanded.next(info.isExpanded.getValue());
             }
         );
index f052cca..d447c70 100644 (file)
@@ -78,7 +78,7 @@
                     [class.text-danger]="!formIsValid">
                     {{formIsValid ? 'Json' : 'Not valid'}}
                 </strong>
-                <strong class="text-danger" *ngIf="!formIsValid && !prettyValidationErrors">Not submittable</strong>
+                <span *ngIf="!formIsValid && !prettyValidationErrors">Invalid form</span>
                 <span *ngIf="prettyValidationErrors">— errors:</span>
                 <div *ngIf="prettyValidationErrors" class="text-danger" [innerHTML]="prettyValidationErrors"></div>
             </div>
index 60ffbd3..8ab798e 100644 (file)
@@ -123,20 +123,11 @@ export class PolicyInstanceDialogComponent implements OnInit, AfterViewInit {
         if (!this.policyInstanceId) {
             this.fetchRics();
         }
-        if (this.isSchemaEmpty()) {
-            // Empty schema, hide form, show json instead
-            this.isVisible.form = false;
-            this.isVisible.json = true;
-        }
     }
 
     ngAfterViewInit() {
     }
 
-    private isSchemaEmpty(): boolean {
-        return Object.keys(this.jsonSchemaObject).length === 0;
-    }
-
     onSubmit() {
         if (this.policyInstanceId == null) {
             this.policyInstanceId = uuid.v4();
@@ -145,8 +136,9 @@ export class PolicyInstanceDialogComponent implements OnInit, AfterViewInit {
         const self: PolicyInstanceDialogComponent = this;
         this.dataService.putPolicy(this.policyTypeName, this.policyInstanceId, policyJson, this.ric).subscribe(
             {
-                next(value) {
-                    self.notificationService.success('Policy ' + self.policyTypeName + ':' + self.policyInstanceId + ' submitted');
+                next(_) {
+                    self.notificationService.success('Policy ' + self.policyTypeName + ':' + self.policyInstanceId +
+                    ' submitted');
                 },
                 error(error: HttpErrorResponse) {
                     self.errorService.displayError('Submit failed: ' + error.error);
@@ -163,18 +155,15 @@ export class PolicyInstanceDialogComponent implements OnInit, AfterViewInit {
         this.liveFormData = formData;
     }
 
-    get prettyLiveFormData(): string {
-        if (this.isSchemaEmpty()) {
-            return this.jsonAsString;
-        }
+    get prettyLiveFormData() {
         return JSON.stringify(this.liveFormData, null, 2);
     }
 
-    get schemaAsString(): string {
+    get schemaAsString() {
         return JSON.stringify(this.jsonSchemaObject, null, 2);
     }
 
-    get jsonAsString(): string {
+    get jsonAsString() {
         return JSON.stringify(this.jsonObject, null, 2);
     }
 
index 55f50f7..55d9dcb 100644 (file)
@@ -28,6 +28,7 @@ import { NotificationService } from '../services/ui/notification.service';
 import { PolicyService } from '../services/policy/policy.service';
 import { ConfirmDialogService } from './../services/ui/confirm-dialog.service';
 import { PolicyInstance } from '../interfaces/policy.types';
+import { NoTypePolicyInstanceDialogComponent } from './no-type-policy-instance-dialog.component';
 import { PolicyInstanceDialogComponent } from './policy-instance-dialog.component';
 import { getPolicyDialogProperties } from './policy-instance-dialog.component';
 import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
@@ -75,15 +76,26 @@ export class PolicyInstanceComponent implements OnInit, AfterViewInit {
         }
     }
 
+    private isSchemaEmpty(): boolean {
+        return Object.keys(this.policyType.schemaObject).length === 0;
+    }
+
     modifyInstance(instance: PolicyInstance): void {
         this.policySvc.getPolicy(this.policyType.name, instance.id).subscribe(
             (refreshedJson: any) => {
                 instance.json = JSON.stringify(refreshedJson);
-                this.dialog.open(PolicyInstanceDialogComponent, getPolicyDialogProperties(this.policyType, instance, this.darkMode));
+                if (this.isSchemaEmpty()) {
+                    this.dialog.open(
+                        NoTypePolicyInstanceDialogComponent,
+                        getPolicyDialogProperties(this.policyType, instance, this.darkMode));
+                } else {
+                    this.dialog.open(
+                        PolicyInstanceDialogComponent,
+                        getPolicyDialogProperties(this.policyType, instance, this.darkMode));
+                }
             },
             (httpError: HttpErrorResponse) => {
-                this.notificationService.error('Could not refresh instance ' + httpError.message);
-                this.dialog.open(PolicyInstanceDialogComponent, getPolicyDialogProperties(this.policyType, instance, this.darkMode));
+                this.notificationService.error('Could not refresh instance. Please try again.' + httpError.message);
             }
         );
     }