RIFT-15931, RIFT-15945: Leafref for rsp and internal-interface restored
[osm/UI.git] / skyquake / plugins / composer / src / src / libraries / utils.js
1 /*
2 *
3 * Copyright 2016 RIFT.IO Inc
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19 'use strict';
20
21 import _cloneDeep from 'lodash/cloneDeep';
22 import _isArray from 'lodash/isArray';
23 import _map from 'lodash/map';
24 import _flatten from 'lodash/flatten';
25 import _find from 'lodash/find';
26 import _toNumber from 'lodash/toNumber';
27 import _isNumber from 'lodash/isNumber';
28 import _clone from 'lodash/clone';
29
30 export default {
31 addAuthorizationStub(xhr) {
32 xhr.setRequestHeader('Authorization', 'Basic YWRtaW46YWRtaW4=');
33 },
34 getSearchParams(url) {
35 var a = document.createElement('a');
36 a.href = url;
37 var params = {};
38 var items = a.search.replace('?', '').split('&');
39 for (var i = 0; i < items.length; i++) {
40 if (items[i].length > 0) {
41 var key_value = items[i].split('=');
42 params[key_value[0]] = decodeURIComponent(key_value[1]);
43 }
44 }
45 return params;
46 },
47 parseJSONIgnoreErrors(txt) {
48 try {
49 return JSON.parse(txt);
50 } catch (ignore) {
51 return {};
52 }
53 },
54 resolvePath(obj, path) {
55 // supports a.b, a[1] and foo[bar], etc.
56 // where obj is ['nope', 'yes', {a: {b: 1}, foo: 2}]
57 // then [1] returns 'yes'; [2].a.b returns 1; [2].a[foo] returns 2;
58 path = path.split(/[\.\[\]]/).filter(d => d);
59 return path.reduce((r, p) => {
60 if (r) {
61 return r[p];
62 }
63 }, obj);
64 },
65 assignPathValue(obj, path, value) {
66 path = path.split(/[\.\[\]]/).filter(d => d);
67 // enable look-ahead to determine if type is array or object
68 const pathCopy = path.slice();
69 // last item in path used to assign value on the resolved object
70 const name = path.pop();
71 const resolvedObj = path.reduce((r, p, i) => {
72 if (typeof(r[p]) !== 'object') {
73 // look-ahead to see if next path item is a number
74 const isArray = !isNaN(parseInt(pathCopy[i + 1], 10));
75 r[p] = isArray ? [] : {}
76 }
77 return r[p];
78 }, obj);
79 resolvedObj[name] = value;
80 },
81 updatePathValue(obj, path, value, isCase) {
82 // todo: replace implementation of assignPathValue with this impl and
83 // remove updatePathValue (only need one function, not both)
84 // same as assignPathValue except removes property if value is undefined
85 path = path.split(/[\.\[\]]/).filter(d => d);
86 const isRemove = typeof value === 'undefined';
87 // enable look-ahead to determine if type is array or object
88 const pathCopy = path.slice();
89 // last item in path used to assign value on the resolved object
90 const name = path.pop();
91 const resolvedObj = path.reduce((r, p, i) => {
92 // look-ahead to see if next path item is a number
93 const index = parseInt(pathCopy[i + 1], 10);
94 const isArray = !isNaN(index);
95 if (typeof(r[p]) !== 'object') {
96 r[p] = isArray ? [] : {}
97 }
98 if (isRemove && ((i + 1) === path.length)) {
99 if (isArray) {
100 r[p] = r[p].filter((d, i) => i !== index);
101 } else {
102 if(isCase) {
103 delete r[name];
104 } else {
105 delete r[p][name];
106 }
107 }
108 }
109 if(isCase) {
110 return r;
111 } else {
112 return r[p];
113 }
114
115 }, obj);
116 if (!isRemove) {
117 resolvedObj[name] = value;
118 }
119 },
120 removePathValue(obj, path, isCase) {
121 // note updatePathValue removes value if third argument is undefined
122 return this.updatePathValue(obj, path, undefined, isCase);
123 },
124
125 suffixAsInteger: (field) => {
126 return (obj) =>{
127 const str = String(obj[field]);
128 const value = str.replace(str.replace(/[\d]+$/, ''), '');
129 return 1 + parseInt(value, 10) || 0;
130 };
131 },
132
133 toBiggestValue: (maxIndex, curIndex) => Math.max(maxIndex, curIndex),
134
135 isRelativePath (path) {
136 if (path.split('/')[0] == '..') {
137 return true;
138 }
139 return false;
140 },
141
142 getResults (topLevelObject, pathArray) {
143 let objectCopy = _cloneDeep(topLevelObject);
144 let i = pathArray.length;
145 let results = [];
146
147 while(pathArray[pathArray.length - i]) {
148 if (_isArray(objectCopy[pathArray[pathArray.length - i]])) {
149 if (i == 2) {
150 results = _map(objectCopy[pathArray[pathArray.length - i]], pathArray[pathArray.length - 1]);
151 } else {
152 objectCopy = objectCopy[pathArray[pathArray.length - i]];
153 }
154 } else if (_isArray(objectCopy)) {
155 objectCopy.map((object) => {
156 if (_isArray(object[pathArray[pathArray.length - i]])) {
157 if (i == 2) {
158 results = results.concat(_map(object[pathArray[pathArray.length - i]], pathArray[pathArray.length - 1]));
159 }
160 }
161 })
162 }
163 i--;
164 }
165
166 return results;
167 },
168
169 getAbsoluteResults (topLevelObject, pathArray) {
170 let i = pathArray.length;
171 let objectCopy = _cloneDeep(topLevelObject);
172 let results = [];
173
174 let fragment = pathArray[pathArray.length - i]
175
176 while (fragment) {
177 if (i == 1) {
178 // last fragment
179 if (_isArray(objectCopy)) {
180 // results will be obtained from a map
181 results = _map(objectCopy, fragment);
182 } else {
183 // object
184 if (fragment.match(/\[.*\]/g)) {
185 // contains a predicate
186 // shouldn't reach here
187 console.log('Something went wrong while resolving a leafref. Reached a leaf with predicate.');
188 } else {
189 // contains no predicate
190 results.push(objectCopy[fragment]);
191 }
192 }
193 } else {
194 if (_isArray(objectCopy)) {
195 // is array
196 objectCopy = _map(objectCopy, fragment);
197
198 // If any of the deeper object is an array, flatten the entire list.
199 // This would usually be a bad leafref going out of its scope.
200 // Log it too
201 for (let i = 0; i < objectCopy.length; i++) {
202 if (_isArray(objectCopy[i])) {
203 objectCopy = _flatten(objectCopy);
204 console.log('This might be a bad leafref. Verify with backend team.')
205 break;
206 }
207 }
208 } else {
209 // is object
210 if (fragment.match(/\[.*\]/g)) {
211 // contains a predicate
212 let predicateStr = fragment.match(/\[.*\]/g)[0];
213 // Clip leading [ and trailing ]
214 predicateStr = predicateStr.substr(1, predicateStr.length - 2);
215 const predicateKeyValue = predicateStr.split('=');
216 const predicateKey = predicateKeyValue[0];
217 const predicateValue = predicateKeyValue[1];
218 // get key for object to search into
219 let key = fragment.split('[')[0];
220 let searchObject = {};
221 searchObject[predicateKey] = predicateValue;
222 let found = _find(objectCopy[key], searchObject);
223 if (found) {
224 objectCopy = found;
225 } else {
226 // check for numerical value
227 if (predicateValue != "" &&
228 predicateValue != null &&
229 predicateValue != NaN &&
230 predicateValue != Infinity &&
231 predicateValue != -Infinity) {
232 let numericalPredicateValue = _toNumber(predicateValue);
233 if (_isNumber(numericalPredicateValue)) {
234 searchObject[predicateKey] = numericalPredicateValue;
235 found = _find(objectCopy[key], searchObject);
236 }
237 }
238 if (found) {
239 objectCopy = found;
240 } else {
241 return [];
242 }
243 }
244 } else {
245 // contains no predicate
246 objectCopy = objectCopy[fragment];
247 if (!objectCopy) {
248 // contains no value
249 break;
250 }
251 }
252 }
253 }
254 i--;
255 fragment = pathArray[pathArray.length - i];
256 }
257
258 return results;
259 },
260
261 resolveCurrentPredicate (leafRefPath, container, pathCopy) {
262 if (leafRefPath.indexOf('current()') != -1) {
263 // contains current
264
265 // Get the relative path from current
266 let relativePath = leafRefPath.match("current\\(\\)\/(.*)\]");
267 let relativePathArray = relativePath[1].split('/');
268
269 while (relativePathArray[0] == '..') {
270 pathCopy.pop();
271 relativePathArray.shift();
272 }
273
274 // Supports only one relative path up
275 // To support deeper paths, will need to massage the string more
276 // i.e. change '/'' to '.'
277 const searchPath = pathCopy.join('.').concat('.', relativePathArray[0]);
278 const searchValue = this.resolvePath(container.model, searchPath);
279
280 const predicateStr = leafRefPath.match("(current.*)\]")[1];
281 leafRefPath = leafRefPath.replace(predicateStr, searchValue);
282 }
283 return leafRefPath;
284 },
285
286 resolveLeafRefPath (catalogs, leafRefPath, fieldKey, path, container) {
287 let pathCopy = _clone(path);
288 // Strip any prefixes
289 let leafRefPathCopy = leafRefPath.replace(/[\w\d]*:/g, '');
290 // Strip any spaces
291 leafRefPathCopy = leafRefPathCopy.replace(/\s/g, '');
292
293 // resolve any current paths
294 leafRefPathCopy = this.resolveCurrentPredicate(leafRefPathCopy, container, pathCopy);
295
296 // Split on delimiter (/)
297 const pathArray = leafRefPathCopy.split('/');
298 let fieldKeyArray = fieldKey.split(':');
299 let results = [];
300
301 // Check if relative path or not
302 // TODO: Below works but
303 // better to convert the pathCopy to absolute/rooted path
304 // and use the absolute module instead
305 if (this.isRelativePath(leafRefPathCopy)) {
306 let i = pathArray.length;
307 while (pathArray[pathArray.length - i] == '..') {
308 fieldKeyArray.splice(-1, 1);
309 if (!isNaN(Number(fieldKeyArray[fieldKeyArray.length - 1]))) {
310 // found a number, so an index. strip it
311 fieldKeyArray.splice(-1, 1);
312 }
313 i--;
314 }
315
316 // traversed all .. - now traverse down
317 if (fieldKeyArray.length == 1) {
318 for (let key in catalogs) {
319 for (let subKey in catalogs[key]) {
320 let found = _find(catalogs[key][subKey], {id: fieldKeyArray[0]});
321 if (found) {
322 results = this.getAbsoluteResults(found, pathArray.splice(-i, i));
323 return results;
324 }
325 }
326 }
327 } else if (fieldKeyArray.length == 2) {
328 for (let key in catalogs) {
329 for (let subKey in catalogs[key]) {
330 console.log(key, subKey);
331 var found = _find(catalogs[key][subKey], {id: fieldKeyArray[0]});
332 if (found) {
333 for (let foundKey in found) {
334 if (_isArray(found[foundKey])) {
335 let topLevel = _find(found[foundKey], {id: fieldKeyArray[1]});
336 if (topLevel) {
337 results = this.getAbsoluteResults(topLevel, pathArray.splice(-i, i));
338 return results;
339 }
340 } else {
341 if (foundKey == fieldKeyArray[1]) {
342 results = this.getAbsoluteResults(found[foundKey], pathArray.splice(-i, i));
343 return results;
344 }
345 }
346 }
347 }
348 }
349 }
350 } else if (fieldKeyArray.length == 3) {
351 for (let key in catalogs) {
352 for (let subKey in catalogs[key]) {
353 let found = _find(catalogs[key][subKey], {id: fieldKeyArray[0]});
354 if (found) {
355 for (let foundKey in found) {
356 if (_isArray(found[foundKey])) {
357 let topLevel = _find(found[foundKey], {id: fieldKeyArray[1]});
358 if (topLevel) {
359 results = this.getAbsoluteResults(topLevel, pathArray.splice(-i, i));
360 return results;
361 }
362 } else {
363 if (foundKey == fieldKeyArray[1]) {
364 results = this.getAbsoluteResults(found[foundKey], pathArray.splice(-i, i));
365 return results;
366 }
367 }
368 }
369 }
370 }
371 }
372 } else {
373 // not supported - too many levels deep ... maybe some day
374 console.log('The relative path is from a node too many levels deep from root. This is not supported at the time');
375 }
376 } else {
377 // absolute path
378 if (pathArray[0] == "") {
379 pathArray.splice(0, 1);
380 }
381
382 let catalogKey = pathArray[0];
383 let topLevelObject = {};
384
385 topLevelObject[catalogKey] = catalogs[catalogKey];
386
387 results = this.getAbsoluteResults(topLevelObject, pathArray);
388
389 return results;
390 }
391 }
392 }