Skip to content

Commit bcaf067

Browse files
authored
fix: routing-forms reporting - "in" operator logic is broken + overall refactor (#17398)
* fix: routing-forms reporting - "in" operator logic is broken * fix * refactor to make it more readable * add tests
1 parent 120cfed commit bcaf067

File tree

2 files changed

+188
-86
lines changed

2 files changed

+188
-86
lines changed

packages/app-store/routing-forms/__tests__/jsonLogicToPrisma.test.ts

+113-59
Original file line numberDiff line numberDiff line change
@@ -227,83 +227,137 @@ describe("jsonLogicToPrisma(Reporting)", () => {
227227
],
228228
});
229229
});
230-
});
231230

232-
it("should support where All Match ['Equals', 'Equals'] operator", () => {
233-
const prismaWhere = jsonLogicToPrisma({
234-
logic: {
235-
and: [
236-
{ "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "a"] },
237-
{ "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "b"] },
231+
it("should support 'Any In' operator", () => {
232+
const prismaWhere = jsonLogicToPrisma({
233+
logic: { and: [{ in: [{ var: "267c7817-81a5-4bef-9d5b-d0faa4cd0d71" }, ["C", "D"]] }] },
234+
});
235+
expect(prismaWhere).toEqual({
236+
AND: [
237+
{
238+
OR: [
239+
{
240+
response: {
241+
path: ["267c7817-81a5-4bef-9d5b-d0faa4cd0d71", "value"],
242+
equals: "C",
243+
},
244+
},
245+
{
246+
response: {
247+
path: ["267c7817-81a5-4bef-9d5b-d0faa4cd0d71", "value"],
248+
equals: "D",
249+
},
250+
},
251+
],
252+
},
238253
],
239-
},
254+
});
240255
});
241256

242-
expect(prismaWhere).toEqual({
243-
AND: [
244-
{
245-
response: {
246-
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
247-
equals: "a",
248-
},
249-
},
250-
{
251-
response: {
252-
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
253-
equals: "b",
257+
it("should support 'Not In' operator", () => {
258+
const prismaWhere = jsonLogicToPrisma({
259+
logic: { and: [{ "!": { in: [{ var: "267c7817-81a5-4bef-9d5b-d0faa4cd0d71" }, ["C", "D"]] } }] },
260+
});
261+
expect(prismaWhere).toEqual({
262+
AND: [
263+
{
264+
NOT: {
265+
OR: [
266+
{
267+
response: {
268+
path: ["267c7817-81a5-4bef-9d5b-d0faa4cd0d71", "value"],
269+
equals: "C",
270+
},
271+
},
272+
{
273+
response: {
274+
path: ["267c7817-81a5-4bef-9d5b-d0faa4cd0d71", "value"],
275+
equals: "D",
276+
},
277+
},
278+
],
279+
},
254280
},
255-
},
256-
],
257-
});
258-
});
259-
260-
it("should support where Any Match ['Equals', 'Equals'] operator", () => {
261-
const prismaWhere = jsonLogicToPrisma({
262-
logic: {
263-
or: [
264-
{ "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "a"] },
265-
{ "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "b"] },
266281
],
267-
},
282+
});
268283
});
269284

270-
expect(prismaWhere).toEqual({
271-
OR: [
272-
{
273-
response: {
274-
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
275-
equals: "a",
276-
},
285+
it("should support where All Match ['Equals', 'Equals'] operator", () => {
286+
const prismaWhere = jsonLogicToPrisma({
287+
logic: {
288+
and: [
289+
{ "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "a"] },
290+
{ "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "b"] },
291+
],
277292
},
278-
{
279-
response: {
280-
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
281-
equals: "b",
293+
});
294+
295+
expect(prismaWhere).toEqual({
296+
AND: [
297+
{
298+
response: {
299+
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
300+
equals: "a",
301+
},
282302
},
283-
},
284-
],
303+
{
304+
response: {
305+
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
306+
equals: "b",
307+
},
308+
},
309+
],
310+
});
285311
});
286-
});
287312

288-
it("should support where None Match ['Equals', 'Equals'] operator", () => {
289-
const prismaWhere = jsonLogicToPrisma({
290-
logic: {
291-
"!": {
313+
it("should support where Any Match ['Equals', 'Equals'] operator", () => {
314+
const prismaWhere = jsonLogicToPrisma({
315+
logic: {
292316
or: [
293-
{ "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "abc"] },
294-
{ "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "abcd"] },
317+
{ "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "a"] },
318+
{ "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "b"] },
295319
],
296320
},
297-
},
298-
});
321+
});
299322

300-
expect(prismaWhere).toEqual({
301-
NOT: {
323+
expect(prismaWhere).toEqual({
302324
OR: [
303-
{ response: { path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"], equals: "abc" } },
304-
{ response: { path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"], equals: "abcd" } },
325+
{
326+
response: {
327+
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
328+
equals: "a",
329+
},
330+
},
331+
{
332+
response: {
333+
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
334+
equals: "b",
335+
},
336+
},
305337
],
306-
},
338+
});
339+
});
340+
341+
it("should support where None Match ['Equals', 'Equals'] operator", () => {
342+
const prismaWhere = jsonLogicToPrisma({
343+
logic: {
344+
"!": {
345+
or: [
346+
{ "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "abc"] },
347+
{ "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "abcd"] },
348+
],
349+
},
350+
},
351+
});
352+
353+
expect(prismaWhere).toEqual({
354+
NOT: {
355+
OR: [
356+
{ response: { path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"], equals: "abc" } },
357+
{ response: { path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"], equals: "abcd" } },
358+
],
359+
},
360+
});
307361
});
308362
});
309363
});

packages/app-store/routing-forms/jsonLogicToPrisma.ts

+75-27
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,18 @@ const GROUP_OPERATOR_MAP = {
7575
} as const;
7676

7777
const NumberOperators = [">", ">=", "<", "<="];
78+
79+
const negatePrismaWhereClauseIfNeeded = (data: any, isNegation: boolean) => {
80+
if (!isNegation) {
81+
return data;
82+
}
83+
return {
84+
NOT: {
85+
...data,
86+
},
87+
};
88+
};
89+
7890
const convertSingleQueryToPrismaWhereClause = (
7991
operatorName: keyof typeof OPERATOR_MAP,
8092
logicData: LogicData,
@@ -87,31 +99,72 @@ const convertSingleQueryToPrismaWhereClause = (
8799
const operands =
88100
logicData[operatorName] instanceof Array ? logicData[operatorName] : [logicData[operatorName]];
89101

90-
const mainOperand = operatorName !== "in" ? operands[0].var : operands[1].var;
102+
if (operatorName === "in" && operands[0]?.var && Array.isArray(operands[1])) {
103+
// Case A: Item "in" array
104+
// operands[0]: { var: ... }
105+
// operands[1]: items to test against
106+
107+
// Convert 'in' operator to union of OR clauses
108+
return negatePrismaWhereClauseIfNeeded(
109+
{
110+
OR: (operands[1] ?? []).map((value) => ({
111+
response: {
112+
path: [operands[0].var, "value"],
113+
[`${OPERATOR_MAP["=="].operator}`]: value,
114+
},
115+
})),
116+
},
117+
isNegation
118+
);
119+
}
120+
121+
if (operatorName === "in" && typeof operands[0] === "string" && operands[1]?.var) {
122+
// Case B: String "in" string
123+
// operands[0]: string to test against
124+
// operands[1]: { var: ... }
125+
126+
return negatePrismaWhereClauseIfNeeded(
127+
{
128+
response: {
129+
path: [operands[1].var, "value"],
130+
[`${prismaOperator}`]: operands[0],
131+
},
132+
},
133+
isNegation
134+
);
135+
}
136+
137+
const mainOperand = operands[0].var;
138+
let secondaryOperand;
91139

92-
let secondaryOperand = staticSecondaryOperand || (operatorName !== "in" ? operands[1] : operands[0]) || "";
93140
if (operatorName === "all") {
94-
secondaryOperand = secondaryOperand.in[1];
141+
secondaryOperand = operands[1].in[1];
142+
} else {
143+
secondaryOperand = staticSecondaryOperand || operands[1] || "";
95144
}
96145

97146
const isNumberOperator = NumberOperators.includes(operatorName);
98147
const secondaryOperandAsNumber = typeof secondaryOperand === "string" ? Number(secondaryOperand) : null;
99148

100-
let prismaWhere;
101-
if (secondaryOperandAsNumber) {
149+
if (secondaryOperandAsNumber && isNumberOperator) {
102150
// We know that it's number operator so Prisma should query number
103151
// Note that if we get string values in DB(e.g. '100'), those values can't be filtered with number operators.
104-
if (isNumberOperator) {
105-
prismaWhere = {
152+
return negatePrismaWhereClauseIfNeeded(
153+
{
106154
response: {
107155
path: [mainOperand, "value"],
108156
[`${prismaOperator}`]: secondaryOperandAsNumber,
109157
},
110-
};
111-
} else {
112-
// We know that it's not number operator but the input field might have been a number and thus stored value in DB as number.
113-
// Also, even for input type=number we might accidentally get string value(e.g. '100'). So, let reporting do it's best job with both number and string.
114-
prismaWhere = {
158+
},
159+
isNegation
160+
);
161+
}
162+
163+
if (secondaryOperandAsNumber && !isNumberOperator) {
164+
// We know that it's not number operator but the input field might have been a number and thus stored value in DB as number.
165+
// Also, even for input type=number we might accidentally get string value(e.g. '100'). So, let reporting do it's best job with both number and string.
166+
return negatePrismaWhereClauseIfNeeded(
167+
{
115168
OR: [
116169
{
117170
response: {
@@ -128,25 +181,20 @@ const convertSingleQueryToPrismaWhereClause = (
128181
},
129182
},
130183
],
131-
};
132-
}
133-
} else {
134-
prismaWhere = {
135-
response: {
136-
path: [mainOperand, "value"],
137-
[`${prismaOperator}`]: secondaryOperand,
138184
},
139-
};
185+
isNegation
186+
);
140187
}
141188

142-
if (isNegation) {
143-
return {
144-
NOT: {
145-
...prismaWhere,
189+
return negatePrismaWhereClauseIfNeeded(
190+
{
191+
response: {
192+
path: [mainOperand, "value"],
193+
[`${prismaOperator}`]: secondaryOperand,
146194
},
147-
};
148-
}
149-
return prismaWhere;
195+
},
196+
isNegation
197+
);
150198
};
151199

152200
const isNegation = (logicData: LogicData | NegatedLogicData) => {

0 commit comments

Comments
 (0)