mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-08 02:43:06 -05:00
fix: Let CTA and consent question store nothing if dismissed (#2977)
Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
@@ -104,9 +104,12 @@ export const transformAnswer = (
|
|||||||
): string | number | string[] => {
|
): string | number | string[] => {
|
||||||
switch (question.type) {
|
switch (question.type) {
|
||||||
case TSurveyQuestionTypeEnum.OpenText:
|
case TSurveyQuestionTypeEnum.OpenText:
|
||||||
case TSurveyQuestionTypeEnum.MultipleChoiceSingle:
|
case TSurveyQuestionTypeEnum.MultipleChoiceSingle: {
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
case TSurveyQuestionTypeEnum.Consent:
|
case TSurveyQuestionTypeEnum.Consent:
|
||||||
case TSurveyQuestionTypeEnum.CTA: {
|
case TSurveyQuestionTypeEnum.CTA: {
|
||||||
|
if (answer === "dismissed") return "";
|
||||||
return answer;
|
return answer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,6 +146,6 @@ export const transformAnswer = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return "dismissed";
|
return "";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+105
@@ -0,0 +1,105 @@
|
|||||||
|
/* eslint-disable no-console -- logging is allowed in migration scripts */
|
||||||
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
import { type TSurveyQuestion, TSurveyQuestionTypeEnum } 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...");
|
||||||
|
|
||||||
|
// Get all surveys with status not in draft and questions containing cta or consent
|
||||||
|
const relevantSurveys = await tx.survey.findMany({
|
||||||
|
where: {
|
||||||
|
status: {
|
||||||
|
notIn: ["draft"],
|
||||||
|
},
|
||||||
|
OR: [
|
||||||
|
{
|
||||||
|
questions: {
|
||||||
|
array_contains: [{ type: "cta" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
questions: {
|
||||||
|
array_contains: [{ type: "consent" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
questions: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Process each survey
|
||||||
|
const migrationPromises = relevantSurveys.map(async (survey) => {
|
||||||
|
const ctaOrConsentQuestionIds = survey.questions
|
||||||
|
.filter(
|
||||||
|
(ques: TSurveyQuestion) =>
|
||||||
|
ques.type === TSurveyQuestionTypeEnum.CTA || ques.type === TSurveyQuestionTypeEnum.Consent
|
||||||
|
)
|
||||||
|
.map((ques: TSurveyQuestion) => ques.id);
|
||||||
|
|
||||||
|
// Get all responses for this survey
|
||||||
|
const responses = await prisma.response.findMany({
|
||||||
|
where: {
|
||||||
|
surveyId: survey.id,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
data: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update each response
|
||||||
|
return responses.map(async (response) => {
|
||||||
|
const updatedData = { ...response.data };
|
||||||
|
|
||||||
|
ctaOrConsentQuestionIds.forEach((questionId: string) => {
|
||||||
|
if (updatedData[questionId] && updatedData[questionId] === "dismissed") {
|
||||||
|
updatedData[questionId] = "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return prisma.response.update({
|
||||||
|
where: { id: response.id },
|
||||||
|
data: { data: updatedData },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const migrationPromisesFlat = migrationPromises.flat();
|
||||||
|
await Promise.all(migrationPromisesFlat);
|
||||||
|
|
||||||
|
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();
|
||||||
@@ -45,7 +45,8 @@
|
|||||||
"data-migration:multiple-endings": "ts-node ./data-migrations/20240801120500_thankYouCard_to_endings/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:simplified-email-verification": "ts-node ./data-migrations/20240726124100_replace_verifyEmail_with_isVerifyEmailEnabled/data-migration.ts",
|
||||||
"data-migration:fix-logic-end-destination": "ts-node ./data-migrations/20240806120500_fix-logic-end-destination/data-migration.ts",
|
"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"
|
"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",
|
||||||
|
"data-migration:remove-dismissed-value-inconsistency": "ts-node ./data-migrations/20240807120500_cta_consent_dismissed_inconsistency/data-migration.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "^5.18.0",
|
"@prisma/client": "^5.18.0",
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ export const buildWhereClause = (filterCriteria?: TResponseFilterCriteria) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "skipped": // need to handle dismissed case for CTA type question, that would hinder other ques(eg open text)
|
case "skipped":
|
||||||
data.push({
|
data.push({
|
||||||
OR: [
|
OR: [
|
||||||
{
|
{
|
||||||
@@ -214,12 +214,6 @@ export const buildWhereClause = (filterCriteria?: TResponseFilterCriteria) => {
|
|||||||
equals: Prisma.DbNull,
|
equals: Prisma.DbNull,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
data: {
|
|
||||||
path: [key],
|
|
||||||
equals: "dismissed",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
data: {
|
data: {
|
||||||
path: [key],
|
path: [key],
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export const evaluateCondition = (logic: TSurveyLogic, responseValue: any): bool
|
|||||||
return responseValue === "clicked";
|
return responseValue === "clicked";
|
||||||
case "submitted":
|
case "submitted":
|
||||||
if (typeof responseValue === "string") {
|
if (typeof responseValue === "string") {
|
||||||
return responseValue !== "dismissed" && responseValue !== "" && responseValue !== null;
|
return responseValue !== "" && responseValue !== null;
|
||||||
} else if (Array.isArray(responseValue)) {
|
} else if (Array.isArray(responseValue)) {
|
||||||
return responseValue.length > 0;
|
return responseValue.length > 0;
|
||||||
} else if (typeof responseValue === "number") {
|
} else if (typeof responseValue === "number") {
|
||||||
@@ -46,8 +46,7 @@ export const evaluateCondition = (logic: TSurveyLogic, responseValue: any): bool
|
|||||||
return (
|
return (
|
||||||
(Array.isArray(responseValue) && responseValue.length === 0) ||
|
(Array.isArray(responseValue) && responseValue.length === 0) ||
|
||||||
responseValue === "" ||
|
responseValue === "" ||
|
||||||
responseValue === null ||
|
responseValue === null
|
||||||
responseValue === "dismissed"
|
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export const QuestionConditional = ({
|
|||||||
autoFocusEnabled,
|
autoFocusEnabled,
|
||||||
currentQuestionId,
|
currentQuestionId,
|
||||||
}: QuestionConditionalProps) => {
|
}: QuestionConditionalProps) => {
|
||||||
if (!value && prefilledQuestionValue) {
|
if (!value && (prefilledQuestionValue || prefilledQuestionValue === "")) {
|
||||||
if (skipPrefilled) {
|
if (skipPrefilled) {
|
||||||
onSubmit({ [question.id]: prefilledQuestionValue }, { [question.id]: 0 });
|
onSubmit({ [question.id]: prefilledQuestionValue }, { [question.id]: 0 });
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ export const CTAQuestion = ({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
|
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
|
||||||
setTtc(updatedTtcObj);
|
setTtc(updatedTtcObj);
|
||||||
onSubmit({ [question.id]: "dismissed" }, updatedTtcObj);
|
onSubmit({ [question.id]: "" }, updatedTtcObj);
|
||||||
onChange({ [question.id]: "dismissed" });
|
onChange({ [question.id]: "" });
|
||||||
}}
|
}}
|
||||||
className="fb-text-heading focus:fb-ring-focus fb-mr-4 fb-flex fb-items-center fb-rounded-md fb-px-3 fb-py-3 fb-text-base fb-font-medium fb-leading-4 hover:fb-opacity-90 focus:fb-outline-none focus:fb-ring-2 focus:fb-ring-offset-2">
|
className="fb-text-heading focus:fb-ring-focus fb-mr-4 fb-flex fb-items-center fb-rounded-md fb-px-3 fb-py-3 fb-text-base fb-font-medium fb-leading-4 hover:fb-opacity-90 focus:fb-outline-none focus:fb-ring-2 focus:fb-ring-offset-2">
|
||||||
{getLocalizedValue(question.dismissButtonLabel, languageCode) || "Skip"}
|
{getLocalizedValue(question.dismissButtonLabel, languageCode) || "Skip"}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export const ConsentQuestion = ({
|
|||||||
if (e.target instanceof HTMLInputElement && e.target.checked) {
|
if (e.target instanceof HTMLInputElement && e.target.checked) {
|
||||||
onChange({ [question.id]: "accepted" });
|
onChange({ [question.id]: "accepted" });
|
||||||
} else {
|
} else {
|
||||||
onChange({ [question.id]: "dismissed" });
|
onChange({ [question.id]: "" });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
checked={value === "accepted"}
|
checked={value === "accepted"}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export const evaluateCondition = (
|
|||||||
return responseValue === "clicked";
|
return responseValue === "clicked";
|
||||||
case "submitted":
|
case "submitted":
|
||||||
if (typeof responseValue === "string") {
|
if (typeof responseValue === "string") {
|
||||||
return responseValue !== "dismissed" && responseValue !== "" && responseValue !== null;
|
return responseValue !== "" && responseValue !== null;
|
||||||
} else if (Array.isArray(responseValue)) {
|
} else if (Array.isArray(responseValue)) {
|
||||||
return responseValue.length > 0;
|
return responseValue.length > 0;
|
||||||
} else if (typeof responseValue === "number") {
|
} else if (typeof responseValue === "number") {
|
||||||
@@ -55,7 +55,6 @@ export const evaluateCondition = (
|
|||||||
responseValue === "" ||
|
responseValue === "" ||
|
||||||
responseValue === null ||
|
responseValue === null ||
|
||||||
responseValue === undefined ||
|
responseValue === undefined ||
|
||||||
responseValue === "dismissed" ||
|
|
||||||
(isObject && Object.entries(responseValue).length === 0)
|
(isObject && Object.entries(responseValue).length === 0)
|
||||||
);
|
);
|
||||||
case "uploaded":
|
case "uploaded":
|
||||||
|
|||||||
Reference in New Issue
Block a user