update from RIFT as of 696b75d2fe9fb046261b08c616f1bcf6c0b54a9b third try
[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 mergePathData(obj, path, data) {
82 path = path.split(/[\.\[\]]/).filter(d => d);
83 // enable look-ahead to determine if type is array or object
84 const pathCopy = path.slice();
85 let resolvedObj = obj;
86 if (path.length) {
87 // last item in path used to assign value on the resolved object
88 const name = path.pop();
89 resolvedObj = path.reduce((r, p, i) => {
90 if (typeof (r[p]) !== 'object') {
91 // look-ahead to see if next path item is a number
92 const isArray = !isNaN(parseInt(pathCopy[i + 1], 10));
93 r[p] = isArray ? [] : {}
94 }
95 return r[p];
96 }, obj)[name];
97 }
98 Object.assign(resolvedObj, data);
99 },
100 updatePathValue(obj, path, value, isCase) {
101 // todo: replace implementation of assignPathValue with this impl and
102 // remove updatePathValue (only need one function, not both)
103 // same as assignPathValue except removes property if value is undefined
104 path = path.split(/[\.\[\]]/).filter(d => d);
105 const isRemove = typeof value === 'undefined';
106 // enable look-ahead to determine if type is array or object
107 const pathCopy = path.slice();
108 // last item in path used to assign value on the resolved object
109 const name = path.pop();
110 const resolvedObj = path.reduce((r, p, i) => {
111 // look-ahead to see if next path item is a number
112 const index = parseInt(pathCopy[i + 1], 10);
113 const isArray = !isNaN(index);
114 if (typeof (r[p]) !== 'object') {
115 r[p] = isArray ? [] : {}
116 }
117 if (isRemove && ((i + 1) === path.length)) {
118 if (isArray) {
119 r[p] = r[p].filter((d, i) => i !== index);
120 } else {
121 if (isCase) {
122 delete r[name];
123 } else {
124 delete r[p][name];
125 }
126 }
127 }
128 if (isCase) {
129 return r;
130 } else {
131 return r[p];
132 }
133
134 }, obj);
135 if (!isRemove) {
136 resolvedObj[name] = value;
137 }
138 },
139 removePathValue(obj, path, isCase) {
140 // note updatePathValue removes value if third argument is undefined
141 return this.updatePathValue(obj, path, undefined, isCase);
142 },
143
144 suffixAsInteger: (field) => {
145 return (obj) => {
146 const str = String(obj[field]);
147 const value = str.replace(str.replace(/[\d]+$/, ''), '');
148 return 1 + parseInt(value, 10) || 0;
149 };
150 },
151
152 toBiggestValue: (maxIndex, curIndex) => Math.max(maxIndex, curIndex),
153
154 isRelativePath(path) {
155 if (path.split('/')[0] == '..') {
156 return true;
157 }
158 return false;
159 },
160
161 getResults(topLevelObject, pathArray) {
162 let objectCopy = _cloneDeep(topLevelObject);
163 let i = pathArray.length;
164 let results = [];
165
166 while (pathArray[pathArray.length - i]) {
167 if (_isArray(objectCopy[pathArray[pathArray.length - i]])) {
168 if (i == 2) {
169 results = _map(objectCopy[pathArray[pathArray.length - i]], pathArray[pathArray.length - 1]);
170 } else {
171 objectCopy = objectCopy[pathArray[pathArray.length - i]];
172 }
173 } else if (_isArray(objectCopy)) {
174 objectCopy.map((object) => {
175 if (_isArray(object[pathArray[pathArray.length - i]])) {
176 if (i == 2) {
177 results = results.concat(_map(object[pathArray[pathArray.length - i]], pathArray[pathArray.length - 1]));
178 }
179 }
180 })
181 }
182 i--;
183 }
184
185 return results;
186 },
187
188 getAbsoluteResults(topLevelObject, pathArray) {
189 let i = pathArray.length;
190 let objectCopy = _cloneDeep(topLevelObject);
191 let results = [];
192
193 let fragment = pathArray[pathArray.length - i]
194
195 while (fragment) {
196 if (i == 1) {
197 // last fragment
198 if (_isArray(objectCopy)) {
199 // results will be obtained from a map
200 results = _map(objectCopy, fragment);
201 } else {
202 // object
203 if (fragment.match(/\[.*\]/g)) {
204 // contains a predicate
205 // shouldn't reach here
206 console.log('Something went wrong while resolving a leafref. Reached a leaf with predicate.');
207 } else {
208 // contains no predicate
209 if (!objectCopy) {
210 break;
211 }
212 results.push(objectCopy[fragment]);
213 }
214 }
215 } else {
216 if (_isArray(objectCopy)) {
217 // is array
218 objectCopy = _map(objectCopy, fragment);
219
220 // If any of the deeper object is an array, flatten the entire list.
221 // This would usually be a bad leafref going out of its scope.
222 // Log it too
223 for (let i = 0; i < objectCopy.length; i++) {
224 if (_isArray(objectCopy[i])) {
225 objectCopy = _flatten(objectCopy);
226 console.log('This might be a bad leafref. Verify with backend team.')
227 break;
228 }
229 }
230 } else {
231 // is object
232 if (fragment.match(/\[.*\]/g)) {
233 // contains a predicate
234 let predicateStr = fragment.match(/\[.*\]/g)[0];
235 // Clip leading [ and trailing ]
236 predicateStr = predicateStr.substr(1, predicateStr.length - 2);
237 const predicateKeyValue = predicateStr.split('=');
238 const predicateKey = predicateKeyValue[0];
239 const predicateValue = predicateKeyValue[1];
240 // get key for object to search into
241 let key = fragment.split('[')[0];
242 let searchObject = {};
243 searchObject[predicateKey] = predicateValue;
244 let found = _find(objectCopy[key], searchObject);
245 if (found) {
246 objectCopy = found;
247 } else {
248 // check for numerical value
249 if (predicateValue != "" &&
250 predicateValue != null &&
251 predicateValue != NaN &&
252 predicateValue != Infinity &&
253 predicateValue != -Infinity) {
254 let numericalPredicateValue = _toNumber(predicateValue);
255 if (_isNumber(numericalPredicateValue)) {
256 searchObject[predicateKey] = numericalPredicateValue;
257 found = _find(objectCopy[key], searchObject);
258 }
259 }
260 if (found) {
261 objectCopy = found;
262 } else {
263 return [];
264 }
265 }
266 } else {
267 // contains no predicate
268 if (!objectCopy) {
269 break;
270 }
271 objectCopy = objectCopy[fragment];
272 if (!objectCopy) {
273 // contains no value
274 break;
275 }
276 }
277 }
278 }
279 i--;
280 fragment = pathArray[pathArray.length - i];
281 }
282
283 return results;
284 },
285
286 resolveCurrentPredicate(leafRefPath, container, pathCopy) {
287 if (leafRefPath.indexOf('current()') != -1) {
288 // contains current
289
290 // Get the relative path from current
291 let relativePath = leafRefPath.match("current\\(\\)\/(.*)\]");
292 let relativePathArray = relativePath[1].split('/');
293
294 while (relativePathArray[0] == '..') {
295 pathCopy.pop();
296 relativePathArray.shift();
297 }
298
299 // Supports only one relative path up
300 // To support deeper paths, will need to massage the string more
301 // i.e. change '/'' to '.'
302 const searchPath = pathCopy.join('.').concat('.', relativePathArray[0]);
303 const searchValue = this.resolvePath(container.model, searchPath);
304
305 const predicateStr = leafRefPath.match("(current.*)\]")[1];
306 leafRefPath = leafRefPath.replace(predicateStr, searchValue);
307 }
308 return leafRefPath;
309 },
310
311 cleanupFieldKeyArray (fieldKeyArray) {
312 fieldKeyArray.map((fieldKey, fieldKeyIndex) => {
313 fieldKeyArray[fieldKeyIndex] = fieldKey.replace(/.*\/(.*)/, '$1');
314 });
315 return fieldKeyArray;
316 },
317
318 resolveLeafRefPath(catalogs, leafRefPath, fieldKey, path, container) {
319 let pathCopy = _clone(path);
320 // Strip any prefixes
321 let leafRefPathCopy = leafRefPath.replace(/[-\w]*:/g, '');
322 // Strip any spaces
323 leafRefPathCopy = leafRefPathCopy.replace(/\s/g, '');
324
325 // resolve any current paths
326 leafRefPathCopy = this.resolveCurrentPredicate(leafRefPathCopy, container, pathCopy);
327
328 // Split on delimiter (/)
329 const pathArray = leafRefPathCopy.split('/');
330
331 let fieldKeyArray = fieldKey.split(':');
332
333 // strip prepending qualifiers from fieldKeys
334 fieldKeyArray = this.cleanupFieldKeyArray(fieldKeyArray);
335 let results = [];
336
337 // Check if relative path or not
338 // TODO: Below works but
339 // better to convert the pathCopy to absolute/rooted path
340 // and use the absolute module instead
341 if (this.isRelativePath(leafRefPathCopy)) {
342 let i = pathArray.length;
343 while (pathArray[pathArray.length - i] == '..') {
344 fieldKeyArray.splice(-1, 1);
345 if (!isNaN(Number(fieldKeyArray[fieldKeyArray.length - 1]))) {
346 // found a number, so an index. strip it
347 fieldKeyArray.splice(-1, 1);
348 }
349 i--;
350 }
351
352 // traversed all .. - now traverse down
353 if (fieldKeyArray.length == 1) {
354 for (let key in catalogs) {
355 for (let subKey in catalogs[key]) {
356 let found = _find(catalogs[key][subKey], {
357 id: fieldKeyArray[0]
358 });
359 if (found) {
360 results = this.getAbsoluteResults(found, pathArray.splice(-i, i));
361 return results;
362 }
363 }
364 }
365 } else if (fieldKeyArray.length == 2) {
366 for (let key in catalogs) {
367 for (let subKey in catalogs[key]) {
368 console.log(key, subKey);
369 var found = _find(catalogs[key][subKey], {
370 id: fieldKeyArray[0]
371 });
372 if (found) {
373 for (let foundKey in found) {
374 if (_isArray(found[foundKey])) {
375 let topLevel = _find(found[foundKey], {
376 id: fieldKeyArray[1]
377 });
378 if (topLevel) {
379 results = this.getAbsoluteResults(topLevel, pathArray.splice(-i, i));
380 return results;
381 }
382 } else {
383 if (foundKey == fieldKeyArray[1]) {
384 results = this.getAbsoluteResults(found[foundKey], pathArray.splice(-i, i));
385 return results;
386 }
387 }
388 }
389 }
390 }
391 }
392 } else if (fieldKeyArray.length == 3) {
393 for (let key in catalogs) {
394 for (let subKey in catalogs[key]) {
395 let found = _find(catalogs[key][subKey], {
396 id: fieldKeyArray[0]
397 });
398 if (found) {
399 for (let foundKey in found) {
400 if (_isArray(found[foundKey])) {
401 let topLevel = _find(found[foundKey], {
402 id: fieldKeyArray[1]
403 });
404 if (topLevel) {
405 results = this.getAbsoluteResults(topLevel, pathArray.splice(-i, i));
406 return results;
407 }
408 } else {
409 if (foundKey == fieldKeyArray[1]) {
410 results = this.getAbsoluteResults(found[foundKey], pathArray.splice(-i, i));
411 return results;
412 }
413 }
414 }
415 }
416 }
417 }
418 } else {
419 // not supported - too many levels deep ... maybe some day
420 console.log('The relative path is from a node too many levels deep from root. This is not supported at the time');
421 }
422 } else {
423 // absolute path
424 if (pathArray[0] == "") {
425 pathArray.splice(0, 1);
426 }
427
428 let catalogKey = pathArray[0];
429 let topLevelObject = {};
430
431 topLevelObject[catalogKey] = catalogs[catalogKey];
432
433 results = this.getAbsoluteResults(topLevelObject, pathArray);
434
435 return results;
436 }
437 }
438 }