6a276226383ceb61016e657fbb4977d5b1447e77
[sdc.git] / catalog-ui / src / app / ng2 / pages / properties-assignment / services / properties.utils.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 * as _ from "lodash";
22 import {Injectable} from '@angular/core';
23 import {
24     DataTypeModel,
25     DerivedFEProperty,
26     DerivedPropertyType,
27     InputFEModel,
28     InstanceBePropertiesMap,
29     InstanceFePropertiesMap,
30     PropertyBEModel,
31     PropertyFEModel
32 } from "app/models";
33 import {DataTypeService} from "app/ng2/services/data-type.service";
34 import {PropertiesService} from "app/ng2/services/properties.service";
35 import {PROPERTY_TYPES} from "app/utils";
36 import { SubPropertyToscaFunction } from "app/models/sub-property-tosca-function";
37
38 @Injectable()
39 export class PropertiesUtils {
40
41     constructor(private dataTypeService:DataTypeService, private propertiesService: PropertiesService) {}
42
43     /**
44      * Entry point when getting properties from server
45      * For each instance, loop through each property, and:
46      * 1. Create flattened children
47      * 2. Check against inputs to see if any props are declared and disable them
48      * 3. Initialize valueObj (which also creates any new list/map flattened children as needed)
49      * Returns InstanceFePropertiesMap
50      */
51     public convertPropertiesMapToFEAndCreateChildren = (instancePropertiesMap:InstanceBePropertiesMap, isVF:boolean, inputs?:Array<InputFEModel>, model?:string): InstanceFePropertiesMap => {
52         let instanceFePropertiesMap:InstanceFePropertiesMap = new InstanceFePropertiesMap();
53         angular.forEach(instancePropertiesMap, (properties:Array<PropertyBEModel>, instanceId:string) => {
54             let propertyFeArray: Array<PropertyFEModel> = [];
55             _.forEach(properties, (property: PropertyBEModel) => {
56         
57                 if (this.dataTypeService.getDataTypeByModelAndTypeName(model, property.type)) { // if type not exist in data types remove property from list
58
59                     let newFEProp: PropertyFEModel = new PropertyFEModel(property); //Convert property to FE
60                     if (!newFEProp.parentUniqueId) {
61                         newFEProp.parentUniqueId = instanceId;
62                     }
63                     this.initValueObjectRef(newFEProp); //initialize valueObj AND creates flattened children
64                     propertyFeArray.push(newFEProp);
65                     newFEProp.updateExpandedChildPropertyId(newFEProp.name); //display only the first level of children
66                     this.dataTypeService.checkForCustomBehavior(newFEProp);
67
68                     //if this prop (or any children) are declared, set isDeclared and disable checkbox on parents/children
69                     if (newFEProp.getInputValues && newFEProp.getInputValues.length) {
70                         newFEProp.getInputValues.forEach(propInputDetail => {
71                             let inputPath = propInputDetail.inputPath;
72                             if (!inputPath) { //TODO: this is a workaround until Marina adds inputPath
73                                 let input = inputs.find(input => input.uniqueId == propInputDetail.inputId);
74                                 if (!input) { console.log("CANNOT FIND INPUT FOR " + propInputDetail.inputId); return; }
75                                 else inputPath = input.inputPath;
76                             }
77                             if (inputPath == newFEProp.name) inputPath = undefined; // if not complex we need to remove the inputPath from FEModel so we not look for a child
78                             newFEProp.setAsDeclared(inputPath); //if a path is sent, its a child prop. this param is optional
79                             this.propertiesService.disableRelatedProperties(newFEProp, inputPath);
80                         });
81                     }
82                     if (newFEProp.getPolicyValues && newFEProp.getPolicyValues.length) {
83                         newFEProp.setAsDeclared(newFEProp.inputPath); //if a path is sent, its a child prop. this param is optional
84                         this.propertiesService.disableRelatedProperties(newFEProp, newFEProp.inputPath);
85                     }
86                 }
87             });
88             instanceFePropertiesMap[instanceId] = propertyFeArray;
89
90         });
91         return instanceFePropertiesMap;
92     }
93
94     public convertAddPropertyBAToPropertyFE = (property: PropertyBEModel): PropertyFEModel => {
95         const newFEProp: PropertyFEModel = new PropertyFEModel(property); //Convert property to FE
96         this.initValueObjectRef(newFEProp);
97         newFEProp.updateExpandedChildPropertyId(newFEProp.name); //display only the first level of children
98         this.dataTypeService.checkForCustomBehavior(newFEProp);
99         return newFEProp;
100     }
101
102     public createListOrMapChildren = (property:PropertyFEModel | DerivedFEProperty, key: string, valueObj: any): Array<DerivedFEProperty> => {
103         let newProps: Array<DerivedFEProperty> = [];
104         let parentProp = new DerivedFEProperty(property, property.propertiesName, true, key, valueObj);
105         newProps.push(parentProp);
106
107         if (!property.schema.property.isSimpleType) {
108             let additionalChildren:Array<DerivedFEProperty> = this.createFlattenedChildren(property.schema.property.type, parentProp.propertiesName, key, parentProp.toscaPath);
109             this.assignFlattenedChildrenValues(parentProp.valueObj, additionalChildren, parentProp.propertiesName);
110             additionalChildren.forEach(prop => {
111                 prop.canBeDeclared = false;
112                 if (property.subPropertyToscaFunctions != null) {
113                     const subToscaFunctArray : SubPropertyToscaFunction[] = property.subPropertyToscaFunctions;
114                     subToscaFunctArray.forEach(subToscaFunct => {
115                         if (subToscaFunct.subPropertyPath.toString() === prop.toscaPath.toString()) {
116                             prop.toscaFunction = subToscaFunct.toscaFunction;
117                         }
118                     });
119                     
120                 }
121             });
122             newProps.push(...additionalChildren);
123         }
124         return newProps;
125     }
126
127     /**
128      * Creates derivedFEProperties of a specified type and returns them.
129      */
130     private createFlattenedChildren = (type: string, parentName: string, key: string, toscaPath?: string[]):Array<DerivedFEProperty> => {
131         let tempProps: Array<DerivedFEProperty> = [];
132         let dataTypeObj: DataTypeModel = this.dataTypeService.getDataTypeByTypeName(type);
133         this.dataTypeService.getDerivedDataTypeProperties(dataTypeObj, tempProps, parentName, toscaPath);
134         if (key != '') {
135             tempProps.forEach(tempDervObj => {
136                 tempDervObj.mapKey = key;
137             });
138         }
139         return _.sortBy(tempProps, ['propertiesName']);
140     }
141
142     /* Sets the valueObj of parent property and its children.
143     * Note: This logic is different than assignflattenedchildrenvalues - here we merge values, there we pick either the parents value, props value, or default value - without merging.
144     */
145     public initValueObjectRef = (property: PropertyFEModel): void => {
146         property.resetValueObjValidation();
147         if (property.isDeclared) { //if property is declared, it gets a simple input instead. List and map values and pseudo-children will be handled in property component
148             property.valueObj = property.value || property.defaultValue || null;  // use null for empty value object
149             if (property.valueObj && typeof property.valueObj == 'object') {
150                 property.valueObj = JSON.stringify(property.valueObj);
151             }
152         } else {
153             property.valueObj = property.getValueObj();
154             if (property.derivedDataType == DerivedPropertyType.LIST || property.derivedDataType == DerivedPropertyType.MAP) {
155                 property.flattenedChildren = [];
156                 Object.keys(property.valueObj).forEach((key) => {
157                     property.flattenedChildren.push(...this.createListOrMapChildren(property, key, property.valueObj[key]));
158                     const lastCreatedChild = property.flattenedChildren.slice(-1)[0];
159                     if (property.schemaType == PROPERTY_TYPES.MAP && property.valueObj[key]){
160                         const nestedValue:object = property.valueObj[key];
161                         Object.keys(nestedValue).forEach((keyNested) => {
162                             property.flattenedChildren.push(...this.createListOrMapChildren(lastCreatedChild, keyNested, nestedValue[keyNested]));
163                         });
164                     }
165                     if (property.flattenedChildren && property.subPropertyToscaFunctions) {
166                         property.flattenedChildren.forEach((prop, index) => {
167                             property.subPropertyToscaFunctions.forEach(subPropertyToscaFunction => {
168                                 const toscaFunctionPath = subPropertyToscaFunction.subPropertyPath.join('#');
169                                 if (subPropertyToscaFunction.subPropertyPath.toString() === prop.toscaPath.toString()) {
170                                     prop.toscaFunction = subPropertyToscaFunction.toscaFunction;
171                                 }
172                             });
173                         });
174                     }
175                 });
176             } else if (property.derivedDataType === DerivedPropertyType.COMPLEX) {
177                 property.flattenedChildren = this.createFlattenedChildren(property.type, property.name, "");
178                 this.assignFlattenedChildrenValues(property.valueObj, property.flattenedChildren, property.name);
179                 this.setFlattenedChildernToscaFunction(property.subPropertyToscaFunctions, property.flattenedChildren, property.name);
180                 property.flattenedChildren.forEach((childProp) => {
181                     property.childPropUpdated(childProp);
182                 });
183
184             } else if (property.derivedDataType === DerivedPropertyType.RANGE) {
185                 property.valueObj = JSON.stringify(property.getValueObj());
186             }
187         }
188         property.updateValueObjOrig();
189     };
190
191     public setFlattenedChildernToscaFunction = (subPropertyToscaFunctions: SubPropertyToscaFunction[], derivedPropArray: Array<DerivedFEProperty>, topLevelPropertyName: string) => {
192         if (!subPropertyToscaFunctions || !derivedPropArray || !topLevelPropertyName){
193             return;
194         }
195         derivedPropArray.forEach((prop, index) => {
196             const subPropertyPath = prop.propertiesName.substring(prop.propertiesName.indexOf(topLevelPropertyName) + topLevelPropertyName.length + 1);
197             subPropertyToscaFunctions.forEach(subPropertyToscaFunction => {
198                 const toscaFunctionPath = subPropertyToscaFunction.subPropertyPath.join('#');
199                 if (subPropertyPath === toscaFunctionPath || subPropertyToscaFunction.subPropertyPath.toString() === prop.toscaPath.toString()) {
200                     prop.toscaFunction = subPropertyToscaFunction.toscaFunction;
201                 }
202             });
203         });
204     }
205
206     /*
207     * Loops through flattened properties array and to assign values
208     * Then, convert any neccessary strings to objects, and vis-versa
209     * For list or map property, creates new children props if valueObj has values
210     */
211     public assignFlattenedChildrenValues = (parentValueJSON: any, derivedPropArray: Array<DerivedFEProperty>, parentName: string) => {
212         if (!derivedPropArray || !parentName) return;
213         let propsToPushMap: Map<number, Array<DerivedFEProperty>> = new Map<number, Array<DerivedFEProperty>>();
214         derivedPropArray.forEach((prop, index) => {
215
216             let propNameInObj = prop.propertiesName.substring(prop.propertiesName.indexOf(parentName) + parentName.length + 1).split('#').join('.'); //extract everything after parent name
217             prop.valueObj = _.get(parentValueJSON, propNameInObj, prop.value || prop.defaultValue || null); //assign value -first value of parent if exists. If not, prop.value if not, prop.defaultvalue
218             prop.value = (prop.valueObj !== null && (typeof prop.valueObj) != 'string') ? JSON.stringify(prop.valueObj) : prop.valueObj;
219
220             if ((prop.isDeclared || prop.type == PROPERTY_TYPES.STRING || prop.type == PROPERTY_TYPES.JSON)) { //Stringify objects of items that are declared or from type string/json
221                 prop.valueObj = (prop.valueObj !== null && typeof prop.valueObj == 'object') ? JSON.stringify(prop.valueObj) : prop.valueObj;
222             } else if(prop.type == PROPERTY_TYPES.INTEGER || prop.type == PROPERTY_TYPES.FLOAT || prop.type == PROPERTY_TYPES.BOOLEAN){ //parse ints and non-string simple types
223                 prop.valueObj = (prop.valueObj !== null && typeof prop.valueObj == PROPERTY_TYPES.STRING) ? JSON.parse(prop.valueObj) : prop.valueObj;
224             } else { //parse strings that should be objects
225                 if (prop.derivedDataType == DerivedPropertyType.COMPLEX) {
226                     prop.valueObj = (prop.valueObj === null || typeof prop.valueObj != 'object') ? JSON.parse(prop.valueObj || '{}') : prop.valueObj;
227                 } else if (prop.derivedDataType == DerivedPropertyType.LIST) {
228                     prop.valueObj = (prop.valueObj === null || typeof prop.valueObj != 'object') ? JSON.parse(prop.valueObj || '[]') : prop.valueObj;
229                 } else if (prop.derivedDataType == DerivedPropertyType.MAP) {
230                     if (!prop.isChildOfListOrMap) {
231                         prop.valueObj = (prop.valueObj === null || typeof prop.valueObj != 'object') ? JSON.parse(prop.valueObj || '{}') : prop.valueObj;
232                     }
233                 }
234                 if ((prop.derivedDataType == DerivedPropertyType.LIST || prop.derivedDataType == DerivedPropertyType.MAP) && typeof prop.valueObj == 'object' && prop.valueObj !== null && Object.keys(prop.valueObj).length) {
235                     let newProps: Array<DerivedFEProperty> = [];
236                     Object.keys(prop.valueObj).forEach((key) => {
237                         newProps.push(...this.createListOrMapChildren(prop, key, prop.valueObj[key]));//create new children, assign their values, and then add to array
238                     });
239                     propsToPushMap[index + 1] = newProps;
240                 }
241             }
242
243             prop.valueObj = PropertyFEModel.cleanValueObj(prop.valueObj);
244         });
245
246         //add props after we're done looping (otherwise our loop gets messed up). Push in reverse order, so we dont mess up indexes.
247         Object.keys(propsToPushMap).reverse().forEach((indexToInsert) => {
248             derivedPropArray.splice(+indexToInsert, 0, ...propsToPushMap[indexToInsert]); //slacker parsing
249         });
250     }
251
252     public resetPropertyValue = (property: PropertyFEModel, newValue: string, nestedPath?: string): void => {
253         property.value = newValue;
254         if (nestedPath) {
255             let newProp = property.flattenedChildren.find(prop => prop.propertiesName == nestedPath);
256             newProp && this.assignFlattenedChildrenValues(JSON.parse(newValue), [newProp], property.name);
257             property.updateValueObjOrig();
258         } else {
259             this.initValueObjectRef(property);
260         }
261     }
262
263 }