mirror of
https://github.com/formbricks/formbricks.git
synced 2026-02-12 10:37:22 -06:00
fix: removes empty imageUrl and videoUrl keys from elements (#6950)
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import type { MigrationScript } from "../../src/scripts/migration-runner";
|
||||
import { type SurveyRecord } from "./types";
|
||||
|
||||
export const removeEmptyImageAndVideoUrlsFromElements: MigrationScript = {
|
||||
type: "data",
|
||||
id: "ohw7fb1f64yfh2vax294agp0",
|
||||
name: "20251208033316_remove_empty_image_and_video_urls_from_elements",
|
||||
run: async ({ tx }) => {
|
||||
// Find all surveys with empty imageUrl or videoUrl
|
||||
const surveysFindQuery = `
|
||||
SELECT s.id, s.blocks, s."welcomeCard", s.endings
|
||||
FROM "Survey" AS s
|
||||
WHERE EXISTS (
|
||||
SELECT 1
|
||||
FROM unnest(s.blocks) AS block
|
||||
CROSS JOIN jsonb_array_elements(block->'elements') AS element
|
||||
WHERE element->>'imageUrl' = ''
|
||||
OR element->>'videoUrl' = ''
|
||||
) OR s."welcomeCard"->>'fileUrl' = ''
|
||||
OR s."welcomeCard"->>'videoUrl' = ''
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM unnest(s.endings) AS ending
|
||||
WHERE ending->>'imageUrl' = ''
|
||||
OR ending->>'videoUrl' = ''
|
||||
)
|
||||
`;
|
||||
const surveysWithEmptyUrls: SurveyRecord[] = await tx.$queryRaw`${Prisma.raw(surveysFindQuery)}`;
|
||||
|
||||
logger.info(`Found ${surveysWithEmptyUrls.length.toString()} surveys with empty imageUrl or videoUrl`);
|
||||
|
||||
// Process in batches to avoid overwhelming the connection pool
|
||||
const BATCH_SIZE = 1000;
|
||||
|
||||
for (let i = 0; i < surveysWithEmptyUrls.length; i += BATCH_SIZE) {
|
||||
const batch = surveysWithEmptyUrls.slice(i, i + BATCH_SIZE);
|
||||
|
||||
const batchPromises = batch.map((survey) => {
|
||||
// Clean the blocks
|
||||
const cleanedBlocks = survey.blocks.map((block) => {
|
||||
const cleanedElements = block.elements.map((element) => {
|
||||
const cleanedElement = { ...element };
|
||||
if (cleanedElement.imageUrl === "") {
|
||||
delete cleanedElement.imageUrl;
|
||||
}
|
||||
if (cleanedElement.videoUrl === "") {
|
||||
delete cleanedElement.videoUrl;
|
||||
}
|
||||
return cleanedElement;
|
||||
});
|
||||
|
||||
return { ...block, elements: cleanedElements };
|
||||
});
|
||||
|
||||
const cleanedWelcomeCard = { ...survey.welcomeCard };
|
||||
if (cleanedWelcomeCard.fileUrl === "") {
|
||||
delete cleanedWelcomeCard.fileUrl;
|
||||
}
|
||||
if (cleanedWelcomeCard.videoUrl === "") {
|
||||
delete cleanedWelcomeCard.videoUrl;
|
||||
}
|
||||
|
||||
const cleanedEndings = survey.endings.map((ending) => {
|
||||
const cleanedEnding = { ...ending };
|
||||
if (cleanedEnding.imageUrl === "") {
|
||||
delete cleanedEnding.imageUrl;
|
||||
}
|
||||
if (cleanedEnding.videoUrl === "") {
|
||||
delete cleanedEnding.videoUrl;
|
||||
}
|
||||
return cleanedEnding;
|
||||
});
|
||||
|
||||
// Convert JSON arrays to PostgreSQL jsonb[] using array_agg + jsonb_array_elements
|
||||
const blocksJson = JSON.stringify(cleanedBlocks);
|
||||
const endingsJson = JSON.stringify(cleanedEndings);
|
||||
const welcomeCardJson = JSON.stringify(cleanedWelcomeCard);
|
||||
|
||||
return tx.$executeRaw`
|
||||
UPDATE "Survey"
|
||||
SET
|
||||
blocks = (SELECT array_agg(elem) FROM jsonb_array_elements(${blocksJson}::jsonb) AS elem),
|
||||
endings = (SELECT array_agg(elem) FROM jsonb_array_elements(${endingsJson}::jsonb) AS elem),
|
||||
"welcomeCard" = ${welcomeCardJson}::jsonb
|
||||
WHERE id = ${survey.id}
|
||||
`;
|
||||
});
|
||||
|
||||
await Promise.all(batchPromises);
|
||||
logger.info(
|
||||
`Processed batch ${(Math.floor(i / BATCH_SIZE) + 1).toString()}/${Math.ceil(surveysWithEmptyUrls.length / BATCH_SIZE).toString()}`
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`Successfully cleaned ${surveysWithEmptyUrls.length.toString()} surveys`);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
export interface SurveyElement {
|
||||
id: string;
|
||||
imageUrl?: string;
|
||||
videoUrl?: string;
|
||||
}
|
||||
export interface Block {
|
||||
id: string;
|
||||
elements: SurveyElement[];
|
||||
}
|
||||
|
||||
export interface SurveyRecord {
|
||||
id: string;
|
||||
blocks: Block[];
|
||||
welcomeCard: {
|
||||
fileUrl?: string;
|
||||
videoUrl?: string;
|
||||
};
|
||||
endings: {
|
||||
imageUrl?: string;
|
||||
videoUrl?: string;
|
||||
}[];
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { type ZodIssue, z } from "zod";
|
||||
import { ZSurveyFollowUp } from "@formbricks/database/types/survey-follow-up";
|
||||
import { ZActionClass, ZActionClassNoCodeConfig } from "../action-classes";
|
||||
import { ZColor, ZId, ZPlacement, getZSafeUrl } from "../common";
|
||||
import { ZColor, ZId, ZPlacement, ZUrl, getZSafeUrl } from "../common";
|
||||
import { ZContactAttributes } from "../contact-attribute";
|
||||
import { type TI18nString, ZI18nString } from "../i18n";
|
||||
import { ZLanguage } from "../project";
|
||||
@@ -60,16 +60,16 @@ export const ZSurveyEndScreenCard = ZSurveyEndingBase.extend({
|
||||
headline: ZI18nString.optional(),
|
||||
subheader: ZI18nString.optional(),
|
||||
buttonLabel: ZI18nString.optional(),
|
||||
buttonLink: z.string().optional(),
|
||||
imageUrl: z.string().optional(),
|
||||
videoUrl: z.string().optional(),
|
||||
buttonLink: ZUrl.optional(),
|
||||
imageUrl: ZUrl.optional(),
|
||||
videoUrl: ZUrl.optional(),
|
||||
});
|
||||
|
||||
export type TSurveyEndScreenCard = z.infer<typeof ZSurveyEndScreenCard>;
|
||||
|
||||
export const ZSurveyRedirectUrlCard = ZSurveyEndingBase.extend({
|
||||
type: z.literal("redirectToUrl"),
|
||||
url: z.string().optional(),
|
||||
url: ZUrl.optional(),
|
||||
label: z.string().optional(),
|
||||
});
|
||||
|
||||
@@ -143,11 +143,11 @@ export const ZSurveyWelcomeCard = z
|
||||
enabled: z.boolean(),
|
||||
headline: ZI18nString.optional(),
|
||||
subheader: ZI18nString.optional(),
|
||||
fileUrl: z.string().optional(),
|
||||
fileUrl: ZUrl.optional(),
|
||||
buttonLabel: ZI18nString.optional(),
|
||||
timeToFinish: z.boolean().default(true),
|
||||
showResponseCount: z.boolean().default(false),
|
||||
videoUrl: z.string().optional(),
|
||||
videoUrl: ZUrl.optional(),
|
||||
})
|
||||
.refine((schema) => !(schema.enabled && !schema.headline), {
|
||||
message: "Welcome card must have a headline",
|
||||
|
||||
Reference in New Issue
Block a user