From 483bdc0effb83a6378ce900c7d320b10da6626eb Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Mon, 23 Sep 2024 23:21:46 +0530 Subject: [PATCH] fix: code review changes --- .../edit/components/ConditionalLogic.tsx | 11 +- .../edit/components/EditEndingCard.tsx | 2 +- .../edit/components/HiddenFieldsCard.tsx | 2 +- ...dvancedLogicEditor.tsx => LogicEditor.tsx} | 19 +- ...itorActions.tsx => LogicEditorActions.tsx} | 25 +- ...nditions.tsx => LogicEditorConditions.tsx} | 15 +- .../components/MultipleChoiceQuestionForm.tsx | 2 +- .../edit/components/QuestionCard.tsx | 2 +- .../edit/components/QuestionsView.tsx | 12 +- .../components/SurveyVariablesCardItem.tsx | 2 +- .../[surveyId]/edit/lib/logicRuleEngine.ts | 3 +- .../edit/lib/{util.tsx => utils.tsx} | 66 ++--- .../data-migration.ts | 18 +- packages/lib/response/utils.ts | 14 +- packages/lib/survey/logic/utils.ts | 14 +- packages/lib/survey/tests/survey.test.ts | 24 +- packages/lib/utils/evaluateLogic.ts | 8 +- .../surveys/src/components/general/Survey.tsx | 30 +-- packages/surveys/src/lib/logicEvaluator.ts | 8 +- packages/surveys/src/lib/utils.ts | 13 +- packages/types/surveys/logic.ts | 225 ---------------- packages/types/surveys/types.ts | 240 ++++++++++++++++-- packages/types/surveys/validation.ts | 10 +- 23 files changed, 354 insertions(+), 411 deletions(-) rename apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/{AdvancedLogicEditor.tsx => LogicEditor.tsx} (61%) rename apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/{AdvancedLogicEditorActions.tsx => LogicEditorActions.tsx} (94%) rename apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/{AdvancedLogicEditorConditions.tsx => LogicEditorConditions.tsx} (97%) rename apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/{util.tsx => utils.tsx} (95%) delete mode 100644 packages/types/surveys/logic.ts diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/ConditionalLogic.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/ConditionalLogic.tsx index 4b273a4657..f2d11201a3 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/ConditionalLogic.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/ConditionalLogic.tsx @@ -1,5 +1,5 @@ -import { AdvancedLogicEditor } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedLogicEditor"; -import { getDefaultOperatorForQuestion } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/util"; +import { LogicEditor } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditor"; +import { getDefaultOperatorForQuestion } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils"; import { createId } from "@paralleldrive/cuid2"; import { ArrowDownIcon, @@ -14,8 +14,7 @@ import { useMemo } from "react"; import { duplicateLogicItem } from "@formbricks/lib/survey/logic/utils"; import { replaceHeadlineRecall } from "@formbricks/lib/utils/recall"; import { TAttributeClass } from "@formbricks/types/attribute-classes"; -import { TSurveyAdvancedLogic } from "@formbricks/types/surveys/logic"; -import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys/types"; +import { TSurvey, TSurveyLogic, TSurveyQuestion } from "@formbricks/types/surveys/types"; import { Button } from "@formbricks/ui/Button"; import { DropdownMenu, @@ -47,7 +46,7 @@ export function ConditionalLogic({ const addLogic = () => { const operator = getDefaultOperatorForQuestion(question); - const initialCondition: TSurveyAdvancedLogic = { + const initialCondition: TSurveyLogic = { id: createId(), conditions: { id: createId(), @@ -120,7 +119,7 @@ export function ConditionalLogic({
- void; question: TSurveyQuestion; questionIdx: number; @@ -14,7 +13,7 @@ interface AdvancedLogicEditorProps { isLast: boolean; } -export function AdvancedLogicEditor({ +export function LogicEditor({ localSurvey, logicItem, updateQuestion, @@ -22,10 +21,10 @@ export function AdvancedLogicEditor({ questionIdx, logicIdx, isLast, -}: AdvancedLogicEditorProps) { +}: LogicEditorProps) { return (
- - void; questionIdx: number; } -export function AdvancedLogicEditorActions({ +export function LogicEditorActions({ localSurvey, logicItem, logicIdx, question, updateQuestion, questionIdx, -}: AdvancedLogicEditorActions) { +}: LogicEditorActions) { const actions = logicItem.actions; const handleActionsChange = ( operation: "remove" | "addBelow" | "duplicate" | "update", actionIdx: number, - action?: TSurveyAdvancedLogicAction + action?: TSurveyLogicAction ) => { const logicCopy = structuredClone(question.logic) ?? []; const logicItem = logicCopy[logicIdx]; @@ -80,9 +81,9 @@ export function AdvancedLogicEditorActions({ handleActionsChange("update", actionIdx, actionBody); }; - const handleValuesChange = (actionIdx: number, values: Partial) => { + const handleValuesChange = (actionIdx: number, values: Partial) => { const action = actions[actionIdx]; - const actionBody = { ...action, ...values } as TSurveyAdvancedLogicAction; + const actionBody = { ...action, ...values } as TSurveyLogicAction; handleActionsChange("update", actionIdx, actionBody); }; diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedLogicEditorConditions.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditorConditions.tsx similarity index 97% rename from apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedLogicEditorConditions.tsx rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditorConditions.tsx index 49e4adc909..8b9a8d2e97 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedLogicEditorConditions.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditorConditions.tsx @@ -3,7 +3,7 @@ import { getConditionValueOptions, getDefaultOperatorForQuestion, getMatchValueProps, -} from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/util"; +} from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils"; import { createId } from "@paralleldrive/cuid2"; import { CopyIcon, MoreVerticalIcon, PlusIcon, TrashIcon, WorkflowIcon } from "lucide-react"; import { cn } from "@formbricks/lib/cn"; @@ -21,9 +21,10 @@ import { TDyanmicLogicField, TRightOperand, TSingleCondition, + TSurvey, TSurveyLogicConditionsOperator, -} from "@formbricks/types/surveys/logic"; -import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys/types"; + TSurveyQuestion, +} from "@formbricks/types/surveys/types"; import { DropdownMenu, DropdownMenuContent, @@ -32,7 +33,7 @@ import { } from "@formbricks/ui/DropdownMenu"; import { InputCombobox, TComboboxOption } from "@formbricks/ui/InputCombobox"; -interface AdvancedLogicEditorConditionsProps { +interface LogicEditorConditionsProps { conditions: TConditionGroup; updateQuestion: (questionIdx: number, updatedAttributes: any) => void; question: TSurveyQuestion; @@ -42,7 +43,7 @@ interface AdvancedLogicEditorConditionsProps { depth?: number; } -export function AdvancedLogicEditorConditions({ +export function LogicEditorConditions({ conditions, logicIdx, question, @@ -50,7 +51,7 @@ export function AdvancedLogicEditorConditions({ questionIdx, updateQuestion, depth = 0, -}: AdvancedLogicEditorConditionsProps) { +}: LogicEditorConditionsProps) { const handleAddConditionBelow = (resourceId: string) => { const operator = getDefaultOperatorForQuestion(question); @@ -198,7 +199,7 @@ export function AdvancedLogicEditorConditions({
)}
- { + const updateActions = (actions: TSurveyLogicAction[]): TSurveyLogicAction[] => { return actions.map((action) => { let updatedAction = { ...action }; @@ -145,7 +145,7 @@ export const QuestionsView = ({ // Update advanced logic if (question.logic) { - updatedQuestion.logic = question.logic.map((logicRule: TSurveyAdvancedLogic) => ({ + updatedQuestion.logic = question.logic.map((logicRule: TSurveyLogic) => ({ ...logicRule, conditions: updateConditions(logicRule.conditions), actions: updateActions(logicRule.actions), diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyVariablesCardItem.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyVariablesCardItem.tsx index 5f14652b01..fae5928c80 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyVariablesCardItem.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyVariablesCardItem.tsx @@ -1,6 +1,6 @@ "use client"; -import { findVariableUsedInLogic } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/util"; +import { findVariableUsedInLogic } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils"; import { createId } from "@paralleldrive/cuid2"; import { TrashIcon } from "lucide-react"; import React, { useCallback, useEffect } from "react"; diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/logicRuleEngine.ts b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/logicRuleEngine.ts index aca985f305..81b425558a 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/logicRuleEngine.ts +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/logicRuleEngine.ts @@ -1,5 +1,4 @@ -import { ZSurveyLogicConditionsOperator } from "@formbricks/types/surveys/logic"; -import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types"; +import { TSurveyQuestionTypeEnum, ZSurveyLogicConditionsOperator } from "@formbricks/types/surveys/types"; export const logicRules = { question: { diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/util.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils.tsx similarity index 95% rename from apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/util.tsx rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils.tsx index b5ce399650..fb1b48f75a 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/util.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils.tsx @@ -1,36 +1,17 @@ -import { - ArrowUpFromLineIcon, - CalendarDaysIcon, - CheckIcon, - EyeOffIcon, - FileDigitIcon, - FileType2Icon, - Grid3X3Icon, - HomeIcon, - ImageIcon, - ListIcon, - ListOrderedIcon, - MessageSquareTextIcon, - MousePointerClickIcon, - PhoneIcon, - PresentationIcon, - Rows3Icon, - StarIcon, -} from "lucide-react"; +import { EyeOffIcon, FileDigitIcon, FileType2Icon } from "lucide-react"; import { HTMLInputTypeAttribute } from "react"; import { getLocalizedValue } from "@formbricks/lib/i18n/utils"; import { isConditionGroup } from "@formbricks/lib/survey/logic/utils"; +import { questionTypes } from "@formbricks/lib/utils/questions"; import { TConditionGroup, TLeftOperand, TRightOperand, TSingleCondition, - TSurveyAdvancedLogic, - TSurveyAdvancedLogicAction, - TSurveyLogicConditionsOperator, -} from "@formbricks/types/surveys/logic"; -import { TSurvey, + TSurveyLogic, + TSurveyLogicAction, + TSurveyLogicConditionsOperator, TSurveyQuestion, TSurveyQuestionTypeEnum, TSurveyVariable, @@ -57,22 +38,13 @@ export const formatTextWithSlashes = (text: string) => { }); }; -const questionIconMapping = { - openText: MessageSquareTextIcon, - multipleChoiceSingle: Rows3Icon, - multipleChoiceMulti: ListIcon, - pictureSelection: ImageIcon, - rating: StarIcon, - nps: PresentationIcon, - cta: MousePointerClickIcon, - consent: CheckIcon, - date: CalendarDaysIcon, - fileUpload: ArrowUpFromLineIcon, - cal: PhoneIcon, - matrix: Grid3X3Icon, - ranking: ListOrderedIcon, - address: HomeIcon, -}; +const questionIconMapping = questionTypes.reduce( + (prev, curr) => ({ + ...prev, + [curr.id]: curr.icon, + }), + {} +); export const getConditionValueOptions = ( localSurvey: TSurvey, @@ -763,7 +735,7 @@ export const getMatchValueProps = ( }; export const getActionTargetOptions = ( - action: TSurveyAdvancedLogicAction, + action: TSurveyLogicAction, localSurvey: TSurvey, currQuestionIdx: number ): TComboboxOption[] => { @@ -1039,14 +1011,14 @@ export const findQuestionUsedInLogic = (survey: TSurvey, questionId: string): nu } }; - const isUsedInAction = (action: TSurveyAdvancedLogicAction): boolean => { + const isUsedInAction = (action: TSurveyLogicAction): boolean => { return ( (action.objective === "jumpToQuestion" && action.target === questionId) || (action.objective === "requireAnswer" && action.target === questionId) ); }; - const isUsedInLogicRule = (logicRule: TSurveyAdvancedLogic): boolean => { + const isUsedInLogicRule = (logicRule: TSurveyLogic): boolean => { return isUsedInCondition(logicRule.conditions) || logicRule.actions.some(isUsedInAction); }; @@ -1079,7 +1051,7 @@ export const findOptionUsedInLogic = (survey: TSurvey, questionId: string, optio return false; }; - const isUsedInLogicRule = (logicRule: TSurveyAdvancedLogic): boolean => { + const isUsedInLogicRule = (logicRule: TSurveyLogic): boolean => { return isUsedInCondition(logicRule.conditions); }; @@ -1100,11 +1072,11 @@ export const findVariableUsedInLogic = (survey: TSurvey, variableId: string): nu } }; - const isUsedInAction = (action: TSurveyAdvancedLogicAction): boolean => { + const isUsedInAction = (action: TSurveyLogicAction): boolean => { return action.objective === "calculate" && action.variableId === variableId; }; - const isUsedInLogicRule = (logicRule: TSurveyAdvancedLogic): boolean => { + const isUsedInLogicRule = (logicRule: TSurveyLogic): boolean => { return isUsedInCondition(logicRule.conditions) || logicRule.actions.some(isUsedInAction); }; @@ -1126,7 +1098,7 @@ export const findHiddenFieldUsedInLogic = (survey: TSurvey, hiddenFieldId: strin } }; - const isUsedInLogicRule = (logicRule: TSurveyAdvancedLogic): boolean => { + const isUsedInLogicRule = (logicRule: TSurveyLogic): boolean => { return isUsedInCondition(logicRule.conditions); }; diff --git a/packages/database/data-migrations/20240828122408_advanced_logic_editor/data-migration.ts b/packages/database/data-migrations/20240828122408_advanced_logic_editor/data-migration.ts index 10e61e8c49..107e876866 100644 --- a/packages/database/data-migrations/20240828122408_advanced_logic_editor/data-migration.ts +++ b/packages/database/data-migrations/20240828122408_advanced_logic_editor/data-migration.ts @@ -3,15 +3,13 @@ /* eslint-disable no-console -- logging is allowed in migration scripts */ import { createId } from "@paralleldrive/cuid2"; import { PrismaClient } from "@prisma/client"; -import type { - TRightOperand, - TSingleCondition, - TSurveyAdvancedLogic, - TSurveyAdvancedLogicAction, - TSurveyLogicConditionsOperator, -} from "@formbricks/types/surveys/logic"; import { + type TRightOperand, + type TSingleCondition, type TSurveyEndings, + type TSurveyLogic, + type TSurveyLogicAction, + type TSurveyLogicConditionsOperator, type TSurveyMultipleChoiceQuestion, type TSurveyQuestion, TSurveyQuestionTypeEnum, @@ -25,7 +23,7 @@ interface TOldLogic { destination: string; } -const isOldLogic = (logic: TOldLogic | TSurveyAdvancedLogic): logic is TOldLogic => { +const isOldLogic = (logic: TOldLogic | TSurveyLogic): logic is TOldLogic => { return Object.keys(logic).some((key) => ["condition", "destination", "value"].includes(key)); }; @@ -202,7 +200,7 @@ function convertLogic( surveyEndings: TSurveyEndings, oldLogic: TOldLogic, question: TSurveyQuestion -): TSurveyAdvancedLogic | undefined { +): TSurveyLogic | undefined { if (!oldLogic.condition || !oldLogic.destination) { return undefined; } @@ -223,7 +221,7 @@ function convertLogic( } } - const action: TSurveyAdvancedLogicAction = { + const action: TSurveyLogicAction = { id: createId(), objective: "jumpToQuestion", target: actionTarget, diff --git a/packages/lib/response/utils.ts b/packages/lib/response/utils.ts index 9620dedc19..c90fae92d4 100644 --- a/packages/lib/response/utils.ts +++ b/packages/lib/response/utils.ts @@ -31,7 +31,7 @@ import { import { getLocalizedValue } from "../i18n/utils"; import { processResponseData } from "../responses"; import { getTodaysDateTimeFormatted } from "../time"; -import { evaluateAdvancedLogic, performActions } from "../utils/evaluateLogic"; +import { evaluateLogic, performActions } from "../utils/evaluateLogic"; import { sanitizeString } from "../utils/strings"; export const calculateTtcTotal = (ttc: TResponseTtc) => { @@ -638,7 +638,7 @@ export const getSurveySummaryDropOff = ( } }); - let localSurvey = JSON.parse(JSON.stringify(survey)) as TSurvey; + let localSurvey = structuredClone(survey); let localResponseData: TResponseData = { ...response.data }; let localVariables: TResponseVariables = { ...surveyVariablesData, @@ -742,15 +742,7 @@ const evaluateLogicAndGetNextQuestionId = ( if (currQuesTemp.logic && currQuesTemp.logic.length > 0) { for (const logic of currQuesTemp.logic) { - if ( - evaluateAdvancedLogic( - localSurvey, - data, - localVariables, - logic.conditions, - selectedLanguage ?? "default" - ) - ) { + if (evaluateLogic(localSurvey, data, localVariables, logic.conditions, selectedLanguage ?? "default")) { const { jumpTarget, requiredQuestionIds, calculations } = performActions( updatedSurvey, logic.actions, diff --git a/packages/lib/survey/logic/utils.ts b/packages/lib/survey/logic/utils.ts index 9010d866be..0dc46a416c 100644 --- a/packages/lib/survey/logic/utils.ts +++ b/packages/lib/survey/logic/utils.ts @@ -3,9 +3,9 @@ import { TActionObjective, TConditionGroup, TSingleCondition, - TSurveyAdvancedLogic, - TSurveyAdvancedLogicAction, -} from "@formbricks/types/surveys/logic"; + TSurveyLogic, + TSurveyLogicAction, +} from "@formbricks/types/surveys/types"; type TCondition = TSingleCondition | TConditionGroup; @@ -13,7 +13,7 @@ export const isConditionGroup = (condition: TCondition): condition is TCondition return (condition as TConditionGroup).connector !== undefined; }; -export const duplicateLogicItem = (logicItem: TSurveyAdvancedLogic): TSurveyAdvancedLogic => { +export const duplicateLogicItem = (logicItem: TSurveyLogic): TSurveyLogic => { const duplicateConditionGroup = (group: TConditionGroup): TConditionGroup => { return { ...group, @@ -35,7 +35,7 @@ export const duplicateLogicItem = (logicItem: TSurveyAdvancedLogic): TSurveyAdva }; }; - const duplicateAction = (action: TSurveyAdvancedLogicAction): TSurveyAdvancedLogicAction => { + const duplicateAction = (action: TSurveyLogicAction): TSurveyLogicAction => { return { ...action, id: createId(), @@ -176,9 +176,9 @@ export const updateCondition = ( }; export const getUpdatedActionBody = ( - action: TSurveyAdvancedLogicAction, + action: TSurveyLogicAction, objective: TActionObjective -): TSurveyAdvancedLogicAction => { +): TSurveyLogicAction => { if (objective === action.objective) return action; switch (objective) { case "calculate": diff --git a/packages/lib/survey/tests/survey.test.ts b/packages/lib/survey/tests/survey.test.ts index 07f2d7695b..9bb4d2a6af 100644 --- a/packages/lib/survey/tests/survey.test.ts +++ b/packages/lib/survey/tests/survey.test.ts @@ -1,7 +1,7 @@ import { prisma } from "../../__mocks__/database"; import { mockResponseNote, mockResponseWithMockPerson } from "../../response/tests/__mocks__/data.mock"; import { Prisma } from "@prisma/client"; -import { evaluateAdvancedLogic } from "utils/evaluateLogic"; +import { evaluateLogic } from "utils/evaluateLogic"; import { beforeEach, describe, expect, it } from "vitest"; import { testInputValidation } from "vitestSetup"; import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors"; @@ -39,12 +39,12 @@ beforeEach(() => { prisma.survey.count.mockResolvedValue(1); }); -describe("evaluateAdvancedLogic with mockSurveyWithLogic", () => { +describe("evaluateLogic with mockSurveyWithLogic", () => { it("should return true when q1 answer is blue", () => { const data = { q1: "blue" }; const variablesData = {}; - const result = evaluateAdvancedLogic( + const result = evaluateLogic( mockSurveyWithLogic, data, variablesData, @@ -58,7 +58,7 @@ describe("evaluateAdvancedLogic with mockSurveyWithLogic", () => { const data = { q1: "red" }; const variablesData = {}; - const result = evaluateAdvancedLogic( + const result = evaluateLogic( mockSurveyWithLogic, data, variablesData, @@ -72,7 +72,7 @@ describe("evaluateAdvancedLogic with mockSurveyWithLogic", () => { const data = { q1: "blue", q2: "pizza" }; const variablesData = {}; - const result = evaluateAdvancedLogic( + const result = evaluateLogic( mockSurveyWithLogic, data, variablesData, @@ -86,7 +86,7 @@ describe("evaluateAdvancedLogic with mockSurveyWithLogic", () => { const data = { q1: "blue", q2: "burger" }; const variablesData = {}; - const result = evaluateAdvancedLogic( + const result = evaluateLogic( mockSurveyWithLogic, data, variablesData, @@ -100,7 +100,7 @@ describe("evaluateAdvancedLogic with mockSurveyWithLogic", () => { const data = { q2: "pizza", q3: "Inception" }; const variablesData = {}; - const result = evaluateAdvancedLogic( + const result = evaluateLogic( mockSurveyWithLogic, data, variablesData, @@ -114,7 +114,7 @@ describe("evaluateAdvancedLogic with mockSurveyWithLogic", () => { const data = { q4: "lmao" }; const variablesData = { siog1dabtpo3l0a3xoxw2922: "lmao" }; - const result = evaluateAdvancedLogic( + const result = evaluateLogic( mockSurveyWithLogic, data, variablesData, @@ -128,7 +128,7 @@ describe("evaluateAdvancedLogic with mockSurveyWithLogic", () => { const data = { q4: "lol" }; const variablesData = { siog1dabtpo3l0a3xoxw2922: "damn" }; - const result = evaluateAdvancedLogic( + const result = evaluateLogic( mockSurveyWithLogic, data, variablesData, @@ -142,7 +142,7 @@ describe("evaluateAdvancedLogic with mockSurveyWithLogic", () => { const data = { q5: "40" }; const variablesData = { km1srr55owtn2r7lkoh5ny1u: 35 }; - const result = evaluateAdvancedLogic( + const result = evaluateLogic( mockSurveyWithLogic, data, variablesData, @@ -156,7 +156,7 @@ describe("evaluateAdvancedLogic with mockSurveyWithLogic", () => { const data = { q5: "40" }; const variablesData = { km1srr55owtn2r7lkoh5ny1u: 25 }; - const result = evaluateAdvancedLogic( + const result = evaluateLogic( mockSurveyWithLogic, data, variablesData, @@ -170,7 +170,7 @@ describe("evaluateAdvancedLogic with mockSurveyWithLogic", () => { const data = { q6: ["lmao", "XD"], q1: "green", q2: "pizza", q3: "inspection", name: "pizza" }; const variablesData = { siog1dabtpo3l0a3xoxw2922: "tokyo" }; - const result = evaluateAdvancedLogic( + const result = evaluateLogic( mockSurveyWithLogic, data, variablesData, diff --git a/packages/lib/utils/evaluateLogic.ts b/packages/lib/utils/evaluateLogic.ts index c8c4bf243f..f44a9b3e86 100644 --- a/packages/lib/utils/evaluateLogic.ts +++ b/packages/lib/utils/evaluateLogic.ts @@ -3,10 +3,8 @@ import { TActionCalculate, TConditionGroup, TSingleCondition, - TSurveyAdvancedLogicAction, -} from "@formbricks/types/surveys/logic"; -import { TSurvey, + TSurveyLogicAction, TSurveyQuestion, TSurveyQuestionTypeEnum, TSurveyVariable, @@ -14,7 +12,7 @@ import { import { getLocalizedValue } from "../i18n/utils"; import { isConditionGroup } from "../survey/logic/utils"; -export const evaluateAdvancedLogic = ( +export const evaluateLogic = ( localSurvey: TSurvey, data: TResponseData, variablesData: TResponseVariables, @@ -359,7 +357,7 @@ const getRightOperandValue = ( export const performActions = ( survey: TSurvey, - actions: TSurveyAdvancedLogicAction[], + actions: TSurveyLogicAction[], data: TResponseData, calculationResults: TResponseVariables ): { diff --git a/packages/surveys/src/components/general/Survey.tsx b/packages/surveys/src/components/general/Survey.tsx index 5367947d8d..e4c0595d32 100644 --- a/packages/surveys/src/components/general/Survey.tsx +++ b/packages/surveys/src/components/general/Survey.tsx @@ -8,7 +8,7 @@ import { SurveyCloseButton } from "@/components/general/SurveyCloseButton"; import { WelcomeCard } from "@/components/general/WelcomeCard"; import { AutoCloseWrapper } from "@/components/wrappers/AutoCloseWrapper"; import { StackedCardsContainer } from "@/components/wrappers/StackedCardsContainer"; -import { evaluateAdvancedLogic, performActions } from "@/lib/logicEvaluator"; +import { evaluateLogic, performActions } from "@/lib/logicEvaluator"; import { parseRecallInformation } from "@/lib/recall"; import { cn } from "@/lib/utils"; import { useEffect, useMemo, useRef, useState } from "preact/hooks"; @@ -179,14 +179,18 @@ export const Survey = ({ }; const makeQuestionsRequired = (questionIds: string[]): void => { - const localSurveyClone = structuredClone(localSurvey); - localSurveyClone.questions.forEach((question) => { - if (questionIds.includes(question.id)) { - question.required = true; - } - }); - - setlocalSurvey(localSurveyClone); + setlocalSurvey((prevSurvey) => ({ + ...prevSurvey, + questions: prevSurvey.questions.map((question) => { + if (questionIds.includes(question.id)) { + return { + ...question, + required: true, + }; + } + return question; + }), + })); }; const pushVariableState = (questionId: string) => { @@ -224,13 +228,7 @@ export const Survey = ({ if (currQuesTemp.logic && currQuesTemp.logic.length > 0) { for (const logic of currQuesTemp.logic) { if ( - evaluateAdvancedLogic( - localSurvey, - localResponseData, - currentVariables, - logic.conditions, - selectedLanguage - ) + evaluateLogic(localSurvey, localResponseData, currentVariables, logic.conditions, selectedLanguage) ) { const { jumpTarget, requiredQuestionIds, calculations } = performActions( localSurvey, diff --git a/packages/surveys/src/lib/logicEvaluator.ts b/packages/surveys/src/lib/logicEvaluator.ts index f3851ec8ca..7ddfc17804 100644 --- a/packages/surveys/src/lib/logicEvaluator.ts +++ b/packages/surveys/src/lib/logicEvaluator.ts @@ -5,16 +5,14 @@ import { TActionCalculate, TConditionGroup, TSingleCondition, - TSurveyAdvancedLogicAction, -} from "@formbricks/types/surveys/logic"; -import { TSurvey, + TSurveyLogicAction, TSurveyQuestion, TSurveyQuestionTypeEnum, TSurveyVariable, } from "@formbricks/types/surveys/types"; -export const evaluateAdvancedLogic = ( +export const evaluateLogic = ( localSurvey: TSurvey, data: TResponseData, variablesData: TResponseVariables, @@ -359,7 +357,7 @@ const getRightOperandValue = ( export const performActions = ( survey: TSurvey, - actions: TSurveyAdvancedLogicAction[], + actions: TSurveyLogicAction[], data: TResponseData, calculationResults: TResponseVariables ): { diff --git a/packages/surveys/src/lib/utils.ts b/packages/surveys/src/lib/utils.ts index 9070af4024..04fec8b7d0 100644 --- a/packages/surveys/src/lib/utils.ts +++ b/packages/surveys/src/lib/utils.ts @@ -1,5 +1,10 @@ -import { TSurveyAdvancedLogic, TSurveyAdvancedLogicAction } from "@formbricks/types/surveys/logic"; -import { TSurvey, TSurveyQuestion, TSurveyQuestionChoice } from "@formbricks/types/surveys/types"; +import { + TSurvey, + TSurveyLogic, + TSurveyLogicAction, + TSurveyQuestion, + TSurveyQuestionChoice, +} from "@formbricks/types/surveys/types"; export const cn = (...classes: string[]) => { return classes.filter(Boolean).join(" "); @@ -63,8 +68,8 @@ const getPossibleNextQuestions = (question: TSurveyQuestion): string[] => { const possibleDestinations: string[] = []; - question.logic.forEach((logic: TSurveyAdvancedLogic) => { - logic.actions.forEach((action: TSurveyAdvancedLogicAction) => { + question.logic.forEach((logic: TSurveyLogic) => { + logic.actions.forEach((action: TSurveyLogicAction) => { if (action.objective === "jumpToQuestion") { possibleDestinations.push(action.target); } diff --git a/packages/types/surveys/logic.ts b/packages/types/surveys/logic.ts deleted file mode 100644 index 4a7be1e672..0000000000 --- a/packages/types/surveys/logic.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { z } from "zod"; -import { ZId } from "../common"; - -export const ZSurveyLogicConditionsOperator = z.enum([ - "equals", - "doesNotEqual", - "contains", - "doesNotContain", - "startsWith", - "doesNotStartWith", - "endsWith", - "doesNotEndWith", - "isSubmitted", - "isSkipped", - "isGreaterThan", - "isLessThan", - "isGreaterThanOrEqual", - "isLessThanOrEqual", - "equalsOneOf", - "includesAllOf", - "includesOneOf", - "isClicked", - "isAccepted", - "isBefore", - "isAfter", - "isBooked", - "isPartiallySubmitted", - "isCompletelySubmitted", -]); - -const operatorsWithoutRightOperand = [ - ZSurveyLogicConditionsOperator.Enum.isSubmitted, - ZSurveyLogicConditionsOperator.Enum.isSkipped, - ZSurveyLogicConditionsOperator.Enum.isClicked, - ZSurveyLogicConditionsOperator.Enum.isAccepted, - ZSurveyLogicConditionsOperator.Enum.isBooked, - ZSurveyLogicConditionsOperator.Enum.isPartiallySubmitted, - ZSurveyLogicConditionsOperator.Enum.isCompletelySubmitted, -] as const; - -export const ZDyanmicLogicField = z.enum(["question", "variable", "hiddenField"]); -export const ZActionObjective = z.enum(["calculate", "requireAnswer", "jumpToQuestion"]); -export const ZActionTextVariableCalculateOperator = z.enum(["assign", "concat"], { - message: "Conditional Logic: Invalid operator for a text variable", -}); -export const ZActionNumberVariableCalculateOperator = z.enum( - ["add", "subtract", "multiply", "divide", "assign"], - { message: "Conditional Logic: Invalid operator for a number variable" } -); - -const ZDynamicQuestion = z.object({ - type: z.literal("question"), - value: z.string().min(1, "Conditional Logic: Question id cannot be empty"), -}); - -const ZDynamicVariable = z.object({ - type: z.literal("variable"), - value: z - .string() - .cuid2({ message: "Conditional Logic: Variable id must be a valid cuid" }) - .min(1, "Conditional Logic: Variable id cannot be empty"), -}); - -const ZDynamicHiddenField = z.object({ - type: z.literal("hiddenField"), - value: z.string().min(1, "Conditional Logic: Hidden field id cannot be empty"), -}); - -const ZDynamicLogicFieldValue = z.union([ZDynamicQuestion, ZDynamicVariable, ZDynamicHiddenField], { - message: "Conditional Logic: Invalid dynamic field value", -}); - -export type TSurveyLogicConditionsOperator = z.infer; -export type TDyanmicLogicField = z.infer; -export type TActionObjective = z.infer; -export type TActionTextVariableCalculateOperator = z.infer; -export type TActionNumberVariableCalculateOperator = z.infer; - -// Conditions -const ZLeftOperand = ZDynamicLogicFieldValue; -export type TLeftOperand = z.infer; - -export const ZRightOperandStatic = z.object({ - type: z.literal("static"), - value: z.union([z.string(), z.number(), z.array(z.string())]), -}); - -export const ZRightOperand = z.union([ZRightOperandStatic, ZDynamicLogicFieldValue]); -export type TRightOperand = z.infer; - -export const ZSingleCondition = z - .object({ - id: ZId, - leftOperand: ZLeftOperand, - operator: ZSurveyLogicConditionsOperator, - rightOperand: ZRightOperand.optional(), - }) - .and( - z.object({ - connector: z.undefined(), - }) - ) - .superRefine((val, ctx) => { - if ( - !operatorsWithoutRightOperand.includes(val.operator as (typeof operatorsWithoutRightOperand)[number]) - ) { - if (val.rightOperand === undefined) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: `Conditional Logic: right operand is required for operator "${val.operator}"`, - path: ["rightOperand"], - }); - } else if (val.rightOperand.type === "static" && val.rightOperand.value === "") { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: `Conditional Logic: right operand value cannot be empty for operator "${val.operator}"`, - }); - } - } else if (val.rightOperand !== undefined) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: `Conditional Logic: right operand should not be present for operator "${val.operator}"`, - path: ["rightOperand"], - }); - } - }); - -export type TSingleCondition = z.infer; - -export interface TConditionGroup { - id: string; - connector: "and" | "or"; - conditions: (TSingleCondition | TConditionGroup)[]; -} - -const ZConditionGroup: z.ZodType = z.lazy(() => - z.object({ - id: ZId, - connector: z.enum(["and", "or"]), - conditions: z.array(z.union([ZSingleCondition, ZConditionGroup])), - }) -); - -// Actions -export const ZActionVariableValueType = z.union([z.literal("static"), ZDyanmicLogicField]); -export type TActionVariableValueType = z.infer; - -const ZActionBase = z.object({ - id: ZId, - objective: ZActionObjective, -}); - -export type TActionBase = z.infer; - -const ZActionCalculateBase = ZActionBase.extend({ - objective: z.literal("calculate"), - variableId: z.string(), -}); - -export const ZActionCalculateText = ZActionCalculateBase.extend({ - operator: ZActionTextVariableCalculateOperator, - value: z.union([ - z.object({ - type: z.literal("static"), - value: z - .string({ message: "Conditional Logic: Value must be a string for text variable" }) - .min(1, "Conditional Logic: Please enter a value in logic field"), - }), - ZDynamicLogicFieldValue, - ]), -}); - -export const ZActionCalculateNumber = ZActionCalculateBase.extend({ - operator: ZActionNumberVariableCalculateOperator, - value: z.union([ - z.object({ - type: z.literal("static"), - value: z.number({ message: "Conditional Logic: Value must be a number for number variable" }), - }), - ZDynamicLogicFieldValue, - ]), -}).superRefine((val, ctx) => { - if (val.operator === "divide" && val.value.type === "static" && val.value.value === 0) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "Conditional Logic: Cannot divide by zero", - path: ["value", "value"], - }); - } -}); - -const ZActionCalculate = z.union([ZActionCalculateText, ZActionCalculateNumber]); - -export type TActionCalculate = z.infer; - -const ZActionRequireAnswer = ZActionBase.extend({ - objective: z.literal("requireAnswer"), - target: z.string().min(1, "Conditional Logic: Target question id cannot be empty"), -}); -export type TActionRequireAnswer = z.infer; - -const ZActionJumpToQuestion = ZActionBase.extend({ - objective: z.literal("jumpToQuestion"), - target: z.string().min(1, "Conditional Logic: Target question id cannot be empty"), -}); - -export type TActionJumpToQuestion = z.infer; - -export const ZSurveyAdvancedLogicAction = z.union([ - ZActionCalculate, - ZActionRequireAnswer, - ZActionJumpToQuestion, -]); - -export type TSurveyAdvancedLogicAction = z.infer; - -const ZSurveyAdvancedLogicActions = z.array(ZSurveyAdvancedLogicAction); - -export const ZSurveyAdvancedLogic = z.object({ - id: ZId, - conditions: ZConditionGroup, - actions: ZSurveyAdvancedLogicActions, -}); - -export type TSurveyAdvancedLogic = z.infer; diff --git a/packages/types/surveys/types.ts b/packages/types/surveys/types.ts index 6f04f6e60c..76836c9182 100644 --- a/packages/types/surveys/types.ts +++ b/packages/types/surveys/types.ts @@ -5,16 +5,6 @@ import { ZAllowedFileExtension, ZColor, ZId, ZPlacement } from "../common"; import { ZLanguage } from "../product"; import { ZSegment } from "../segment"; import { ZBaseStyling } from "../styling"; -import { - type TConditionGroup, - type TSingleCondition, - type TSurveyAdvancedLogic, - type TSurveyAdvancedLogicAction, - type TSurveyLogicConditionsOperator, - ZActionCalculateNumber, - ZActionCalculateText, - ZSurveyAdvancedLogic, -} from "./logic"; import { FORBIDDEN_IDS, findLanguageCodesForDuplicateLabels, @@ -238,6 +228,226 @@ export const ZSurveyPictureChoice = z.object({ export type TSurveyQuestionChoice = z.infer; +// Logic types +export const ZSurveyLogicConditionsOperator = z.enum([ + "equals", + "doesNotEqual", + "contains", + "doesNotContain", + "startsWith", + "doesNotStartWith", + "endsWith", + "doesNotEndWith", + "isSubmitted", + "isSkipped", + "isGreaterThan", + "isLessThan", + "isGreaterThanOrEqual", + "isLessThanOrEqual", + "equalsOneOf", + "includesAllOf", + "includesOneOf", + "isClicked", + "isAccepted", + "isBefore", + "isAfter", + "isBooked", + "isPartiallySubmitted", + "isCompletelySubmitted", +]); + +const operatorsWithoutRightOperand = [ + ZSurveyLogicConditionsOperator.Enum.isSubmitted, + ZSurveyLogicConditionsOperator.Enum.isSkipped, + ZSurveyLogicConditionsOperator.Enum.isClicked, + ZSurveyLogicConditionsOperator.Enum.isAccepted, + ZSurveyLogicConditionsOperator.Enum.isBooked, + ZSurveyLogicConditionsOperator.Enum.isPartiallySubmitted, + ZSurveyLogicConditionsOperator.Enum.isCompletelySubmitted, +] as const; + +export const ZDyanmicLogicField = z.enum(["question", "variable", "hiddenField"]); +export const ZActionObjective = z.enum(["calculate", "requireAnswer", "jumpToQuestion"]); +export const ZActionTextVariableCalculateOperator = z.enum(["assign", "concat"], { + message: "Conditional Logic: Invalid operator for a text variable", +}); +export const ZActionNumberVariableCalculateOperator = z.enum( + ["add", "subtract", "multiply", "divide", "assign"], + { message: "Conditional Logic: Invalid operator for a number variable" } +); + +const ZDynamicQuestion = z.object({ + type: z.literal("question"), + value: z.string().min(1, "Conditional Logic: Question id cannot be empty"), +}); + +const ZDynamicVariable = z.object({ + type: z.literal("variable"), + value: z + .string() + .cuid2({ message: "Conditional Logic: Variable id must be a valid cuid" }) + .min(1, "Conditional Logic: Variable id cannot be empty"), +}); + +const ZDynamicHiddenField = z.object({ + type: z.literal("hiddenField"), + value: z.string().min(1, "Conditional Logic: Hidden field id cannot be empty"), +}); + +const ZDynamicLogicFieldValue = z.union([ZDynamicQuestion, ZDynamicVariable, ZDynamicHiddenField], { + message: "Conditional Logic: Invalid dynamic field value", +}); + +export type TSurveyLogicConditionsOperator = z.infer; +export type TDyanmicLogicField = z.infer; +export type TActionObjective = z.infer; +export type TActionTextVariableCalculateOperator = z.infer; +export type TActionNumberVariableCalculateOperator = z.infer; + +// Conditions +const ZLeftOperand = ZDynamicLogicFieldValue; +export type TLeftOperand = z.infer; + +export const ZRightOperandStatic = z.object({ + type: z.literal("static"), + value: z.union([z.string(), z.number(), z.array(z.string())]), +}); + +export const ZRightOperand = z.union([ZRightOperandStatic, ZDynamicLogicFieldValue]); +export type TRightOperand = z.infer; + +export const ZSingleCondition = z + .object({ + id: ZId, + leftOperand: ZLeftOperand, + operator: ZSurveyLogicConditionsOperator, + rightOperand: ZRightOperand.optional(), + }) + .and( + z.object({ + connector: z.undefined(), + }) + ) + .superRefine((val, ctx) => { + if ( + !operatorsWithoutRightOperand.includes(val.operator as (typeof operatorsWithoutRightOperand)[number]) + ) { + if (val.rightOperand === undefined) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: `Conditional Logic: right operand is required for operator "${val.operator}"`, + path: ["rightOperand"], + }); + } else if (val.rightOperand.type === "static" && val.rightOperand.value === "") { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: `Conditional Logic: right operand value cannot be empty for operator "${val.operator}"`, + }); + } + } else if (val.rightOperand !== undefined) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: `Conditional Logic: right operand should not be present for operator "${val.operator}"`, + path: ["rightOperand"], + }); + } + }); + +export type TSingleCondition = z.infer; + +export interface TConditionGroup { + id: string; + connector: "and" | "or"; + conditions: (TSingleCondition | TConditionGroup)[]; +} + +const ZConditionGroup: z.ZodType = z.lazy(() => + z.object({ + id: ZId, + connector: z.enum(["and", "or"]), + conditions: z.array(z.union([ZSingleCondition, ZConditionGroup])), + }) +); + +// Actions +export const ZActionVariableValueType = z.union([z.literal("static"), ZDyanmicLogicField]); +export type TActionVariableValueType = z.infer; + +const ZActionBase = z.object({ + id: ZId, + objective: ZActionObjective, +}); + +export type TActionBase = z.infer; + +const ZActionCalculateBase = ZActionBase.extend({ + objective: z.literal("calculate"), + variableId: z.string(), +}); + +export const ZActionCalculateText = ZActionCalculateBase.extend({ + operator: ZActionTextVariableCalculateOperator, + value: z.union([ + z.object({ + type: z.literal("static"), + value: z + .string({ message: "Conditional Logic: Value must be a string for text variable" }) + .min(1, "Conditional Logic: Please enter a value in logic field"), + }), + ZDynamicLogicFieldValue, + ]), +}); + +export const ZActionCalculateNumber = ZActionCalculateBase.extend({ + operator: ZActionNumberVariableCalculateOperator, + value: z.union([ + z.object({ + type: z.literal("static"), + value: z.number({ message: "Conditional Logic: Value must be a number for number variable" }), + }), + ZDynamicLogicFieldValue, + ]), +}).superRefine((val, ctx) => { + if (val.operator === "divide" && val.value.type === "static" && val.value.value === 0) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Conditional Logic: Cannot divide by zero", + path: ["value", "value"], + }); + } +}); + +const ZActionCalculate = z.union([ZActionCalculateText, ZActionCalculateNumber]); + +export type TActionCalculate = z.infer; + +const ZActionRequireAnswer = ZActionBase.extend({ + objective: z.literal("requireAnswer"), + target: z.string().min(1, "Conditional Logic: Target question id cannot be empty"), +}); +export type TActionRequireAnswer = z.infer; + +const ZActionJumpToQuestion = ZActionBase.extend({ + objective: z.literal("jumpToQuestion"), + target: z.string().min(1, "Conditional Logic: Target question id cannot be empty"), +}); + +export type TActionJumpToQuestion = z.infer; + +export const ZSurveyLogicAction = z.union([ZActionCalculate, ZActionRequireAnswer, ZActionJumpToQuestion]); + +export type TSurveyLogicAction = z.infer; + +const ZSurveyLogicActions = z.array(ZSurveyLogicAction); + +export const ZSurveyLogic = z.object({ + id: ZId, + conditions: ZConditionGroup, + actions: ZSurveyLogicActions, +}); + +export type TSurveyLogic = z.infer; + export const ZSurveyQuestionBase = z.object({ id: ZSurveyQuestionId, type: z.string(), @@ -250,7 +460,7 @@ export const ZSurveyQuestionBase = z.object({ backButtonLabel: ZI18nString.optional(), scale: z.enum(["number", "smiley", "star"]).optional(), range: z.union([z.literal(5), z.literal(3), z.literal(4), z.literal(7), z.literal(10)]).optional(), - logic: z.array(ZSurveyAdvancedLogic).optional(), + logic: z.array(ZSurveyLogic).optional(), isDraft: z.boolean().optional(), }); @@ -1628,7 +1838,7 @@ const validateActions = ( survey: TSurvey, questionIndex: number, logicIndex: number, - actions: TSurveyAdvancedLogicAction[] + actions: TSurveyLogicAction[] ): z.ZodIssue[] => { const questionIds = survey.questions.map((q) => q.id); @@ -1701,11 +1911,7 @@ const validateActions = ( return actionIssues.filter((issue) => issue !== undefined); }; -const validateLogic = ( - survey: TSurvey, - questionIndex: number, - logic: TSurveyAdvancedLogic[] -): z.ZodIssue[] => { +const validateLogic = (survey: TSurvey, questionIndex: number, logic: TSurveyLogic[]): z.ZodIssue[] => { const logicIssues = logic.map((logicItem, logicIndex) => { return [ ...validateConditions(survey, questionIndex, logicIndex, logicItem.conditions), diff --git a/packages/types/surveys/validation.ts b/packages/types/surveys/validation.ts index d3eea0a7ca..a34c549b6d 100644 --- a/packages/types/surveys/validation.ts +++ b/packages/types/surveys/validation.ts @@ -2,10 +2,12 @@ import { z } from "zod"; import type { TActionJumpToQuestion, TConditionGroup, + TI18nString, TSingleCondition, - TSurveyAdvancedLogicAction, -} from "./logic"; -import type { TI18nString, TSurveyLanguage, TSurveyQuestion } from "./types"; + TSurveyLanguage, + TSurveyLogicAction, + TSurveyQuestion, +} from "./types"; export const FORBIDDEN_IDS = [ "userId", @@ -230,7 +232,7 @@ export const findQuestionsWithCyclicLogic = (questions: TSurveyQuestion[]): stri }; // Helper function to find all "jumpToQuestion" actions in the logic -const findJumpToQuestionActions = (actions: TSurveyAdvancedLogicAction[]): TActionJumpToQuestion[] => { +const findJumpToQuestionActions = (actions: TSurveyLogicAction[]): TActionJumpToQuestion[] => { return actions.filter((action) => action.objective === "jumpToQuestion"); };