fix: Let CTA and consent question store nothing if dismissed (#2977)

Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
Piyush Gupta
2024-08-19 14:20:19 +05:30
committed by GitHub
parent ec16159497
commit f386e47efa
9 changed files with 120 additions and 19 deletions

View File

@@ -104,9 +104,12 @@ export const transformAnswer = (
): string | number | string[] => {
switch (question.type) {
case TSurveyQuestionTypeEnum.OpenText:
case TSurveyQuestionTypeEnum.MultipleChoiceSingle:
case TSurveyQuestionTypeEnum.MultipleChoiceSingle: {
return answer;
}
case TSurveyQuestionTypeEnum.Consent:
case TSurveyQuestionTypeEnum.CTA: {
if (answer === "dismissed") return "";
return answer;
}
@@ -143,6 +146,6 @@ export const transformAnswer = (
}
default:
return "dismissed";
return "";
}
};

View File

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

View File

@@ -45,7 +45,8 @@
"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: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": {
"@prisma/client": "^5.18.0",

View File

@@ -205,7 +205,7 @@ export const buildWhereClause = (filterCriteria?: TResponseFilterCriteria) => {
},
});
break;
case "skipped": // need to handle dismissed case for CTA type question, that would hinder other ques(eg open text)
case "skipped":
data.push({
OR: [
{
@@ -214,12 +214,6 @@ export const buildWhereClause = (filterCriteria?: TResponseFilterCriteria) => {
equals: Prisma.DbNull,
},
},
{
data: {
path: [key],
equals: "dismissed",
},
},
{
data: {
path: [key],

View File

@@ -35,7 +35,7 @@ export const evaluateCondition = (logic: TSurveyLogic, responseValue: any): bool
return responseValue === "clicked";
case "submitted":
if (typeof responseValue === "string") {
return responseValue !== "dismissed" && responseValue !== "" && responseValue !== null;
return responseValue !== "" && responseValue !== null;
} else if (Array.isArray(responseValue)) {
return responseValue.length > 0;
} else if (typeof responseValue === "number") {
@@ -46,8 +46,7 @@ export const evaluateCondition = (logic: TSurveyLogic, responseValue: any): bool
return (
(Array.isArray(responseValue) && responseValue.length === 0) ||
responseValue === "" ||
responseValue === null ||
responseValue === "dismissed"
responseValue === null
);
default:
return false;

View File

@@ -52,7 +52,7 @@ export const QuestionConditional = ({
autoFocusEnabled,
currentQuestionId,
}: QuestionConditionalProps) => {
if (!value && prefilledQuestionValue) {
if (!value && (prefilledQuestionValue || prefilledQuestionValue === "")) {
if (skipPrefilled) {
onSubmit({ [question.id]: prefilledQuestionValue }, { [question.id]: 0 });
} else {

View File

@@ -78,8 +78,8 @@ export const CTAQuestion = ({
onClick={() => {
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
setTtc(updatedTtcObj);
onSubmit({ [question.id]: "dismissed" }, updatedTtcObj);
onChange({ [question.id]: "dismissed" });
onSubmit({ [question.id]: "" }, updatedTtcObj);
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">
{getLocalizedValue(question.dismissButtonLabel, languageCode) || "Skip"}

View File

@@ -87,7 +87,7 @@ export const ConsentQuestion = ({
if (e.target instanceof HTMLInputElement && e.target.checked) {
onChange({ [question.id]: "accepted" });
} else {
onChange({ [question.id]: "dismissed" });
onChange({ [question.id]: "" });
}
}}
checked={value === "accepted"}

View File

@@ -42,7 +42,7 @@ export const evaluateCondition = (
return responseValue === "clicked";
case "submitted":
if (typeof responseValue === "string") {
return responseValue !== "dismissed" && responseValue !== "" && responseValue !== null;
return responseValue !== "" && responseValue !== null;
} else if (Array.isArray(responseValue)) {
return responseValue.length > 0;
} else if (typeof responseValue === "number") {
@@ -55,7 +55,6 @@ export const evaluateCondition = (
responseValue === "" ||
responseValue === null ||
responseValue === undefined ||
responseValue === "dismissed" ||
(isObject && Object.entries(responseValue).length === 0)
);
case "uploaded":