mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-21 13:40:31 -06:00
fix: add data-migration fixing invalid jump end jump destination (#2972)
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -56,5 +56,5 @@ Zone.Identifier
|
||||
packages/lib/uploads
|
||||
|
||||
# Vite Timestamps
|
||||
vite.config.*.timestamp-*
|
||||
*vite.config.*.timestamp-*
|
||||
|
||||
|
||||
@@ -191,12 +191,12 @@ docker compose up -d
|
||||
<CodeGroup title="Migrate the data">
|
||||
|
||||
```bash
|
||||
docker pull ghcr.io/formbricks/data-migrations:latest && \
|
||||
docker pull ghcr.io/formbricks/data-migrations:v2.3.0 && \
|
||||
docker run --rm \
|
||||
--network=formbricks_default \
|
||||
-e DATABASE_URL="postgresql://postgres:postgres@postgres:5432/formbricks?schema=public" \
|
||||
-e UPGRADE_TO_VERSION="v2.3" \
|
||||
ghcr.io/formbricks/data-migrations:latest
|
||||
ghcr.io/formbricks/data-migrations:v2.3.0
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
@@ -286,12 +286,12 @@ docker compose up -d
|
||||
<CodeGroup title="Migrate the data">
|
||||
|
||||
```bash
|
||||
docker pull ghcr.io/formbricks/data-migrations:latest && \
|
||||
docker pull ghcr.io/formbricks/data-migrations:v2.2 && \
|
||||
docker run --rm \
|
||||
--network=formbricks_default \
|
||||
-e DATABASE_URL="postgresql://postgres:postgres@postgres:5432/formbricks?schema=public" \
|
||||
-e UPGRADE_TO_VERSION="v2.2" \
|
||||
ghcr.io/formbricks/data-migrations:latest
|
||||
ghcr.io/formbricks/data-migrations:v2.2
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
@@ -390,12 +390,12 @@ docker compose up -d
|
||||
<CodeGroup title="Migrate the data">
|
||||
|
||||
```bash
|
||||
docker pull ghcr.io/formbricks/data-migrations:latest && \
|
||||
docker pull ghcr.io/formbricks/data-migrations:v2.1.0 && \
|
||||
docker run --rm \
|
||||
--network=formbricks_default \
|
||||
-e DATABASE_URL="postgresql://postgres:postgres@postgres:5432/formbricks?schema=public" \
|
||||
-e UPGRADE_TO_VERSION="v2.1" \
|
||||
ghcr.io/formbricks/data-migrations:latest
|
||||
ghcr.io/formbricks/data-migrations:v2.1.0
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
@@ -504,12 +504,12 @@ docker compose up -d
|
||||
<CodeGroup title="Migrate the data">
|
||||
|
||||
```bash
|
||||
docker pull ghcr.io/formbricks/data-migrations:latest && \
|
||||
docker pull ghcr.io/formbricks/data-migrations:v2.0.3 && \
|
||||
docker run --rm \
|
||||
--network=formbricks_default \
|
||||
-e DATABASE_URL="postgresql://postgres:postgres@postgres:5432/formbricks?schema=public" \
|
||||
-e UPGRADE_TO_VERSION="v2.0" \
|
||||
ghcr.io/formbricks/data-migrations:latest
|
||||
ghcr.io/formbricks/data-migrations:v2.0.3
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
@@ -82,7 +82,6 @@ export const ConnectWithFormbricks = ({
|
||||
</div>
|
||||
<Button
|
||||
id="finishOnboarding"
|
||||
className="text-slate-400 hover:text-slate-700"
|
||||
variant={widgetSetupCompleted ? "primary" : "minimal"}
|
||||
onClick={handleFinishOnboarding}
|
||||
EndIcon={ArrowRight}>
|
||||
|
||||
@@ -14,7 +14,7 @@ import { createId } from "@paralleldrive/cuid2";
|
||||
import React, { SetStateAction, useEffect, useMemo, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { MultiLanguageCard } from "@formbricks/ee/multi-language/components/multi-language-card";
|
||||
import { extractLanguageCodes, getLocalizedValue, translateQuestion } from "@formbricks/lib/i18n/utils";
|
||||
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
|
||||
import { structuredClone } from "@formbricks/lib/pollyfills/structuredClone";
|
||||
import { getDefaultEndingCard } from "@formbricks/lib/templates";
|
||||
import { checkForEmptyFallBackValue, extractRecallInfo } from "@formbricks/lib/utils/recall";
|
||||
@@ -258,18 +258,16 @@ export const QuestionsView = ({
|
||||
toast.success("Question duplicated.");
|
||||
};
|
||||
|
||||
const addQuestion = (question: any, index?: number) => {
|
||||
const addQuestion = (question: TSurveyQuestion, index?: number) => {
|
||||
const updatedSurvey = { ...localSurvey };
|
||||
if (backButtonLabel) {
|
||||
question.backButtonLabel = backButtonLabel;
|
||||
}
|
||||
const languageSymbols = extractLanguageCodes(localSurvey.languages);
|
||||
const translatedQuestion = translateQuestion(question, languageSymbols);
|
||||
|
||||
if (index) {
|
||||
updatedSurvey.questions.splice(index, 0, { ...translatedQuestion, isDraft: true });
|
||||
updatedSurvey.questions.splice(index, 0, { ...question, isDraft: true });
|
||||
} else {
|
||||
updatedSurvey.questions.push({ ...translatedQuestion, isDraft: true });
|
||||
updatedSurvey.questions.push({ ...question, isDraft: true });
|
||||
}
|
||||
|
||||
setLocalSurvey(updatedSurvey);
|
||||
|
||||
@@ -21,11 +21,10 @@ import {
|
||||
} from "@formbricks/lib/posthogServer";
|
||||
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
|
||||
import { COLOR_DEFAULTS } from "@formbricks/lib/styling/constants";
|
||||
import { getSyncSurveys, transformToLegacySurvey } from "@formbricks/lib/survey/service";
|
||||
import { getSyncSurveys } from "@formbricks/lib/survey/service";
|
||||
import { transformToLegacySurvey } from "@formbricks/lib/survey/utils";
|
||||
import { isVersionGreaterThanOrEqualTo } from "@formbricks/lib/utils/version";
|
||||
import { TJsAppLegacyStateSync, TJsAppStateSync, ZJsPeopleUserIdInput } from "@formbricks/types/js";
|
||||
import { TLegacySurvey } from "@formbricks/types/legacy-surveys";
|
||||
import { TProductLegacy } from "@formbricks/types/product";
|
||||
import { TJsAppStateSync, ZJsPeopleUserIdInput } from "@formbricks/types/js";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
|
||||
export const OPTIONS = async (): Promise<Response> => {
|
||||
@@ -181,7 +180,7 @@ export const GET = async (
|
||||
throw new Error("Product not found");
|
||||
}
|
||||
|
||||
const updatedProduct: TProductLegacy = {
|
||||
const updatedProduct: any = {
|
||||
...product,
|
||||
brandColor: product.styling.brandColor?.light ?? COLOR_DEFAULTS.brandColor,
|
||||
...(product.styling.highlightBorderColor?.light && {
|
||||
@@ -194,10 +193,10 @@ export const GET = async (
|
||||
|
||||
// Scenario 1: Multi language and updated trigger action classes supported.
|
||||
// Use the surveys as they are.
|
||||
let transformedSurveys: TLegacySurvey[] | TSurvey[] = surveys;
|
||||
let transformedSurveys: TSurvey[] = surveys;
|
||||
|
||||
// creating state object
|
||||
let state: TJsAppStateSync | TJsAppLegacyStateSync = {
|
||||
let state: TJsAppStateSync = {
|
||||
surveys: !isMonthlyResponsesLimitReached
|
||||
? transformedSurveys.map((survey) => replaceAttributeRecall(survey, attributes))
|
||||
: [],
|
||||
@@ -212,13 +211,13 @@ export const GET = async (
|
||||
// Convert to legacy surveys with default language
|
||||
// convert triggers to array of actionClasses Names
|
||||
transformedSurveys = await Promise.all(
|
||||
surveys.map((survey: TSurvey | TLegacySurvey) => {
|
||||
surveys.map((survey) => {
|
||||
const languageCode = "default";
|
||||
return transformToLegacySurvey(survey as TSurvey, languageCode);
|
||||
})
|
||||
);
|
||||
|
||||
state = {
|
||||
const legacyState: any = {
|
||||
surveys: !isMonthlyResponsesLimitReached
|
||||
? transformedSurveys.map((survey) => replaceAttributeRecallInLegacySurveys(survey, attributes))
|
||||
: [],
|
||||
@@ -227,6 +226,7 @@ export const GET = async (
|
||||
language,
|
||||
product: updatedProduct,
|
||||
};
|
||||
return responses.successResponse({ ...legacyState }, true);
|
||||
}
|
||||
|
||||
return responses.successResponse({ ...state }, true);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { parseRecallInfo } from "@formbricks/lib/utils/recall";
|
||||
import { TAttributes } from "@formbricks/types/attributes";
|
||||
import { TLegacySurvey } from "@formbricks/types/legacy-surveys";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
|
||||
export const replaceAttributeRecall = (survey: TSurvey, attributes: TAttributes): TSurvey => {
|
||||
@@ -42,10 +41,7 @@ export const replaceAttributeRecall = (survey: TSurvey, attributes: TAttributes)
|
||||
return surveyTemp;
|
||||
};
|
||||
|
||||
export const replaceAttributeRecallInLegacySurveys = (
|
||||
survey: TLegacySurvey,
|
||||
attributes: TAttributes
|
||||
): TLegacySurvey => {
|
||||
export const replaceAttributeRecallInLegacySurveys = (survey: any, attributes: TAttributes): any => {
|
||||
const surveyTemp = structuredClone(survey);
|
||||
surveyTemp.questions.forEach((question) => {
|
||||
if (question.headline.includes("recall:")) {
|
||||
|
||||
@@ -14,11 +14,10 @@ import {
|
||||
} from "@formbricks/lib/posthogServer";
|
||||
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
|
||||
import { COLOR_DEFAULTS } from "@formbricks/lib/styling/constants";
|
||||
import { getSurveys, transformToLegacySurvey } from "@formbricks/lib/survey/service";
|
||||
import { getSurveys } from "@formbricks/lib/survey/service";
|
||||
import { transformToLegacySurvey } from "@formbricks/lib/survey/utils";
|
||||
import { isVersionGreaterThanOrEqualTo } from "@formbricks/lib/utils/version";
|
||||
import { TJsWebsiteLegacyStateSync, TJsWebsiteStateSync, ZJsWebsiteSyncInput } from "@formbricks/types/js";
|
||||
import { TLegacySurvey } from "@formbricks/types/legacy-surveys";
|
||||
import { TProductLegacy } from "@formbricks/types/product";
|
||||
import { TJsWebsiteStateSync, ZJsWebsiteSyncInput } from "@formbricks/types/js";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
|
||||
export const OPTIONS = async (): Promise<Response> => {
|
||||
@@ -122,7 +121,7 @@ export const GET = async (
|
||||
// && (!survey.segment || survey.segment.filters.length === 0)
|
||||
);
|
||||
|
||||
const updatedProduct: TProductLegacy = {
|
||||
const updatedProduct: any = {
|
||||
...product,
|
||||
brandColor: product.styling.brandColor?.light ?? COLOR_DEFAULTS.brandColor,
|
||||
...(product.styling.highlightBorderColor?.light && {
|
||||
@@ -133,8 +132,8 @@ export const GET = async (
|
||||
const noCodeActionClasses = actionClasses.filter((actionClass) => actionClass.type === "noCode");
|
||||
|
||||
// Define 'transformedSurveys' which can be an array of either TLegacySurvey or TSurvey.
|
||||
let transformedSurveys: TLegacySurvey[] | TSurvey[] = filteredSurveys;
|
||||
let state: TJsWebsiteStateSync | TJsWebsiteLegacyStateSync = {
|
||||
let transformedSurveys: TSurvey[] = filteredSurveys;
|
||||
let state: TJsWebsiteStateSync = {
|
||||
surveys: !isWebsiteSurveyResponseLimitReached ? transformedSurveys : [],
|
||||
actionClasses,
|
||||
product: updatedProduct,
|
||||
@@ -152,11 +151,16 @@ export const GET = async (
|
||||
})
|
||||
);
|
||||
|
||||
state = {
|
||||
const legacyState: any = {
|
||||
surveys: isWebsiteSurveyResponseLimitReached ? [] : transformedSurveys,
|
||||
noCodeActionClasses,
|
||||
product: updatedProduct,
|
||||
};
|
||||
return responses.successResponse(
|
||||
{ ...legacyState },
|
||||
true,
|
||||
"public, s-maxage=600, max-age=840, stale-while-revalidate=600, stale-if-error=600"
|
||||
);
|
||||
}
|
||||
|
||||
return responses.successResponse(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { authenticateRequest } from "@/app/api/v1/auth";
|
||||
import { responses } from "@/app/lib/api/response";
|
||||
import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { translateSurvey } from "@formbricks/lib/i18n/utils";
|
||||
import { createSurvey, getSurveys } from "@formbricks/lib/survey/service";
|
||||
import { DatabaseError } from "@formbricks/types/errors";
|
||||
import { ZSurveyCreateInput } from "@formbricks/types/surveys/types";
|
||||
@@ -38,13 +37,6 @@ export const POST = async (request: Request): Promise<Response> => {
|
||||
return responses.badRequestResponse("Malformed JSON input, please check your request body");
|
||||
}
|
||||
|
||||
if (surveyInput?.questions && surveyInput.questions[0].headline) {
|
||||
const questionHeadline = surveyInput.questions[0].headline;
|
||||
if (typeof questionHeadline === "string") {
|
||||
// its a legacy survey
|
||||
surveyInput = translateSurvey(surveyInput, []);
|
||||
}
|
||||
}
|
||||
const inputValidation = ZSurveyCreateInput.safeParse(surveyInput);
|
||||
|
||||
if (!inputValidation.success) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable -- leacy support workaround for now to avoid rewrite after eslint rules have been changed */
|
||||
// migration script to translate surveys where thankYouCard buttonLabel is a string or question subheaders are strings
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { hasStringSubheaders, translateSurvey } from "./lib/i18n";
|
||||
@@ -8,7 +9,7 @@ const main = async () => {
|
||||
await prisma.$transaction(
|
||||
async (tx) => {
|
||||
// Translate Surveys
|
||||
const surveys = await tx.survey.findMany({
|
||||
const surveys: any = await tx.survey.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
questions: true,
|
||||
@@ -17,11 +18,6 @@ const main = async () => {
|
||||
},
|
||||
});
|
||||
|
||||
if (!surveys) {
|
||||
// stop the migration if there are no surveys
|
||||
return;
|
||||
}
|
||||
|
||||
for (const survey of surveys) {
|
||||
if (typeof survey.thankYouCard.buttonLabel === "string" || hasStringSubheaders(survey.questions)) {
|
||||
const translatedSurvey = translateSurvey(survey, []);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable -- leacy support workaround for now to avoid rewrite after eslint rules have been changed */
|
||||
// migration script to convert range field in rating question from string to number
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable -- leacy support workaround for now to avoid rewrite after eslint rules have been changed */
|
||||
// migration script to add empty strings to welcome card headline in default language, if it does not exist
|
||||
// WelcomeCard.headline = {} -> WelcomeCard.headline = {"default":""}
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable -- leacy support workaround for now to avoid rewrite after eslint rules have been changed */
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { AttributeType } from "@prisma/client";
|
||||
import { translateSurvey } from "./lib/i18n";
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
/* eslint-disable -- leacy support workaround for now to avoid rewrite after eslint rules have been changed */
|
||||
import { type TLanguage } from "@formbricks/types/product";
|
||||
import {
|
||||
TLegacySurveyChoice,
|
||||
TLegacySurveyQuestion,
|
||||
TLegacySurveyThankYouCard,
|
||||
TLegacySurveyWelcomeCard,
|
||||
} from "@formbricks/types/legacy-surveys";
|
||||
import { TLanguage } from "@formbricks/types/product";
|
||||
import {
|
||||
TI18nString,
|
||||
TSurvey,
|
||||
TSurveyCTAQuestion,
|
||||
TSurveyChoice,
|
||||
TSurveyConsentQuestion,
|
||||
TSurveyMultipleChoiceQuestion,
|
||||
TSurveyNPSQuestion,
|
||||
TSurveyOpenTextQuestion,
|
||||
TSurveyQuestion,
|
||||
TSurveyQuestions,
|
||||
TSurveyRatingQuestion,
|
||||
TSurveyThankYouCard,
|
||||
TSurveyWelcomeCard,
|
||||
type TI18nString,
|
||||
type TSurveyCTAQuestion,
|
||||
type TSurveyChoice,
|
||||
type TSurveyConsentQuestion,
|
||||
type TSurveyMultipleChoiceQuestion,
|
||||
type TSurveyNPSQuestion,
|
||||
type TSurveyOpenTextQuestion,
|
||||
type TSurveyQuestion,
|
||||
type TSurveyQuestions,
|
||||
type TSurveyRatingQuestion,
|
||||
type TSurveyWelcomeCard,
|
||||
ZSurveyCTAQuestion,
|
||||
ZSurveyCalQuestion,
|
||||
ZSurveyConsentQuestion,
|
||||
@@ -29,7 +22,6 @@ import {
|
||||
ZSurveyPictureSelectionQuestion,
|
||||
ZSurveyQuestion,
|
||||
ZSurveyRatingQuestion,
|
||||
ZSurveyThankYouCard,
|
||||
ZSurveyWelcomeCard,
|
||||
} from "@formbricks/types/surveys/types";
|
||||
|
||||
@@ -71,7 +63,7 @@ export const createI18nString = (text: string | TI18nString, languages: string[]
|
||||
};
|
||||
|
||||
// Function to translate a choice label
|
||||
const translateChoice = (choice: TSurveyChoice | TLegacySurveyChoice, languages: string[]): TSurveyChoice => {
|
||||
const translateChoice = (choice: TSurveyChoice, languages: string[]): TSurveyChoice => {
|
||||
if (typeof choice.label !== "undefined") {
|
||||
return {
|
||||
...choice,
|
||||
@@ -86,7 +78,7 @@ const translateChoice = (choice: TSurveyChoice | TLegacySurveyChoice, languages:
|
||||
};
|
||||
|
||||
export const translateWelcomeCard = (
|
||||
welcomeCard: TSurveyWelcomeCard | TLegacySurveyWelcomeCard,
|
||||
welcomeCard: TSurveyWelcomeCard,
|
||||
languages: string[]
|
||||
): TSurveyWelcomeCard => {
|
||||
const clonedWelcomeCard = structuredClone(welcomeCard);
|
||||
@@ -103,10 +95,7 @@ export const translateWelcomeCard = (
|
||||
return ZSurveyWelcomeCard.parse(clonedWelcomeCard);
|
||||
};
|
||||
|
||||
const translateThankYouCard = (
|
||||
thankYouCard: TSurveyThankYouCard | TLegacySurveyThankYouCard,
|
||||
languages: string[]
|
||||
): TSurveyThankYouCard => {
|
||||
const translateThankYouCard = (thankYouCard: any, languages: string[]): any => {
|
||||
const clonedThankYouCard = structuredClone(thankYouCard);
|
||||
|
||||
if (typeof thankYouCard.headline !== "undefined") {
|
||||
@@ -120,14 +109,11 @@ const translateThankYouCard = (
|
||||
if (typeof clonedThankYouCard.buttonLabel !== "undefined") {
|
||||
clonedThankYouCard.buttonLabel = createI18nString(thankYouCard.buttonLabel ?? "", languages);
|
||||
}
|
||||
return ZSurveyThankYouCard.parse(clonedThankYouCard);
|
||||
return clonedThankYouCard;
|
||||
};
|
||||
|
||||
// Function that will translate a single question
|
||||
const translateQuestion = (
|
||||
question: TLegacySurveyQuestion | TSurveyQuestion,
|
||||
languages: string[]
|
||||
): TSurveyQuestion => {
|
||||
const translateQuestion = (question: TSurveyQuestion, languages: string[]): TSurveyQuestion => {
|
||||
// Clone the question to avoid mutating the original
|
||||
const clonedQuestion = structuredClone(question);
|
||||
|
||||
@@ -250,12 +236,9 @@ export const extractLanguageIds = (languages: TLanguage[]): string[] => {
|
||||
return languages.map((language) => language.id);
|
||||
};
|
||||
|
||||
// Function to translate an entire survey
|
||||
export const translateSurvey = (
|
||||
survey: Pick<TSurvey, "questions" | "welcomeCard" | "thankYouCard">,
|
||||
languageCodes: string[]
|
||||
): Pick<TSurvey, "questions" | "welcomeCard" | "thankYouCard"> => {
|
||||
const translatedQuestions = survey.questions.map((question) => {
|
||||
// Function to translate an entire survey (from old survey format to new survey format)
|
||||
export const translateSurvey = (survey: any, languageCodes: string[]) => {
|
||||
const translatedQuestions = survey.questions.map((question: any) => {
|
||||
return translateQuestion(question, languageCodes);
|
||||
});
|
||||
const translatedWelcomeCard = translateWelcomeCard(survey.welcomeCard, languageCodes);
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/* eslint-disable no-console -- logging is allowed in migration scripts */
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { type TSurveyEnding, type TSurveyQuestion } from "@formbricks/types/surveys/types";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function runMigration(): Promise<void> {
|
||||
await prisma.$transaction(
|
||||
async (tx) => {
|
||||
const startTime = Date.now();
|
||||
console.log("Starting data migration...");
|
||||
|
||||
// Fetch all surveys
|
||||
const surveys: { id: string; questions: TSurveyQuestion[]; endings: TSurveyEnding[] }[] =
|
||||
await tx.survey.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
questions: true,
|
||||
endings: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (surveys.length === 0) {
|
||||
// Stop the migration if there are no surveys
|
||||
console.log("No Surveys found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all surveys that have a logic rule that has "end" as the destination
|
||||
const surveysWithEndDestination = surveys.filter((survey) =>
|
||||
survey.questions.some((question) => question.logic?.some((rule) => rule.destination === "end"))
|
||||
);
|
||||
|
||||
console.log(`Total surveys to update found: ${surveysWithEndDestination.length.toString()}`);
|
||||
|
||||
let transformedSurveyCount = 0;
|
||||
|
||||
const updatePromises = surveysWithEndDestination.map((survey) => {
|
||||
const updatedSurvey = structuredClone(survey);
|
||||
|
||||
// Remove logic rule if there are no endings
|
||||
if (updatedSurvey.endings.length === 0) {
|
||||
// remove logic rule if there are no endings
|
||||
updatedSurvey.questions.forEach((question) => {
|
||||
if (typeof question.logic === "undefined") {
|
||||
return;
|
||||
}
|
||||
question.logic.forEach((rule, index) => {
|
||||
if (rule.destination === "end") {
|
||||
if (question.logic) question.logic.splice(index, 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
// get id of first ending
|
||||
const firstEnding = survey.endings[0];
|
||||
|
||||
// replace logic destination with ending id
|
||||
updatedSurvey.questions.forEach((question) => {
|
||||
if (typeof question.logic === "undefined") {
|
||||
return;
|
||||
}
|
||||
question.logic.forEach((rule) => {
|
||||
if (rule.destination === "end") {
|
||||
rule.destination = firstEnding.id;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
transformedSurveyCount++;
|
||||
|
||||
// Return the update promise
|
||||
return tx.survey.update({
|
||||
where: { id: survey.id },
|
||||
data: {
|
||||
questions: updatedSurvey.questions,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(updatePromises);
|
||||
|
||||
console.log(`${transformedSurveyCount.toString()} surveys transformed`);
|
||||
const endTime = Date.now();
|
||||
console.log(`Data migration completed. Total time: ${((endTime - startTime) / 1000).toString()}s`);
|
||||
},
|
||||
{
|
||||
timeout: 180000, // 3 minutes
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function handleError(error: unknown): void {
|
||||
console.error("An error occurred during migration:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function handleDisconnectError(): void {
|
||||
console.error("Failed to disconnect Prisma client");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function main(): void {
|
||||
runMigration()
|
||||
.catch(handleError)
|
||||
.finally(() => {
|
||||
prisma.$disconnect().catch(handleDisconnectError);
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import { type TBaseFilters } from "@formbricks/types/segment";
|
||||
import {
|
||||
type TSurveyClosedMessage,
|
||||
type TSurveyEndings,
|
||||
type TSurveyEnding,
|
||||
type TSurveyHiddenFields,
|
||||
type TSurveyProductOverwrites,
|
||||
type TSurveyQuestions,
|
||||
@@ -32,7 +32,7 @@ declare global {
|
||||
export type ResponsePersonAttributes = TResponsePersonAttributes;
|
||||
export type SurveyWelcomeCard = TSurveyWelcomeCard;
|
||||
export type SurveyQuestions = TSurveyQuestions;
|
||||
export type SurveyEndings = TSurveyEndings;
|
||||
export type SurveyEnding = TSurveyEnding;
|
||||
export type SurveyHiddenFields = TSurveyHiddenFields;
|
||||
export type SurveyProductOverwrites = TSurveyProductOverwrites;
|
||||
export type SurveyStyling = TSurveyStyling;
|
||||
|
||||
@@ -44,7 +44,8 @@
|
||||
"data-migration:segments-cleanup": "ts-node ./data-migrations/20240712123456_segments_cleanup/data-migration.ts",
|
||||
"data-migration:multiple-endings": "ts-node ./data-migrations/20240801120500_thankYouCard_to_endings/data-migration.ts",
|
||||
"data-migration:simplified-email-verification": "ts-node ./data-migrations/20240726124100_replace_verifyEmail_with_isVerifyEmailEnabled/data-migration.ts",
|
||||
"data-migration:v2.4": "pnpm data-migration:segments-cleanup && pnpm data-migration:multiple-endings && pnpm data-migration:simplified-email-verification"
|
||||
"data-migration:fix-logic-end-destination": "ts-node ./data-migrations/20240806120500_fix-logic-end-destination/data-migration.ts",
|
||||
"data-migration:v2.4": "pnpm data-migration:segments-cleanup && pnpm data-migration:multiple-endings && pnpm data-migration:simplified-email-verification && pnpm data-migration:fix-logic-end-destination"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^5.17.0",
|
||||
|
||||
@@ -271,6 +271,8 @@ model Survey {
|
||||
/// @zod.custom(imports.ZSurveyQuestions)
|
||||
/// [SurveyQuestions]
|
||||
questions Json @default("[]")
|
||||
/// @zod.custom(imports.ZSurveyEnding)
|
||||
/// [SurveyEnding]
|
||||
endings Json[] @default([])
|
||||
thankYouCard Json? //deprecated
|
||||
/// @zod.custom(imports.ZSurveyHiddenFields)
|
||||
|
||||
@@ -20,6 +20,7 @@ export {
|
||||
ZSurveyStyling,
|
||||
ZSurveySingleUse,
|
||||
ZSurveyInlineTriggers,
|
||||
ZSurveyEnding,
|
||||
} from "@formbricks/types/surveys/types";
|
||||
|
||||
export { ZSegmentFilters } from "@formbricks/types/segment";
|
||||
|
||||
@@ -6,7 +6,6 @@ import Link from "next/link";
|
||||
import type { FC } from "react";
|
||||
import { useState } from "react";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { extractLanguageCodes, translateSurvey } from "@formbricks/lib/i18n/utils";
|
||||
import type { TLanguage, TProduct } from "@formbricks/types/product";
|
||||
import type { TSurvey, TSurveyLanguage } from "@formbricks/types/surveys/types";
|
||||
import { AdvancedOptionToggle } from "@formbricks/ui/AdvancedOptionToggle";
|
||||
@@ -74,9 +73,7 @@ export const MultiLanguageCard: FC<MultiLanguageCardProps> = ({
|
||||
};
|
||||
|
||||
const updateSurveyTranslations = (survey: TSurvey, updatedLanguages: TSurveyLanguage[]) => {
|
||||
const translatedSurveyResult = translateSurvey(survey, extractLanguageCodes(updatedLanguages));
|
||||
|
||||
const updatedSurvey = { ...translatedSurveyResult, languages: updatedLanguages };
|
||||
const updatedSurvey = { ...survey, languages: updatedLanguages };
|
||||
setLocalSurvey(updatedSurvey as TSurvey);
|
||||
};
|
||||
|
||||
|
||||
@@ -7,12 +7,8 @@ import {
|
||||
TDisplay,
|
||||
TDisplayCreateInput,
|
||||
TDisplayFilters,
|
||||
TDisplayLegacyCreateInput,
|
||||
TDisplayLegacyUpdateInput,
|
||||
TDisplayUpdateInput,
|
||||
ZDisplayCreateInput,
|
||||
ZDisplayLegacyCreateInput,
|
||||
ZDisplayLegacyUpdateInput,
|
||||
ZDisplayUpdateInput,
|
||||
} from "@formbricks/types/displays";
|
||||
import { ZId } from "@formbricks/types/environment";
|
||||
@@ -115,47 +111,6 @@ export const updateDisplay = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const updateDisplayLegacy = async (
|
||||
displayId: string,
|
||||
displayInput: TDisplayLegacyUpdateInput
|
||||
): Promise<TDisplay> => {
|
||||
validateInputs([displayInput, ZDisplayLegacyUpdateInput]);
|
||||
try {
|
||||
const data = {
|
||||
...(displayInput.personId && {
|
||||
person: {
|
||||
connect: {
|
||||
id: displayInput.personId,
|
||||
},
|
||||
},
|
||||
}),
|
||||
...(displayInput.responseId && {
|
||||
responseId: displayInput.responseId,
|
||||
}),
|
||||
};
|
||||
const display = await prisma.display.update({
|
||||
where: {
|
||||
id: displayId,
|
||||
},
|
||||
data,
|
||||
select: selectDisplay,
|
||||
});
|
||||
displayCache.revalidate({
|
||||
id: display.id,
|
||||
surveyId: display.surveyId,
|
||||
});
|
||||
|
||||
return display;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const createDisplay = async (displayInput: TDisplayCreateInput): Promise<TDisplay> => {
|
||||
validateInputs([displayInput, ZDisplayCreateInput]);
|
||||
|
||||
@@ -201,80 +156,6 @@ export const createDisplay = async (displayInput: TDisplayCreateInput): Promise<
|
||||
}
|
||||
};
|
||||
|
||||
export const createDisplayLegacy = async (displayInput: TDisplayLegacyCreateInput): Promise<TDisplay> => {
|
||||
validateInputs([displayInput, ZDisplayLegacyCreateInput]);
|
||||
try {
|
||||
const display = await prisma.display.create({
|
||||
data: {
|
||||
survey: {
|
||||
connect: {
|
||||
id: displayInput.surveyId,
|
||||
},
|
||||
},
|
||||
|
||||
...(displayInput.personId && {
|
||||
person: {
|
||||
connect: {
|
||||
id: displayInput.personId,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
select: selectDisplay,
|
||||
});
|
||||
|
||||
displayCache.revalidate({
|
||||
id: display.id,
|
||||
personId: display.personId,
|
||||
surveyId: display.surveyId,
|
||||
});
|
||||
|
||||
return display;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const markDisplayRespondedLegacy = async (displayId: string): Promise<TDisplay> => {
|
||||
validateInputs([displayId, ZId]);
|
||||
|
||||
try {
|
||||
if (!displayId) throw new Error("Display ID is required");
|
||||
|
||||
const display = await prisma.display.update({
|
||||
where: {
|
||||
id: displayId,
|
||||
},
|
||||
data: {
|
||||
status: "responded",
|
||||
},
|
||||
select: selectDisplay,
|
||||
});
|
||||
|
||||
if (!display) {
|
||||
throw new ResourceNotFoundError("Display", displayId);
|
||||
}
|
||||
|
||||
displayCache.revalidate({
|
||||
id: display.id,
|
||||
personId: display.personId,
|
||||
surveyId: display.surveyId,
|
||||
});
|
||||
|
||||
return display;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getDisplaysByPersonId = reactCache(
|
||||
async (personId: string, page?: number): Promise<TDisplay[]> =>
|
||||
cache(
|
||||
|
||||
@@ -45,29 +45,8 @@ export const mockDisplayInputWithResponseId = {
|
||||
responseId: mockResponseId,
|
||||
};
|
||||
|
||||
export const mockDisplayLegacyInput = {
|
||||
responseId: mockResponseId,
|
||||
surveyId: mockSurveyId,
|
||||
};
|
||||
export const mockDisplayLegacyInputWithPersonId = {
|
||||
...mockDisplayLegacyInput,
|
||||
personId: mockPersonId,
|
||||
};
|
||||
|
||||
export const mockDisplayUpdate = {
|
||||
environmentId: mockEnvironmentId,
|
||||
userId: mockUserId,
|
||||
responseId: mockResponseId,
|
||||
};
|
||||
|
||||
export const mockDisplayLegacyUpdateInput = {
|
||||
personId: mockPersonId,
|
||||
responseId: mockResponseId,
|
||||
};
|
||||
|
||||
export const mockDisplayLegacyWithRespondedStatus: Prisma.DisplayGetPayload<{
|
||||
select: typeof selectDisplay;
|
||||
}> = {
|
||||
...mockDisplayWithPersonId,
|
||||
status: "responded",
|
||||
};
|
||||
|
||||
@@ -4,10 +4,6 @@ import {
|
||||
mockDisplay,
|
||||
mockDisplayInput,
|
||||
mockDisplayInputWithUserId,
|
||||
mockDisplayLegacyInput,
|
||||
mockDisplayLegacyInputWithPersonId,
|
||||
mockDisplayLegacyUpdateInput,
|
||||
mockDisplayLegacyWithRespondedStatus,
|
||||
mockDisplayUpdate,
|
||||
mockDisplayWithPersonId,
|
||||
mockDisplayWithResponseId,
|
||||
@@ -20,14 +16,11 @@ import { testInputValidation } from "vitestSetup";
|
||||
import { DatabaseError } from "@formbricks/types/errors";
|
||||
import {
|
||||
createDisplay,
|
||||
createDisplayLegacy,
|
||||
deleteDisplayByResponseId,
|
||||
getDisplay,
|
||||
getDisplayCountBySurveyId,
|
||||
getDisplaysByPersonId,
|
||||
markDisplayRespondedLegacy,
|
||||
updateDisplay,
|
||||
updateDisplayLegacy,
|
||||
} from "../service";
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -173,88 +166,6 @@ describe("Tests for updateDisplay Service", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Tests for createDisplayLegacy service", () => {
|
||||
describe("Happy Path", () => {
|
||||
it("Creates a display when a person ID exist", async () => {
|
||||
prisma.display.create.mockResolvedValue(mockDisplayWithPersonId);
|
||||
|
||||
const display = await createDisplayLegacy(mockDisplayLegacyInputWithPersonId);
|
||||
expect(display).toEqual(mockDisplayWithPersonId);
|
||||
});
|
||||
it("Creates a display when a person ID does not exist", async () => {
|
||||
prisma.display.create.mockResolvedValue(mockDisplay);
|
||||
|
||||
const display = await createDisplayLegacy(mockDisplayLegacyInput);
|
||||
expect(display).toEqual(mockDisplay);
|
||||
});
|
||||
});
|
||||
describe("Sad Path", () => {
|
||||
testInputValidation(createDisplayLegacy, "123");
|
||||
|
||||
it("Throws DatabaseError on PrismaClientKnownRequestError occurrence", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
const errToThrow = new Prisma.PrismaClientKnownRequestError(mockErrorMessage, {
|
||||
code: "P2002",
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prisma.display.create.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(createDisplayLegacy(mockDisplayLegacyInputWithPersonId)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
it("Throws a generic Error for other exceptions", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prisma.display.create.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(createDisplayLegacy(mockDisplayLegacyInputWithPersonId)).rejects.toThrow(Error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Tests for updateDisplayLegacy Service", () => {
|
||||
describe("Happy Path", () => {
|
||||
it("Updates a display", async () => {
|
||||
prisma.display.update.mockResolvedValue(mockDisplayWithPersonId);
|
||||
|
||||
const display = await updateDisplayLegacy(mockDisplay.id, mockDisplayLegacyUpdateInput);
|
||||
expect(display).toEqual(mockDisplayWithPersonId);
|
||||
});
|
||||
|
||||
it("marks display as responded legacy", async () => {
|
||||
prisma.display.update.mockResolvedValue(mockDisplayLegacyWithRespondedStatus);
|
||||
|
||||
const display = await markDisplayRespondedLegacy(mockDisplay.id);
|
||||
expect(display).toEqual(mockDisplayLegacyWithRespondedStatus);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Sad Path", () => {
|
||||
testInputValidation(updateDisplayLegacy, "123", "123");
|
||||
|
||||
it("Throws DatabaseError on PrismaClientKnownRequestError", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
const errToThrow = new Prisma.PrismaClientKnownRequestError(mockErrorMessage, {
|
||||
code: "P2002",
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
|
||||
prisma.display.update.mockRejectedValue(errToThrow);
|
||||
|
||||
await expect(updateDisplayLegacy(mockDisplay.id, mockDisplayLegacyUpdateInput)).rejects.toThrow(
|
||||
DatabaseError
|
||||
);
|
||||
});
|
||||
|
||||
it("Throws a generic Error for other unexpected issues", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
prisma.display.update.mockRejectedValue(new Error(mockErrorMessage));
|
||||
|
||||
await expect(updateDisplayLegacy(mockDisplay.id, mockDisplayLegacyUpdateInput)).rejects.toThrow(Error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Tests for deleteDisplayByResponseId service", () => {
|
||||
describe("Happy Path", () => {
|
||||
it("Deletes a display when a response associated to it is deleted", async () => {
|
||||
@@ -265,8 +176,6 @@ describe("Tests for deleteDisplayByResponseId service", () => {
|
||||
});
|
||||
});
|
||||
describe("Sad Path", () => {
|
||||
testInputValidation(createDisplayLegacy, "123");
|
||||
|
||||
it("Throws DatabaseError on PrismaClientKnownRequestError occurrence", async () => {
|
||||
const mockErrorMessage = "Mock error message";
|
||||
const errToThrow = new Prisma.PrismaClientKnownRequestError(mockErrorMessage, {
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { mockSegment } from "segment/tests/__mocks__/segment.mock";
|
||||
import { mockSurveyLanguages } from "survey/tests/__mock__/survey.mock";
|
||||
import { TLegacySurveyThankYouCard } from "@formbricks/types/legacySurveys";
|
||||
import {
|
||||
TSurvey,
|
||||
TSurveyCTAQuestion,
|
||||
TSurveyCalQuestion,
|
||||
TSurveyConsentQuestion,
|
||||
TSurveyDateQuestion,
|
||||
TSurveyEndings,
|
||||
TSurveyEndScreenCard,
|
||||
TSurveyFileUploadQuestion,
|
||||
TSurveyMultipleChoiceQuestion,
|
||||
TSurveyNPSQuestion,
|
||||
@@ -139,6 +138,7 @@ export const mockRatingQuestion: TSurveyRatingQuestion = {
|
||||
subheader: {
|
||||
default: "Don't worry, be honest.",
|
||||
},
|
||||
isColorCodingEnabled: false,
|
||||
scale: "star",
|
||||
range: 5,
|
||||
lowerLabel: {
|
||||
@@ -166,6 +166,7 @@ export const mockNpsQuestion: TSurveyNPSQuestion = {
|
||||
id: "m9pemgdih2p4exvkmeeqq6jf",
|
||||
type: TSurveyQuestionTypeEnum.NPS,
|
||||
isDraft: true,
|
||||
isColorCodingEnabled: false,
|
||||
};
|
||||
|
||||
export const mockCtaQuestion: TSurveyCTAQuestion = {
|
||||
@@ -235,10 +236,10 @@ export const mockCalQuestion: TSurveyCalQuestion = {
|
||||
isDraft: true,
|
||||
};
|
||||
|
||||
export const mockEndings: TSurveyEndings = [
|
||||
export const mockEndings = [
|
||||
{
|
||||
type: "endScreen",
|
||||
id: "umyknohldc7w26ocjdhaa62c",
|
||||
type: "endScreen",
|
||||
headline: {
|
||||
default: "Thank you!",
|
||||
},
|
||||
@@ -247,7 +248,7 @@ export const mockEndings: TSurveyEndings = [
|
||||
},
|
||||
buttonLink: "https://formbricks.com/signup",
|
||||
buttonLabel: { default: "Create your own Survey" },
|
||||
},
|
||||
} as TSurveyEndScreenCard,
|
||||
];
|
||||
|
||||
export const mockSurvey: TSurvey = {
|
||||
@@ -506,7 +507,7 @@ export const mockTranslatedEndings = [
|
||||
},
|
||||
];
|
||||
|
||||
export const mockLegacyThankYouCard: TLegacySurveyThankYouCard = {
|
||||
export const mockLegacyThankYouCard = {
|
||||
buttonLink: "https://formbricks.com/signup",
|
||||
enabled: true,
|
||||
headline: "Thank you!",
|
||||
@@ -553,4 +554,5 @@ export const mockLegacySurvey = {
|
||||
welcomeCard: mockLegacyWelcomeCard,
|
||||
thankYouCard: mockLegacyThankYouCard,
|
||||
endings: undefined,
|
||||
redirectUrl: null,
|
||||
};
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
mockEndings,
|
||||
mockLegacySurvey,
|
||||
mockSurvey,
|
||||
mockTranslatedEndings,
|
||||
mockTranslatedSurvey,
|
||||
mockTranslatedWelcomeCard,
|
||||
mockWelcomeCard,
|
||||
} from "./i18n.mock";
|
||||
import { mockLegacySurvey, mockTranslatedSurvey } from "./i18n.mock";
|
||||
import { reverseTranslateSurvey } from "./reverseTranslation";
|
||||
import { createI18nString, translateEndings, translateSurvey, translateWelcomeCard } from "./utils";
|
||||
import { createI18nString } from "./utils";
|
||||
|
||||
describe("createI18nString", () => {
|
||||
it("should create an i18n string from a regular string", () => {
|
||||
@@ -43,30 +35,6 @@ describe("createI18nString", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("translateWelcomeCard", () => {
|
||||
it("should translate all text fields of a welcome card", () => {
|
||||
const languages = ["default", "de"];
|
||||
const translatedWelcomeCard = translateWelcomeCard(mockWelcomeCard, languages);
|
||||
expect(translatedWelcomeCard).toEqual(mockTranslatedWelcomeCard);
|
||||
});
|
||||
});
|
||||
|
||||
describe("translateEndings", () => {
|
||||
it("should translate all text fields of first endingCard", () => {
|
||||
const languages = ["default", "de"];
|
||||
const translatedEndings = translateEndings(mockEndings, languages);
|
||||
expect(translatedEndings).toEqual(mockTranslatedEndings);
|
||||
});
|
||||
});
|
||||
|
||||
describe("translateSurvey", () => {
|
||||
it("should translate all questions of a Survey", () => {
|
||||
const languageCodes = ["default", "de"];
|
||||
const translatedSurvey = translateSurvey(mockSurvey, languageCodes);
|
||||
expect(translatedSurvey).toEqual(mockTranslatedSurvey);
|
||||
});
|
||||
});
|
||||
|
||||
describe("translate to Legacy Survey", () => {
|
||||
it("should translate all questions of a normal survey to Legacy Survey", () => {
|
||||
const translatedSurvey = reverseTranslateSurvey(mockTranslatedSurvey, "default");
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import "server-only";
|
||||
import { TLegacySurvey, TLegacySurveyThankYouCard, ZLegacySurvey } from "@formbricks/types/legacy-surveys";
|
||||
import { TI18nString, TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { structuredClone } from "../pollyfills/structuredClone";
|
||||
import { getLocalizedValue, isI18nObject } from "./utils";
|
||||
@@ -28,7 +27,7 @@ const reverseTranslateObject = <T extends Record<string, any>>(obj: T, languageC
|
||||
return clonedObj;
|
||||
};
|
||||
|
||||
const reverseTranslateEndings = (survey: TSurvey, languageCode: string): TLegacySurveyThankYouCard => {
|
||||
const reverseTranslateEndings = (survey: TSurvey, languageCode: string): any => {
|
||||
const firstEndingCard = survey.endings[0];
|
||||
if (firstEndingCard && firstEndingCard.type === "endScreen") {
|
||||
return {
|
||||
@@ -43,7 +42,7 @@ const reverseTranslateEndings = (survey: TSurvey, languageCode: string): TLegacy
|
||||
}
|
||||
};
|
||||
|
||||
export const reverseTranslateSurvey = (survey: TSurvey, languageCode: string = "default"): TLegacySurvey => {
|
||||
export const reverseTranslateSurvey = (survey: TSurvey, languageCode: string = "default"): any => {
|
||||
const reversedSurvey = structuredClone(survey);
|
||||
reversedSurvey.questions = reversedSurvey.questions.map((question) =>
|
||||
reverseTranslateObject(question, languageCode)
|
||||
@@ -67,5 +66,5 @@ export const reverseTranslateSurvey = (survey: TSurvey, languageCode: string = "
|
||||
}
|
||||
// @ts-expect-error
|
||||
reversedSurvey.endings = undefined;
|
||||
return ZLegacySurvey.parse(reversedSurvey);
|
||||
return reversedSurvey;
|
||||
};
|
||||
|
||||
@@ -1,38 +1,5 @@
|
||||
import {
|
||||
TLegacySurveyChoice,
|
||||
TLegacySurveyQuestion,
|
||||
TLegacySurveyThankYouCard,
|
||||
TLegacySurveyWelcomeCard,
|
||||
} from "@formbricks/types/legacy-surveys";
|
||||
import { TLanguage } from "@formbricks/types/product";
|
||||
import {
|
||||
TI18nString,
|
||||
TSurvey,
|
||||
TSurveyCTAQuestion,
|
||||
TSurveyChoice,
|
||||
TSurveyConsentQuestion,
|
||||
TSurveyEndScreenCard,
|
||||
TSurveyEndings,
|
||||
TSurveyLanguage,
|
||||
TSurveyMultipleChoiceQuestion,
|
||||
TSurveyNPSQuestion,
|
||||
TSurveyOpenTextQuestion,
|
||||
TSurveyQuestion,
|
||||
TSurveyRatingQuestion,
|
||||
TSurveyWelcomeCard,
|
||||
ZSurveyCTAQuestion,
|
||||
ZSurveyCalQuestion,
|
||||
ZSurveyConsentQuestion,
|
||||
ZSurveyEndScreenCard,
|
||||
ZSurveyFileUploadQuestion,
|
||||
ZSurveyMultipleChoiceQuestion,
|
||||
ZSurveyNPSQuestion,
|
||||
ZSurveyOpenTextQuestion,
|
||||
ZSurveyPictureSelectionQuestion,
|
||||
ZSurveyQuestion,
|
||||
ZSurveyRatingQuestion,
|
||||
ZSurveyWelcomeCard,
|
||||
} from "@formbricks/types/surveys/types";
|
||||
import { TI18nString, TSurveyLanguage } from "@formbricks/types/surveys/types";
|
||||
import { structuredClone } from "../pollyfills/structuredClone";
|
||||
|
||||
// Helper function to create an i18nString from a regular string.
|
||||
@@ -109,225 +76,10 @@ export const getEnabledLanguages = (surveyLanguages: TSurveyLanguage[]) => {
|
||||
return surveyLanguages.filter((surveyLanguage) => surveyLanguage.enabled);
|
||||
};
|
||||
|
||||
const translateChoice = (choice: TSurveyChoice | TLegacySurveyChoice, languages: string[]): TSurveyChoice => {
|
||||
if (typeof choice.label !== "undefined") {
|
||||
return {
|
||||
...choice,
|
||||
label: createI18nString(choice.label, languages),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...choice,
|
||||
label: choice.label,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// LGEGACY
|
||||
// Helper function to maintain backwards compatibility for old survey objects before Multi Language
|
||||
export const translateWelcomeCard = (
|
||||
welcomeCard: TSurveyWelcomeCard | TLegacySurveyWelcomeCard,
|
||||
languages: string[]
|
||||
): TSurveyWelcomeCard => {
|
||||
const clonedWelcomeCard = structuredClone(welcomeCard);
|
||||
if (typeof welcomeCard.headline !== "undefined") {
|
||||
clonedWelcomeCard.headline = createI18nString(welcomeCard.headline ?? "", languages);
|
||||
}
|
||||
if (typeof welcomeCard.html !== "undefined") {
|
||||
clonedWelcomeCard.html = createI18nString(welcomeCard.html ?? "", languages);
|
||||
}
|
||||
if (typeof welcomeCard.buttonLabel !== "undefined") {
|
||||
clonedWelcomeCard.buttonLabel = createI18nString(clonedWelcomeCard.buttonLabel ?? "", languages);
|
||||
}
|
||||
|
||||
return ZSurveyWelcomeCard.parse(clonedWelcomeCard);
|
||||
};
|
||||
|
||||
// LGEGACY
|
||||
// Helper function to maintain backwards compatibility for old survey objects before Multi Language
|
||||
export const translateEndings = (
|
||||
endings: TSurveyEndings | TLegacySurveyThankYouCard,
|
||||
languages: string[]
|
||||
): TSurveyEndings => {
|
||||
const isEndingsArray = Array.isArray(endings);
|
||||
if (isEndingsArray) {
|
||||
return endings.map((ending) => {
|
||||
if (ending.type === "redirectToUrl") return ending;
|
||||
else {
|
||||
const clonedEndingCard = structuredClone(ending);
|
||||
|
||||
if (typeof ending.headline !== "undefined") {
|
||||
clonedEndingCard.headline = createI18nString(ending.headline ?? "", languages);
|
||||
}
|
||||
|
||||
if (typeof ending.subheader !== "undefined") {
|
||||
clonedEndingCard.subheader = createI18nString(ending.subheader ?? "", languages);
|
||||
}
|
||||
|
||||
if (typeof ending.buttonLabel !== "undefined") {
|
||||
clonedEndingCard.buttonLabel = createI18nString(ending.buttonLabel ?? "", languages);
|
||||
}
|
||||
return ZSurveyEndScreenCard.parse(clonedEndingCard);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const clonedEndingCard = structuredClone(endings) as unknown as TSurveyEndScreenCard;
|
||||
|
||||
if (typeof clonedEndingCard.headline !== "undefined") {
|
||||
clonedEndingCard.headline = createI18nString(clonedEndingCard.headline ?? "", languages);
|
||||
}
|
||||
|
||||
if (typeof clonedEndingCard.subheader !== "undefined") {
|
||||
clonedEndingCard.subheader = createI18nString(clonedEndingCard.subheader ?? "", languages);
|
||||
}
|
||||
|
||||
if (typeof clonedEndingCard.buttonLabel !== "undefined") {
|
||||
clonedEndingCard.buttonLabel = createI18nString(clonedEndingCard.buttonLabel ?? "", languages);
|
||||
}
|
||||
return [ZSurveyEndScreenCard.parse(clonedEndingCard)];
|
||||
}
|
||||
};
|
||||
|
||||
// LGEGACY
|
||||
// Helper function to maintain backwards compatibility for old survey objects before Multi Language
|
||||
export const translateQuestion = (
|
||||
question: TLegacySurveyQuestion | TSurveyQuestion,
|
||||
languages: string[]
|
||||
): TSurveyQuestion => {
|
||||
// Clone the question to avoid mutating the original
|
||||
const clonedQuestion = structuredClone(question);
|
||||
|
||||
//common question properties
|
||||
if (typeof question.headline !== "undefined") {
|
||||
clonedQuestion.headline = createI18nString(question.headline ?? "", languages);
|
||||
}
|
||||
|
||||
if (typeof question.subheader !== "undefined") {
|
||||
clonedQuestion.subheader = createI18nString(question.subheader ?? "", languages);
|
||||
}
|
||||
|
||||
if (typeof question.buttonLabel !== "undefined") {
|
||||
clonedQuestion.buttonLabel = createI18nString(question.buttonLabel ?? "", languages);
|
||||
}
|
||||
|
||||
if (typeof question.backButtonLabel !== "undefined") {
|
||||
clonedQuestion.backButtonLabel = createI18nString(question.backButtonLabel ?? "", languages);
|
||||
}
|
||||
|
||||
switch (question.type) {
|
||||
case "openText":
|
||||
if (typeof question.placeholder !== "undefined") {
|
||||
(clonedQuestion as TSurveyOpenTextQuestion).placeholder = createI18nString(
|
||||
question.placeholder ?? "",
|
||||
languages
|
||||
);
|
||||
}
|
||||
return ZSurveyOpenTextQuestion.parse(clonedQuestion);
|
||||
|
||||
case "multipleChoiceSingle":
|
||||
case "multipleChoiceMulti":
|
||||
(clonedQuestion as TSurveyMultipleChoiceQuestion).choices = question.choices.map((choice) => {
|
||||
return translateChoice(choice, languages);
|
||||
});
|
||||
if (typeof (clonedQuestion as TSurveyMultipleChoiceQuestion).otherOptionPlaceholder !== "undefined") {
|
||||
(clonedQuestion as TSurveyMultipleChoiceQuestion).otherOptionPlaceholder = createI18nString(
|
||||
question.otherOptionPlaceholder ?? "",
|
||||
languages
|
||||
);
|
||||
}
|
||||
return ZSurveyMultipleChoiceQuestion.parse(clonedQuestion);
|
||||
|
||||
case "cta":
|
||||
if (typeof question.dismissButtonLabel !== "undefined") {
|
||||
(clonedQuestion as TSurveyCTAQuestion).dismissButtonLabel = createI18nString(
|
||||
question.dismissButtonLabel ?? "",
|
||||
languages
|
||||
);
|
||||
}
|
||||
if (typeof question.html !== "undefined") {
|
||||
(clonedQuestion as TSurveyCTAQuestion).html = createI18nString(question.html ?? "", languages);
|
||||
}
|
||||
return ZSurveyCTAQuestion.parse(clonedQuestion);
|
||||
|
||||
case "consent":
|
||||
if (typeof question.html !== "undefined") {
|
||||
(clonedQuestion as TSurveyConsentQuestion).html = createI18nString(question.html ?? "", languages);
|
||||
}
|
||||
|
||||
if (typeof question.label !== "undefined") {
|
||||
(clonedQuestion as TSurveyConsentQuestion).label = createI18nString(question.label ?? "", languages);
|
||||
}
|
||||
return ZSurveyConsentQuestion.parse(clonedQuestion);
|
||||
|
||||
case "nps":
|
||||
if (typeof question.lowerLabel !== "undefined") {
|
||||
(clonedQuestion as TSurveyNPSQuestion).lowerLabel = createI18nString(
|
||||
question.lowerLabel ?? "",
|
||||
languages
|
||||
);
|
||||
}
|
||||
if (typeof question.upperLabel !== "undefined") {
|
||||
(clonedQuestion as TSurveyNPSQuestion).upperLabel = createI18nString(
|
||||
question.upperLabel ?? "",
|
||||
languages
|
||||
);
|
||||
}
|
||||
return ZSurveyNPSQuestion.parse(clonedQuestion);
|
||||
|
||||
case "rating":
|
||||
if (typeof question.lowerLabel !== "undefined") {
|
||||
(clonedQuestion as TSurveyRatingQuestion).lowerLabel = createI18nString(
|
||||
question.lowerLabel ?? "",
|
||||
languages
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof question.upperLabel !== "undefined") {
|
||||
(clonedQuestion as TSurveyRatingQuestion).upperLabel = createI18nString(
|
||||
question.upperLabel ?? "",
|
||||
languages
|
||||
);
|
||||
}
|
||||
return ZSurveyRatingQuestion.parse(clonedQuestion);
|
||||
|
||||
case "fileUpload":
|
||||
return ZSurveyFileUploadQuestion.parse(clonedQuestion);
|
||||
|
||||
case "pictureSelection":
|
||||
return ZSurveyPictureSelectionQuestion.parse(clonedQuestion);
|
||||
|
||||
case "cal":
|
||||
return ZSurveyCalQuestion.parse(clonedQuestion);
|
||||
|
||||
default:
|
||||
return ZSurveyQuestion.parse(clonedQuestion);
|
||||
}
|
||||
};
|
||||
|
||||
export const extractLanguageIds = (languages: TLanguage[]): string[] => {
|
||||
return languages.map((language) => language.code);
|
||||
};
|
||||
|
||||
// LGEGACY
|
||||
// Helper function to maintain backwards compatibility for old survey objects before Multi Language
|
||||
export const translateSurvey = (
|
||||
survey: Pick<TSurvey, "questions" | "welcomeCard" | "endings">,
|
||||
languageCodes: string[]
|
||||
): Pick<TSurvey, "questions" | "welcomeCard" | "endings"> => {
|
||||
const translatedQuestions = survey.questions.map((question) => {
|
||||
return translateQuestion(question, languageCodes);
|
||||
});
|
||||
const translatedWelcomeCard = translateWelcomeCard(survey.welcomeCard, languageCodes);
|
||||
const translatedEndings = translateEndings(survey.endings, languageCodes);
|
||||
const translatedSurvey = structuredClone(survey);
|
||||
return {
|
||||
...translatedSurvey,
|
||||
questions: translatedQuestions,
|
||||
welcomeCard: translatedWelcomeCard,
|
||||
endings: translatedEndings,
|
||||
};
|
||||
};
|
||||
|
||||
export const getLanguageCode = (surveyLanguages: TSurveyLanguage[], languageCode: string | null) => {
|
||||
if (!surveyLanguages?.length || !languageCode) return "default";
|
||||
const language = surveyLanguages.find((surveyLanguage) => surveyLanguage.language.code === languageCode);
|
||||
|
||||
@@ -11,11 +11,9 @@ import {
|
||||
TResponse,
|
||||
TResponseFilterCriteria,
|
||||
TResponseInput,
|
||||
TResponseLegacyInput,
|
||||
TResponseUpdateInput,
|
||||
ZResponseFilterCriteria,
|
||||
ZResponseInput,
|
||||
ZResponseLegacyInput,
|
||||
ZResponseUpdateInput,
|
||||
} from "@formbricks/types/responses";
|
||||
import { TSurveySummary } from "@formbricks/types/surveys/types";
|
||||
@@ -26,7 +24,7 @@ import { IS_FORMBRICKS_CLOUD, ITEMS_PER_PAGE, WEBAPP_URL } from "../constants";
|
||||
import { displayCache } from "../display/cache";
|
||||
import { deleteDisplayByResponseId, getDisplayCountBySurveyId } from "../display/service";
|
||||
import { getMonthlyOrganizationResponseCount, getOrganizationByEnvironmentId } from "../organization/service";
|
||||
import { createPerson, getPerson, getPersonByUserId } from "../person/service";
|
||||
import { createPerson, getPersonByUserId } from "../person/service";
|
||||
import { sendPlanLimitsReachedEventToPosthogWeekly } from "../posthogServer";
|
||||
import { responseNoteCache } from "../responseNote/cache";
|
||||
import { getResponseNotes } from "../responseNote/service";
|
||||
@@ -307,82 +305,6 @@ export const createResponse = async (responseInput: TResponseInput): Promise<TRe
|
||||
}
|
||||
};
|
||||
|
||||
export const createResponseLegacy = async (responseInput: TResponseLegacyInput): Promise<TResponse> => {
|
||||
validateInputs([responseInput, ZResponseLegacyInput]);
|
||||
captureTelemetry("response created");
|
||||
|
||||
try {
|
||||
let person: TPerson | null = null;
|
||||
let attributes: TAttributes | null = null;
|
||||
|
||||
if (responseInput.personId) {
|
||||
person = await getPerson(responseInput.personId);
|
||||
}
|
||||
const ttcTemp = responseInput.ttc ?? {};
|
||||
const questionId = Object.keys(ttcTemp)[0];
|
||||
const ttc =
|
||||
responseInput.finished && responseInput.ttc
|
||||
? {
|
||||
...ttcTemp,
|
||||
_total: ttcTemp[questionId], // Add _total property with the same value
|
||||
}
|
||||
: ttcTemp;
|
||||
|
||||
if (person?.id) {
|
||||
attributes = await getAttributes(person?.id as string);
|
||||
}
|
||||
|
||||
const responsePrisma = await prisma.response.create({
|
||||
data: {
|
||||
survey: {
|
||||
connect: {
|
||||
id: responseInput.surveyId,
|
||||
},
|
||||
},
|
||||
finished: responseInput.finished,
|
||||
data: responseInput.data,
|
||||
ttc,
|
||||
...(responseInput.personId && {
|
||||
person: {
|
||||
connect: {
|
||||
id: responseInput.personId,
|
||||
},
|
||||
},
|
||||
personAttributes: attributes,
|
||||
}),
|
||||
|
||||
...(responseInput.meta && ({ meta: responseInput?.meta } as Prisma.JsonObject)),
|
||||
singleUseId: responseInput.singleUseId,
|
||||
language: responseInput.language,
|
||||
},
|
||||
select: responseSelection,
|
||||
});
|
||||
|
||||
const response: TResponse = {
|
||||
...responsePrisma,
|
||||
tags: responsePrisma.tags.map((tagPrisma: { tag: TTag }) => tagPrisma.tag),
|
||||
};
|
||||
|
||||
responseCache.revalidate({
|
||||
id: response.id,
|
||||
personId: response.person?.id,
|
||||
surveyId: response.surveyId,
|
||||
});
|
||||
|
||||
responseNoteCache.revalidate({
|
||||
responseId: response.id,
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getResponse = reactCache(
|
||||
(responseId: string): Promise<TResponse | null> =>
|
||||
cache(
|
||||
|
||||
@@ -55,6 +55,9 @@ export const mockResponseNote: ResponseNoteMock = {
|
||||
export const mockPerson = {
|
||||
id: mockPersonId,
|
||||
userId: mockUserId,
|
||||
createdAt: new Date(2000, 1, 1, 19),
|
||||
updatedAt: new Date(2000, 1, 1, 19),
|
||||
environmentId: mockEnvironmentId,
|
||||
};
|
||||
|
||||
export const mockTags = [
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
mockEnvironmentId,
|
||||
mockMeta,
|
||||
mockPerson,
|
||||
mockPersonId,
|
||||
mockResponse,
|
||||
mockResponseData,
|
||||
mockResponseNote,
|
||||
@@ -18,15 +17,10 @@ import {
|
||||
mockUserId,
|
||||
} from "./__mocks__/data.mock";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { beforeEach, describe, expect, it, vitest } from "vitest";
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { testInputValidation } from "vitestSetup";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import {
|
||||
TResponse,
|
||||
TResponseFilterCriteria,
|
||||
TResponseInput,
|
||||
TResponseLegacyInput,
|
||||
} from "@formbricks/types/responses";
|
||||
import { TResponse, TResponseFilterCriteria, TResponseInput } from "@formbricks/types/responses";
|
||||
import { TTag } from "@formbricks/types/tags";
|
||||
import { selectPerson } from "../../person/service";
|
||||
import {
|
||||
@@ -36,7 +30,6 @@ import {
|
||||
} from "../../survey/tests/__mock__/survey.mock";
|
||||
import {
|
||||
createResponse,
|
||||
createResponseLegacy,
|
||||
deleteResponse,
|
||||
getResponse,
|
||||
getResponseBySingleUseId,
|
||||
@@ -84,16 +77,6 @@ const mockResponseInputWithUserId: TResponseInput = {
|
||||
userId: mockUserId,
|
||||
};
|
||||
|
||||
const createMockResponseLegacyInput = (personId?: string): TResponseLegacyInput => ({
|
||||
finished: constantsForTests.boolean,
|
||||
personId: personId ?? null,
|
||||
surveyId: mockSurveyId,
|
||||
meta: mockMeta,
|
||||
singleUseId: mockSingleUseId,
|
||||
ttc: {},
|
||||
data: {},
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// @ts-expect-error
|
||||
prisma.response.create.mockImplementation(async (args) => {
|
||||
@@ -277,20 +260,6 @@ describe("Tests for createResponse service", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Tests for createResponseLegacy service", () => {
|
||||
describe("Happy Path", () => {
|
||||
it("Creates a response linked to an existing user", async () => {
|
||||
const response = await createResponseLegacy(createMockResponseLegacyInput(mockPersonId));
|
||||
expect(response).toEqual(expectedResponseWithPerson);
|
||||
});
|
||||
|
||||
it("Creates a legacy response without an associated user ID", async () => {
|
||||
const response = await createResponseLegacy(createMockResponseLegacyInput());
|
||||
expect(response).toEqual(expectedResponseWithoutPerson);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Tests for getResponse service", () => {
|
||||
describe("Happy Path", () => {
|
||||
it("Retrieves a specific response by its ID", async () => {
|
||||
|
||||
@@ -7,7 +7,6 @@ import { TActionClass } from "@formbricks/types/action-classes";
|
||||
import { ZOptionalNumber } from "@formbricks/types/common";
|
||||
import { TEnvironment, ZId } from "@formbricks/types/environment";
|
||||
import { DatabaseError, InvalidInputError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { TLegacySurvey } from "@formbricks/types/legacy-surveys";
|
||||
import { TPerson } from "@formbricks/types/people";
|
||||
import { TProduct } from "@formbricks/types/product";
|
||||
import { TSegment, ZSegmentFilters } from "@formbricks/types/segment";
|
||||
@@ -28,7 +27,6 @@ import { ITEMS_PER_PAGE } from "../constants";
|
||||
import { displayCache } from "../display/cache";
|
||||
import { getDisplaysByPersonId } from "../display/service";
|
||||
import { getEnvironment } from "../environment/service";
|
||||
import { reverseTranslateSurvey } from "../i18n/reverseTranslation";
|
||||
import { subscribeOrganizationMembersToSurveyResponses } from "../organization/service";
|
||||
import { personCache } from "../person/cache";
|
||||
import { getPerson } from "../person/service";
|
||||
@@ -324,23 +322,6 @@ export const getSurveys = reactCache(
|
||||
)()
|
||||
);
|
||||
|
||||
export const transformToLegacySurvey = async (
|
||||
survey: TSurvey,
|
||||
languageCode?: string
|
||||
): Promise<TLegacySurvey> => {
|
||||
const targetLanguage = languageCode ?? "default";
|
||||
|
||||
// workaround to handle triggers for legacy surveys
|
||||
// because we dont wanna do this in the `reverseTranslateSurvey` function
|
||||
const surveyToTransform: any = {
|
||||
...structuredClone(survey),
|
||||
triggers: survey.triggers.map((trigger) => trigger.actionClass.name),
|
||||
};
|
||||
|
||||
const transformedSurvey = reverseTranslateSurvey(surveyToTransform as TSurvey, targetLanguage);
|
||||
return transformedSurvey;
|
||||
};
|
||||
|
||||
export const getSurveyCount = reactCache(
|
||||
(environmentId: string): Promise<number> =>
|
||||
cache(
|
||||
@@ -984,7 +965,7 @@ export const getSyncSurveys = reactCache(
|
||||
options?: {
|
||||
version?: string;
|
||||
}
|
||||
): Promise<TSurvey[] | TLegacySurvey[]> =>
|
||||
): Promise<TSurvey[]> =>
|
||||
cache(
|
||||
async () => {
|
||||
validateInputs([environmentId, ZId]);
|
||||
@@ -1001,7 +982,7 @@ export const getSyncSurveys = reactCache(
|
||||
throw new Error("Person not found");
|
||||
}
|
||||
|
||||
let surveys: TSurvey[] | TLegacySurvey[] = await getSurveys(environmentId);
|
||||
let surveys = await getSurveys(environmentId);
|
||||
|
||||
// filtered surveys for running and web
|
||||
surveys = surveys.filter((survey) => survey.status === "inProgress" && survey.type === "app");
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import "server-only";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { TLegacySurvey } from "@formbricks/types/legacy-surveys";
|
||||
import { TSegment } from "@formbricks/types/segment";
|
||||
import { TSurvey, TSurveyFilterCriteria } from "@formbricks/types/surveys/types";
|
||||
import { reverseTranslateSurvey } from "../i18n/reverseTranslation";
|
||||
|
||||
export const transformPrismaSurvey = (surveyPrisma: any): TSurvey => {
|
||||
let segment: TSegment | null = null;
|
||||
@@ -81,7 +81,7 @@ export const buildOrderByClause = (
|
||||
return [{ [sortBy]: "desc" }];
|
||||
};
|
||||
|
||||
export const anySurveyHasFilters = (surveys: TSurvey[] | TLegacySurvey[]): boolean => {
|
||||
export const anySurveyHasFilters = (surveys: TSurvey[]): boolean => {
|
||||
return surveys.some((survey) => {
|
||||
if ("segment" in survey && survey.segment) {
|
||||
return survey.segment.filters && survey.segment.filters.length > 0;
|
||||
@@ -89,3 +89,17 @@ export const anySurveyHasFilters = (surveys: TSurvey[] | TLegacySurvey[]): boole
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
export const transformToLegacySurvey = async (survey: TSurvey, languageCode?: string): Promise<any> => {
|
||||
const targetLanguage = languageCode ?? "default";
|
||||
|
||||
// workaround to handle triggers for legacy surveys
|
||||
// because we dont wanna do this in the `reverseTranslateSurvey` function
|
||||
const surveyToTransform: any = {
|
||||
...structuredClone(survey),
|
||||
triggers: survey.triggers.map((trigger) => trigger.actionClass.name),
|
||||
};
|
||||
|
||||
const transformedSurvey = reverseTranslateSurvey(surveyToTransform as TSurvey, targetLanguage);
|
||||
return transformedSurvey;
|
||||
};
|
||||
|
||||
@@ -18,13 +18,3 @@ export const ZActionInput = z.object({
|
||||
});
|
||||
|
||||
export type TActionInput = z.infer<typeof ZActionInput>;
|
||||
|
||||
export const ZActionLegacyInput = z.object({
|
||||
environmentId: z.string().cuid2(),
|
||||
personId: z.string().optional(),
|
||||
sessionId: z.string().optional(),
|
||||
name: z.string(),
|
||||
properties: z.record(z.string()),
|
||||
});
|
||||
|
||||
export type TActionLegacyInput = z.infer<typeof ZActionLegacyInput>;
|
||||
|
||||
@@ -21,14 +21,6 @@ export const ZDisplayCreateInput = z.object({
|
||||
|
||||
export type TDisplayCreateInput = z.infer<typeof ZDisplayCreateInput>;
|
||||
|
||||
export const ZDisplayLegacyCreateInput = z.object({
|
||||
surveyId: z.string().cuid(),
|
||||
personId: z.string().cuid().optional(),
|
||||
responseId: z.string().cuid().optional(),
|
||||
});
|
||||
|
||||
export type TDisplayLegacyCreateInput = z.infer<typeof ZDisplayLegacyCreateInput>;
|
||||
|
||||
export const ZDisplayUpdateInput = z.object({
|
||||
environmentId: z.string().cuid(),
|
||||
userId: z.string().optional(),
|
||||
@@ -37,13 +29,6 @@ export const ZDisplayUpdateInput = z.object({
|
||||
|
||||
export type TDisplayUpdateInput = z.infer<typeof ZDisplayUpdateInput>;
|
||||
|
||||
export const ZDisplayLegacyUpdateInput = z.object({
|
||||
personId: z.string().cuid().optional(),
|
||||
responseId: z.string().cuid().optional(),
|
||||
});
|
||||
|
||||
export type TDisplayLegacyUpdateInput = z.infer<typeof ZDisplayLegacyUpdateInput>;
|
||||
|
||||
export const ZDisplaysWithSurveyName = ZDisplay.extend({
|
||||
surveyName: z.string(),
|
||||
});
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { z } from "zod";
|
||||
import { ZActionClass } from "./action-classes";
|
||||
import { ZAttributes } from "./attributes";
|
||||
import { ZLegacySurvey } from "./legacy-surveys";
|
||||
import { ZPerson } from "./people";
|
||||
import { ZProduct } from "./product";
|
||||
import { ZResponseHiddenFieldValue } from "./responses";
|
||||
import { ZSurvey } from "./surveys/types";
|
||||
@@ -34,7 +32,7 @@ export type TJSWebsiteStateDisplay = z.infer<typeof ZJSWebsiteStateDisplay>;
|
||||
export const ZJsAppStateSync = z.object({
|
||||
person: ZJsPerson.nullish(),
|
||||
userId: z.string().optional(),
|
||||
surveys: z.union([z.array(ZSurvey), z.array(ZLegacySurvey)]),
|
||||
surveys: z.array(ZSurvey),
|
||||
actionClasses: z.array(ZActionClass),
|
||||
product: ZProduct,
|
||||
language: z.string().optional(),
|
||||
@@ -65,32 +63,6 @@ export const ZJsWebsiteState = z.object({
|
||||
|
||||
export type TJsWebsiteState = z.infer<typeof ZJsWebsiteState>;
|
||||
|
||||
export const ZJsAppLegacyStateSync = z.object({
|
||||
person: ZJsPerson.nullish(),
|
||||
userId: z.string().optional(),
|
||||
surveys: z.union([z.array(ZSurvey), z.array(ZLegacySurvey)]),
|
||||
noCodeActionClasses: z.array(ZActionClass),
|
||||
product: ZProduct,
|
||||
language: z.string().optional(),
|
||||
});
|
||||
|
||||
export type TJsAppLegacyStateSync = z.infer<typeof ZJsAppLegacyStateSync>;
|
||||
|
||||
export const ZJsWebsiteLegacyStateSync = ZJsAppLegacyStateSync.omit({ person: true });
|
||||
|
||||
export type TJsWebsiteLegacyStateSync = z.infer<typeof ZJsWebsiteLegacyStateSync>;
|
||||
|
||||
export const ZJsLegacyState = z.object({
|
||||
person: ZPerson.nullable().or(z.object({})),
|
||||
session: z.object({}),
|
||||
surveys: z.array(ZSurveyWithTriggers),
|
||||
noCodeActionClasses: z.array(ZActionClass),
|
||||
product: ZProduct,
|
||||
displays: z.array(ZJSWebsiteStateDisplay).optional(),
|
||||
});
|
||||
|
||||
export type TJsLegacyState = z.infer<typeof ZJsLegacyState>;
|
||||
|
||||
export const ZJsWebsiteSyncInput = z.object({
|
||||
environmentId: z.string().cuid(),
|
||||
version: z.string().optional(),
|
||||
@@ -180,14 +152,6 @@ export const ZJsPeopleAttributeInput = z.object({
|
||||
|
||||
export type TJsPeopleAttributeInput = z.infer<typeof ZJsPeopleAttributeInput>;
|
||||
|
||||
export const ZJsPeopleLegacyAttributeInput = z.object({
|
||||
environmentId: z.string().cuid(),
|
||||
key: z.string(),
|
||||
value: z.string(),
|
||||
});
|
||||
|
||||
export type TJsPeopleLegacyAttributeInput = z.infer<typeof ZJsPeopleLegacyAttributeInput>;
|
||||
|
||||
export const ZJsActionInput = z.object({
|
||||
environmentId: z.string().cuid(),
|
||||
userId: z.string().optional(),
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
import { z } from "zod";
|
||||
import { ZAllowedFileExtension } from "./common";
|
||||
import {
|
||||
TSurveyQuestionTypeEnum,
|
||||
ZSurvey,
|
||||
ZSurveyCTALogic,
|
||||
ZSurveyCalLogic,
|
||||
ZSurveyConsentLogic,
|
||||
ZSurveyEndings,
|
||||
ZSurveyFileUploadLogic,
|
||||
ZSurveyMultipleChoiceLogic,
|
||||
ZSurveyNPSLogic,
|
||||
ZSurveyOpenTextLogic,
|
||||
ZSurveyOpenTextQuestionInputType,
|
||||
ZSurveyPictureChoice,
|
||||
ZSurveyPictureSelectionLogic,
|
||||
ZSurveyQuestionBase,
|
||||
ZSurveyRatingLogic,
|
||||
} from "./surveys/types";
|
||||
|
||||
const ZLegacySurveyQuestionBase = ZSurveyQuestionBase.extend({
|
||||
headline: z.string(),
|
||||
subheader: z.string().optional(),
|
||||
buttonLabel: z.string().optional(),
|
||||
backButtonLabel: z.string().optional(),
|
||||
});
|
||||
|
||||
export const ZLegacySurveyOpenTextQuestion = ZLegacySurveyQuestionBase.extend({
|
||||
type: z.literal(TSurveyQuestionTypeEnum.OpenText),
|
||||
placeholder: z.string().optional(),
|
||||
longAnswer: z.boolean().optional(),
|
||||
logic: z.array(ZSurveyOpenTextLogic).optional(),
|
||||
inputType: ZSurveyOpenTextQuestionInputType.optional().default("text"),
|
||||
});
|
||||
|
||||
export type TLegacySurveyOpenTextQuestion = z.infer<typeof ZLegacySurveyOpenTextQuestion>;
|
||||
|
||||
export const ZLegacySurveyConsentQuestion = ZLegacySurveyQuestionBase.extend({
|
||||
type: z.literal(TSurveyQuestionTypeEnum.Consent),
|
||||
html: z.string().optional(),
|
||||
label: z.string(),
|
||||
placeholder: z.string().optional(),
|
||||
logic: z.array(ZSurveyConsentLogic).optional(),
|
||||
});
|
||||
|
||||
export type TLegacySurveyConsentQuestion = z.infer<typeof ZLegacySurveyConsentQuestion>;
|
||||
|
||||
export const ZLegacySurveyChoice = z.object({
|
||||
id: z.string(),
|
||||
label: z.string(),
|
||||
});
|
||||
|
||||
export type TLegacySurveyChoice = z.infer<typeof ZLegacySurveyChoice>;
|
||||
|
||||
export const ZLegacySurveyMultipleChoiceSingleQuestion = ZLegacySurveyQuestionBase.extend({
|
||||
type: z.literal(TSurveyQuestionTypeEnum.MultipleChoiceSingle),
|
||||
choices: z.array(ZLegacySurveyChoice),
|
||||
logic: z.array(ZSurveyMultipleChoiceLogic).optional(),
|
||||
shuffleOption: z.enum(["none", "all", "exceptLast"]).optional(),
|
||||
otherOptionPlaceholder: z.string().optional(),
|
||||
});
|
||||
|
||||
export type TLegacySurveyMultipleChoiceSingleQuestion = z.infer<
|
||||
typeof ZLegacySurveyMultipleChoiceSingleQuestion
|
||||
>;
|
||||
|
||||
export const ZLegacySurveyMultipleChoiceMultiQuestion = ZLegacySurveyQuestionBase.extend({
|
||||
type: z.literal(TSurveyQuestionTypeEnum.MultipleChoiceMulti),
|
||||
choices: z.array(ZLegacySurveyChoice),
|
||||
logic: z.array(ZSurveyMultipleChoiceLogic).optional(),
|
||||
shuffleOption: z.enum(["none", "all", "exceptLast"]).optional(),
|
||||
otherOptionPlaceholder: z.string().optional(),
|
||||
});
|
||||
|
||||
export type TLegacySurveyMultipleChoiceMultiQuestion = z.infer<
|
||||
typeof ZLegacySurveyMultipleChoiceMultiQuestion
|
||||
>;
|
||||
|
||||
export const ZLegacySurveyNPSQuestion = ZLegacySurveyQuestionBase.extend({
|
||||
type: z.literal(TSurveyQuestionTypeEnum.NPS),
|
||||
lowerLabel: z.string(),
|
||||
upperLabel: z.string(),
|
||||
logic: z.array(ZSurveyNPSLogic).optional(),
|
||||
});
|
||||
|
||||
export type TLegacySurveyNPSQuestion = z.infer<typeof ZLegacySurveyNPSQuestion>;
|
||||
|
||||
export const ZLegacySurveyCTAQuestion = ZLegacySurveyQuestionBase.extend({
|
||||
type: z.literal(TSurveyQuestionTypeEnum.CTA),
|
||||
html: z.string().optional(),
|
||||
buttonUrl: z.string().optional(),
|
||||
buttonExternal: z.boolean(),
|
||||
dismissButtonLabel: z.string().optional(),
|
||||
logic: z.array(ZSurveyCTALogic).optional(),
|
||||
});
|
||||
|
||||
export type TLegacySurveyCTAQuestion = z.infer<typeof ZLegacySurveyCTAQuestion>;
|
||||
|
||||
export const ZLegacySurveyRatingQuestion = ZLegacySurveyQuestionBase.extend({
|
||||
type: z.literal(TSurveyQuestionTypeEnum.Rating),
|
||||
scale: z.enum(["number", "smiley", "star"]),
|
||||
range: z.union([z.literal(5), z.literal(3), z.literal(4), z.literal(7), z.literal(10)]),
|
||||
lowerLabel: z.string(),
|
||||
upperLabel: z.string(),
|
||||
logic: z.array(ZSurveyRatingLogic).optional(),
|
||||
});
|
||||
|
||||
export const ZLegacySurveyDateQuestion = ZLegacySurveyQuestionBase.extend({
|
||||
type: z.literal(TSurveyQuestionTypeEnum.Date),
|
||||
html: z.string().optional(),
|
||||
format: z.enum(["M-d-y", "d-M-y", "y-M-d"]),
|
||||
});
|
||||
|
||||
export type TLegacySurveyDateQuestion = z.infer<typeof ZLegacySurveyDateQuestion>;
|
||||
|
||||
export type TLegacySurveyRatingQuestion = z.infer<typeof ZLegacySurveyRatingQuestion>;
|
||||
|
||||
export const ZLegacySurveyPictureSelectionQuestion = ZLegacySurveyQuestionBase.extend({
|
||||
type: z.literal(TSurveyQuestionTypeEnum.PictureSelection),
|
||||
allowMulti: z.boolean().optional().default(false),
|
||||
choices: z.array(ZSurveyPictureChoice),
|
||||
logic: z.array(ZSurveyPictureSelectionLogic).optional(),
|
||||
});
|
||||
|
||||
export type TLegacySurveyPictureSelectionQuestion = z.infer<typeof ZLegacySurveyPictureSelectionQuestion>;
|
||||
|
||||
export const ZLegacySurveyFileUploadQuestion = ZLegacySurveyQuestionBase.extend({
|
||||
type: z.literal(TSurveyQuestionTypeEnum.FileUpload),
|
||||
allowMultipleFiles: z.boolean(),
|
||||
maxSizeInMB: z.number().optional(),
|
||||
allowedFileExtensions: z.array(ZAllowedFileExtension).optional(),
|
||||
logic: z.array(ZSurveyFileUploadLogic).optional(),
|
||||
});
|
||||
|
||||
export type TLegacySurveyFileUploadQuestion = z.infer<typeof ZLegacySurveyFileUploadQuestion>;
|
||||
|
||||
export const ZLegacySurveyCalQuestion = ZLegacySurveyQuestionBase.extend({
|
||||
type: z.literal(TSurveyQuestionTypeEnum.Cal),
|
||||
calUserName: z.string(),
|
||||
calHost: z.string().optional(),
|
||||
logic: z.array(ZSurveyCalLogic).optional(),
|
||||
});
|
||||
|
||||
export type TLegacySurveyCalQuestion = z.infer<typeof ZLegacySurveyCalQuestion>;
|
||||
|
||||
export const ZLegacySurveyQuestion = z.union([
|
||||
ZLegacySurveyOpenTextQuestion,
|
||||
ZLegacySurveyConsentQuestion,
|
||||
ZLegacySurveyMultipleChoiceSingleQuestion,
|
||||
ZLegacySurveyMultipleChoiceMultiQuestion,
|
||||
ZLegacySurveyNPSQuestion,
|
||||
ZLegacySurveyCTAQuestion,
|
||||
ZLegacySurveyRatingQuestion,
|
||||
ZLegacySurveyPictureSelectionQuestion,
|
||||
ZLegacySurveyDateQuestion,
|
||||
ZLegacySurveyFileUploadQuestion,
|
||||
ZLegacySurveyCalQuestion,
|
||||
]);
|
||||
|
||||
export const ZLegacySurveyThankYouCard = z.object({
|
||||
enabled: z.boolean(),
|
||||
headline: z.string().optional(),
|
||||
subheader: z.string().optional(),
|
||||
buttonLabel: z.optional(z.string()),
|
||||
buttonLink: z.optional(z.string()),
|
||||
imageUrl: z.string().optional(),
|
||||
});
|
||||
|
||||
export type TLegacySurveyThankYouCard = z.infer<typeof ZLegacySurveyThankYouCard>;
|
||||
|
||||
export const ZLegacySurveyWelcomeCard = z.object({
|
||||
enabled: z.boolean(),
|
||||
headline: z.string(),
|
||||
html: z.string().optional(),
|
||||
fileUrl: z.string().optional(),
|
||||
buttonLabel: z.string().optional(),
|
||||
timeToFinish: z.boolean().default(true),
|
||||
showResponseCount: z.boolean().default(false),
|
||||
});
|
||||
|
||||
export type TLegacySurveyWelcomeCard = z.infer<typeof ZLegacySurveyWelcomeCard>;
|
||||
|
||||
export type TLegacySurveyQuestion = z.infer<typeof ZLegacySurveyQuestion>;
|
||||
|
||||
export const ZLegacySurveyQuestions = z.array(ZLegacySurveyQuestion);
|
||||
|
||||
// ZSurvey is a refinement, so to extend it to ZLegacySurvey, we need to extend the innerType and then apply the same refinements.
|
||||
export const ZLegacySurvey = ZSurvey.innerType().extend({
|
||||
questions: ZLegacySurveyQuestions,
|
||||
thankYouCard: ZLegacySurveyThankYouCard,
|
||||
welcomeCard: ZLegacySurveyWelcomeCard,
|
||||
triggers: z.array(z.string()),
|
||||
endings: ZSurveyEndings.optional(),
|
||||
});
|
||||
|
||||
export type TLegacySurvey = z.infer<typeof ZLegacySurvey>;
|
||||
@@ -74,33 +74,6 @@ export const ZProduct = z.object({
|
||||
|
||||
export type TProduct = z.infer<typeof ZProduct>;
|
||||
|
||||
export const ZProductLegacy = z.object({
|
||||
id: z.string().cuid2(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
name: z.string().trim().min(1, { message: "Product name cannot be empty" }),
|
||||
organizationId: z.string(),
|
||||
styling: ZProductStyling,
|
||||
recontactDays: z
|
||||
.number({ message: "Recontact days is required" })
|
||||
.int()
|
||||
.min(0, { message: "Must be a positive number" })
|
||||
.max(365, { message: "Must be less than 365" }),
|
||||
inAppSurveyBranding: z.boolean(),
|
||||
linkSurveyBranding: z.boolean(),
|
||||
config: ZProductConfig,
|
||||
placement: ZPlacement,
|
||||
clickOutsideClose: z.boolean(),
|
||||
darkOverlay: z.boolean(),
|
||||
environments: z.array(ZEnvironment),
|
||||
brandColor: ZColor.nullish(),
|
||||
highlightBorderColor: ZColor.nullish(),
|
||||
languages: z.array(ZLanguage),
|
||||
logo: ZLogo.nullish(),
|
||||
});
|
||||
|
||||
export type TProductLegacy = z.infer<typeof ZProductLegacy>;
|
||||
|
||||
export const ZProductUpdateInput = z.object({
|
||||
name: z.string().trim().min(1, { message: "Product name cannot be empty" }).optional(),
|
||||
organizationId: z.string().optional(),
|
||||
|
||||
@@ -270,12 +270,6 @@ export const ZResponseInput = z.object({
|
||||
|
||||
export type TResponseInput = z.infer<typeof ZResponseInput>;
|
||||
|
||||
export const ZResponseLegacyInput = ZResponseInput.omit({ userId: true, environmentId: true }).extend({
|
||||
personId: z.string().cuid2().nullable(),
|
||||
});
|
||||
|
||||
export type TResponseLegacyInput = z.infer<typeof ZResponseLegacyInput>;
|
||||
|
||||
export const ZResponseUpdateInput = z.object({
|
||||
finished: z.boolean(),
|
||||
data: ZResponseData,
|
||||
|
||||
@@ -20,11 +20,8 @@ export const ZI18nString = z.record(z.string()).refine((obj) => "default" in obj
|
||||
|
||||
export type TI18nString = z.infer<typeof ZI18nString>;
|
||||
|
||||
const ZEndScreenType = z.union([z.literal("endScreen"), z.literal("redirectToUrl")]);
|
||||
|
||||
const ZSurveyEndingBase = z.object({
|
||||
id: z.string().cuid2(),
|
||||
type: ZEndScreenType,
|
||||
});
|
||||
|
||||
export const ZSurveyEndScreenCard = ZSurveyEndingBase.extend({
|
||||
@@ -36,6 +33,7 @@ export const ZSurveyEndScreenCard = ZSurveyEndingBase.extend({
|
||||
imageUrl: z.string().optional(),
|
||||
videoUrl: z.string().optional(),
|
||||
});
|
||||
|
||||
export type TSurveyEndScreenCard = z.infer<typeof ZSurveyEndScreenCard>;
|
||||
|
||||
export const ZSurveyRedirectUrlCard = ZSurveyEndingBase.extend({
|
||||
@@ -43,9 +41,16 @@ export const ZSurveyRedirectUrlCard = ZSurveyEndingBase.extend({
|
||||
url: z.string().url("Invalid redirect Url in Ending card").optional(),
|
||||
label: z.string().optional(),
|
||||
});
|
||||
|
||||
export type TSurveyRedirectUrlCard = z.infer<typeof ZSurveyRedirectUrlCard>;
|
||||
|
||||
export const ZSurveyEndings = z.array(z.union([ZSurveyEndScreenCard, ZSurveyRedirectUrlCard]));
|
||||
export const ZSurveyEnding = z.union([ZSurveyEndScreenCard, ZSurveyRedirectUrlCard]);
|
||||
|
||||
export type TSurveyEnding = z.infer<typeof ZSurveyEnding>;
|
||||
|
||||
export const ZSurveyEndings = z.array(ZSurveyEnding);
|
||||
|
||||
export type TSurveyEndings = z.infer<typeof ZSurveyEnding>;
|
||||
|
||||
export enum TSurveyQuestionTypeEnum {
|
||||
FileUpload = "fileUpload",
|
||||
@@ -103,6 +108,8 @@ export const ZSurveyWelcomeCard = z
|
||||
message: "Welcome card must have a headline",
|
||||
});
|
||||
|
||||
export type TSurveyWelcomeCard = z.infer<typeof ZSurveyWelcomeCard>;
|
||||
|
||||
export const ZSurveyHiddenFields = z.object({
|
||||
enabled: z.boolean(),
|
||||
fieldIds: z.optional(
|
||||
@@ -134,6 +141,8 @@ export const ZSurveyHiddenFields = z.object({
|
||||
),
|
||||
});
|
||||
|
||||
export type TSurveyHiddenFields = z.infer<typeof ZSurveyHiddenFields>;
|
||||
|
||||
export const ZSurveyProductOverwrites = z.object({
|
||||
brandColor: ZColor.nullish(),
|
||||
highlightBorderColor: ZColor.nullish(),
|
||||
@@ -163,6 +172,8 @@ export const ZSurveyClosedMessage = z
|
||||
.nullable()
|
||||
.optional();
|
||||
|
||||
export type TSurveyClosedMessage = z.infer<typeof ZSurveyClosedMessage>;
|
||||
|
||||
export const ZSurveySingleUse = z
|
||||
.object({
|
||||
enabled: z.boolean(),
|
||||
@@ -174,14 +185,6 @@ export const ZSurveySingleUse = z
|
||||
|
||||
export type TSurveySingleUse = z.infer<typeof ZSurveySingleUse>;
|
||||
|
||||
export type TSurveyWelcomeCard = z.infer<typeof ZSurveyWelcomeCard>;
|
||||
|
||||
export type TSurveyEndings = z.infer<typeof ZSurveyEndings>;
|
||||
|
||||
export type TSurveyHiddenFields = z.infer<typeof ZSurveyHiddenFields>;
|
||||
|
||||
export type TSurveyClosedMessage = z.infer<typeof ZSurveyClosedMessage>;
|
||||
|
||||
export const ZSurveyChoice = z.object({
|
||||
id: z.string(),
|
||||
label: ZI18nString,
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import {
|
||||
ZLegacySurveyQuestions,
|
||||
ZLegacySurveyThankYouCard,
|
||||
ZLegacySurveyWelcomeCard,
|
||||
} from "./legacy-surveys";
|
||||
import { ZProductConfigChannel, ZProductConfigIndustry } from "./product";
|
||||
import { ZSurveyEndings, ZSurveyHiddenFields, ZSurveyQuestions, ZSurveyWelcomeCard } from "./surveys/types";
|
||||
import { ZUserObjective } from "./user";
|
||||
@@ -30,18 +25,6 @@ export const ZTemplate = z.object({
|
||||
|
||||
export type TTemplate = z.infer<typeof ZTemplate>;
|
||||
|
||||
export const ZLegacyTemplate = ZTemplate.extend({
|
||||
preset: z.object({
|
||||
name: z.string(),
|
||||
welcomeCard: ZLegacySurveyWelcomeCard,
|
||||
questions: ZLegacySurveyQuestions,
|
||||
thankYouCard: ZLegacySurveyThankYouCard,
|
||||
hiddenFields: ZSurveyHiddenFields,
|
||||
}),
|
||||
});
|
||||
|
||||
export type TLegacyTemplate = z.infer<typeof ZLegacyTemplate>;
|
||||
|
||||
export const ZTemplateFilter = z.union([
|
||||
ZProductConfigChannel,
|
||||
ZProductConfigIndustry,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "@formbricks/config-typescript/base.json",
|
||||
"extends": "@formbricks/config-typescript/node16.json",
|
||||
"include": ["."],
|
||||
"exclude": ["dist", "build", "node_modules"],
|
||||
"compilerOptions": {
|
||||
|
||||
Reference in New Issue
Block a user