Skip to content

Commit 64e2249

Browse files
authored
Merge pull request #59 from panosc-eu/API-filters-changes
Multiple parameters condition
2 parents 7c668ab + 96c247b commit 64e2249

File tree

3 files changed

+209
-1
lines changed

3 files changed

+209
-1
lines changed

common/mixins/parameters.js

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
module.exports = (Model, options) => {
2+
3+
/**
4+
* trying to address the following include filter
5+
* {
6+
* "include" : [
7+
* {
8+
* "relation":"parameters",
9+
* "scope": {
10+
* "where":{
11+
* "and": [
12+
* {"name":"chemical_formula"},
13+
* {"value":"Cu"}
14+
* ]
15+
* }
16+
* }
17+
* },
18+
* {
19+
* "relation":"parameters",
20+
* "scope": {
21+
* "where":{
22+
* "and": [
23+
* {"name":"sample_state"},
24+
* {"value":"solid"}
25+
* ]
26+
* }
27+
* }
28+
* }
29+
* ]
30+
* }
31+
*
32+
* one liner
33+
* {"include":[{"relation":"parameters","scope":{"where":{"and": [{"name":"chemical_formula"},{"value":"Cu"}]}}},{"relation":"parameters","scope":{"where":{"and": [{"name":"sample_state"},{"value":"solid"}]}}}]}
34+
*/
35+
36+
// we intercept the find command to check the filter on parameters
37+
Model.beforeRemote('find', (ctx, unused, next) => {
38+
39+
const filter = ctx.args.filter ? ctx.args.filter : {};
40+
const [parametersRelations, newFilter] = getParametersRelations(filter);
41+
42+
// we change filter and save the parameters relations
43+
// only if we have found parameters relations
44+
if (parametersRelations) {
45+
dSet(ctx, "data.relations.parameters", parametersRelations);
46+
ctx.args.filter = newFilter;
47+
}
48+
49+
next();
50+
});
51+
52+
// we intercept the find command to check the filter on parameters
53+
Model.afterRemote('find', (ctx, result, next) => {
54+
55+
const parametersFilters = ctx.data.relations.parameters ? ctx.data.relations.parameters : [];
56+
57+
if (parametersFilters) {
58+
ctx.result = filterOnParameters(ctx.result, parametersFilters);
59+
}
60+
61+
next();
62+
});
63+
64+
};
65+
66+
/**
67+
* extract parameters relations and modify filter to extract all parameters
68+
* @param {object} filter LoopBack filter object
69+
* @returns {array} Parameters relations array
70+
* @returns {object} Modified LoopBack object filter
71+
*/
72+
const getParametersRelations = (filter) => {
73+
74+
// remove filter on parameter
75+
const newFilter = JSON.parse(JSON.stringify(filter));
76+
newFilter.include = filter.include
77+
? filter.include
78+
.filter(
79+
(primary) => (primary.relation != "parameters")
80+
)
81+
: [];
82+
83+
// extract filter on parameter
84+
const parametersRelations = filter.include
85+
? filter.include
86+
.filter(
87+
(primary) => (primary.relation == "parameters")
88+
)
89+
: [];
90+
// if we have a filter on parameter
91+
// adds the relation with parameter, so we can retrieve all of them
92+
if (parametersRelations.length > 0) {
93+
newFilter.include.push({ "relation": "parameters" });
94+
95+
}
96+
return [
97+
parametersRelations.map((item) => {
98+
/*
99+
let simpleItem = {};
100+
item.scope.where.and.forEach((condition) => {
101+
simpleItem = {
102+
...simpleItem,
103+
...condition
104+
}
105+
})
106+
return simpleItem;
107+
*/
108+
return item.scope.where;
109+
}),
110+
newFilter];
111+
}
112+
113+
/**
114+
* extract results that matches all the parameters filters
115+
* @param {object} filter LoopBack filter object
116+
* @returns {array} Parameters relations array
117+
* @returns {object} Modified LoopBack object filter
118+
*/
119+
const filterOnParameters = (result, parametersFilter) => {
120+
121+
const filteredResults = result.filter((item) => {
122+
// we check if each item match all the conditions
123+
return parametersFilter.every(
124+
f => item['__data']['parameters'].some(
125+
p => cop(f, p['__data'])))
126+
// p => Object.keys(f).every(
127+
// k => condition(p[k], f[k]))))
128+
})
129+
130+
return filteredResults;
131+
}
132+
133+
/**
134+
* condition operator
135+
* @param {*} c - condition
136+
* @param {*} v - values on which apply the condition
137+
* @returns {boolean} true if the condition is met
138+
*/
139+
const cop = (c, v) => {
140+
if (Object.prototype.toString.call(c) === '[object Object]') {
141+
const k = Object.keys(c)[0];
142+
switch (k) {
143+
case 'or':
144+
return c.or.some(sc => cop(sc, v));
145+
break;
146+
case 'and':
147+
return c.and.every(sc => cop(sc, v));
148+
break;
149+
}
150+
// the k is a property of the object
151+
if (Object.prototype.toString.call(c[k]) === '[object Object]') {
152+
const ck = Object.keys(c[k])[0];
153+
switch (ck) {
154+
case 'between':
155+
return (v[k] >= c[k][ck][0]) && (v[k] <= c[k][ck][1]);
156+
break;
157+
case 'lt':
158+
return v[k] < c[k][ck];
159+
break;
160+
case 'gt':
161+
return v[k] > c[k][ck];
162+
break;
163+
}
164+
}
165+
// we need to compare a key of value with a key of condition
166+
return v[k] == c[k]
167+
}
168+
// normal element comparison
169+
return v == c;
170+
}
171+
172+
173+
/**
174+
* Dynamically sets a deeply nested value in an object.
175+
* @function
176+
* @param {!object} obj - The object which contains the value you want to change/set.
177+
* @param {!array|string} inPath - Array or string representation of path to the value you want to change/set.
178+
* @param {!mixed} value - The value you want to set it to.
179+
* @param {boolean} setrecursively - If true, will set value of non-existing path as well.
180+
*/
181+
function dSet(obj, inPath, value, setrecursively = true) {
182+
const path =
183+
Array.isArray(inPath)
184+
? inPath
185+
: (
186+
(typeof inPath === 'string' || inPath instanceof String)
187+
? inPath.split(".")
188+
: []);
189+
return path.reduce(
190+
(a, b, level) => {
191+
if (setrecursively && typeof a[b] === "undefined" && level !== (path.length - 1)) {
192+
a[b] = {};
193+
}
194+
195+
if (level === (path.length - 1)) {
196+
a[b] = value;
197+
return value;
198+
}
199+
return a[b];
200+
},
201+
obj
202+
);
203+
}

common/models/dataset.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"validateUpsert": true
99
},
1010
"mixins": {
11+
"Parameters": {},
1112
"Sanitize": {},
1213
"Score": {}
1314
},

test/dataset.test.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ describe('Dataset', () => {
7777
});
7878
});
7979

80+
// {"include":[{"relation":"parameters","scope":{"where":{"and":[{"name":"photon_energy"},{"value":{"between":[880,990]}},{"unit":"eV"}]}}}]}
8081
context(
8182
'where parameters has a photon energy in the range 880-990 eV',
8283
() => {
@@ -135,8 +136,9 @@ describe('Dataset', () => {
135136
},
136137
);
137138

139+
// {"include":[{"relation":"parameters","scope":{"where":{"or":[{"and":[{"name":"sample_state"},{"value":"solid"}]},{"and":[{"name":"chemical_formula"},{"value":"Cu"}]}]}}}]}
138140
context(
139-
'where parameters includes a solid sample containing copper',
141+
'where parameters includes a solid sample or containing copper',
140142
() => {
141143
it('should return en array of datasets matching the parameter', (done) => {
142144
const filter = JSON.stringify({
@@ -201,6 +203,7 @@ describe('Dataset', () => {
201203
},
202204
);
203205

206+
// {"include":[{"relation":"parameters","scope":{"where":{"and":[{"name":"temperature"},{"value":{"lt":80}},{"unit":"celsius"}]}}}]}
204207
context('where parameters has a temperature below 80 °C', () => {
205208
it('should return en array of datasets matching the parameter', (done) => {
206209
const filter = JSON.stringify({
@@ -256,6 +259,7 @@ describe('Dataset', () => {
256259
});
257260
});
258261

262+
// {"include":[{"relation":"files","scope":{"where":{"text":"file1"}}}]}
259263
context('where file matches text `file1`', () => {
260264
it('should return en array of datasets matching the query', (done) => {
261265
const filter = JSON.stringify({

0 commit comments

Comments
 (0)