RIFT-14803: UI Composer: References should be dropdowns https://osm.etsi.org/gerrit...
[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 changeCase from 'change-case';
22
23 export default {
24 addAuthorizationStub(xhr) {
25 xhr.setRequestHeader('Authorization', 'Basic YWRtaW46YWRtaW4=');
26 },
27 getSearchParams(url) {
28 var a = document.createElement('a');
29 a.href = url;
30 var params = {};
31 var items = a.search.replace('?', '').split('&');
32 for (var i = 0; i < items.length; i++) {
33 if (items[i].length > 0) {
34 var key_value = items[i].split('=');
35 params[key_value[0]] = decodeURIComponent(key_value[1]);
36 }
37 }
38 return params;
39 },
40 parseJSONIgnoreErrors(txt) {
41 try {
42 return JSON.parse(txt);
43 } catch (ignore) {
44 return {};
45 }
46 },
47 resolvePath(obj, path) {
48 // supports a.b, a[1] and foo[bar], etc.
49 // where obj is ['nope', 'yes', {a: {b: 1}, foo: 2}]
50 // then [1] returns 'yes'; [2].a.b returns 1; [2].a[foo] returns 2;
51 path = path.split(/[\.\[\]]/).filter(d => d);
52 return path.reduce((r, p) => {
53 if (r) {
54 return r[p];
55 }
56 }, obj);
57 },
58 assignPathValue(obj, path, value) {
59 path = path.split(/[\.\[\]]/).filter(d => d);
60 // enable look-ahead to determine if type is array or object
61 const pathCopy = path.slice();
62 // last item in path used to assign value on the resolved object
63 const name = path.pop();
64 const resolvedObj = path.reduce((r, p, i) => {
65 if (typeof(r[p]) !== 'object') {
66 // look-ahead to see if next path item is a number
67 const isArray = !isNaN(parseInt(pathCopy[i + 1], 10));
68 r[p] = isArray ? [] : {}
69 }
70 return r[p];
71 }, obj);
72 resolvedObj[name] = value;
73 },
74 updatePathValue(obj, path, value, isCase) {
75 // todo: replace implementation of assignPathValue with this impl and
76 // remove updatePathValue (only need one function, not both)
77 // same as assignPathValue except removes property if value is undefined
78 path = path.split(/[\.\[\]]/).filter(d => d);
79 const isRemove = typeof value === 'undefined';
80 // enable look-ahead to determine if type is array or object
81 const pathCopy = path.slice();
82 // last item in path used to assign value on the resolved object
83 const name = path.pop();
84 const resolvedObj = path.reduce((r, p, i) => {
85 // look-ahead to see if next path item is a number
86 const index = parseInt(pathCopy[i + 1], 10);
87 const isArray = !isNaN(index);
88 if (typeof(r[p]) !== 'object') {
89 r[p] = isArray ? [] : {}
90 }
91 if (isRemove && ((i + 1) === path.length)) {
92 if (isArray) {
93 r[p] = r[p].filter((d, i) => i !== index);
94 } else {
95 if(isCase) {
96 delete r[name];
97 } else {
98 delete r[p][name];
99 }
100 }
101 }
102 if(isCase) {
103 return r;
104 } else {
105 return r[p];
106 }
107
108 }, obj);
109 if (!isRemove) {
110 resolvedObj[name] = value;
111 }
112 },
113 removePathValue(obj, path, isCase) {
114 // note updatePathValue removes value if third argument is undefined
115 return this.updatePathValue(obj, path, undefined, isCase);
116 },
117
118 suffixAsInteger: (field) => {
119 return (obj) =>{
120 const str = String(obj[field]);
121 const value = str.replace(str.replace(/[\d]+$/, ''), '');
122 return 1 + parseInt(value, 10) || 0;
123 };
124 },
125
126 toBiggestValue: (maxIndex, curIndex) => Math.max(maxIndex, curIndex),
127
128 isRelativePath (path) {
129 if (path.split('/')[0] == '..') {
130 return true;
131 }
132 return false;
133 },
134
135 getResults (topLevelObject, pathArray) {
136 let objectCopy = _.cloneDeep(topLevelObject);
137 let i = pathArray.length;
138 let results = [];
139
140 while(pathArray[pathArray.length - i]) {
141 if (_.isArray(objectCopy[pathArray[pathArray.length - i]])) {
142 if (i == 2) {
143 results = _.map(objectCopy[pathArray[pathArray.length - i]], pathArray[pathArray.length - 1]);
144 } else {
145 objectCopy = objectCopy[pathArray[pathArray.length - i]];
146 }
147 } else if (_.isArray(objectCopy)) {
148 objectCopy.map((object) => {
149 if (_.isArray(object[pathArray[pathArray.length - i]])) {
150 if (i == 2) {
151 results = results.concat(_.map(object[pathArray[pathArray.length - i]], pathArray[pathArray.length - 1]));
152 }
153 }
154 })
155 }
156 i--;
157 }
158
159 return results;
160 },
161
162 getAbsoluteResults (topLevelObject, pathArray) {
163 let i = pathArray.length;
164 let objectCopy = _.cloneDeep(topLevelObject);
165 let results = [];
166
167 let fragment = pathArray[pathArray.length - i]
168
169 while (fragment) {
170 if (i == 1) {
171 // last fragment
172 if (_.isArray(objectCopy)) {
173 // results will be obtained from a map
174 results = _.map(objectCopy, fragment);
175 } else {
176 // object
177 if (fragment.match(/\[.*\]/g)) {
178 // contains a predicate
179 // shouldn't reach here
180 console.log('Something went wrong while resolving a leafref. Reached a leaf with predicate.');
181 } else {
182 // contains no predicate
183 results.push(objectCopy[fragment]);
184 }
185 }
186 } else {
187 if (_.isArray(objectCopy)) {
188 // is array
189 objectCopy = _.map(objectCopy, fragment);
190
191 // If any of the deeper object is an array, flatten the entire list.
192 // This would usually be a bad leafref going out of its scope.
193 // Log it too
194 for (let i = 0; i < objectCopy.length; i++) {
195 if (_.isArray(objectCopy[i])) {
196 objectCopy = _.flatten(objectCopy);
197 console.log('This might be a bad leafref. Verify with backend team.')
198 break;
199 }
200 }
201 } else {
202 // is object
203 if (fragment.match(/\[.*\]/g)) {
204 // contains a predicate
205 let predicateStr = fragment.match(/\[.*\]/g)[0];
206 // Clip leading [ and trailing ]
207 predicateStr = predicateStr.substr(1, predicateStr.length - 2);
208 const predicateKeyValue = predicateStr.split('=');
209 const predicateKey = predicateKeyValue[0];
210 const predicateValue = predicateKeyValue[1];
211 // get key for object to search into
212 let key = fragment.split('[')[0];
213 let searchObject = {};
214 searchObject[predicateKey] = predicateValue;
215 objectCopy = _.find(objectCopy[key], searchObject);
216 if (!objectCopy) {
217 return [];
218 }
219 } else {
220 // contains no predicate
221 objectCopy = objectCopy[fragment];
222 }
223 }
224 }
225 i--;
226 fragment = pathArray[pathArray.length - i];
227 }
228
229 return results;
230 },
231
232 resolveCurrentPredicate (leafRefPath, container, pathCopy) {
233 if (leafRefPath.indexOf('current()') != -1) {
234 // contains current
235
236 // Get the relative path from current
237 let relativePath = leafRefPath.match("current\\(\\)\/(.*)\]");
238 let relativePathArray = relativePath[1].split('/');
239
240 while (relativePathArray[0] == '..') {
241 pathCopy.pop();
242 relativePathArray.shift();
243 }
244
245 // Supports only one relative path up
246 // To support deeper paths, will need to massage the string more
247 // i.e. change '/'' to '.'
248 const searchPath = pathCopy.join('.').concat('.', relativePathArray[0]);
249 const searchValue = this.resolvePath(container.model, searchPath);
250
251 const predicateStr = leafRefPath.match("(current.*)\]")[1];
252 leafRefPath = leafRefPath.replace(predicateStr, searchValue);
253 }
254 return leafRefPath;
255 },
256
257 resolveLeafRefPath (catalogs, leafRefPath, fieldKey, path, container) {
258 let pathCopy = _.clone(path);
259 // Strip any prefixes
260 let leafRefPathCopy = leafRefPath.replace(/[\w\d]*:/g, '');
261 // Strip any spaces
262 leafRefPathCopy = leafRefPathCopy.replace(/\s/g, '');
263
264 // resolve any current paths
265 leafRefPathCopy = this.resolveCurrentPredicate(leafRefPathCopy, container, pathCopy);
266
267 // Split on delimiter (/)
268 const pathArray = leafRefPathCopy.split('/');
269 let fieldKeyArray = fieldKey.split(':');
270 let results = [];
271
272 // Check if relative path or not
273 // TODO: Below works but
274 // better to convert the pathCopy to absolute/rooted path
275 // and use the absolute module instead
276 if (this.isRelativePath(leafRefPathCopy)) {
277 let i = pathArray.length;
278 while (pathArray[pathArray.length - i] == '..') {
279 fieldKeyArray.splice(-1, 1);
280 if (!isNaN(Number(fieldKeyArray[fieldKeyArray.length - 1]))) {
281 // found a number, so an index. strip it
282 fieldKeyArray.splice(-1, 1);
283 }
284 i--;
285 }
286
287 // traversed all .. - now traverse down
288 if (fieldKeyArray.length == 1) {
289 for (let key in catalogs) {
290 for (let subKey in catalogs[key]) {
291 let found = _.find(catalogs[key][subKey], {id: fieldKeyArray[0]});
292 if (found) {
293 results = this.getResults(found, pathArray.splice(-i, i));
294 return results;
295 }
296 }
297 }
298 } else if (fieldKeyArray.length == 2) {
299 for (let key in catalogs) {
300 for (let subKey in catalogs[key]) {
301 let found = _.find(catalogs[key][subKey], {id: fieldKeyArray[0]});
302 if (found) {
303 for (let foundKey in found) {
304 let topLevel = _.find(found[foundKey], {id: fieldKeyArray[1]});
305 if (topLevel) {
306 results = this.getResults(topLevel, pathArray.splice(-i, i));
307 return results;
308 }
309 }
310 }
311 }
312 }
313 } else {
314 // not supported - too many levels deep ... maybe some day
315 console.log('The relative path is from a node too many levels deep from root. This is not supported at the time');
316 }
317 } else {
318 // absolute path
319 if (pathArray[0] == "") {
320 pathArray.splice(0, 1);
321 }
322
323 let catalogKey = pathArray[0];
324 let topLevelObject = {};
325
326 topLevelObject[catalogKey] = catalogs[catalogKey];
327
328 results = this.getAbsoluteResults(topLevelObject, pathArray);
329
330 return results;
331 }
332 }
333 }