fix: code review changes

This commit is contained in:
Piyush Gupta
2024-09-23 23:21:46 +05:30
parent 744f3410ae
commit 483bdc0eff
23 changed files with 354 additions and 411 deletions

View File

@@ -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({
<div
key={logicItem.id}
className="flex w-full grow items-start gap-2 rounded-lg border border-slate-200 bg-slate-50 p-4">
<AdvancedLogicEditor
<LogicEditor
localSurvey={transformedSurvey}
logicItem={logicItem}
updateQuestion={updateQuestion}

View File

@@ -3,7 +3,7 @@
import { EditorCardMenu } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/EditorCardMenu";
import { EndScreenForm } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/EndScreenForm";
import { RedirectUrlForm } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/RedirectUrlForm";
import { formatTextWithSlashes } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/util";
import { formatTextWithSlashes } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { createId } from "@paralleldrive/cuid2";

View File

@@ -1,6 +1,6 @@
"use client";
import { findHiddenFieldUsedInLogic } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/util";
import { findHiddenFieldUsedInLogic } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils";
import * as Collapsible from "@radix-ui/react-collapsible";
import { EyeOff } from "lucide-react";
import { useState } from "react";

View File

@@ -1,12 +1,11 @@
import { AdvancedLogicEditorActions } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedLogicEditorActions";
import { AdvancedLogicEditorConditions } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedLogicEditorConditions";
import { LogicEditorActions } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditorActions";
import { LogicEditorConditions } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditorConditions";
import { ArrowRightIcon } from "lucide-react";
import { TSurveyAdvancedLogic } from "@formbricks/types/surveys/logic";
import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys/types";
import { TSurvey, TSurveyLogic, TSurveyQuestion } from "@formbricks/types/surveys/types";
interface AdvancedLogicEditorProps {
interface LogicEditorProps {
localSurvey: TSurvey;
logicItem: TSurveyAdvancedLogic;
logicItem: TSurveyLogic;
updateQuestion: (questionIdx: number, updatedAttributes: any) => 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 (
<div className="flex w-full grow flex-col gap-4 overflow-x-auto text-sm">
<AdvancedLogicEditorConditions
<LogicEditorConditions
conditions={logicItem.conditions}
updateQuestion={updateQuestion}
question={question}
@@ -33,7 +32,7 @@ export function AdvancedLogicEditor({
localSurvey={localSurvey}
logicIdx={logicIdx}
/>
<AdvancedLogicEditorActions
<LogicEditorActions
logicItem={logicItem}
logicIdx={logicIdx}
question={question}

View File

@@ -4,7 +4,7 @@ import {
getActionTargetOptions,
getActionValueOptions,
getActionVariableOptions,
} 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, CornerDownRightIcon, MoreVerticalIcon, PlusIcon, TrashIcon } from "lucide-react";
import { getUpdatedActionBody } from "@formbricks/lib/survey/logic/utils";
@@ -13,10 +13,11 @@ import {
TActionObjective,
TActionTextVariableCalculateOperator,
TActionVariableValueType,
TSurveyAdvancedLogic,
TSurveyAdvancedLogicAction,
} from "@formbricks/types/surveys/logic";
import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys/types";
TSurvey,
TSurveyLogic,
TSurveyLogicAction,
TSurveyQuestion,
} from "@formbricks/types/surveys/types";
import {
DropdownMenu,
DropdownMenuContent,
@@ -25,29 +26,29 @@ import {
} from "@formbricks/ui/DropdownMenu";
import { InputCombobox } from "@formbricks/ui/InputCombobox";
interface AdvancedLogicEditorActions {
interface LogicEditorActions {
localSurvey: TSurvey;
logicItem: TSurveyAdvancedLogic;
logicItem: TSurveyLogic;
logicIdx: number;
question: TSurveyQuestion;
updateQuestion: (questionIdx: number, updatedAttributes: any) => 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<TSurveyAdvancedLogicAction>) => {
const handleValuesChange = (actionIdx: number, values: Partial<TSurveyLogicAction>) => {
const action = actions[actionIdx];
const actionBody = { ...action, ...values } as TSurveyAdvancedLogicAction;
const actionBody = { ...action, ...values } as TSurveyLogicAction;
handleActionsChange("update", actionIdx, actionBody);
};

View File

@@ -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({
</div>
)}
<div className="rounded-lg border border-slate-400 p-3">
<AdvancedLogicEditorConditions
<LogicEditorConditions
conditions={condition}
updateQuestion={updateQuestion}
localSurvey={localSurvey}

View File

@@ -1,6 +1,6 @@
"use client";
import { findOptionUsedInLogic } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/util";
import { findOptionUsedInLogic } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils";
import { DndContext } from "@dnd-kit/core";
import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
import { createId } from "@paralleldrive/cuid2";

View File

@@ -1,7 +1,7 @@
"use client";
import { RankingQuestionForm } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/RankingQuestionForm";
import { formatTextWithSlashes } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/util";
import { formatTextWithSlashes } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import * as Collapsible from "@radix-ui/react-collapsible";

View File

@@ -2,7 +2,7 @@
import { AddEndingCardButton } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AddEndingCardButton";
import { SurveyVariablesCard } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyVariablesCard";
import { findQuestionUsedInLogic } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/util";
import { findQuestionUsedInLogic } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils";
import {
DndContext,
DragEndEvent,
@@ -27,9 +27,9 @@ import { TProduct } from "@formbricks/types/product";
import {
TConditionGroup,
TSingleCondition,
TSurveyAdvancedLogic,
TSurveyAdvancedLogicAction,
} from "@formbricks/types/surveys/logic";
TSurveyLogic,
TSurveyLogicAction,
} from "@formbricks/types/surveys/types";
import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys/types";
import { findQuestionsWithCyclicLogic } from "@formbricks/types/surveys/validation";
import {
@@ -115,7 +115,7 @@ export const QuestionsView = ({
return updatedCondition;
};
const updateActions = (actions: TSurveyAdvancedLogicAction[]): TSurveyAdvancedLogicAction[] => {
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),

View File

@@ -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";

View File

@@ -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: {

View File

@@ -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);
};

View File

@@ -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,

View File

@@ -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,

View File

@@ -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":

View File

@@ -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,

View File

@@ -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
): {

View File

@@ -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,

View File

@@ -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
): {

View File

@@ -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);
}

View File

@@ -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<typeof ZSurveyLogicConditionsOperator>;
export type TDyanmicLogicField = z.infer<typeof ZDyanmicLogicField>;
export type TActionObjective = z.infer<typeof ZActionObjective>;
export type TActionTextVariableCalculateOperator = z.infer<typeof ZActionTextVariableCalculateOperator>;
export type TActionNumberVariableCalculateOperator = z.infer<typeof ZActionNumberVariableCalculateOperator>;
// Conditions
const ZLeftOperand = ZDynamicLogicFieldValue;
export type TLeftOperand = z.infer<typeof ZLeftOperand>;
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<typeof ZRightOperand>;
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<typeof ZSingleCondition>;
export interface TConditionGroup {
id: string;
connector: "and" | "or";
conditions: (TSingleCondition | TConditionGroup)[];
}
const ZConditionGroup: z.ZodType<TConditionGroup> = 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<typeof ZActionVariableValueType>;
const ZActionBase = z.object({
id: ZId,
objective: ZActionObjective,
});
export type TActionBase = z.infer<typeof ZActionBase>;
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<typeof ZActionCalculate>;
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<typeof ZActionRequireAnswer>;
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<typeof ZActionJumpToQuestion>;
export const ZSurveyAdvancedLogicAction = z.union([
ZActionCalculate,
ZActionRequireAnswer,
ZActionJumpToQuestion,
]);
export type TSurveyAdvancedLogicAction = z.infer<typeof ZSurveyAdvancedLogicAction>;
const ZSurveyAdvancedLogicActions = z.array(ZSurveyAdvancedLogicAction);
export const ZSurveyAdvancedLogic = z.object({
id: ZId,
conditions: ZConditionGroup,
actions: ZSurveyAdvancedLogicActions,
});
export type TSurveyAdvancedLogic = z.infer<typeof ZSurveyAdvancedLogic>;

View File

@@ -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<typeof ZSurveyQuestionChoice>;
// 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<typeof ZSurveyLogicConditionsOperator>;
export type TDyanmicLogicField = z.infer<typeof ZDyanmicLogicField>;
export type TActionObjective = z.infer<typeof ZActionObjective>;
export type TActionTextVariableCalculateOperator = z.infer<typeof ZActionTextVariableCalculateOperator>;
export type TActionNumberVariableCalculateOperator = z.infer<typeof ZActionNumberVariableCalculateOperator>;
// Conditions
const ZLeftOperand = ZDynamicLogicFieldValue;
export type TLeftOperand = z.infer<typeof ZLeftOperand>;
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<typeof ZRightOperand>;
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<typeof ZSingleCondition>;
export interface TConditionGroup {
id: string;
connector: "and" | "or";
conditions: (TSingleCondition | TConditionGroup)[];
}
const ZConditionGroup: z.ZodType<TConditionGroup> = 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<typeof ZActionVariableValueType>;
const ZActionBase = z.object({
id: ZId,
objective: ZActionObjective,
});
export type TActionBase = z.infer<typeof ZActionBase>;
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<typeof ZActionCalculate>;
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<typeof ZActionRequireAnswer>;
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<typeof ZActionJumpToQuestion>;
export const ZSurveyLogicAction = z.union([ZActionCalculate, ZActionRequireAnswer, ZActionJumpToQuestion]);
export type TSurveyLogicAction = z.infer<typeof ZSurveyLogicAction>;
const ZSurveyLogicActions = z.array(ZSurveyLogicAction);
export const ZSurveyLogic = z.object({
id: ZId,
conditions: ZConditionGroup,
actions: ZSurveyLogicActions,
});
export type TSurveyLogic = z.infer<typeof ZSurveyLogic>;
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),

View File

@@ -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");
};