3 * Copyright 2016 RIFT.IO Inc
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
21 import changeCase
from 'change-case';
24 addAuthorizationStub(xhr
) {
25 xhr
.setRequestHeader('Authorization', 'Basic YWRtaW46YWRtaW4=');
27 getSearchParams(url
) {
28 var a
= document
.createElement('a');
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]);
40 parseJSONIgnoreErrors(txt
) {
42 return JSON
.parse(txt
);
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
) => {
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
? [] : {}
72 resolvedObj
[name
] = value
;
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
? [] : {}
91 if (isRemove
&& ((i
+ 1) === path
.length
)) {
93 r
[p
] = r
[p
].filter((d
, i
) => i
!== index
);
110 resolvedObj
[name
] = value
;
113 removePathValue(obj
, path
, isCase
) {
114 // note updatePathValue removes value if third argument is undefined
115 return this.updatePathValue(obj
, path
, undefined, isCase
);
118 suffixAsInteger
: (field
) => {
120 const str
= String(obj
[field
]);
121 const value
= str
.replace(str
.replace(/[\d]+$/, ''), '');
122 return 1 + parseInt(value
, 10) || 0;
126 toBiggestValue
: (maxIndex
, curIndex
) => Math
.max(maxIndex
, curIndex
),
128 isRelativePath (path
) {
129 if (path
.split('/')[0] == '..') {
135 getResults (topLevelObject
, pathArray
) {
136 let objectCopy
= _
.cloneDeep(topLevelObject
);
137 let i
= pathArray
.length
;
140 while(pathArray
[pathArray
.length
- i
]) {
141 if (_
.isArray(objectCopy
[pathArray
[pathArray
.length
- i
]])) {
143 results
= _
.map(objectCopy
[pathArray
[pathArray
.length
- i
]], pathArray
[pathArray
.length
- 1]);
145 objectCopy
= objectCopy
[pathArray
[pathArray
.length
- i
]];
147 } else if (_
.isArray(objectCopy
)) {
148 objectCopy
.map((object
) => {
149 if (_
.isArray(object
[pathArray
[pathArray
.length
- i
]])) {
151 results
= results
.concat(_
.map(object
[pathArray
[pathArray
.length
- i
]], pathArray
[pathArray
.length
- 1]));
162 getAbsoluteResults (topLevelObject
, pathArray
) {
163 let i
= pathArray
.length
;
164 let objectCopy
= _
.cloneDeep(topLevelObject
);
167 let fragment
= pathArray
[pathArray
.length
- i
]
172 if (_
.isArray(objectCopy
)) {
173 // results will be obtained from a map
174 results
= _
.map(objectCopy
, fragment
);
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.');
182 // contains no predicate
183 results
.push(objectCopy
[fragment
]);
187 if (_
.isArray(objectCopy
)) {
189 objectCopy
= _
.map(objectCopy
, fragment
);
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.
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.')
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 let found
= _
.find(objectCopy
[key
], searchObject
);
219 // check for numerical value
220 if (predicateValue
!= "" &&
221 predicateValue
!= null &&
222 predicateValue
!= NaN
&&
223 predicateValue
!= Infinity
&&
224 predicateValue
!= -Infinity
) {
225 let numericalPredicateValue
= _
.toNumber(predicateValue
);
226 if (_
.isNumber(numericalPredicateValue
)) {
227 searchObject
[predicateKey
] = numericalPredicateValue
;
228 found
= _
.find(objectCopy
[key
], searchObject
);
238 // contains no predicate
239 objectCopy
= objectCopy
[fragment
];
248 fragment
= pathArray
[pathArray
.length
- i
];
254 resolveCurrentPredicate (leafRefPath
, container
, pathCopy
) {
255 if (leafRefPath
.indexOf('current()') != -1) {
258 // Get the relative path from current
259 let relativePath
= leafRefPath
.match("current\\(\\)\/(.*)\]");
260 let relativePathArray
= relativePath
[1].split('/');
262 while (relativePathArray
[0] == '..') {
264 relativePathArray
.shift();
267 // Supports only one relative path up
268 // To support deeper paths, will need to massage the string more
269 // i.e. change '/'' to '.'
270 const searchPath
= pathCopy
.join('.').concat('.', relativePathArray
[0]);
271 const searchValue
= this.resolvePath(container
.model
, searchPath
);
273 const predicateStr
= leafRefPath
.match("(current.*)\]")[1];
274 leafRefPath
= leafRefPath
.replace(predicateStr
, searchValue
);
279 resolveLeafRefPath (catalogs
, leafRefPath
, fieldKey
, path
, container
) {
280 let pathCopy
= _
.clone(path
);
281 // Strip any prefixes
282 let leafRefPathCopy
= leafRefPath
.replace(/[\w\d]*:/g, '');
284 leafRefPathCopy
= leafRefPathCopy
.replace(/\s/g, '');
286 // resolve any current paths
287 leafRefPathCopy
= this.resolveCurrentPredicate(leafRefPathCopy
, container
, pathCopy
);
289 // Split on delimiter (/)
290 const pathArray
= leafRefPathCopy
.split('/');
291 let fieldKeyArray
= fieldKey
.split(':');
294 // Check if relative path or not
295 // TODO: Below works but
296 // better to convert the pathCopy to absolute/rooted path
297 // and use the absolute module instead
298 if (this.isRelativePath(leafRefPathCopy
)) {
299 let i
= pathArray
.length
;
300 while (pathArray
[pathArray
.length
- i
] == '..') {
301 fieldKeyArray
.splice(-1, 1);
302 if (!isNaN(Number(fieldKeyArray
[fieldKeyArray
.length
- 1]))) {
303 // found a number, so an index. strip it
304 fieldKeyArray
.splice(-1, 1);
309 // traversed all .. - now traverse down
310 if (fieldKeyArray
.length
== 1) {
311 for (let key
in catalogs
) {
312 for (let subKey
in catalogs
[key
]) {
313 let found
= _
.find(catalogs
[key
][subKey
], {id
: fieldKeyArray
[0]});
315 results
= this.getAbsoluteResults(found
, pathArray
.splice(-i
, i
));
320 } else if (fieldKeyArray
.length
== 2) {
321 for (let key
in catalogs
) {
322 for (let subKey
in catalogs
[key
]) {
323 let found
= _
.find(catalogs
[key
][subKey
], {id
: fieldKeyArray
[0]});
325 for (let foundKey
in found
) {
326 // let topLevel = _.find(found[foundKey], {id: fieldKeyArray[1]});
327 if (foundKey
== fieldKeyArray
[1]) {
328 results
= this.getAbsoluteResults(found
[foundKey
], pathArray
.splice(-i
, i
));
335 } else if (fieldKeyArray
.length
== 3) {
336 for (let key
in catalogs
) {
337 for (let subKey
in catalogs
[key
]) {
338 let found
= _
.find(catalogs
[key
][subKey
], {id
: fieldKeyArray
[0]});
340 for (let foundKey
in found
) {
341 let topLevel
= _
.find(found
[foundKey
], {id
: fieldKeyArray
[1]});
343 results
= this.getAbsoluteResults(topLevel
, pathArray
.splice(-i
, i
));
351 // not supported - too many levels deep ... maybe some day
352 console
.log('The relative path is from a node too many levels deep from root. This is not supported at the time');
356 if (pathArray
[0] == "") {
357 pathArray
.splice(0, 1);
360 let catalogKey
= pathArray
[0];
361 let topLevelObject
= {};
363 topLevelObject
[catalogKey
] = catalogs
[catalogKey
];
365 results
= this.getAbsoluteResults(topLevelObject
, pathArray
);