51b1823ce28f079dfb93ab7fdc47bc9d766632fc
[sdc.git] / catalog-ui / src / app / ng2 / pages / attributes-outputs / services / attributes.utils.ts
1 /*-
2  * ============LICENSE_START=======================================================
3  * SDC
4  * ================================================================================
5  * Copyright (C) 2021 Nordix Foundation. 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 { DataTypeService } from "app/ng2/services/data-type.service";
24 import { PROPERTY_TYPES } from "app/utils";
25 import { AttributesService } from "app/ng2/services/attributes.service";
26 import { InstanceBeAttributesMap, InstanceFeAttributesMap } from "app/models/attributes-outputs/attribute-fe-map";
27 import {OutputFEModel} from "../../../../models/attributes-outputs/output-fe-model";
28 import { AttributeBEModel, DerivedAttributeType } from "app/models/attributes-outputs/attribute-be-model";
29 import { AttributeFEModel } from "app/models/attributes-outputs/attribute-fe-model";
30 import { DerivedFEAttribute } from "app/models/attributes-outputs/derived-fe-attribute";
31 import { DataTypeModel } from "app/models";
32
33 @Injectable()
34 export class AttributesUtils {
35
36     constructor(private dataTypeService:DataTypeService, private attributesService: AttributesService) {}
37
38     /**
39      * Entry point when getting attributes from server
40      * For each instance, loop through each property, and:
41      * 1. Create flattened children
42      * 2. Check against outputs to see if any props are declared and disable them
43      * 3. Initialize valueObj (which also creates any new list/map flattened children as needed)
44      * Returns InstanceFeAttributesMap
45      */
46     public convertAttributesMapToFEAndCreateChildren = (instanceAttributesMap:InstanceBeAttributesMap, isVF:boolean, outputs?:Array<OutputFEModel>): InstanceFeAttributesMap => {
47         let instanceFeAttributesMap:InstanceFeAttributesMap = new InstanceFeAttributesMap();
48         angular.forEach(instanceAttributesMap, (attributes:Array<AttributeBEModel>, instanceId:string) => {
49             let propertyFeArray: Array<AttributeFEModel> = [];
50             _.forEach(attributes, (property: AttributeBEModel) => {
51
52                 if (this.dataTypeService.getDataTypeByTypeName(property.type)) { // if type not exist in data types remove property from list
53
54                     let newFEAttrib: AttributeFEModel = new AttributeFEModel(property); //Convert property to FE
55
56                     this.initValueObjectRef(newFEAttrib); //initialize valueObj AND creates flattened children
57                     propertyFeArray.push(newFEAttrib);
58                     newFEAttrib.updateExpandedChildAttributeId(newFEAttrib.name); //display only the first level of children
59
60                     //if this prop (or any children) are declared, set isDeclared and disable checkbox on parents/children
61                     if (newFEAttrib.getOutputValues && newFEAttrib.getOutputValues.length) {
62                         newFEAttrib.getOutputValues.forEach(propOutputDetail => {
63                             let outputPath = propOutputDetail.outputPath;
64                             if (!outputPath) { //TODO: this is a workaround until Marina adds outputPath
65                                 let output = outputs.find(output => output.uniqueId == propOutputDetail.outputId);
66                                 if (!output) { console.log("CANNOT FIND INPUT FOR " + propOutputDetail.outputId); return; }
67                                 else outputPath = output.outputPath;
68                             }
69                             if (outputPath == newFEAttrib.name) outputPath = undefined; // if not complex we need to remove the outputPath from FEModel so we not look for a child
70                             newFEAttrib.setAsDeclared(outputPath); //if a path is sent, its a child prop. this param is optional
71                             this.attributesService.disableRelatedAttributes(newFEAttrib, outputPath);
72                         });
73                     }
74                 }
75             });
76             instanceFeAttributesMap[instanceId] = propertyFeArray;
77
78         });
79         return instanceFeAttributesMap;
80     }
81
82     public convertAddAttributeBEToAttributeFE = (property: AttributeBEModel): AttributeFEModel => {
83         const newFEProp: AttributeFEModel = new AttributeFEModel(property); //Convert property to FE
84         this.initValueObjectRef(newFEProp);
85         newFEProp.updateExpandedChildAttributeId(newFEProp.name); //display only the first level of children
86         return newFEProp;
87     }
88
89     public createListOrMapChildren = (property:AttributeFEModel | DerivedFEAttribute, key: string, valueObj: any): Array<DerivedFEAttribute> => {
90         let newProps: Array<DerivedFEAttribute> = [];
91         let parentProp = new DerivedFEAttribute(property, property.attributesName, true, key, valueObj);
92         newProps.push(parentProp);
93
94         if (!property.schema.property.isSimpleType) {
95             let additionalChildren:Array<DerivedFEAttribute> = this.createFlattenedChildren(property.schema.property.type, parentProp.attributesName);
96             this.assignFlattenedChildrenValues(parentProp.valueObj, additionalChildren, parentProp.attributesName);
97             additionalChildren.forEach(prop => prop.canBeDeclared = false);
98             newProps.push(...additionalChildren);
99         }
100         return newProps;
101     }
102
103     /**
104      * Creates derivedFEAttributes of a specified type and returns them.
105      */
106     private createFlattenedChildren = (type: string, parentName: string):Array<DerivedFEAttribute> => {
107         let tempProps: Array<DerivedFEAttribute> = [];
108         let dataTypeObj: DataTypeModel = this.dataTypeService.getDataTypeByTypeName(type);
109         this.dataTypeService.getDerivedDataTypeAttributes(dataTypeObj, tempProps, parentName);
110         return _.sortBy(tempProps, ['propertiesName']);
111     }
112
113     /* Sets the valueObj of parent property and its children.
114     * 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.
115     */
116     public initValueObjectRef = (attribute: AttributeFEModel): void => {
117         attribute.resetValueObjValidation();
118         if (attribute.isDeclared) { //if attribute is declared, it gets a simple output instead. List and map values and pseudo-children will be handled in attribute component
119             attribute.valueObj = attribute.value || attribute.defaultValue || null;  // use null for empty value object
120             if (attribute.valueObj && typeof attribute.valueObj == 'object') {
121                 attribute.valueObj = JSON.stringify(attribute.valueObj);
122             }
123         } else {
124             attribute.valueObj = attribute.getValueObj();
125             if (attribute.derivedDataType == DerivedAttributeType.LIST || attribute.derivedDataType == DerivedAttributeType.MAP) {
126                 attribute.flattenedChildren = [];
127                 Object.keys(attribute.valueObj).forEach((key) => {
128                     attribute.flattenedChildren.push(...this.createListOrMapChildren(attribute, key, attribute.valueObj[key]))
129                 });
130             } else if (attribute.derivedDataType === DerivedAttributeType.COMPLEX) {
131                 attribute.flattenedChildren = this.createFlattenedChildren(attribute.type, attribute.name);
132                 this.assignFlattenedChildrenValues(attribute.valueObj, attribute.flattenedChildren, attribute.name);
133                 attribute.flattenedChildren.forEach((childProp) => {
134                     attribute.childPropUpdated(childProp);
135                 });
136             }
137         }
138         attribute.updateValueObjOrig();
139     };
140
141     /*
142     * Loops through flattened attributes array and to assign values
143     * Then, convert any neccessary strings to objects, and vis-versa
144     * For list or map property, creates new children props if valueObj has values
145     */
146     public assignFlattenedChildrenValues = (parentValueJSON: any, derivedPropArray: Array<DerivedFEAttribute>, parentName: string) => {
147         if (!derivedPropArray || !parentName) return;
148         let propsToPushMap: Map<number, Array<DerivedFEAttribute>> = new Map<number, Array<DerivedFEAttribute>>();
149         derivedPropArray.forEach((prop, index) => {
150
151             let propNameInObj = prop.attributesName.substring(prop.attributesName.indexOf(parentName) + parentName.length + 1).split('#').join('.'); //extract everything after parent name
152             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
153             prop.value = (prop.valueObj !== null && (typeof prop.valueObj) != 'string') ? JSON.stringify(prop.valueObj) : prop.valueObj;
154
155             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
156                 prop.valueObj = (prop.valueObj !== null && typeof prop.valueObj == 'object') ? JSON.stringify(prop.valueObj) : prop.valueObj;
157             } else if(prop.type == PROPERTY_TYPES.INTEGER || prop.type == PROPERTY_TYPES.FLOAT || prop.type == PROPERTY_TYPES.BOOLEAN){ //parse ints and non-string simple types
158                 prop.valueObj = (prop.valueObj !== null && typeof prop.valueObj == PROPERTY_TYPES.STRING) ? JSON.parse(prop.valueObj) : prop.valueObj;
159             } else { //parse strings that should be objects
160                 if (prop.derivedDataType == DerivedAttributeType.COMPLEX) {
161                     prop.valueObj = (prop.valueObj === null || typeof prop.valueObj != 'object') ? JSON.parse(prop.valueObj || '{}') : prop.valueObj;
162                 } else if (prop.derivedDataType == DerivedAttributeType.LIST) {
163                     prop.valueObj = (prop.valueObj === null || typeof prop.valueObj != 'object') ? JSON.parse(prop.valueObj || '[]') : prop.valueObj;
164                 } else if (prop.derivedDataType == DerivedAttributeType.MAP) {
165                     if (!prop.isChildOfListOrMap || !prop.schema.property.isSimpleType) {
166                         prop.valueObj = (prop.valueObj === null || typeof prop.valueObj != 'object') ? JSON.parse(prop.valueObj || '{}') : prop.valueObj;
167                     }
168                 }
169                 if ((prop.derivedDataType == DerivedAttributeType.LIST || prop.derivedDataType == DerivedAttributeType.MAP) && typeof prop.valueObj == 'object' && prop.valueObj !== null && Object.keys(prop.valueObj).length) {
170                     let newProps: Array<DerivedFEAttribute> = [];
171                     Object.keys(prop.valueObj).forEach((key) => {
172                         newProps.push(...this.createListOrMapChildren(prop, key, prop.valueObj[key]));//create new children, assign their values, and then add to array
173                     });
174                     propsToPushMap[index + 1] = newProps;
175                 }
176             }
177
178             prop.valueObj = AttributeFEModel.cleanValueObj(prop.valueObj);
179         });
180
181         //add props after we're done looping (otherwise our loop gets messed up). Push in reverse order, so we dont mess up indexes.
182         Object.keys(propsToPushMap).reverse().forEach((indexToInsert) => {
183             derivedPropArray.splice(+indexToInsert, 0, ...propsToPushMap[indexToInsert]); //slacker parsing
184         });
185     }
186
187     public resetAttributeValue = (attribute: AttributeFEModel, newValue: string, nestedPath?: string): void => {
188         attribute.value = newValue;
189         if (nestedPath) {
190             let newAttrib = attribute.flattenedChildren.find(attrib => attrib.attributesName == nestedPath);
191             newAttrib && this.assignFlattenedChildrenValues(JSON.parse(newValue), [newAttrib], attribute.name);
192             attribute.updateValueObjOrig();
193         } else {
194             this.initValueObjectRef(attribute);
195         }
196     }
197
198 }