diff --git a/packages/app-store/routing-forms/__tests__/jsonLogicToPrisma.test.ts b/packages/app-store/routing-forms/__tests__/jsonLogicToPrisma.test.ts index e7b6636644..c70f971ef0 100644 --- a/packages/app-store/routing-forms/__tests__/jsonLogicToPrisma.test.ts +++ b/packages/app-store/routing-forms/__tests__/jsonLogicToPrisma.test.ts @@ -227,83 +227,137 @@ describe("jsonLogicToPrisma(Reporting)", () => { ], }); }); - }); - it("should support where All Match ['Equals', 'Equals'] operator", () => { - const prismaWhere = jsonLogicToPrisma({ - logic: { - and: [ - { "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "a"] }, - { "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "b"] }, + it("should support 'Any In' operator", () => { + const prismaWhere = jsonLogicToPrisma({ + logic: { and: [{ in: [{ var: "267c7817-81a5-4bef-9d5b-d0faa4cd0d71" }, ["C", "D"]] }] }, + }); + expect(prismaWhere).toEqual({ + AND: [ + { + OR: [ + { + response: { + path: ["267c7817-81a5-4bef-9d5b-d0faa4cd0d71", "value"], + equals: "C", + }, + }, + { + response: { + path: ["267c7817-81a5-4bef-9d5b-d0faa4cd0d71", "value"], + equals: "D", + }, + }, + ], + }, ], - }, + }); }); - expect(prismaWhere).toEqual({ - AND: [ - { - response: { - path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"], - equals: "a", + it("should support 'Not In' operator", () => { + const prismaWhere = jsonLogicToPrisma({ + logic: { and: [{ "!": { in: [{ var: "267c7817-81a5-4bef-9d5b-d0faa4cd0d71" }, ["C", "D"]] } }] }, + }); + expect(prismaWhere).toEqual({ + AND: [ + { + NOT: { + OR: [ + { + response: { + path: ["267c7817-81a5-4bef-9d5b-d0faa4cd0d71", "value"], + equals: "C", + }, + }, + { + response: { + path: ["267c7817-81a5-4bef-9d5b-d0faa4cd0d71", "value"], + equals: "D", + }, + }, + ], + }, }, - }, - { - response: { - path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"], - equals: "b", - }, - }, - ], - }); - }); - - it("should support where Any Match ['Equals', 'Equals'] operator", () => { - const prismaWhere = jsonLogicToPrisma({ - logic: { - or: [ - { "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "a"] }, - { "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "b"] }, ], - }, + }); }); - expect(prismaWhere).toEqual({ - OR: [ - { - response: { - path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"], - equals: "a", - }, - }, - { - response: { - path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"], - equals: "b", - }, - }, - ], - }); - }); - - it("should support where None Match ['Equals', 'Equals'] operator", () => { - const prismaWhere = jsonLogicToPrisma({ - logic: { - "!": { - or: [ - { "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "abc"] }, - { "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "abcd"] }, + it("should support where All Match ['Equals', 'Equals'] operator", () => { + const prismaWhere = jsonLogicToPrisma({ + logic: { + and: [ + { "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "a"] }, + { "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "b"] }, ], }, - }, + }); + + expect(prismaWhere).toEqual({ + AND: [ + { + response: { + path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"], + equals: "a", + }, + }, + { + response: { + path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"], + equals: "b", + }, + }, + ], + }); }); - expect(prismaWhere).toEqual({ - NOT: { + it("should support where Any Match ['Equals', 'Equals'] operator", () => { + const prismaWhere = jsonLogicToPrisma({ + logic: { + or: [ + { "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "a"] }, + { "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "b"] }, + ], + }, + }); + + expect(prismaWhere).toEqual({ OR: [ - { response: { path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"], equals: "abc" } }, - { response: { path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"], equals: "abcd" } }, + { + response: { + path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"], + equals: "a", + }, + }, + { + response: { + path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"], + equals: "b", + }, + }, ], - }, + }); + }); + + it("should support where None Match ['Equals', 'Equals'] operator", () => { + const prismaWhere = jsonLogicToPrisma({ + logic: { + "!": { + or: [ + { "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "abc"] }, + { "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "abcd"] }, + ], + }, + }, + }); + + expect(prismaWhere).toEqual({ + NOT: { + OR: [ + { response: { path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"], equals: "abc" } }, + { response: { path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"], equals: "abcd" } }, + ], + }, + }); }); }); }); diff --git a/packages/app-store/routing-forms/jsonLogicToPrisma.ts b/packages/app-store/routing-forms/jsonLogicToPrisma.ts index 2d2b966b6e..9c19f4b95a 100644 --- a/packages/app-store/routing-forms/jsonLogicToPrisma.ts +++ b/packages/app-store/routing-forms/jsonLogicToPrisma.ts @@ -75,6 +75,18 @@ const GROUP_OPERATOR_MAP = { } as const; const NumberOperators = [">", ">=", "<", "<="]; + +const negatePrismaWhereClauseIfNeeded = (data: any, isNegation: boolean) => { + if (!isNegation) { + return data; + } + return { + NOT: { + ...data, + }, + }; +}; + const convertSingleQueryToPrismaWhereClause = ( operatorName: keyof typeof OPERATOR_MAP, logicData: LogicData, @@ -87,31 +99,72 @@ const convertSingleQueryToPrismaWhereClause = ( const operands = logicData[operatorName] instanceof Array ? logicData[operatorName] : [logicData[operatorName]]; - const mainOperand = operatorName !== "in" ? operands[0].var : operands[1].var; + if (operatorName === "in" && operands[0]?.var && Array.isArray(operands[1])) { + // Case A: Item "in" array + // operands[0]: { var: ... } + // operands[1]: items to test against + + // Convert 'in' operator to union of OR clauses + return negatePrismaWhereClauseIfNeeded( + { + OR: (operands[1] ?? []).map((value) => ({ + response: { + path: [operands[0].var, "value"], + [`${OPERATOR_MAP["=="].operator}`]: value, + }, + })), + }, + isNegation + ); + } + + if (operatorName === "in" && typeof operands[0] === "string" && operands[1]?.var) { + // Case B: String "in" string + // operands[0]: string to test against + // operands[1]: { var: ... } + + return negatePrismaWhereClauseIfNeeded( + { + response: { + path: [operands[1].var, "value"], + [`${prismaOperator}`]: operands[0], + }, + }, + isNegation + ); + } + + const mainOperand = operands[0].var; + let secondaryOperand; - let secondaryOperand = staticSecondaryOperand || (operatorName !== "in" ? operands[1] : operands[0]) || ""; if (operatorName === "all") { - secondaryOperand = secondaryOperand.in[1]; + secondaryOperand = operands[1].in[1]; + } else { + secondaryOperand = staticSecondaryOperand || operands[1] || ""; } const isNumberOperator = NumberOperators.includes(operatorName); const secondaryOperandAsNumber = typeof secondaryOperand === "string" ? Number(secondaryOperand) : null; - let prismaWhere; - if (secondaryOperandAsNumber) { + if (secondaryOperandAsNumber && isNumberOperator) { // We know that it's number operator so Prisma should query number // Note that if we get string values in DB(e.g. '100'), those values can't be filtered with number operators. - if (isNumberOperator) { - prismaWhere = { + return negatePrismaWhereClauseIfNeeded( + { response: { path: [mainOperand, "value"], [`${prismaOperator}`]: secondaryOperandAsNumber, }, - }; - } else { - // 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. - // 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. - prismaWhere = { + }, + isNegation + ); + } + + if (secondaryOperandAsNumber && !isNumberOperator) { + // 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. + // 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. + return negatePrismaWhereClauseIfNeeded( + { OR: [ { response: { @@ -128,25 +181,20 @@ const convertSingleQueryToPrismaWhereClause = ( }, }, ], - }; - } - } else { - prismaWhere = { + }, + isNegation + ); + } + + return negatePrismaWhereClauseIfNeeded( + { response: { path: [mainOperand, "value"], [`${prismaOperator}`]: secondaryOperand, }, - }; - } - - if (isNegation) { - return { - NOT: { - ...prismaWhere, - }, - }; - } - return prismaWhere; + }, + isNegation + ); }; const isNegation = (logicData: LogicData | NegatedLogicData) => {