Final commit to master merge from
[sdc.git] / catalog-ui / src / app / ng2 / pages / properties-assignment / properties-assignment.page.component.ts
1 /*-
2  * ============LICENSE_START=======================================================
3  * SDC
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 import {Component, ViewChild, ElementRef, Renderer, Inject} from "@angular/core";
22 import { PropertiesService } from "../../services/properties.service";
23 import { PropertyFEModel, InstanceFePropertiesMap, InstanceBePropertiesMap, InstancePropertiesAPIMap, Component as ComponentData, FilterPropertiesAssignmentData } from "app/models";
24 import { ResourceType } from "app/utils";
25 import property = require("lodash/property");
26 import {ComponentServiceNg2} from "../../services/component-services/component.service";
27 import {ComponentInstanceServiceNg2} from "../../services/component-instance-services/component-instance.service"
28 import { InputBEModel, InputFEModel, ComponentInstance, PropertyBEModel, DerivedFEProperty, ResourceInstance, SimpleFlatProperty } from "app/models";
29 import { KeysPipe } from 'app/ng2/pipes/keys.pipe';
30 import {WorkspaceMode, EVENTS} from "../../../utils/constants";
31 import {EventListenerService} from "app/services/event-listener-service"
32 import {HierarchyDisplayOptions} from "../../components/logic/hierarchy-navigtion/hierarchy-display-options";
33 import {FilterPropertiesAssignmentComponent} from "../../components/logic/filter-properties-assignment/filter-properties-assignment.component";
34 import {PropertyRowSelectedEvent} from "../../components/logic/properties-table/properties-table.component";
35 import {HierarchyNavService} from "./services/hierarchy-nav.service";
36 import {PropertiesUtils} from "./services/properties.utils";
37 import {ComponentModeService} from "../../services/component-services/component-mode.service";
38
39 @Component({
40     templateUrl: './properties-assignment.page.component.html',
41     styleUrls: ['./properties-assignment.page.component.less']
42 })
43 export class PropertiesAssignmentComponent {
44     title = "Properties & Inputs";
45
46     component: ComponentData;
47     componentInstanceNamesMap: Map<string, string> = new Map<string, string>();//instanceUniqueId, name
48
49     propertiesNavigationData = [];
50     instancesNavigationData = [];
51
52     instanceFePropertiesMap:InstanceFePropertiesMap;
53     inputs: Array<InputFEModel> = [];
54     instances: Array<ComponentInstance> = [];
55     searchQuery: string;
56     propertyStructureHeader: string;
57
58     selectedFlatProperty: SimpleFlatProperty = new SimpleFlatProperty();
59     selectedInstanceType: string;
60     selectedInstanceData: ComponentInstance = new ComponentInstance();
61     checkedPropertiesCount: number = 0;
62
63     hierarchyPropertiesDisplayOptions:HierarchyDisplayOptions = new HierarchyDisplayOptions('path', 'name', 'childrens');
64     hierarchyInstancesDisplayOptions:HierarchyDisplayOptions = new HierarchyDisplayOptions('uniqueId', 'name');
65     displayClearSearch = false;
66     searchPropertyName:string;
67     isInpusTabSelected:boolean;
68     isReadonly:boolean;
69     loadingInstances:boolean = false;
70     loadingInputs:boolean = false;
71     loadingProperties:boolean = false;
72
73     @ViewChild('hierarchyNavTabs') hierarchyNavTabs: ElementRef;
74     @ViewChild('propertyInputTabs') propertyInputTabs: ElementRef;
75     @ViewChild('advanceSearch') advanceSearch: FilterPropertiesAssignmentComponent;
76
77     constructor(private propertiesService: PropertiesService,
78                 private hierarchyNavService: HierarchyNavService,
79                 private propertiesUtils:PropertiesUtils,
80                 private componentServiceNg2:ComponentServiceNg2,
81                 private componentInstanceServiceNg2:ComponentInstanceServiceNg2,
82                 @Inject("$stateParams") _stateParams,
83                 private renderer: Renderer,
84                 private componentModeService:ComponentModeService,
85                 private EventListenerService:EventListenerService) {
86
87         this.instanceFePropertiesMap = new InstanceFePropertiesMap();
88
89         /* This is the way you can access the component data, please do not use any data except metadata, all other data should be received from the new api calls on the first time
90         than if the data is already exist, no need to call the api again - Ask orit if you have any questions*/
91         this.component = _stateParams.component;
92         this.EventListenerService.registerObserverCallback(EVENTS.ON_CHECKOUT, this.onCheckout);
93         this.updateViewMode();
94     }
95
96     ngOnInit() {
97         console.log("==>" + this.constructor.name + ": ngOnInit");
98         this.loadingInputs = true;
99         this.loadingInstances = true;
100         this.loadingProperties = true;
101         this.componentServiceNg2
102             .getComponentInputs(this.component)
103             .subscribe(response => {
104                 _.forEach(response.inputs, (input: InputBEModel) => {
105                     this.inputs.push(new InputFEModel(input)); //only push items that were declared via SDC
106                 });
107                 this.loadingInputs = false;
108
109             }, error => {}); //ignore error
110         this.componentServiceNg2
111             .getComponentResourceInstances(this.component)
112             .subscribe(response => {
113                 this.instances = response.componentInstances;
114
115                 _.forEach(this.instances, (instance) => {
116                     this.instancesNavigationData.push(instance);
117                     this.componentInstanceNamesMap[instance.uniqueId] = instance.name;
118                 });
119                 this.loadingInstances = false;
120                 if (this.instancesNavigationData[0] == undefined) {
121                     this.loadingProperties = false;
122                 }
123                 this.selectFirstInstanceByDefault();
124             }, error => {}); //ignore error
125
126     };
127
128     ngOnDestroy() {
129         this.EventListenerService.unRegisterObserver(EVENTS.ON_CHECKOUT);
130     }
131
132     selectFirstInstanceByDefault = () => {
133         if (this.instancesNavigationData[0] !== undefined) {
134             this.onInstanceSelectedUpdate(this.instancesNavigationData[0]);
135         }
136     };
137
138     updateViewMode = () => {
139         this.isReadonly = this.componentModeService.getComponentMode(this.component) === WorkspaceMode.VIEW;
140     }
141
142     onCheckout = (component:ComponentData) => {
143         this.component = component;
144         this.updateViewMode();
145     }
146
147
148     onInstanceSelectedUpdate = (resourceInstance: ResourceInstance) => {
149         console.log("==>" + this.constructor.name + ": onInstanceSelectedUpdate");
150         let instanceBePropertiesMap: InstanceBePropertiesMap = new InstanceBePropertiesMap();
151         this.selectedInstanceData = resourceInstance;
152         this.selectedInstanceType = resourceInstance.originType;
153
154         this.loadingProperties = true;
155         if(this.isInput(resourceInstance.originType)) {
156             this.componentInstanceServiceNg2
157                 .getComponentInstanceInputs(this.component, resourceInstance)
158                 .subscribe(response => {
159                     instanceBePropertiesMap[resourceInstance.uniqueId] = response;
160                     this.processInstancePropertiesResponse(instanceBePropertiesMap, true);
161                     this.loadingProperties = false;
162
163                 }, error => {}); //ignore error
164         } else {
165             this.componentInstanceServiceNg2
166                 .getComponentInstanceProperties(this.component, resourceInstance.uniqueId)
167                 .subscribe(response => {
168                     instanceBePropertiesMap[resourceInstance.uniqueId] = response;
169                     this.processInstancePropertiesResponse(instanceBePropertiesMap, false);
170                     this.loadingProperties = false;
171                 }, error => {}); //ignore error
172         }
173
174         if(resourceInstance.componentName === "vnfConfiguration") {
175             this.isReadonly = true;
176         }
177
178         if( this.searchPropertyName ){
179             this.clearSearch();
180         }
181         //clear selected property from the navigation
182         this.selectedFlatProperty = new SimpleFlatProperty();
183         this.propertiesNavigationData = [];
184     };
185
186     /**
187      * Entry point handling response from server
188      */
189     processInstancePropertiesResponse = (instanceBePropertiesMap: InstanceBePropertiesMap, originTypeIsVF: boolean) => {
190         this.instanceFePropertiesMap = this.propertiesUtils.convertPropertiesMapToFEAndCreateChildren(instanceBePropertiesMap, originTypeIsVF, this.inputs); //create flattened children, disable declared props, and init values
191         this.checkedPropertiesCount = 0;
192     };
193
194
195     /*** VALUE CHANGE EVENTS ***/
196     propertyValueChanged = (event: PropertyFEModel) => {
197         console.log("==>" + this.constructor.name + ": propertyValueChanged " + event);
198         // Copying the actual value from the object ref into the value if it's from a complex type
199         event.value = event.getJSONValue();
200
201         if (this.isInput(this.selectedInstanceData.originType)) {
202             console.log("I want to update input value on the resource instance");
203             let inputToUpdate = new PropertyBEModel(event);
204             this.componentInstanceServiceNg2
205                 .updateInstanceInput(this.component, this.selectedInstanceData.uniqueId, inputToUpdate)
206                 .subscribe(response => {
207                     console.log("Update resource instance input response: ", response);
208                 }, error => {}); //ignore error
209         }
210         else {
211             let propertyBe = new PropertyBEModel(event);
212             this.componentInstanceServiceNg2
213                 .updateInstanceProperty(this.component, this.selectedInstanceData.uniqueId, propertyBe)
214                 .subscribe(response => {
215                     console.log("Update resource instance property response: ", response);
216                 }, error => {}); //ignore error
217             console.log(event);
218         }
219
220     };
221
222     inputValueChanged = (event) => {
223         console.log("==>" + this.constructor.name + ": inputValueChanged");
224         let inputToUpdate = new PropertyBEModel(event);
225
226         this.componentServiceNg2
227             .updateComponentInput(this.component, inputToUpdate)
228             .subscribe(response => {
229                 console.log("updated the component input and got this response: ", response);
230             }, error => {}); //ignore error
231     };
232
233
234     /*** HEIRARCHY/NAV RELATED FUNCTIONS ***/
235
236     /**
237      * Handle select node in navigation area, and select the row in table
238      */
239     onPropertySelectedUpdate = ($event) => {
240         console.log("==>" + this.constructor.name + ": onPropertySelectedUpdate");
241         this.selectedFlatProperty = $event;
242         let parentProperty:PropertyFEModel = this.propertiesService.getParentPropertyFEModelFromPath(this.instanceFePropertiesMap[this.selectedFlatProperty.instanceName], this.selectedFlatProperty.path);
243         parentProperty.expandedChildPropertyId = this.selectedFlatProperty.path;
244     };
245
246     /**
247      * When user select row in table, this will prepare the hirarchy object for the tree.
248      */
249     selectPropertyRow = (propertyRowSelectedEvent:PropertyRowSelectedEvent) => {
250         console.log("==>" + this.constructor.name + ": selectPropertyRow " + propertyRowSelectedEvent.propertyModel.name);
251         let property = propertyRowSelectedEvent.propertyModel;
252         let instanceName = propertyRowSelectedEvent.instanceName;
253         this.propertyStructureHeader = null;
254
255         // Build hirarchy tree for the navigation and update propertiesNavigationData with it.
256         if(this.selectedInstanceData.originType !== ResourceType.VF) {
257             let simpleFlatProperty:Array<SimpleFlatProperty>;
258             if (property instanceof PropertyFEModel) {
259                 simpleFlatProperty = this.hierarchyNavService.getSimplePropertiesTree(property, instanceName);
260             } else if (property instanceof DerivedFEProperty) {
261                 // Need to find parent PropertyFEModel
262                 let parentPropertyFEModel:PropertyFEModel = _.find(this.instanceFePropertiesMap[instanceName], (tmpFeProperty):boolean => {
263                     return property.propertiesName.indexOf(tmpFeProperty.name)===0;
264                 });
265                 simpleFlatProperty = this.hierarchyNavService.getSimplePropertiesTree(parentPropertyFEModel, instanceName);
266             }
267             this.propertiesNavigationData = simpleFlatProperty;
268         }
269
270         // Update the header in the navigation tree with property name.
271         this.propertyStructureHeader = (property.propertiesName.split('#'))[0];
272
273         // Set selected property in table
274         this.selectedFlatProperty = this.hierarchyNavService.createSimpleFlatProperty(property, instanceName);
275         this.renderer.invokeElementMethod(this.hierarchyNavTabs, 'triggerTabChange', ['Property Structure']);
276     };
277
278
279     selectInstanceRow = ($event) => {//get instance name
280         this.selectedInstanceData =  _.find(this.instancesNavigationData, (instance:ComponentInstance) => {
281             return instance.name == $event;
282         });
283         this.renderer.invokeElementMethod(
284             this.hierarchyNavTabs, 'triggerTabChange', ['Composition']);
285     };
286
287     tabChanged = (event) => {
288         console.log("==>" + this.constructor.name + ": tabChanged " + event);
289         this.isInpusTabSelected = event.title === "Inputs";
290         this.propertyStructureHeader = null;
291         this.searchQuery = '';
292     };
293
294
295
296     /*** DECLARE PROPERTIES/INPUTS ***/
297     declareProperties = (): void => {
298         console.log("==>" + this.constructor.name + ": declareProperties");
299
300         let selectedProperties: InstanceBePropertiesMap = new InstanceBePropertiesMap();
301         let selectedInputs: InstanceBePropertiesMap = new InstanceBePropertiesMap();
302         let instancesIds = new KeysPipe().transform(this.instanceFePropertiesMap, []);
303
304         angular.forEach(instancesIds, (instanceId: string): void => {
305             let selectedInstanceData: ResourceInstance = this.instances.find(instance => instance.uniqueId == instanceId);
306             let originType: string = (selectedInstanceData) ? selectedInstanceData.originType : this.selectedInstanceType;
307             if (!this.isInput(originType)) {
308                 selectedProperties[instanceId] = this.propertiesService.getCheckedProperties(this.instanceFePropertiesMap[instanceId]);
309             } else {
310                 selectedInputs[instanceId] = this.propertiesService.getCheckedProperties(this.instanceFePropertiesMap[instanceId]);
311             }
312         });
313
314         let inputsToCreate: InstancePropertiesAPIMap = new InstancePropertiesAPIMap(selectedInputs, selectedProperties);
315
316         this.componentServiceNg2
317             .createInput(this.component, inputsToCreate)
318             .subscribe(response => {
319                 this.setInputTabIndication(response.length);
320                 this.checkedPropertiesCount = 0;
321                 _.forEach(response, (input: InputBEModel) => {
322                     let newInput: InputFEModel = new InputFEModel(input);
323                     this.inputs.push(newInput);
324                     this.updatePropertyValueAfterDeclare(newInput);
325                 });
326             }, error => {}); //ignore error
327     };
328
329
330     updatePropertyValueAfterDeclare = (input: InputFEModel) => {
331         if (this.instanceFePropertiesMap[input.instanceUniqueId]) {
332             let propertyForUpdatindVal = _.find(this.instanceFePropertiesMap[input.instanceUniqueId], (feProperty: PropertyFEModel) => {
333                 return feProperty.name == input.relatedPropertyName;
334             });
335             let inputPath = (input.inputPath && input.inputPath != propertyForUpdatindVal.name) ? input.inputPath : undefined;
336             propertyForUpdatindVal.setAsDeclared(inputPath); //set prop as declared before assigning value
337             this.propertiesService.disableRelatedProperties(propertyForUpdatindVal, inputPath);
338             this.propertiesUtils.resetPropertyValue(propertyForUpdatindVal, input.relatedPropertyValue, inputPath);
339         }
340     }
341
342     //used for declare button, to keep count of newly checked properties (and ignore declared properties)
343     updateCheckedPropertyCount = (increment: boolean): void => {
344         this.checkedPropertiesCount += (increment) ? 1 : -1;
345         console.log("CheckedProperties count is now.... " + this.checkedPropertiesCount);
346     };
347
348     setInputTabIndication = (numInputs: number): void => {
349         this.renderer.invokeElementMethod(this.propertyInputTabs, 'setTabIndication', ['Inputs', numInputs]);
350     };
351
352     deleteInput = (input: InputFEModel) => {
353         console.log("==>" + this.constructor.name + ": deleteInput");
354         let inputToDelete = new PropertyBEModel(input);
355
356         this.componentServiceNg2
357             .deleteInput(this.component, inputToDelete)
358             .subscribe(response => {
359                 this.inputs = this.inputs.filter(input => input.uniqueId !== response.uniqueId);
360
361                 //Reload the whole instance for now - TODO: CHANGE THIS after the BE starts returning properties within the response, use commented code below instead!
362                 this.onInstanceSelectedUpdate(this.selectedInstanceData);
363                 // let instanceFeProperties = this.instanceFePropertiesMap[this.getInstanceUniqueId(input.instanceName)];
364
365                 // if (instanceFeProperties) {
366                 //     let propToEnable: PropertyFEModel = instanceFeProperties.find((prop) => {
367                 //         return prop.name == input.propertyName;
368                 //     });
369
370                 //     if (propToEnable) {
371                 //         if (propToEnable.name == response.inputPath) response.inputPath = null;
372                 //         propToEnable.setNonDeclared(response.inputPath);
373                 //         //this.propertiesUtils.resetPropertyValue(propToEnable, newValue, response.inputPath);
374                 //         this.propertiesService.undoDisableRelatedProperties(propToEnable, response.inputPath);
375                 //     }
376                 // }
377             }, error => {}); //ignore error
378     };
379
380
381
382     /*** SEARCH RELATED FUNCTIONS ***/
383     searchPropertiesInstances = (filterData:FilterPropertiesAssignmentData) => {
384         let instanceBePropertiesMap:InstanceBePropertiesMap;
385         this.componentServiceNg2
386             .filterComponentInstanceProperties(this.component, filterData)
387             .subscribe(response => {
388
389                 this.processInstancePropertiesResponse(response, false);
390                 this.hierarchyPropertiesDisplayOptions.searchText = filterData.propertyName;//mark results in tree
391                 this.searchPropertyName = filterData.propertyName;//mark in table
392                 this.renderer.invokeElementMethod(this.hierarchyNavTabs, 'triggerTabChange', ['Composition']);
393                 this.propertiesNavigationData = [];
394                 this.displayClearSearch = true;
395             }, error => {}); //ignore error
396
397     };
398
399     clearSearch = () => {
400         this.instancesNavigationData = this.instances;
401         this.searchPropertyName = "";
402         this.hierarchyPropertiesDisplayOptions.searchText = "";
403         this.displayClearSearch = false;
404         this.advanceSearch.clearAll();
405         this.searchQuery = '';
406     };
407
408     clickOnClearSearch = () => {
409         this.clearSearch();
410         this.selectFirstInstanceByDefault();
411         this.renderer.invokeElementMethod(
412             this.hierarchyNavTabs, 'triggerTabChange', ['Composition']);
413     };
414
415     private isInput = (instanceType:string):boolean =>{
416         return instanceType === ResourceType.VF || instanceType === ResourceType.PNF || instanceType === ResourceType.CVFC;
417     }
418
419 }