Support for category specific metadata

Signed-off-by: MichaelMorris <michael.morris@est.tech>
Issue-ID: SDC-3412
Change-Id: I87392cc21dc25253b558bdc1d453d99659d049fa
diff --git a/catalog-ui/src/app/models/category.ts b/catalog-ui/src/app/models/category.ts
index 15df985..64588d0 100644
--- a/catalog-ui/src/app/models/category.ts
+++ b/catalog-ui/src/app/models/category.ts
@@ -28,6 +28,7 @@
     normalizedName:string;
     uniqueId:string;
     icons:Array<string>;
+    metadataKeys: IMetadataKey[];
 
     //custom properties
     filterTerms:string;
@@ -46,3 +47,9 @@
 
 export interface IGroup extends ICategoryBase {
 }
+
+export interface IMetadataKey {
+	name:string;
+	mandatory:boolean
+	validValues: string[];
+}
diff --git a/catalog-ui/src/app/models/component-metadata.ts b/catalog-ui/src/app/models/component-metadata.ts
index 8a4b257..186cd8a 100644
--- a/catalog-ui/src/app/models/component-metadata.ts
+++ b/catalog-ui/src/app/models/component-metadata.ts
@@ -21,6 +21,7 @@
 import { CapabilitiesGroup, RequirementsGroup } from 'app/models';
 import { ComponentType } from 'app/utils';
 import { IMainCategory } from './category';
+import { Metadata } from "app/models/metadata";
 /**
  * Created by obarda on 4/18/2017.
  */
@@ -53,6 +54,7 @@
     vspArchived: boolean;
     selectedCategory: string;
     filterTerm: string;
+    categorySpecificMetadata: Metadata;
 
     // Resource only
     resourceType: string;
@@ -115,6 +117,7 @@
     public toscaResourceName: string;
     public selectedCategory: string;
     public filterTerm: string;
+    public categorySpecificMetadata: Metadata = new Metadata();
 
     // Resource only
     public resourceType: string;
@@ -192,6 +195,7 @@
         this.toscaResourceName = response.toscaResourceName;
         this.capabilities = response.capabilities;
         this.requirements = response.requirements;
+        this.categorySpecificMetadata = response.categorySpecificMetadata;
         return this;
     }
 
diff --git a/catalog-ui/src/app/models/components/component.ts b/catalog-ui/src/app/models/components/component.ts
index a0706b4..1d48151 100644
--- a/catalog-ui/src/app/models/components/component.ts
+++ b/catalog-ui/src/app/models/components/component.ts
@@ -35,6 +35,7 @@
 import {Relationship} from "../graph/relationship";
 import { PolicyInstance } from "app/models/graph/zones/policy-instance";
 import { GroupInstance } from "../graph/zones/group-instance";
+import { Metadata } from "app/models/metadata";
 
 
 // import {}
@@ -142,6 +143,7 @@
     public archived:boolean;
     public vspArchived: boolean;
     public componentMetadata: ComponentMetadata;
+    public categorySpecificMetadata: Metadata = new Metadata();
 
     constructor(componentService:IComponentService, protected $q:ng.IQService, component?:Component) {
         if (component) {
@@ -198,12 +200,36 @@
             this.policies = component.policies;
             this.archived = component.archived;
             this.vspArchived = component.vspArchived;
+
+            if (component.categorySpecificMetadata && component.categories && component.categories[0]){
+                this.copyCategoryMetadata(component);
+                this.copySubcategoryMetadata(component);
+            }
         }
 
         //custom properties
         this.componentService = componentService;
     }
 
+    private copyCategoryMetadata = (component:Component):void => {
+        if (component.categories[0].metadataKeys){
+            for (let key of Object.keys(component.categorySpecificMetadata)) {
+                if (component.categories[0].metadataKeys.some(metadataKey => metadataKey.name == key)) {
+                    this.categorySpecificMetadata[key] = component.categorySpecificMetadata[key];
+                }
+            }
+        }
+    }
+    private copySubcategoryMetadata = (component:Component):void => {
+        if (component.categories[0].subcategories && component.categories[0].subcategories[0] && component.categories[0].subcategories[0].metadataKeys){
+            for (let key of Object.keys(component.categorySpecificMetadata)) {
+                if (component.categories[0].subcategories[0].metadataKeys.some(metadataKey => metadataKey.name == key)) {
+                    this.categorySpecificMetadata[key] = component.categorySpecificMetadata[key];
+                }
+            }
+        }
+    }
+
     public setUniqueId = (uniqueId:string):void => {
         this.uniqueId = uniqueId;
     };
@@ -543,6 +569,11 @@
         this.archived = componentMetadata.archived || false;
         this.vspArchived = componentMetadata.vspArchived;
         this.componentMetadata = componentMetadata;
+        if (componentMetadata.categorySpecificMetadata){
+            this.categorySpecificMetadata = componentMetadata.categorySpecificMetadata;
+        } else {
+            this.categorySpecificMetadata = new Metadata();
+        }
     }
 
     public toJSON = ():any => {
diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/__snapshots__/info-tab.component.spec.ts.snap b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/__snapshots__/info-tab.component.spec.ts.snap
index fdd0dcf..7fcb62d 100644
--- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/__snapshots__/info-tab.component.spec.ts.snap
+++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/__snapshots__/info-tab.component.spec.ts.snap
@@ -50,6 +50,7 @@
       
       
       
+      
       <div
         class="component-details-panel-item description"
       >
diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/info-tab.component.html b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/info-tab.component.html
index 71545f8..cafe93e 100644
--- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/info-tab.component.html
+++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/info-tab.component.html
@@ -116,6 +116,14 @@
             <span class="value" data-tests-id="rightTab_customizationModuleUUID" tooltip="{{component.customizationUUID}}">{{component.customizationUUID}}</span>
         </div>
 
+        <!-- Category specific metadata -->
+        <ng-container *ngFor="let metadata of component.categorySpecificMetadata | keyValue">
+            <div class="component-details-panel-item">
+                <span class="name" innerHTML="{{metadata.key}}"></span>
+                <span class="value" tooltip="{{metadata.value}}">{{metadata.value}}</span>
+            </div>
+        </ng-container>
+
         <!-- DESCRIPTION -->
         <div class="component-details-panel-item description">
             <span class="name" [innerHTML]="'GENERAL_LABEL_DESCRIPTION' | translate"></span>
diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/info-tab.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/info-tab.component.spec.ts
index 6915d65..388e4b5 100644
--- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/info-tab.component.spec.ts
+++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/info-tab.component.spec.ts
@@ -19,6 +19,8 @@
 import { TranslateService } from "../../../../../shared/translator/translate.service";
 import { SdcUiServices } from "onap-ui-angular";
 import { Component as TopologyTemplate, FullComponentInstance, ComponentInstance } from '../../../../../../../app/models';
+import {KeyValuePipe} from "../../../../../pipes/key-value.pipe";
+
 
 
 describe('InfoTabComponent', () => {
@@ -48,7 +50,7 @@
             const configure: ConfigureFn = testBed => {
                 testBed.configureTestingModule({
                     imports: [ ],
-                    declarations: [ InfoTabComponent, TranslatePipe ],
+                    declarations: [ InfoTabComponent, TranslatePipe, KeyValuePipe ],
                     schemas: [ NO_ERRORS_SCHEMA ],
                     providers: [
                         { provide: Store, useValue: {} },
diff --git a/catalog-ui/src/app/view-models/workspace/tabs/general/general-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/general/general-view-model.ts
index e10dc98..cab4b6c 100644
--- a/catalog-ui/src/app/view-models/workspace/tabs/general/general-view-model.ts
+++ b/catalog-ui/src/app/view-models/workspace/tabs/general/general-view-model.ts
@@ -24,12 +24,13 @@
     ResourceType, ComponentState, instantiationType, ComponentFactory} from "app/utils";
 import { EventListenerService, ProgressService} from "app/services";
 import {CacheService, OnboardingService, ImportVSPService} from "app/services-ng2";
-import {IAppConfigurtaion, IValidate, IMainCategory, Resource, ISubCategory,Service, ICsarComponent, Component} from "app/models";
+import {IAppConfigurtaion, IValidate, IMainCategory, Resource, ISubCategory,Service, ICsarComponent, Component, IMetadataKey} from "app/models";
 import {IWorkspaceViewModelScope} from "app/view-models/workspace/workspace-view-model";
 import {Dictionary} from "lodash";
 import { PREVIOUS_CSAR_COMPONENT } from "../../../../utils/constants";
 import { Observable, Subject } from "rxjs";
-
+import { MetadataEntry } from "app/models/metadataEntry";
+import { Metadata } from "app/models/metadata";
 
 export class Validation {
     componentNameValidationPattern:RegExp;
@@ -630,6 +631,20 @@
             this.$scope.component.selectedCategory = this.$scope.componentCategories.selectedCategory;
             this.$scope.component.categories = this.convertCategoryStringToOneArray();
             this.$scope.component.icon = DEFAULT_ICON;
+            if (this.$scope.component.categories[0].metadataKeys) {
+                for (let metadataKey of this.$scope.component.categories[0].metadataKeys) {
+                    if (!this.$scope.component.categorySpecificMetadata[metadataKey.name]) {
+                        this.$scope.component.categorySpecificMetadata[metadataKey.name] = "";
+                   }
+                }
+            }
+            if (this.$scope.component.categories[0].subcategories && this.$scope.component.categories[0].subcategories[0].metadataKeys) {
+                for (let metadataKey of this.$scope.component.categories[0].subcategories[0].metadataKeys) {
+                    if (!this.$scope.component.categorySpecificMetadata[metadataKey.name]) {
+                        this.$scope.component.categorySpecificMetadata[metadataKey.name] = "";
+                   }
+                }
+            }
         };
 
         this.$scope.onEcompGeneratedNamingChange = (): void => {
@@ -645,11 +660,51 @@
         };
         this.EventListenerService.registerObserverCallback(EVENTS.ON_LIFECYCLE_CHANGE, this.$scope.reload);
 
+
+        this.$scope.isMetadataKeyMandatory = (key: string): boolean => {
+            let metadataKey = this.getMetadataKey(this.$scope.component.categories, key);
+            return metadataKey && metadataKey.mandatory;
+        }
+
+        this.$scope.getMetadataKeyValidValues = (key: string): string[] => {
+            let metadataKey = this.getMetadataKey(this.$scope.component.categories, key);
+            if (metadataKey) {
+                return metadataKey.validValues;
+            }
+            return [];	
+        }
+
+        this.$scope.isMetadataKeyForComponentCategory = (key: string): boolean => {
+            return this.getMetadataKey(this.$scope.component.categories, key) != null;
+        }
+
     }
 
     private setUnsavedChanges = (hasChanges: boolean): void => {
         this.$state.current.data.unsavedChanges = hasChanges;
     }
 
+    private getMetadataKey(categories: IMainCategory[], key: string) : IMetadataKey {
+        let metadataKey = this.getSubcategoryMetadataKey(this.$scope.component.categories, key);
+        if (!metadataKey){
+            return this.getCategoryMetadataKey(this.$scope.component.categories, key);
+        }
+        return metadataKey;
+    }
+
+    private getSubcategoryMetadataKey(categories: IMainCategory[], key: string) : IMetadataKey {
+	    if (categories[0].subcategories && categories[0].subcategories[0].metadataKeys && categories[0].subcategories[0].metadataKeys.some(metadataKey => metadataKey.name == key)) {
+            return categories[0].subcategories[0].metadataKeys.find(metadataKey => metadataKey.name == key);
+        }
+        return null;
+    }
+
+    private getCategoryMetadataKey(categories: IMainCategory[], key: string) : IMetadataKey {
+	    if (categories[0].metadataKeys && categories[0].metadataKeys.some(metadataKey => metadataKey.name == key)) {
+            return categories[0].metadataKeys.find(metadataKey => metadataKey.name == key);
+        }
+        return null;
+    }
+
 }
 
diff --git a/catalog-ui/src/app/view-models/workspace/tabs/general/general-view.html b/catalog-ui/src/app/view-models/workspace/tabs/general/general-view.html
index 42a8aa3..a04948e 100644
--- a/catalog-ui/src/app/view-models/workspace/tabs/general/general-view.html
+++ b/catalog-ui/src/app/view-models/workspace/tabs/general/general-view.html
@@ -114,6 +114,65 @@
                             <!--------------------- CATEGORIES -------------------->
                         </div>
                     </div>
+
+                    <!--------------------- Category Specific Metadata -------------------->
+
+                    <div ng-if="component.selectedCategory">
+                        <ng-container ng-repeat="(key, value) in component.categorySpecificMetadata"-->
+                            <div ng-if="isMetadataKeyForComponentCategory(key) && getMetadataKeyValidValues(key) && isMetadataKeyMandatory(key)"
+                                 class="i-sdc-form-item"
+                                 data-ng-class="{'error': validateField(editForm['{{key}}'])}">
+                                <label class="i-sdc-form-label required" translate="{{key}}"></label>
+                                <select class="i-sdc-form-select"
+                                    name="{{key}}"
+                                    data-ng-class="{'view-mode': isViewMode()}"
+                                    data-ng-model="component.categorySpecificMetadata[key]"
+                                    data-tests-id="{{key}}"
+                                    data-required>
+                                   <option ng-repeat="value in getMetadataKeyValidValues(key)">{{value}}</option>
+                                </select>
+                                <div class="input-error" data-ng-show="validateField(editForm['{{key}}'])">
+                                    <span ng-show="editForm['{{key}}'].$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_REQUIRED"></span>
+                                </div>
+                            </div>
+                            <div ng-if="isMetadataKeyForComponentCategory(key) && getMetadataKeyValidValues(key) && !isMetadataKeyMandatory(key)"
+                                 class="i-sdc-form-item">
+                                <label class="i-sdc-form-label" translate="{{key}}"></label>
+                                <select class="i-sdc-form-select"
+                                    name="{{key}}"
+                                    data-ng-class="{'view-mode': isViewMode()}"
+                                    data-ng-model="component.categorySpecificMetadata[key]"
+                                    data-tests-id="{{key}}">
+                                   <option ng-repeat="value in getMetadataKeyValidValues(key)">{{value}}</option>
+                                </select>
+                            </div>
+                            <div ng-if="isMetadataKeyForComponentCategory(key) && !getMetadataKeyValidValues(key) && isMetadataKeyMandatory(key)"
+                                 class="i-sdc-form-item"
+                                 data-ng-class="{'error': validateField(editForm['{{key}}'])}">
+                                <label class="i-sdc-form-label required" translate="{{key}}"></label>
+                                <input class="i-sdc-form-input" type="text"
+                                       data-required
+                                       data-ng-class="{'view-mode': isViewMode()}"
+                                       data-ng-model="component.categorySpecificMetadata[key]"
+                                       name="{{key}}"
+                                       data-tests-id="{{key}}"
+                                />
+                                <div class="input-error" data-ng-show="validateField(editForm['{{key}}'])">
+                                    <span ng-show="editForm['{{key}}'].$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_REQUIRED"></span>
+                                </div>
+                            </div>
+                            <div ng-if="isMetadataKeyForComponentCategory(key) && !getMetadataKeyValidValues(key) && !isMetadataKeyMandatory(key)"
+                                 class="i-sdc-form-item">
+                                <label class="i-sdc-form-label" translate="{{key}}"></label>
+                                <input class="i-sdc-form-input" type="text"
+                                       data-ng-class="{'view-mode': isViewMode()}"
+                                       data-ng-model="component.categorySpecificMetadata[key]"
+                                       name="{{key}}"
+                                       data-tests-id="{{key}}"
+                                />
+                            </div>
+                        </ng-container>
+                    </div>
                 <!--------------------- RESOURCE TAGS -------------------->
                 <div class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.tags)}">
                     <label class="i-sdc-form-label" translate="GENERAL_LABEL_TAGS"></label>
diff --git a/catalog-ui/src/assets/languages/en_US.json b/catalog-ui/src/assets/languages/en_US.json
index cad8fc7..fd4a382 100644
--- a/catalog-ui/src/assets/languages/en_US.json
+++ b/catalog-ui/src/assets/languages/en_US.json
@@ -228,6 +228,7 @@
     "NEW_SERVICE_RESOURCE_ERROR_RESOURCE_DESCRIPTION_REQUIRED": "Description is required.",
     "NEW_SERVICE_RESOURCE_ERROR_VENDOR_NAME_REQUIRED": "Vendor name is required.",
     "NEW_SERVICE_RESOURCE_ERROR_VENDOR_RELEASE_REQUIRED": "Vendor Release is required.",
+    "NEW_SERVICE_RESOURCE_ERROR_REQUIRED": "Value is required.",
     "NEW_SERVICE_RESOURCE_ERROR_TEMPLATE_REQUIRED": "Template is required.",
     "NEW_SERVICE_RESOURCE_ERROR_TAG_PATTERN": "{{text}}",
     "NEW_SERVICE_RESOURCE_ERROR_VALID_TOSCA_EXTENSIONS_TITLE": "Invalid Tosca file",