mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-31 02:40:31 -06:00
fix: build error
This commit is contained in:
@@ -12,215 +12,395 @@ export const XMSurveyDefault: TXMTemplate = {
|
||||
},
|
||||
};
|
||||
|
||||
const NPSSurvey: TXMTemplate = {
|
||||
...XMSurveyDefault,
|
||||
name: "NPS Survey",
|
||||
questions: [
|
||||
{
|
||||
id: createId(),
|
||||
type: TSurveyQuestionTypeEnum.NPS,
|
||||
headline: { default: "How likely are you to recommend {{productName}} to a friend or colleague?" },
|
||||
required: true,
|
||||
lowerLabel: { default: "Not at all likely" },
|
||||
upperLabel: { default: "Extremely likely" },
|
||||
isColorCodingEnabled: true,
|
||||
},
|
||||
{
|
||||
id: createId(),
|
||||
type: TSurveyQuestionTypeEnum.OpenText,
|
||||
headline: { default: "To help us improve, can you describe the reason(s) for your rating?" },
|
||||
required: false,
|
||||
inputType: "text",
|
||||
},
|
||||
{
|
||||
id: createId(),
|
||||
type: TSurveyQuestionTypeEnum.OpenText,
|
||||
headline: { default: "Any other comments, feedback, or concerns?" },
|
||||
required: false,
|
||||
inputType: "text",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const StarRatingSurvey: TXMTemplate = {
|
||||
...XMSurveyDefault,
|
||||
name: "{{productName}}'s Rating Survey",
|
||||
questions: [
|
||||
{
|
||||
id: createId(),
|
||||
type: TSurveyQuestionTypeEnum.Rating,
|
||||
logic: [{ value: 3, condition: "lessEqual", destination: "tk9wpw2gxgb8fa6pbpp3qq5l" }],
|
||||
range: 5,
|
||||
scale: "number",
|
||||
headline: { default: "How do you like {{productName}}?" },
|
||||
required: true,
|
||||
lowerLabel: { default: "Extremely dissatisfied" },
|
||||
upperLabel: { default: "Extremely satisfied" },
|
||||
isColorCodingEnabled: false,
|
||||
},
|
||||
{
|
||||
id: createId(),
|
||||
html: { default: '<p class="fb-editor-paragraph" dir="ltr"><span>This helps us a lot.</span></p>' },
|
||||
type: TSurveyQuestionTypeEnum.CTA,
|
||||
logic: [{ condition: "clicked", destination: XMSurveyDefault.endings[0].id }],
|
||||
headline: { default: "Happy to hear 🙏 Please write a review for us!" },
|
||||
required: true,
|
||||
buttonUrl: "https://formbricks.com/github",
|
||||
buttonLabel: { default: "Write review" },
|
||||
buttonExternal: true,
|
||||
},
|
||||
{
|
||||
id: "tk9wpw2gxgb8fa6pbpp3qq5l",
|
||||
type: TSurveyQuestionTypeEnum.OpenText,
|
||||
headline: { default: "Sorry to hear! What is ONE thing we can do better?" },
|
||||
required: true,
|
||||
subheader: { default: "Help us improve your experience." },
|
||||
buttonLabel: { default: "Send" },
|
||||
placeholder: { default: "Type your answer here..." },
|
||||
inputType: "text",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const CSATSurvey: TXMTemplate = {
|
||||
...XMSurveyDefault,
|
||||
name: "{{productName}} CSAT",
|
||||
questions: [
|
||||
{
|
||||
id: createId(),
|
||||
type: TSurveyQuestionTypeEnum.Rating,
|
||||
logic: [{ value: 3, condition: "lessEqual", destination: "vyo4mkw4ln95ts4ya7qp2tth" }],
|
||||
range: 5,
|
||||
scale: "smiley",
|
||||
headline: { default: "How satisfied are you with your {{productName}} experience?" },
|
||||
required: true,
|
||||
lowerLabel: { default: "Extremely dissatisfied" },
|
||||
upperLabel: { default: "Extremely satisfied" },
|
||||
isColorCodingEnabled: false,
|
||||
},
|
||||
{
|
||||
id: createId(),
|
||||
type: TSurveyQuestionTypeEnum.OpenText,
|
||||
logic: [{ condition: "submitted", destination: XMSurveyDefault.endings[0].id }],
|
||||
headline: { default: "Lovely! Is there anything we can do to improve your experience?" },
|
||||
required: false,
|
||||
placeholder: { default: "Type your answer here..." },
|
||||
inputType: "text",
|
||||
},
|
||||
{
|
||||
id: "vyo4mkw4ln95ts4ya7qp2tth",
|
||||
type: TSurveyQuestionTypeEnum.OpenText,
|
||||
headline: { default: "Ugh, sorry! Is there anything we can do to improve your experience?" },
|
||||
required: false,
|
||||
placeholder: { default: "Type your answer here..." },
|
||||
inputType: "text",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const CESSurvey: TXMTemplate = {
|
||||
...XMSurveyDefault,
|
||||
name: "CES Survey",
|
||||
questions: [
|
||||
{
|
||||
id: createId(),
|
||||
type: TSurveyQuestionTypeEnum.Rating,
|
||||
range: 5,
|
||||
scale: "number",
|
||||
headline: { default: "{{productName}} makes it easy for me to [ADD GOAL]" },
|
||||
required: true,
|
||||
lowerLabel: { default: "Disagree strongly" },
|
||||
upperLabel: { default: "Agree strongly" },
|
||||
isColorCodingEnabled: false,
|
||||
},
|
||||
{
|
||||
id: createId(),
|
||||
type: TSurveyQuestionTypeEnum.OpenText,
|
||||
headline: { default: "Thanks! How could we make it easier for you to [ADD GOAL]?" },
|
||||
required: true,
|
||||
placeholder: { default: "Type your answer here..." },
|
||||
inputType: "text",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const SmileysRatingSurvey: TXMTemplate = {
|
||||
...XMSurveyDefault,
|
||||
name: "Smileys Survey",
|
||||
questions: [
|
||||
{
|
||||
id: createId(),
|
||||
type: TSurveyQuestionTypeEnum.Rating,
|
||||
logic: [{ value: 3, condition: "lessEqual", destination: "tk9wpw2gxgb8fa6pbpp3qq5l" }],
|
||||
range: 5,
|
||||
scale: "smiley",
|
||||
headline: { default: "How do you like {{productName}}?" },
|
||||
required: true,
|
||||
lowerLabel: { default: "Not good" },
|
||||
upperLabel: { default: "Very satisfied" },
|
||||
isColorCodingEnabled: false,
|
||||
},
|
||||
{
|
||||
id: createId(),
|
||||
html: { default: '<p class="fb-editor-paragraph" dir="ltr"><span>This helps us a lot.</span></p>' },
|
||||
type: TSurveyQuestionTypeEnum.CTA,
|
||||
logic: [{ condition: "clicked", destination: XMSurveyDefault.endings[0].id }],
|
||||
headline: { default: "Happy to hear 🙏 Please write a review for us!" },
|
||||
required: true,
|
||||
buttonUrl: "https://formbricks.com/github",
|
||||
buttonLabel: { default: "Write review" },
|
||||
buttonExternal: true,
|
||||
},
|
||||
{
|
||||
id: "tk9wpw2gxgb8fa6pbpp3qq5l",
|
||||
type: TSurveyQuestionTypeEnum.OpenText,
|
||||
headline: { default: "Sorry to hear! What is ONE thing we can do better?" },
|
||||
required: true,
|
||||
subheader: { default: "Help us improve your experience." },
|
||||
buttonLabel: { default: "Send" },
|
||||
placeholder: { default: "Type your answer here..." },
|
||||
inputType: "text",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const eNPSSurvey: TXMTemplate = {
|
||||
...XMSurveyDefault,
|
||||
name: "eNPS Survey",
|
||||
questions: [
|
||||
{
|
||||
id: createId(),
|
||||
type: TSurveyQuestionTypeEnum.NPS,
|
||||
headline: {
|
||||
default: "How likely are you to recommend working at this company to a friend or colleague?",
|
||||
const NPSSurvey = (): TXMTemplate => {
|
||||
return {
|
||||
...XMSurveyDefault,
|
||||
name: "NPS Survey",
|
||||
questions: [
|
||||
{
|
||||
id: createId(),
|
||||
type: TSurveyQuestionTypeEnum.NPS,
|
||||
headline: { default: "How likely are you to recommend {{productName}} to a friend or colleague?" },
|
||||
required: true,
|
||||
lowerLabel: { default: "Not at all likely" },
|
||||
upperLabel: { default: "Extremely likely" },
|
||||
isColorCodingEnabled: true,
|
||||
},
|
||||
required: false,
|
||||
lowerLabel: { default: "Not at all likely" },
|
||||
upperLabel: { default: "Extremely likely" },
|
||||
isColorCodingEnabled: true,
|
||||
},
|
||||
{
|
||||
id: createId(),
|
||||
type: TSurveyQuestionTypeEnum.OpenText,
|
||||
headline: { default: "To help us improve, can you describe the reason(s) for your rating?" },
|
||||
required: false,
|
||||
inputType: "text",
|
||||
},
|
||||
{
|
||||
id: createId(),
|
||||
type: TSurveyQuestionTypeEnum.OpenText,
|
||||
headline: { default: "Any other comments, feedback, or concerns?" },
|
||||
required: false,
|
||||
inputType: "text",
|
||||
},
|
||||
],
|
||||
{
|
||||
id: createId(),
|
||||
type: TSurveyQuestionTypeEnum.OpenText,
|
||||
headline: { default: "To help us improve, can you describe the reason(s) for your rating?" },
|
||||
required: false,
|
||||
inputType: "text",
|
||||
},
|
||||
{
|
||||
id: createId(),
|
||||
type: TSurveyQuestionTypeEnum.OpenText,
|
||||
headline: { default: "Any other comments, feedback, or concerns?" },
|
||||
required: false,
|
||||
inputType: "text",
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
const StarRatingSurvey = (): TXMTemplate => {
|
||||
const reusableQuestionIds = [createId(), createId(), createId()];
|
||||
|
||||
return {
|
||||
...XMSurveyDefault,
|
||||
name: "{{productName}}'s Rating Survey",
|
||||
questions: [
|
||||
{
|
||||
id: reusableQuestionIds[0],
|
||||
type: TSurveyQuestionTypeEnum.Rating,
|
||||
logic: [
|
||||
{
|
||||
id: createId(),
|
||||
conditions: {
|
||||
id: createId(),
|
||||
connector: "and",
|
||||
conditions: [
|
||||
{
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: reusableQuestionIds[0],
|
||||
type: "question",
|
||||
},
|
||||
operator: "isLessThanOrEqual",
|
||||
rightOperand: {
|
||||
type: "static",
|
||||
value: 3,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
id: createId(),
|
||||
objective: "jumpToQuestion",
|
||||
target: reusableQuestionIds[2],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
range: 5,
|
||||
scale: "number",
|
||||
headline: { default: "How do you like {{productName}}?" },
|
||||
required: true,
|
||||
lowerLabel: { default: "Extremely dissatisfied" },
|
||||
upperLabel: { default: "Extremely satisfied" },
|
||||
isColorCodingEnabled: false,
|
||||
},
|
||||
{
|
||||
id: reusableQuestionIds[1],
|
||||
html: { default: '<p class="fb-editor-paragraph" dir="ltr"><span>This helps us a lot.</span></p>' },
|
||||
type: TSurveyQuestionTypeEnum.CTA,
|
||||
logic: [
|
||||
{
|
||||
id: createId(),
|
||||
conditions: {
|
||||
id: createId(),
|
||||
connector: "and",
|
||||
conditions: [
|
||||
{
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: reusableQuestionIds[1],
|
||||
type: "question",
|
||||
},
|
||||
operator: "isClicked",
|
||||
},
|
||||
],
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
id: createId(),
|
||||
objective: "jumpToQuestion",
|
||||
target: XMSurveyDefault.endings[0].id,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
headline: { default: "Happy to hear 🙏 Please write a review for us!" },
|
||||
required: true,
|
||||
buttonUrl: "https://formbricks.com/github",
|
||||
buttonLabel: { default: "Write review" },
|
||||
buttonExternal: true,
|
||||
},
|
||||
{
|
||||
id: reusableQuestionIds[2],
|
||||
type: TSurveyQuestionTypeEnum.OpenText,
|
||||
headline: { default: "Sorry to hear! What is ONE thing we can do better?" },
|
||||
required: true,
|
||||
subheader: { default: "Help us improve your experience." },
|
||||
buttonLabel: { default: "Send" },
|
||||
placeholder: { default: "Type your answer here..." },
|
||||
inputType: "text",
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
const CSATSurvey = (): TXMTemplate => {
|
||||
const reusableQuestionIds = [createId(), createId(), createId()];
|
||||
|
||||
return {
|
||||
...XMSurveyDefault,
|
||||
name: "{{productName}} CSAT",
|
||||
questions: [
|
||||
{
|
||||
id: reusableQuestionIds[0],
|
||||
type: TSurveyQuestionTypeEnum.Rating,
|
||||
logic: [
|
||||
{
|
||||
id: createId(),
|
||||
conditions: {
|
||||
id: createId(),
|
||||
connector: "and",
|
||||
conditions: [
|
||||
{
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: reusableQuestionIds[0],
|
||||
type: "question",
|
||||
},
|
||||
operator: "isLessThanOrEqual",
|
||||
rightOperand: {
|
||||
type: "static",
|
||||
value: 3,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
id: createId(),
|
||||
objective: "jumpToQuestion",
|
||||
target: reusableQuestionIds[2],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
range: 5,
|
||||
scale: "smiley",
|
||||
headline: { default: "How satisfied are you with your {{productName}} experience?" },
|
||||
required: true,
|
||||
lowerLabel: { default: "Extremely dissatisfied" },
|
||||
upperLabel: { default: "Extremely satisfied" },
|
||||
isColorCodingEnabled: false,
|
||||
},
|
||||
{
|
||||
id: reusableQuestionIds[1],
|
||||
type: TSurveyQuestionTypeEnum.OpenText,
|
||||
logic: [
|
||||
{
|
||||
id: createId(),
|
||||
conditions: {
|
||||
id: createId(),
|
||||
connector: "and",
|
||||
conditions: [
|
||||
{
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: reusableQuestionIds[1],
|
||||
type: "question",
|
||||
},
|
||||
operator: "isSubmitted",
|
||||
},
|
||||
],
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
id: createId(),
|
||||
objective: "jumpToQuestion",
|
||||
target: XMSurveyDefault.endings[0].id,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
headline: { default: "Lovely! Is there anything we can do to improve your experience?" },
|
||||
required: false,
|
||||
placeholder: { default: "Type your answer here..." },
|
||||
inputType: "text",
|
||||
},
|
||||
{
|
||||
id: reusableQuestionIds[2],
|
||||
type: TSurveyQuestionTypeEnum.OpenText,
|
||||
headline: { default: "Ugh, sorry! Is there anything we can do to improve your experience?" },
|
||||
required: false,
|
||||
placeholder: { default: "Type your answer here..." },
|
||||
inputType: "text",
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
const CESSurvey = (): TXMTemplate => {
|
||||
return {
|
||||
...XMSurveyDefault,
|
||||
name: "CES Survey",
|
||||
questions: [
|
||||
{
|
||||
id: createId(),
|
||||
type: TSurveyQuestionTypeEnum.Rating,
|
||||
range: 5,
|
||||
scale: "number",
|
||||
headline: { default: "{{productName}} makes it easy for me to [ADD GOAL]" },
|
||||
required: true,
|
||||
lowerLabel: { default: "Disagree strongly" },
|
||||
upperLabel: { default: "Agree strongly" },
|
||||
isColorCodingEnabled: false,
|
||||
},
|
||||
{
|
||||
id: createId(),
|
||||
type: TSurveyQuestionTypeEnum.OpenText,
|
||||
headline: { default: "Thanks! How could we make it easier for you to [ADD GOAL]?" },
|
||||
required: true,
|
||||
placeholder: { default: "Type your answer here..." },
|
||||
inputType: "text",
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
const SmileysRatingSurvey = (): TXMTemplate => {
|
||||
const reusableQuestionIds = [createId(), createId(), createId()];
|
||||
|
||||
return {
|
||||
...XMSurveyDefault,
|
||||
name: "Smileys Survey",
|
||||
questions: [
|
||||
{
|
||||
id: reusableQuestionIds[0],
|
||||
type: TSurveyQuestionTypeEnum.Rating,
|
||||
logic: [
|
||||
{
|
||||
id: createId(),
|
||||
conditions: {
|
||||
id: createId(),
|
||||
connector: "and",
|
||||
conditions: [
|
||||
{
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: reusableQuestionIds[0],
|
||||
type: "question",
|
||||
},
|
||||
operator: "isLessThanOrEqual",
|
||||
rightOperand: {
|
||||
type: "static",
|
||||
value: 3,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
id: createId(),
|
||||
objective: "jumpToQuestion",
|
||||
target: reusableQuestionIds[2],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
range: 5,
|
||||
scale: "smiley",
|
||||
headline: { default: "How do you like {{productName}}?" },
|
||||
required: true,
|
||||
lowerLabel: { default: "Not good" },
|
||||
upperLabel: { default: "Very satisfied" },
|
||||
isColorCodingEnabled: false,
|
||||
},
|
||||
{
|
||||
id: reusableQuestionIds[1],
|
||||
html: { default: '<p class="fb-editor-paragraph" dir="ltr"><span>This helps us a lot.</span></p>' },
|
||||
type: TSurveyQuestionTypeEnum.CTA,
|
||||
logic: [
|
||||
{
|
||||
id: createId(),
|
||||
conditions: {
|
||||
id: createId(),
|
||||
connector: "and",
|
||||
conditions: [
|
||||
{
|
||||
id: createId(),
|
||||
leftOperand: {
|
||||
value: reusableQuestionIds[1],
|
||||
type: "question",
|
||||
},
|
||||
operator: "isClicked",
|
||||
},
|
||||
],
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
id: createId(),
|
||||
objective: "jumpToQuestion",
|
||||
target: XMSurveyDefault.endings[0].id,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
headline: { default: "Happy to hear 🙏 Please write a review for us!" },
|
||||
required: true,
|
||||
buttonUrl: "https://formbricks.com/github",
|
||||
buttonLabel: { default: "Write review" },
|
||||
buttonExternal: true,
|
||||
},
|
||||
{
|
||||
id: reusableQuestionIds[2],
|
||||
type: TSurveyQuestionTypeEnum.OpenText,
|
||||
headline: { default: "Sorry to hear! What is ONE thing we can do better?" },
|
||||
required: true,
|
||||
subheader: { default: "Help us improve your experience." },
|
||||
buttonLabel: { default: "Send" },
|
||||
placeholder: { default: "Type your answer here..." },
|
||||
inputType: "text",
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
const eNPSSurvey = (): TXMTemplate => {
|
||||
return {
|
||||
...XMSurveyDefault,
|
||||
name: "eNPS Survey",
|
||||
questions: [
|
||||
{
|
||||
id: createId(),
|
||||
type: TSurveyQuestionTypeEnum.NPS,
|
||||
headline: {
|
||||
default: "How likely are you to recommend working at this company to a friend or colleague?",
|
||||
},
|
||||
required: false,
|
||||
lowerLabel: { default: "Not at all likely" },
|
||||
upperLabel: { default: "Extremely likely" },
|
||||
isColorCodingEnabled: true,
|
||||
},
|
||||
{
|
||||
id: createId(),
|
||||
type: TSurveyQuestionTypeEnum.OpenText,
|
||||
headline: { default: "To help us improve, can you describe the reason(s) for your rating?" },
|
||||
required: false,
|
||||
inputType: "text",
|
||||
},
|
||||
{
|
||||
id: createId(),
|
||||
type: TSurveyQuestionTypeEnum.OpenText,
|
||||
headline: { default: "Any other comments, feedback, or concerns?" },
|
||||
required: false,
|
||||
inputType: "text",
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
export const XMTemplates: TXMTemplate[] = [
|
||||
NPSSurvey,
|
||||
StarRatingSurvey,
|
||||
CSATSurvey,
|
||||
CESSurvey,
|
||||
SmileysRatingSurvey,
|
||||
eNPSSurvey,
|
||||
NPSSurvey(),
|
||||
StarRatingSurvey(),
|
||||
CSATSurvey(),
|
||||
CESSurvey(),
|
||||
SmileysRatingSurvey(),
|
||||
eNPSSurvey(),
|
||||
];
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
TrashIcon,
|
||||
} from "lucide-react";
|
||||
import { useMemo } from "react";
|
||||
import { duplicateLogicItem } from "@formbricks/lib/survey/logic/utils";
|
||||
import { duplicateLogicItem } from "@formbricks/lib/surveyLogic/utils";
|
||||
import { replaceHeadlineRecall } from "@formbricks/lib/utils/recall";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
import { TSurvey, TSurveyLogic, TSurveyQuestion } from "@formbricks/types/surveys/types";
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} 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";
|
||||
import { getUpdatedActionBody } from "@formbricks/lib/surveyLogic/utils";
|
||||
import {
|
||||
TActionNumberVariableCalculateOperator,
|
||||
TActionObjective,
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
removeCondition,
|
||||
toggleGroupConnector,
|
||||
updateCondition,
|
||||
} from "@formbricks/lib/survey/logic/utils";
|
||||
} from "@formbricks/lib/surveyLogic/utils";
|
||||
import {
|
||||
TConditionGroup,
|
||||
TDyanmicLogicField,
|
||||
|
||||
@@ -18,7 +18,7 @@ import toast from "react-hot-toast";
|
||||
import { MultiLanguageCard } from "@formbricks/ee/multi-language/components/multi-language-card";
|
||||
import { addMultiLanguageLabels, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
||||
import { structuredClone } from "@formbricks/lib/pollyfills/structuredClone";
|
||||
import { isConditionGroup } from "@formbricks/lib/survey/logic/utils";
|
||||
import { isConditionGroup } from "@formbricks/lib/surveyLogic/utils";
|
||||
import { getDefaultEndingCard } from "@formbricks/lib/templates";
|
||||
import { checkForEmptyFallBackValue, extractRecallInfo } from "@formbricks/lib/utils/recall";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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 { isConditionGroup } from "@formbricks/lib/surveyLogic/utils";
|
||||
import { questionTypes } from "@formbricks/lib/utils/questions";
|
||||
import {
|
||||
TConditionGroup,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import "server-only";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { structuredClone } from "pollyfills/structuredClone";
|
||||
import {
|
||||
TResponse,
|
||||
TResponseData,
|
||||
@@ -29,9 +28,10 @@ import {
|
||||
TSurveySummary,
|
||||
} from "@formbricks/types/surveys/types";
|
||||
import { getLocalizedValue } from "../i18n/utils";
|
||||
import { structuredClone } from "../pollyfills/structuredClone";
|
||||
import { processResponseData } from "../responses";
|
||||
import { evaluateLogic, performActions } from "../surveyLogic/utils";
|
||||
import { getTodaysDateTimeFormatted } from "../time";
|
||||
import { evaluateLogic, performActions } from "../utils/evaluateLogic";
|
||||
import { sanitizeString } from "../utils/strings";
|
||||
|
||||
export const calculateTtcTotal = (ttc: TResponseTtc) => {
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import {
|
||||
TActionObjective,
|
||||
TConditionGroup,
|
||||
TSingleCondition,
|
||||
TSurveyLogic,
|
||||
TSurveyLogicAction,
|
||||
} from "@formbricks/types/surveys/types";
|
||||
|
||||
type TCondition = TSingleCondition | TConditionGroup;
|
||||
|
||||
export const isConditionGroup = (condition: TCondition): condition is TConditionGroup => {
|
||||
return (condition as TConditionGroup).connector !== undefined;
|
||||
};
|
||||
|
||||
export const duplicateLogicItem = (logicItem: TSurveyLogic): TSurveyLogic => {
|
||||
const duplicateConditionGroup = (group: TConditionGroup): TConditionGroup => {
|
||||
return {
|
||||
...group,
|
||||
id: createId(),
|
||||
conditions: group.conditions.map((condition) => {
|
||||
if (isConditionGroup(condition)) {
|
||||
return duplicateConditionGroup(condition);
|
||||
} else {
|
||||
return duplicateCondition(condition);
|
||||
}
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
const duplicateCondition = (condition: TSingleCondition): TSingleCondition => {
|
||||
return {
|
||||
...condition,
|
||||
id: createId(),
|
||||
};
|
||||
};
|
||||
|
||||
const duplicateAction = (action: TSurveyLogicAction): TSurveyLogicAction => {
|
||||
return {
|
||||
...action,
|
||||
id: createId(),
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
...logicItem,
|
||||
id: createId(),
|
||||
conditions: duplicateConditionGroup(logicItem.conditions),
|
||||
actions: logicItem.actions.map(duplicateAction),
|
||||
};
|
||||
};
|
||||
|
||||
export const addConditionBelow = (
|
||||
group: TConditionGroup,
|
||||
resourceId: string,
|
||||
condition: TSingleCondition
|
||||
) => {
|
||||
for (let i = 0; i < group.conditions.length; i++) {
|
||||
const item = group.conditions[i];
|
||||
|
||||
if (isConditionGroup(item)) {
|
||||
if (item.id === resourceId) {
|
||||
group.conditions.splice(i + 1, 0, condition);
|
||||
break;
|
||||
} else {
|
||||
addConditionBelow(item, resourceId, condition);
|
||||
}
|
||||
} else {
|
||||
if (item.id === resourceId) {
|
||||
group.conditions.splice(i + 1, 0, condition);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const toggleGroupConnector = (group: TConditionGroup, resourceId: string) => {
|
||||
if (group.id === resourceId) {
|
||||
group.connector = group.connector === "and" ? "or" : "and";
|
||||
return;
|
||||
}
|
||||
|
||||
for (const condition of group.conditions) {
|
||||
if (condition.connector) {
|
||||
toggleGroupConnector(condition, resourceId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const removeCondition = (group: TConditionGroup, resourceId: string) => {
|
||||
for (let i = 0; i < group.conditions.length; i++) {
|
||||
const item = group.conditions[i];
|
||||
|
||||
if (item.id === resourceId) {
|
||||
group.conditions.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isConditionGroup(item)) {
|
||||
removeCondition(item, resourceId);
|
||||
}
|
||||
}
|
||||
|
||||
deleteEmptyGroups(group);
|
||||
};
|
||||
|
||||
export const duplicateCondition = (group: TConditionGroup, resourceId: string) => {
|
||||
for (let i = 0; i < group.conditions.length; i++) {
|
||||
const item = group.conditions[i];
|
||||
|
||||
if (item.id === resourceId) {
|
||||
const newItem: TCondition = {
|
||||
...item,
|
||||
id: createId(),
|
||||
};
|
||||
group.conditions.splice(i + 1, 0, newItem);
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.connector) {
|
||||
duplicateCondition(item, resourceId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteEmptyGroups = (group: TConditionGroup) => {
|
||||
for (let i = 0; i < group.conditions.length; i++) {
|
||||
const resource = group.conditions[i];
|
||||
|
||||
if (isConditionGroup(resource) && resource.conditions.length === 0) {
|
||||
group.conditions.splice(i, 1);
|
||||
} else if (isConditionGroup(resource)) {
|
||||
deleteEmptyGroups(resource);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const createGroupFromResource = (group: TConditionGroup, resourceId: string) => {
|
||||
for (let i = 0; i < group.conditions.length; i++) {
|
||||
const item = group.conditions[i];
|
||||
|
||||
if (item.id === resourceId) {
|
||||
const newGroup: TConditionGroup = {
|
||||
id: createId(),
|
||||
connector: "and",
|
||||
conditions: [item],
|
||||
};
|
||||
group.conditions[i] = newGroup;
|
||||
group.connector = group.connector ?? "and";
|
||||
return;
|
||||
}
|
||||
|
||||
if (isConditionGroup(item)) {
|
||||
createGroupFromResource(item, resourceId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const updateCondition = (
|
||||
group: TConditionGroup,
|
||||
resourceId: string,
|
||||
condition: Partial<TSingleCondition>
|
||||
) => {
|
||||
for (let i = 0; i < group.conditions.length; i++) {
|
||||
const item = group.conditions[i];
|
||||
|
||||
if (item.id === resourceId && !("connector" in item)) {
|
||||
group.conditions[i] = { ...item, ...condition } as TSingleCondition;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isConditionGroup(item)) {
|
||||
updateCondition(item, resourceId, condition);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getUpdatedActionBody = (
|
||||
action: TSurveyLogicAction,
|
||||
objective: TActionObjective
|
||||
): TSurveyLogicAction => {
|
||||
if (objective === action.objective) return action;
|
||||
switch (objective) {
|
||||
case "calculate":
|
||||
return {
|
||||
id: action.id,
|
||||
objective: "calculate",
|
||||
variableId: "",
|
||||
operator: "assign",
|
||||
value: { type: "static", value: "" },
|
||||
};
|
||||
case "requireAnswer":
|
||||
return {
|
||||
id: action.id,
|
||||
objective: "requireAnswer",
|
||||
target: "",
|
||||
};
|
||||
case "jumpToQuestion":
|
||||
return {
|
||||
id: action.id,
|
||||
objective: "jumpToQuestion",
|
||||
target: "",
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { prisma } from "../../__mocks__/database";
|
||||
import { mockResponseNote, mockResponseWithMockPerson } from "../../response/tests/__mocks__/data.mock";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { evaluateLogic } from "utils/evaluateLogic";
|
||||
import { evaluateLogic } from "surveyLogic/utils";
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { testInputValidation } from "vitestSetup";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
|
||||
@@ -1,16 +1,215 @@
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import { TResponseData, TResponseVariables } from "@formbricks/types/responses";
|
||||
import {
|
||||
TActionCalculate,
|
||||
TActionObjective,
|
||||
TConditionGroup,
|
||||
TSingleCondition,
|
||||
TSurvey,
|
||||
TSurveyLogic,
|
||||
TSurveyLogicAction,
|
||||
TSurveyQuestion,
|
||||
TSurveyQuestionTypeEnum,
|
||||
TSurveyVariable,
|
||||
} from "@formbricks/types/surveys/types";
|
||||
import { getLocalizedValue } from "../i18n/utils";
|
||||
import { isConditionGroup } from "../survey/logic/utils";
|
||||
|
||||
type TCondition = TSingleCondition | TConditionGroup;
|
||||
|
||||
export const isConditionGroup = (condition: TCondition): condition is TConditionGroup => {
|
||||
return (condition as TConditionGroup).connector !== undefined;
|
||||
};
|
||||
|
||||
export const duplicateLogicItem = (logicItem: TSurveyLogic): TSurveyLogic => {
|
||||
const duplicateConditionGroup = (group: TConditionGroup): TConditionGroup => {
|
||||
return {
|
||||
...group,
|
||||
id: createId(),
|
||||
conditions: group.conditions.map((condition) => {
|
||||
if (isConditionGroup(condition)) {
|
||||
return duplicateConditionGroup(condition);
|
||||
} else {
|
||||
return duplicateCondition(condition);
|
||||
}
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
const duplicateCondition = (condition: TSingleCondition): TSingleCondition => {
|
||||
return {
|
||||
...condition,
|
||||
id: createId(),
|
||||
};
|
||||
};
|
||||
|
||||
const duplicateAction = (action: TSurveyLogicAction): TSurveyLogicAction => {
|
||||
return {
|
||||
...action,
|
||||
id: createId(),
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
...logicItem,
|
||||
id: createId(),
|
||||
conditions: duplicateConditionGroup(logicItem.conditions),
|
||||
actions: logicItem.actions.map(duplicateAction),
|
||||
};
|
||||
};
|
||||
|
||||
export const addConditionBelow = (
|
||||
group: TConditionGroup,
|
||||
resourceId: string,
|
||||
condition: TSingleCondition
|
||||
) => {
|
||||
for (let i = 0; i < group.conditions.length; i++) {
|
||||
const item = group.conditions[i];
|
||||
|
||||
if (isConditionGroup(item)) {
|
||||
if (item.id === resourceId) {
|
||||
group.conditions.splice(i + 1, 0, condition);
|
||||
break;
|
||||
} else {
|
||||
addConditionBelow(item, resourceId, condition);
|
||||
}
|
||||
} else {
|
||||
if (item.id === resourceId) {
|
||||
group.conditions.splice(i + 1, 0, condition);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const toggleGroupConnector = (group: TConditionGroup, resourceId: string) => {
|
||||
if (group.id === resourceId) {
|
||||
group.connector = group.connector === "and" ? "or" : "and";
|
||||
return;
|
||||
}
|
||||
|
||||
for (const condition of group.conditions) {
|
||||
if (condition.connector) {
|
||||
toggleGroupConnector(condition, resourceId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const removeCondition = (group: TConditionGroup, resourceId: string) => {
|
||||
for (let i = 0; i < group.conditions.length; i++) {
|
||||
const item = group.conditions[i];
|
||||
|
||||
if (item.id === resourceId) {
|
||||
group.conditions.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isConditionGroup(item)) {
|
||||
removeCondition(item, resourceId);
|
||||
}
|
||||
}
|
||||
|
||||
deleteEmptyGroups(group);
|
||||
};
|
||||
|
||||
export const duplicateCondition = (group: TConditionGroup, resourceId: string) => {
|
||||
for (let i = 0; i < group.conditions.length; i++) {
|
||||
const item = group.conditions[i];
|
||||
|
||||
if (item.id === resourceId) {
|
||||
const newItem: TCondition = {
|
||||
...item,
|
||||
id: createId(),
|
||||
};
|
||||
group.conditions.splice(i + 1, 0, newItem);
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.connector) {
|
||||
duplicateCondition(item, resourceId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteEmptyGroups = (group: TConditionGroup) => {
|
||||
for (let i = 0; i < group.conditions.length; i++) {
|
||||
const resource = group.conditions[i];
|
||||
|
||||
if (isConditionGroup(resource) && resource.conditions.length === 0) {
|
||||
group.conditions.splice(i, 1);
|
||||
} else if (isConditionGroup(resource)) {
|
||||
deleteEmptyGroups(resource);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const createGroupFromResource = (group: TConditionGroup, resourceId: string) => {
|
||||
for (let i = 0; i < group.conditions.length; i++) {
|
||||
const item = group.conditions[i];
|
||||
|
||||
if (item.id === resourceId) {
|
||||
const newGroup: TConditionGroup = {
|
||||
id: createId(),
|
||||
connector: "and",
|
||||
conditions: [item],
|
||||
};
|
||||
group.conditions[i] = newGroup;
|
||||
group.connector = group.connector ?? "and";
|
||||
return;
|
||||
}
|
||||
|
||||
if (isConditionGroup(item)) {
|
||||
createGroupFromResource(item, resourceId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const updateCondition = (
|
||||
group: TConditionGroup,
|
||||
resourceId: string,
|
||||
condition: Partial<TSingleCondition>
|
||||
) => {
|
||||
for (let i = 0; i < group.conditions.length; i++) {
|
||||
const item = group.conditions[i];
|
||||
|
||||
if (item.id === resourceId && !("connector" in item)) {
|
||||
group.conditions[i] = { ...item, ...condition } as TSingleCondition;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isConditionGroup(item)) {
|
||||
updateCondition(item, resourceId, condition);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getUpdatedActionBody = (
|
||||
action: TSurveyLogicAction,
|
||||
objective: TActionObjective
|
||||
): TSurveyLogicAction => {
|
||||
if (objective === action.objective) return action;
|
||||
switch (objective) {
|
||||
case "calculate":
|
||||
return {
|
||||
id: action.id,
|
||||
objective: "calculate",
|
||||
variableId: "",
|
||||
operator: "assign",
|
||||
value: { type: "static", value: "" },
|
||||
};
|
||||
case "requireAnswer":
|
||||
return {
|
||||
id: action.id,
|
||||
objective: "requireAnswer",
|
||||
target: "",
|
||||
};
|
||||
case "jumpToQuestion":
|
||||
return {
|
||||
id: action.id,
|
||||
objective: "jumpToQuestion",
|
||||
target: "",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const evaluateLogic = (
|
||||
localSurvey: TSurvey,
|
||||
@@ -8,10 +8,10 @@ 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 { evaluateLogic, performActions } from "@/lib/logicEvaluator";
|
||||
import { parseRecallInformation } from "@/lib/recall";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { evaluateLogic, performActions } from "@formbricks/lib/surveyLogic/utils";
|
||||
import { SurveyBaseProps } from "@formbricks/types/formbricks-surveys";
|
||||
import type {
|
||||
TResponseData,
|
||||
|
||||
@@ -1,459 +0,0 @@
|
||||
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
|
||||
import { isConditionGroup } from "@formbricks/lib/survey/logic/utils";
|
||||
import { TResponseData, TResponseVariables } from "@formbricks/types/responses";
|
||||
import {
|
||||
TActionCalculate,
|
||||
TConditionGroup,
|
||||
TSingleCondition,
|
||||
TSurvey,
|
||||
TSurveyLogicAction,
|
||||
TSurveyQuestion,
|
||||
TSurveyQuestionTypeEnum,
|
||||
TSurveyVariable,
|
||||
} from "@formbricks/types/surveys/types";
|
||||
|
||||
export const evaluateLogic = (
|
||||
localSurvey: TSurvey,
|
||||
data: TResponseData,
|
||||
variablesData: TResponseVariables,
|
||||
conditions: TConditionGroup,
|
||||
selectedLanguage: string
|
||||
): boolean => {
|
||||
const evaluateConditionGroup = (group: TConditionGroup): boolean => {
|
||||
const results = group.conditions.map((condition) => {
|
||||
if (isConditionGroup(condition)) {
|
||||
return evaluateConditionGroup(condition);
|
||||
} else {
|
||||
return evaluateSingleCondition(localSurvey, data, variablesData, condition, selectedLanguage);
|
||||
}
|
||||
});
|
||||
|
||||
return group.connector === "or" ? results.some((r) => r) : results.every((r) => r);
|
||||
};
|
||||
|
||||
return evaluateConditionGroup(conditions);
|
||||
};
|
||||
|
||||
const evaluateSingleCondition = (
|
||||
localSurvey: TSurvey,
|
||||
data: TResponseData,
|
||||
variablesData: TResponseVariables,
|
||||
condition: TSingleCondition,
|
||||
selectedLanguage: string
|
||||
): boolean => {
|
||||
try {
|
||||
let leftValue = getLeftOperandValue(
|
||||
localSurvey,
|
||||
data,
|
||||
variablesData,
|
||||
condition.leftOperand,
|
||||
selectedLanguage
|
||||
);
|
||||
let rightValue = condition.rightOperand
|
||||
? getRightOperandValue(localSurvey, data, variablesData, condition.rightOperand)
|
||||
: undefined;
|
||||
|
||||
let leftField: TSurveyQuestion | TSurveyVariable | string;
|
||||
|
||||
if (condition.leftOperand?.type === "question") {
|
||||
leftField = localSurvey.questions.find((q) => q.id === condition.leftOperand?.value) as TSurveyQuestion;
|
||||
} else if (condition.leftOperand?.type === "variable") {
|
||||
leftField = localSurvey.variables.find((v) => v.id === condition.leftOperand?.value) as TSurveyVariable;
|
||||
} else if (condition.leftOperand?.type === "hiddenField") {
|
||||
leftField = condition.leftOperand.value as string;
|
||||
} else {
|
||||
leftField = "";
|
||||
}
|
||||
|
||||
let rightField: TSurveyQuestion | TSurveyVariable | string;
|
||||
|
||||
if (condition.rightOperand?.type === "question") {
|
||||
rightField = localSurvey.questions.find(
|
||||
(q) => q.id === condition.rightOperand?.value
|
||||
) as TSurveyQuestion;
|
||||
} else if (condition.rightOperand?.type === "variable") {
|
||||
rightField = localSurvey.variables.find(
|
||||
(v) => v.id === condition.rightOperand?.value
|
||||
) as TSurveyVariable;
|
||||
} else if (condition.rightOperand?.type === "hiddenField") {
|
||||
rightField = condition.rightOperand.value as string;
|
||||
} else {
|
||||
rightField = "";
|
||||
}
|
||||
|
||||
if (
|
||||
condition.leftOperand.type === "variable" &&
|
||||
(leftField as TSurveyVariable).type === "number" &&
|
||||
condition.rightOperand?.type === "hiddenField"
|
||||
) {
|
||||
rightValue = Number(rightValue as string);
|
||||
}
|
||||
|
||||
switch (condition.operator) {
|
||||
case "equals":
|
||||
if (condition.leftOperand.type === "question") {
|
||||
if (
|
||||
(leftField as TSurveyQuestion).type === TSurveyQuestionTypeEnum.Date &&
|
||||
typeof leftValue === "string" &&
|
||||
typeof rightValue === "string"
|
||||
) {
|
||||
// when left value is of date question and right value is string
|
||||
return new Date(leftValue).getTime() === new Date(rightValue).getTime();
|
||||
}
|
||||
}
|
||||
|
||||
// when left value is of openText, hiddenField, variable and right value is of multichoice
|
||||
if (condition.rightOperand?.type === "question") {
|
||||
if ((rightField as TSurveyQuestion).type === TSurveyQuestionTypeEnum.MultipleChoiceMulti) {
|
||||
if (Array.isArray(rightValue) && typeof leftValue === "string" && rightValue.length === 1) {
|
||||
return rightValue.includes(leftValue as string);
|
||||
} else return false;
|
||||
} else if (
|
||||
(rightField as TSurveyQuestion).type === TSurveyQuestionTypeEnum.Date &&
|
||||
typeof leftValue === "string" &&
|
||||
typeof rightValue === "string"
|
||||
) {
|
||||
return new Date(leftValue).getTime() === new Date(rightValue).getTime();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
(Array.isArray(leftValue) &&
|
||||
leftValue.length === 1 &&
|
||||
typeof rightValue === "string" &&
|
||||
leftValue.includes(rightValue)) ||
|
||||
leftValue === rightValue
|
||||
);
|
||||
case "doesNotEqual":
|
||||
// when left value is of picture selection question and right value is its option
|
||||
if (
|
||||
condition.leftOperand.type === "question" &&
|
||||
(leftField as TSurveyQuestion).type === TSurveyQuestionTypeEnum.PictureSelection &&
|
||||
Array.isArray(leftValue) &&
|
||||
leftValue.length > 0 &&
|
||||
typeof rightValue === "string"
|
||||
) {
|
||||
return !leftValue.includes(rightValue);
|
||||
}
|
||||
|
||||
// when left value is of date question and right value is string
|
||||
if (
|
||||
condition.leftOperand.type === "question" &&
|
||||
(leftField as TSurveyQuestion).type === TSurveyQuestionTypeEnum.Date &&
|
||||
typeof leftValue === "string" &&
|
||||
typeof rightValue === "string"
|
||||
) {
|
||||
return new Date(leftValue).getTime() !== new Date(rightValue).getTime();
|
||||
}
|
||||
|
||||
// when left value is of openText, hiddenField, variable and right value is of multichoice
|
||||
if (condition.rightOperand?.type === "question") {
|
||||
if ((rightField as TSurveyQuestion).type === TSurveyQuestionTypeEnum.MultipleChoiceMulti) {
|
||||
if (Array.isArray(rightValue) && typeof leftValue === "string" && rightValue.length === 1) {
|
||||
return !rightValue.includes(leftValue as string);
|
||||
} else return false;
|
||||
} else if (
|
||||
(rightField as TSurveyQuestion).type === TSurveyQuestionTypeEnum.Date &&
|
||||
typeof leftValue === "string" &&
|
||||
typeof rightValue === "string"
|
||||
) {
|
||||
return new Date(leftValue).getTime() !== new Date(rightValue).getTime();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
(Array.isArray(leftValue) &&
|
||||
leftValue.length === 1 &&
|
||||
typeof rightValue === "string" &&
|
||||
!leftValue.includes(rightValue)) ||
|
||||
leftValue !== rightValue
|
||||
);
|
||||
case "contains":
|
||||
return String(leftValue).includes(String(rightValue));
|
||||
case "doesNotContain":
|
||||
return !String(leftValue).includes(String(rightValue));
|
||||
case "startsWith":
|
||||
return String(leftValue).startsWith(String(rightValue));
|
||||
case "doesNotStartWith":
|
||||
return !String(leftValue).startsWith(String(rightValue));
|
||||
case "endsWith":
|
||||
return String(leftValue).endsWith(String(rightValue));
|
||||
case "doesNotEndWith":
|
||||
return !String(leftValue).endsWith(String(rightValue));
|
||||
case "isSubmitted":
|
||||
if (typeof leftValue === "string") {
|
||||
if (
|
||||
condition.leftOperand.type === "question" &&
|
||||
(leftField as TSurveyQuestion).type === TSurveyQuestionTypeEnum.FileUpload &&
|
||||
leftValue
|
||||
) {
|
||||
return leftValue !== "skipped";
|
||||
}
|
||||
return leftValue !== "" && leftValue !== null;
|
||||
} else if (Array.isArray(leftValue)) {
|
||||
return leftValue.length > 0;
|
||||
} else if (typeof leftValue === "number") {
|
||||
return leftValue !== null;
|
||||
}
|
||||
return false;
|
||||
case "isSkipped":
|
||||
return (
|
||||
(Array.isArray(leftValue) && leftValue.length === 0) ||
|
||||
leftValue === "" ||
|
||||
leftValue === null ||
|
||||
leftValue === undefined ||
|
||||
(typeof leftValue === "object" && Object.entries(leftValue).length === 0)
|
||||
);
|
||||
case "isGreaterThan":
|
||||
return Number(leftValue) > Number(rightValue);
|
||||
case "isLessThan":
|
||||
return Number(leftValue) < Number(rightValue);
|
||||
case "isGreaterThanOrEqual":
|
||||
return Number(leftValue) >= Number(rightValue);
|
||||
case "isLessThanOrEqual":
|
||||
return Number(leftValue) <= Number(rightValue);
|
||||
case "equalsOneOf":
|
||||
return Array.isArray(rightValue) && typeof leftValue === "string" && rightValue.includes(leftValue);
|
||||
case "includesAllOf":
|
||||
return (
|
||||
Array.isArray(leftValue) &&
|
||||
Array.isArray(rightValue) &&
|
||||
rightValue.every((v) => leftValue.includes(v))
|
||||
);
|
||||
case "includesOneOf":
|
||||
return (
|
||||
Array.isArray(leftValue) &&
|
||||
Array.isArray(rightValue) &&
|
||||
rightValue.some((v) => leftValue.includes(v))
|
||||
);
|
||||
case "isAccepted":
|
||||
return leftValue === "accepted";
|
||||
case "isClicked":
|
||||
return leftValue === "clicked";
|
||||
case "isAfter":
|
||||
return new Date(String(leftValue)) > new Date(String(rightValue));
|
||||
case "isBefore":
|
||||
return new Date(String(leftValue)) < new Date(String(rightValue));
|
||||
case "isBooked":
|
||||
return leftValue === "booked" || !!(leftValue && leftValue !== "");
|
||||
case "isPartiallySubmitted":
|
||||
if (typeof leftValue === "object") {
|
||||
return Object.values(leftValue).includes("");
|
||||
} else return false;
|
||||
case "isCompletelySubmitted":
|
||||
if (typeof leftValue === "object") {
|
||||
const values = Object.values(leftValue);
|
||||
return values.length > 0 && !values.includes("");
|
||||
} else return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const getLeftOperandValue = (
|
||||
localSurvey: TSurvey,
|
||||
data: TResponseData,
|
||||
variablesData: TResponseVariables,
|
||||
leftOperand: TSingleCondition["leftOperand"],
|
||||
selectedLanguage: string
|
||||
) => {
|
||||
switch (leftOperand.type) {
|
||||
case "question":
|
||||
const currentQuestion = localSurvey.questions.find((q) => q.id === leftOperand.value);
|
||||
if (!currentQuestion) return undefined;
|
||||
|
||||
const responseValue = data[leftOperand.value];
|
||||
|
||||
if (currentQuestion.type === "openText" && currentQuestion.inputType === "number") {
|
||||
return Number(responseValue) || 0;
|
||||
}
|
||||
|
||||
if (currentQuestion.type === "multipleChoiceSingle" || currentQuestion.type === "multipleChoiceMulti") {
|
||||
const isOthersEnabled = currentQuestion.choices.at(-1)?.id === "other";
|
||||
|
||||
if (typeof responseValue === "string") {
|
||||
const choice = currentQuestion.choices.find((choice) => {
|
||||
return getLocalizedValue(choice.label, selectedLanguage) === responseValue;
|
||||
});
|
||||
|
||||
if (!choice) {
|
||||
if (isOthersEnabled) {
|
||||
return "other";
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return choice.id;
|
||||
} else if (Array.isArray(responseValue)) {
|
||||
let choice: string[] = [];
|
||||
responseValue.forEach((value) => {
|
||||
const foundChoice = currentQuestion.choices.find((choice) => {
|
||||
return getLocalizedValue(choice.label, selectedLanguage) === value;
|
||||
});
|
||||
|
||||
if (foundChoice) {
|
||||
choice.push(foundChoice.id);
|
||||
} else if (isOthersEnabled) {
|
||||
choice.push("other");
|
||||
}
|
||||
});
|
||||
if (choice) {
|
||||
return Array.from(new Set(choice));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data[leftOperand.value];
|
||||
case "variable":
|
||||
const variables = localSurvey.variables || [];
|
||||
const variable = variables.find((v) => v.id === leftOperand.value);
|
||||
|
||||
if (!variable) return undefined;
|
||||
|
||||
const variableValue = variablesData[leftOperand.value];
|
||||
|
||||
if (variable.type === "number") return Number(variableValue) || 0;
|
||||
return variableValue || "";
|
||||
case "hiddenField":
|
||||
return data[leftOperand.value];
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const getRightOperandValue = (
|
||||
localSurvey: TSurvey,
|
||||
data: TResponseData,
|
||||
variablesData: TResponseVariables,
|
||||
rightOperand: TSingleCondition["rightOperand"]
|
||||
) => {
|
||||
if (!rightOperand) return undefined;
|
||||
|
||||
switch (rightOperand.type) {
|
||||
case "question":
|
||||
return data[rightOperand.value];
|
||||
case "variable":
|
||||
const variables = localSurvey.variables || [];
|
||||
const variable = variables.find((v) => v.id === rightOperand.value);
|
||||
|
||||
if (!variable) return undefined;
|
||||
|
||||
const variableValue = variablesData[rightOperand.value];
|
||||
|
||||
if (variable.type === "number") return Number(variableValue) || 0;
|
||||
return variableValue || "";
|
||||
case "hiddenField":
|
||||
return data[rightOperand.value];
|
||||
case "static":
|
||||
return rightOperand.value;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export const performActions = (
|
||||
survey: TSurvey,
|
||||
actions: TSurveyLogicAction[],
|
||||
data: TResponseData,
|
||||
calculationResults: TResponseVariables
|
||||
): {
|
||||
jumpTarget: string | undefined;
|
||||
requiredQuestionIds: string[];
|
||||
calculations: TResponseVariables;
|
||||
} => {
|
||||
let jumpTarget: string | undefined;
|
||||
const requiredQuestionIds: string[] = [];
|
||||
const calculations: TResponseVariables = { ...calculationResults };
|
||||
|
||||
actions.forEach((action) => {
|
||||
switch (action.objective) {
|
||||
case "calculate":
|
||||
const result = performCalculation(survey, action, data, calculations);
|
||||
if (result !== undefined) calculations[action.variableId] = result;
|
||||
break;
|
||||
case "requireAnswer":
|
||||
requiredQuestionIds.push(action.target);
|
||||
break;
|
||||
case "jumpToQuestion":
|
||||
if (!jumpTarget) {
|
||||
jumpTarget = action.target;
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return { jumpTarget, requiredQuestionIds, calculations };
|
||||
};
|
||||
|
||||
const performCalculation = (
|
||||
survey: TSurvey,
|
||||
action: TActionCalculate,
|
||||
data: TResponseData,
|
||||
calculations: Record<string, number | string>
|
||||
): number | string | undefined => {
|
||||
const variables = survey.variables || [];
|
||||
const variable = variables.find((v) => v.id === action.variableId);
|
||||
|
||||
if (!variable) return undefined;
|
||||
|
||||
let currentValue = calculations[action.variableId];
|
||||
if (currentValue === undefined) {
|
||||
currentValue = variable.type === "number" ? 0 : "";
|
||||
}
|
||||
let operandValue: string | number | undefined;
|
||||
|
||||
// Determine the operand value based on the action.value type
|
||||
switch (action.value.type) {
|
||||
case "static":
|
||||
operandValue = action.value.value;
|
||||
break;
|
||||
case "variable":
|
||||
const value = calculations[action.value.value];
|
||||
if (typeof value === "number" || typeof value === "string") {
|
||||
operandValue = value;
|
||||
}
|
||||
break;
|
||||
case "question":
|
||||
case "hiddenField":
|
||||
const val = data[action.value.value];
|
||||
if (typeof val === "number" || typeof val === "string") {
|
||||
if (variable.type === "number" && !isNaN(Number(val))) {
|
||||
operandValue = Number(val);
|
||||
}
|
||||
operandValue = val;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (operandValue === undefined || operandValue === null) return undefined;
|
||||
|
||||
let result: number | string;
|
||||
|
||||
switch (action.operator) {
|
||||
case "add":
|
||||
result = Number(currentValue) + Number(operandValue);
|
||||
break;
|
||||
case "subtract":
|
||||
result = Number(currentValue) - Number(operandValue);
|
||||
break;
|
||||
case "multiply":
|
||||
result = Number(currentValue) * Number(operandValue);
|
||||
break;
|
||||
case "divide":
|
||||
if (Number(operandValue) === 0) return undefined;
|
||||
result = Number(currentValue) / Number(operandValue);
|
||||
break;
|
||||
case "assign":
|
||||
result = operandValue;
|
||||
break;
|
||||
case "concat":
|
||||
result = String(currentValue) + String(operandValue);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
Reference in New Issue
Block a user