Fix serialization of a leaf-list field.
[osm/UI.git] / skyquake / plugins / composer / src / src / libraries / model / DescriptorModelMetaFactory.js
1 /**
2 * Created by onvelocity on 1/27/16.
3 *
4 * This class provides methods to get the metadata about descriptor models.
5 */
6
7 import _cloneDeep from 'lodash/cloneDeep'
8 import _isEmpty from 'lodash/isEmpty'
9 import _pick from 'lodash/pick'
10 import _get from 'lodash/get'
11 import _set from 'lodash/set'
12 import DescriptorModelMetaProperty from './DescriptorModelMetaProperty'
13 import CommonUtils from 'utils/utils';
14 const assign = Object.assign;
15
16 const exportInnerTypesMap = {
17 'constituent-vnfd': 'nsd.constituent-vnfd',
18 'vdu': 'vnfd.vdu'
19 };
20
21 function getPathForType(type) {
22 if (exportInnerTypesMap[type]) {
23 return exportInnerTypesMap[type];
24 }
25 return type;
26 }
27
28 const uiStateToSave = ['containerPositionMap'];
29
30 //////
31 // Data serialization will be done on a meta model basis. That is,
32 // given a schema and data, retrieve from the data only that which is
33 // defined by the schema.
34 //
35
36 // serialize data for a list of properties
37 function serializeAll(properties, data) {
38 if (data) {
39 return properties.reduce((obj, p) => {
40 return Object.assign(obj, p.serialize(data));
41 }, {});
42 }
43 return null;
44 }
45
46 function serialize_container(data) {
47 data = data[this.name];
48 if (_isEmpty(data)) {
49 return null;
50 }
51 let obj = {};
52 obj[this.name] = serializeAll(this.properties, data);
53 return obj;
54 }
55
56 function serialize_list(data) {
57 data = data[this.name];
58 if (data) {
59 if (!Array.isArray(data)) {
60 return serializeAll(this.properties, data);
61 } else if (data.length) {
62 let list = data.reduce((c, d) => {
63 let obj = serializeAll(this.properties, d);
64 if (!_isEmpty(obj)) {
65 c.push(obj);
66 }
67 return c;
68 }, []);
69 if (!_isEmpty(list)){
70 let obj = {};
71 obj[this.name] = list;
72 return obj;
73 }
74 }
75 }
76 return null;
77 }
78
79 function serialize_leaf(data) {
80 let value = data[this.name];
81 if (value === null || typeof value === 'undefined' || value === '') {
82 return null;
83 }
84 let obj = {};
85 if (this['data-type'] === 'empty') {
86 value = ''; // empty string does get sent as value
87 }
88 obj[this.name] = value;
89 return obj;
90 }
91
92 function serialize_leaf_empty(data) {
93 let value = data[this.name];
94 if (value) {
95 let obj = {};
96 obj[this.name] = "";
97 return obj;
98 }
99 return null;
100 }
101
102 function serialize_leaf_list(data) {
103 data = data[this.name];
104 if (data) {
105 data = data.reduce((result, value) => {
106 if (value !== '' && value !== null && value !== undefined && typeof value !== 'object') {
107 result.push(value);
108 }
109 return result;
110 }, []);
111 if (data.length){
112 let obj = {};
113 obj[this.name] = data;
114 return obj;
115 }
116 }
117 return null;
118 }
119
120 function serialize_choice(data) {
121 let keys = Object.keys(data);
122 if (keys) {
123 const chosen = this.properties.find(
124 c => c.type === 'case' && c.properties && c.properties.some(p => keys.indexOf(p.name) > -1));
125 return chosen && serializeAll(chosen.properties, data);
126 }
127 return null;
128 }
129
130 function serialize_case(data) {
131 return Serializer.container.call(this, data);
132 }
133
134 // special ui data handler for leaf of type string named 'meta'
135 function serialize_meta(data) {
136 let uiState = data['uiState'];
137 let meta = uiState && _pick(uiState, uiStateToSave);
138 // if there is no uiState to save perhaps this was not a ui state property
139 return _isEmpty(meta) ? null : {meta: JSON.stringify(meta)};
140 }
141
142 function serialize_unsupported(data) {
143 console.error('unsupported property', property);
144 return null;
145 }
146
147 function getSerializer(property) {
148 switch (property.name) {
149 case 'rw-nsd:meta':
150 case 'rw-vnfd:meta':
151 return serialize_meta.bind(property);
152 }
153 switch (property.type) {
154 case 'list':
155 return serialize_list.bind(property);
156 case 'container':
157 return serialize_container.bind(property);
158 case 'choice':
159 return serialize_choice.bind(property);
160 case 'case':
161 return serialize_case.bind(property);
162 case 'leaf_list':
163 return serialize_leaf_list.bind(property);
164 case 'leaf':
165 switch (property['data-type']){
166 case 'empty':
167 return serialize_leaf_empty.bind(property);
168 }
169 return serialize_leaf.bind(property);
170 }
171 return serialize_unsupported.bind(property);
172 }
173
174 let modelMetaByPropertyNameMap = [];
175
176 let cachedDescriptorModelMetaRequest = null;
177
178 export default {
179 init() {
180 if (!cachedDescriptorModelMetaRequest) {
181 cachedDescriptorModelMetaRequest = new Promise(function (resolve, reject) {
182 CommonUtils.getDescriptorModelMeta().then(function (data) {
183 let DescriptorModelMetaJSON = data;
184 modelMetaByPropertyNameMap = Object.keys(DescriptorModelMetaJSON).reduce((map, key) => {
185 function mapProperties(parentMap, parentObj) {
186 // let's beef up the meta info with a helper (more to come?)
187 parentObj.serialize = getSerializer(parentObj);
188 parentMap[':meta'] = parentObj;
189 const properties = parentObj && parentObj.properties ? parentObj.properties : [];
190 properties.forEach(p => {
191 parentMap[p.name] = mapProperties({}, assign(p, {
192 ':qualified-type': parentObj[':qualified-type'] + '.' + p.name
193 }));
194 return map;
195 }, parentMap);
196 return parentMap;
197 }
198 map[key] = mapProperties({}, assign(DescriptorModelMetaJSON[key], {
199 ':qualified-type': key
200 }));
201 return map;
202 }, {});
203
204 (() => {
205 // initialize the UI centric properties that CONFD could care less about
206 _set(modelMetaByPropertyNameMap, 'nsd.meta.:meta.preserve-line-breaks', true);
207 _set(modelMetaByPropertyNameMap, 'vnfd.meta.:meta.preserve-line-breaks', true);
208 _set(modelMetaByPropertyNameMap, 'vnfd.vdu.cloud-init.:meta.preserve-line-breaks', true);
209 _set(modelMetaByPropertyNameMap, 'nsd.constituent-vnfd.vnf-configuration.config-template.:meta.preserve-line-breaks', true);
210 })();
211 resolve();
212 }, function (error) {
213 cachedDescriptorModelMetaRequest = null;
214 })
215 })
216 }
217
218 return cachedDescriptorModelMetaRequest;
219 },
220 /**
221 * Create a new instance of the indicated property and, if relevent, use the given
222 * unique name for the instance's key (see generateItemUniqueName)
223 *
224 * @param {Object | string} typeOrPath a property definition object or a path to a property
225 * @param [{string}] uniqueName optional
226 * @returns
227 */
228 createModelInstanceForType(typeOrPath, uniqueName) {
229 const modelMeta = this.getModelMetaForType(typeOrPath);
230 return DescriptorModelMetaProperty.createModelInstance(modelMeta, uniqueName);
231 },
232 getModelMetaForType(typeOrPath, filterProperties = () => true) {
233 // resolve paths like 'nsd' or 'vnfd.vdu' or 'nsd.constituent-vnfd'
234 const found = _get(modelMetaByPropertyNameMap, getPathForType(typeOrPath));
235 if (found) {
236 const uiState = _cloneDeep(found[':meta']);
237 uiState.properties = uiState.properties.filter(filterProperties);
238 return uiState;
239 }
240 console.warn('no model uiState found for type', typeOrPath);
241 },
242 getModelFieldNamesForType(typeOrPath) {
243 // resolve paths like 'nsd' or 'vnfd.vdu' or 'nsd.constituent-vnfd'
244 const found = _get(modelMetaByPropertyNameMap, getPathForType(typeOrPath));
245 if (found) {
246 let result = [];
247 found[':meta'].properties.map((p) => {
248 // if(false) {
249 if (p.type == 'choice') {
250 result.push(p.name)
251 return p.properties.map(function (q) {
252 result.push(q.properties[0].name);
253 })
254
255 } else {
256 return result.push(p.name);
257 }
258 })
259 return result;
260 }
261 console.warn('no model uiState found for type', typeOrPath);
262 },
263 /**
264 * For a list with a single valued key that is of type string, generate a unique name
265 * for a new entry to be added to the indicated list. This name will use the provided
266 * prefix (or the list's name) followed by a number. The number will be based on the
267 * current length of the array but will insure there is no collision with an existing
268 * name.
269 *
270 * @param {Array} list the list model data
271 * @param {prooerty} property the schema definition of the list
272 * @param [{any} prefix] the perferred prefix for the name. If not provide property.name
273 * will be used.
274 * @returns {string}
275 */
276 generateItemUniqueName(list, property, prefix) {
277 if (property.type !== 'list' ||
278 property.key.length !== 1 ||
279 property.properties.find(prop => prop.name === property.key[0])['data-type'] !== 'string') {
280 // only support list with a single key of type string
281 return null;
282 }
283 if (!prefix) {
284 prefix = property.name;
285 }
286 let key = property.key[0];
287 let suffix = list ? list.length + 1 : 1
288 let keyValue = prefix + '-' + suffix;
289
290 function makeUniqueName() {
291 if (list) {
292 for (let i = 0; i < list.length; i = ++i) {
293 if (list[i][key] === keyValue) {
294 keyValue = keyValue + '-' + (i + 1);
295 makeUniqueName(); // not worried about recursing too deep (chances ??)
296 break;
297 }
298 }
299 }
300 }
301 makeUniqueName();
302 return keyValue;
303 }
304
305 }