Skip to content

Commit 7d47e2f

Browse files
committed
🔧 Support setId translations in sqons for the Venn route
1 parent ed12664 commit 7d47e2f

File tree

4 files changed

+105
-42
lines changed

4 files changed

+105
-42
lines changed

src/app.ts

+17-28
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,13 @@ import { CreateSetBody, Set, SetSqon, UpdateSetContentBody, UpdateSetTagBody } f
2020
import { getStatistics, getStudiesStatistics } from './endpoints/statistics';
2121
import transcriptomicsRouter from './endpoints/transcriptomics/route';
2222
import { computeUpset } from './endpoints/upset';
23-
import { venn } from './endpoints/venn/venn';
23+
import { reformatVenn, venn } from './endpoints/venn/venn';
2424
import { esHost, keycloakURL, userApiURL } from './env';
2525
import { globalErrorHandler, globalErrorLogger } from './errors';
26-
import {
27-
flushAllCache,
28-
STATISTICS_CACHE_ID,
29-
STATISTICS_PUBLIC_CACHE_ID,
30-
twineWithCache,
31-
} from './middleware/cache';
26+
import { flushAllCache, STATISTICS_CACHE_ID, STATISTICS_PUBLIC_CACHE_ID, twineWithCache } from './middleware/cache';
3227
import { injectBodyHttpHeaders } from './middleware/injectBodyHttpHeaders';
3328
import { resolveSetIdMiddleware } from './middleware/resolveSetIdInSqon';
34-
import { sqonContainsSet } from './sqon/manipulateSqon';
35-
import { resolveSetsInSqon } from './sqon/resolveSetInSqon';
36-
import { Sqon } from './sqon/types';
29+
import { replaceIdsWithSetId, resolveSetsInAllSqonsWithMapper } from './sqon/resolveSetInSqon';
3730

3831
export default (keycloak: Keycloak, getProject: (projectId: string) => ArrangerProject): Express => {
3932
const app = express();
@@ -196,7 +189,7 @@ export default (keycloak: Keycloak, getProject: (projectId: string) => ArrangerP
196189
});
197190

198191
app.post('/authorized-studies', keycloak.protect(), async (req, res, next) => {
199-
computeAuthorizedStudiesForAllFences(req, res, next);
192+
await computeAuthorizedStudiesForAllFences(req, res, next);
200193
});
201194

202195
app.post('/upset', keycloak.protect(), async (req, res, next) => {
@@ -210,25 +203,21 @@ export default (keycloak: Keycloak, getProject: (projectId: string) => ArrangerP
210203

211204
app.post('/venn', keycloak.protect(), async (req, res, next) => {
212205
try {
213-
if ([2, 3].includes(req.body?.sqons?.length)) {
214-
// Convert sqon(s) with set_id if exists to intelligible sqon for ES query translation.
215-
const sqons: Sqon[] = [];
216-
for (const s of req.body.sqons) {
217-
if (sqonContainsSet(s)) {
218-
const accessToken = req.headers.authorization;
219-
const r = await resolveSetsInSqon(s, null, accessToken);
220-
sqons.push(r);
221-
} else {
222-
sqons.push(s);
223-
}
224-
}
225-
const data = await venn(sqons);
226-
res.send({
227-
data,
228-
});
229-
} else {
206+
if (![2, 3].includes(req.body?.sqons?.length)) {
230207
res.status(StatusCodes.UNPROCESSABLE_ENTITY).send('Bad Inputs');
208+
return;
231209
}
210+
// Convert sqon(s) with set_id if exists to intelligible sqon for ES query translation.
211+
const { resolvedSqons: sqons, m: mSetItToIds } = await resolveSetsInAllSqonsWithMapper(
212+
req.body.sqons,
213+
null,
214+
req.headers.authorization,
215+
);
216+
const data1 = await venn(sqons);
217+
const data2 = data1.map(x => ({ ...x, sqon: replaceIdsWithSetId(x.sqon, mSetItToIds) }));
218+
res.send({
219+
data: reformatVenn(data2),
220+
});
232221
} catch (e) {
233222
next(e);
234223
}

src/endpoints/venn/venn.ts

+14-9
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@ import { getNestedFieldsForIndex } from '../../sqon/getNestedFieldsForIndex';
55
import { and, not } from '../../sqon/manipulateSqon';
66
import { Sqon } from '../../sqon/types';
77

8-
type OutputElement = {
8+
type Output = {
99
operation: string;
1010
count: number;
11+
sqon: Sqon;
12+
};
13+
14+
type OutputReformattedElement = Output & {
1115
setId?: string;
1216
};
1317

14-
type Output = {
15-
summary: OutputElement[];
16-
operations: OutputElement[];
18+
type OutputReformatted = {
19+
summary: OutputReformattedElement[];
20+
operations: OutputReformattedElement[];
1721
};
1822

1923
const setFormulasDuo = (s1: Sqon, s2: Sqon) => [
@@ -84,7 +88,7 @@ const setFormulasTrio = (s1: Sqon, s2: Sqon, s3: Sqon) => [
8488

8589
let nestedFields: string[] = null;
8690

87-
export const venn = async (sqons: Sqon[]): Promise<Output> => {
91+
export const venn = async (sqons: Sqon[]): Promise<Output[]> => {
8892
const setFormulas =
8993
sqons.length === 2 ? setFormulasDuo(sqons[0], sqons[1]) : setFormulasTrio(sqons[0], sqons[1], sqons[2]);
9094

@@ -114,14 +118,15 @@ export const venn = async (sqons: Sqon[]): Promise<Output> => {
114118

115119
const responses = r.body?.responses || [];
116120

117-
const data = setFormulas.map((x, i) => ({
121+
return setFormulas.map((x, i) => ({
118122
...x,
119123
count: responses[i].hits.total.value,
120124
}));
125+
};
121126

122-
// Reformatting for UI
127+
export const reformatVenn = (data: Output[]): OutputReformatted => {
123128
const tables = data.reduce(
124-
(xs: Output, x: OutputElement) => {
129+
(xs: OutputReformatted, x: OutputReformattedElement) => {
125130
if (['Q₁', 'Q₂', 'Q₃'].some(y => y === x.operation)) {
126131
return { ...xs, summary: [...xs.summary, x] };
127132
}
@@ -132,6 +137,6 @@ export const venn = async (sqons: Sqon[]): Promise<Output> => {
132137

133138
return {
134139
summary: tables.summary,
135-
operations: tables.operations.map((x: OutputElement, i: number) => ({ ...x, setId: `set-${i}` })),
140+
operations: tables.operations.map((x: Output, i: number) => ({ ...x, setId: `set-${i}` })),
136141
};
137142
};

src/sqon/manipulateSqon.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export const removeSqonToSetSqon = (setSqon: SetSqon, sqonToRemove: SetSqon): Se
1818
} as SetSqon;
1919
};
2020

21-
export const sqonContainsSet = (s: string) => JSON.stringify(s).includes('"set_id:');
21+
export const sqonContainsSet = (s: Sqon) => JSON.stringify(s).includes('"set_id:');
2222

2323
// Taken as-is from "@overture-stack/sqon-builder": "^1.1.0" but zod Types were removed
2424
const combine = (op, sqon, content, pivot) => {

src/sqon/resolveSetInSqon.ts

+73-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { Dictionary, flattenDeep, get, isArray, zipObject } from 'lodash';
22

33
import { SetSqon } from '../endpoints/sets/setsTypes';
44
import { getSharedSet, getUserSets, UserSet } from '../userApi/userApiClient';
5+
import { sqonContainsSet } from './manipulateSqon';
6+
import { Sqon } from './types';
57

68
const getSetIdsFromSqon = (sqon: SetSqon, collection = []) =>
79
(isArray(sqon.content)
@@ -28,7 +30,14 @@ const injectIdsIntoSqon = (sqon: SetSqon, setIdsToValueMap: Dictionary<string[]>
2830
})),
2931
});
3032

31-
export const resolveSetsInSqon = async (sqon: SetSqon, userId: string, accessToken: string): Promise<SetSqon> => {
33+
export const resolveSetsInSqonWithMapper = async (
34+
sqon: SetSqon,
35+
userId: string,
36+
accessToken: string,
37+
): Promise<{
38+
resolvedSqon: SetSqon;
39+
m?: Dictionary<string[]>;
40+
}> => {
3241
const setIds: string[] = getSetIdsFromSqon(sqon || ({} as SetSqon));
3342
if (setIds.length) {
3443
const userSets: UserSet[] = await retrieveSetsFromUsers(accessToken, setIds);
@@ -38,12 +47,47 @@ export const resolveSetsInSqon = async (sqon: SetSqon, userId: string, accessTok
3847
ids,
3948
);
4049

41-
return injectIdsIntoSqon(sqon, setIdsToValueMap);
42-
} else {
43-
return sqon;
50+
return {
51+
resolvedSqon: injectIdsIntoSqon(sqon, setIdsToValueMap),
52+
m: setIdsToValueMap,
53+
};
4454
}
55+
return {
56+
resolvedSqon: sqon,
57+
m: null,
58+
};
4559
};
4660

61+
export const resolveSetsInAllSqonsWithMapper = async (
62+
sqons: Sqon[],
63+
userId: string,
64+
accessToken: string,
65+
): Promise<{
66+
resolvedSqons: Sqon[];
67+
m?: Map<string, string[]>;
68+
}> => {
69+
const resolvedSqons = [];
70+
let mSetItToIds = new Map();
71+
for (const s of sqons) {
72+
if (sqonContainsSet(s)) {
73+
const r = await resolveSetsInSqonWithMapper(s, null, accessToken);
74+
resolvedSqons.push(r.resolvedSqon);
75+
if (r.m) {
76+
mSetItToIds = new Map([...mSetItToIds, ...new Map(Object.entries(r.m))]);
77+
}
78+
} else {
79+
resolvedSqons.push(s);
80+
}
81+
}
82+
return {
83+
resolvedSqons: resolvedSqons,
84+
m: mSetItToIds,
85+
};
86+
};
87+
88+
export const resolveSetsInSqon = async (sqon: SetSqon, userId: string, accessToken: string): Promise<SetSqon> =>
89+
(await resolveSetsInSqonWithMapper(sqon, userId, accessToken)).resolvedSqon;
90+
4791
export const retrieveSetsFromUsers = async (accessToken: string, setIds: string[]): Promise<UserSet[]> => {
4892
// Get all user sets
4993
const userSets = await getUserSets(accessToken);
@@ -60,3 +104,28 @@ export const retrieveSetsFromUsers = async (accessToken: string, setIds: string[
60104

61105
return userSets;
62106
};
107+
108+
const hasSameElements = (a, b) => a.length === b.length && [...new Set(a)].every(ax => b.includes(ax));
109+
110+
export const replaceIdsWithSetId = (sqon: Sqon, setIdsToValueMap: Map<string, string[]>): Sqon => ({
111+
...sqon,
112+
content: sqon.content.map(x => {
113+
if (Array.isArray(x.content)) {
114+
return {
115+
...x,
116+
content: replaceIdsWithSetId(x, setIdsToValueMap).content,
117+
};
118+
}
119+
120+
const setId = Array.isArray(x.content.value)
121+
? [...setIdsToValueMap].find(([, v]) => hasSameElements(v, x.content.value))?.[0]
122+
: null;
123+
return {
124+
...x,
125+
content: {
126+
...x.content,
127+
value: setId ? [setId] : x.content.value,
128+
},
129+
};
130+
}),
131+
});

0 commit comments

Comments
 (0)