NOTICKET: Merging OSM/master to OSM/projects
[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 if (!objectCopy) {
191 break;
192 }
193 results.push(objectCopy[fragment]);
194 }
195 }
196 } else {
197 if (_isArray(objectCopy)) {
198 // is array
199 objectCopy = _map(objectCopy, fragment);
200
201 // If any of the deeper object is an array, flatten the entire list.
202 // This would usually be a bad leafref going out of its scope.
203 // Log it too
204 for (let i = 0; i < objectCopy.length; i++) {
205 if (_isArray(objectCopy[i])) {
206 objectCopy = _flatten(objectCopy);
207 console.log('This might be a bad leafref. Verify with backend team.')
208 break;
209 }
210 }
211 } else {
212 // is object
213 if (fragment.match(/\[.*\]/g)) {
214 // contains a predicate
215 let predicateStr = fragment.match(/\[.*\]/g)[0];
216 // Clip leading [ and trailing ]
217 predicateStr = predicateStr.substr(1, predicateStr.length - 2);
218 const predicateKeyValue = predicateStr.split('=');
219 const predicateKey = predicateKeyValue[0];
220 const predicateValue = predicateKeyValue[1];
221 // get key for object to search into
222 let key = fragment.split('[')[0];
223 let searchObject = {};
224 searchObject[predicateKey] = predicateValue;
225 let found = _find(objectCopy[key], searchObject);
226 if (found) {
227 objectCopy = found;
228 } else {
229 // check for numerical value
230 if (predicateValue != "" &&
231 predicateValue != null &&
232 predicateValue != NaN &&
233 predicateValue != Infinity &&
234 predicateValue != -Infinity) {
235 let numericalPredicateValue = _toNumber(predicateValue);
236 if (_isNumber(numericalPredicateValue)) {
237 searchObject[predicateKey] = numericalPredicateValue;
238 found = _find(objectCopy[key], searchObject);
239 }
240 }
241 if (found) {
242 objectCopy = found;
243 } else {
244 return [];
245 }
246 }
247 } else {
248 // contains no predicate
249 if (!objectCopy) {
250 break;
251 }
252 objectCopy = objectCopy[fragment];
253 if (!objectCopy) {
254 // contains no value
255 break;
256 }
257 }
258 }
259 }
260 i--;
261 fragment = pathArray[pathArray.length - i];
262 }
263
264 return results;
265 },
266
267 resolveCurrentPredicate (leafRefPath, container, pathCopy) {
268 if (leafRefPath.indexOf('current()') != -1) {
269 // contains current
270
271 // Get the relative path from current
272 let relativePath = leafRefPath.match("current\\(\\)\/(.*)\]");
273 let relativePathArray = relativePath[1].split('/');
274
275 while (relativePathArray[0] == '..') {
276 pathCopy.pop();
277 relativePathArray.shift();
278 }
279
280 // Supports only one relative path up
281 // To support deeper paths, will need to massage the string more
282 // i.e. change '/'' to '.'
283 const searchPath = pathCopy.join('.').concat('.', relativePathArray[0]);
284 const searchValue = this.resolvePath(container.model, searchPath);
285
286 const predicateStr = leafRefPath.match("(current.*)\]")[1];
287 leafRefPath = leafRefPath.replace(predicateStr, searchValue);
288 }
289 return leafRefPath;
290 },
291
292 resolveLeafRefPath (catalogs, leafRefPath, fieldKey, path, container) {
293 let pathCopy = _clone(path);
294 // Strip any prefixes
295 let leafRefPathCopy = leafRefPath.replace(/[\w\d]*:/g, '');
296 // Strip any spaces
297 leafRefPathCopy = leafRefPathCopy.replace(/\s/g, '');
298
299 // resolve any current paths
300 leafRefPathCopy = this.resolveCurrentPredicate(leafRefPathCopy, container, pathCopy);
301
302 // Split on delimiter (/)
303 const pathArray = leafRefPathCopy.split('/');
304 let fieldKeyArray = fieldKey.split(':');
305 let results = [];
306
307 // Check if relative path or not
308 // TODO: Below works but
309 // better to convert the pathCopy to absolute/rooted path
310 // and use the absolute module instead
311 if (this.isRelativePath(leafRefPathCopy)) {
312 let i = pathArray.length;
313 while (pathArray[pathArray.length - i] == '..') {
314 fieldKeyArray.splice(-1, 1);
315 if (!isNaN(Number(fieldKeyArray[fieldKeyArray.length - 1]))) {
316 // found a number, so an index. strip it
317 fieldKeyArray.splice(-1, 1);
318 }
319 i--;
320 }
321
322 // traversed all .. - now traverse down
323 if (fieldKeyArray.length == 1) {
324 for (let key in catalogs) {
325 for (let subKey in catalogs[key]) {
326 let found = _find(catalogs[key][subKey], {id: fieldKeyArray[0]});
327 if (found) {
328 results = this.getAbsoluteResults(found, pathArray.splice(-i, i));
329 return results;
330 }
331 }
332 }
333 } else if (fieldKeyArray.length == 2) {
334 for (let key in catalogs) {
335 for (let subKey in catalogs[key]) {
336 console.log(key, subKey);
337 var found = _find(catalogs[key][subKey], {id: fieldKeyArray[0]});
338 if (found) {
339 for (let foundKey in found) {
340 if (_isArray(found[foundKey])) {
341 let topLevel = _find(found[foundKey], {id: fieldKeyArray[1]});
342 if (topLevel) {
343 results = this.getAbsoluteResults(topLevel, pathArray.splice(-i, i));
344 return results;
345 }
346 } else {
347 if (foundKey == fieldKeyArray[1]) {
348 results = this.getAbsoluteResults(found[foundKey], pathArray.splice(-i, i));
349 return results;
350 }
351 }
352 }
353 }
354 }
355 }
356 } else if (fieldKeyArray.length == 3) {
357 for (let key in catalogs) {
358 for (let subKey in catalogs[key]) {
359 let found = _find(catalogs[key][subKey], {id: fieldKeyArray[0]});
360 if (found) {
361 for (let foundKey in found) {
362 if (_isArray(found[foundKey])) {
363 let topLevel = _find(found[foundKey], {id: fieldKeyArray[1]});
364 if (topLevel) {
365 results = this.getAbsoluteResults(topLevel, pathArray.splice(-i, i));
366 return results;
367 }
368 } else {
369 if (foundKey == fieldKeyArray[1]) {
370 results = this.getAbsoluteResults(found[foundKey], pathArray.splice(-i, i));
371 return results;
372 }
373 }
374 }
375 }
376 }
377 }
378 } else {
379 // not supported - too many levels deep ... maybe some day
380 console.log('The relative path is from a node too many levels deep from root. This is not supported at the time');
381 }
382 } else {
383 // absolute path
384 if (pathArray[0] == "") {
385 pathArray.splice(0, 1);
386 }
387
388 let catalogKey = pathArray[0];
389 let topLevelObject = {};
390
391 topLevelObject[catalogKey] = catalogs[catalogKey];
392
393 results = this.getAbsoluteResults(topLevelObject, pathArray);
394
395 return results;
396 }
397 }
398 }