mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-07 14:21:52 -05:00
feat: Refactor Triggers and combine Action Classes and Inline Triggers (#2562)
Co-authored-by: Matthias Nannt <mail@matthiasnannt.com> Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com> Co-authored-by: Johannes <johannes@formbricks.com>
This commit is contained in:
+205
@@ -0,0 +1,205 @@
|
||||
import { init } from "@paralleldrive/cuid2";
|
||||
import { Prisma, PrismaClient } from "@prisma/client";
|
||||
|
||||
const createId = init({ length: 5 });
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
await prisma.$transaction(
|
||||
async (tx) => {
|
||||
const startTime = Date.now();
|
||||
console.log("Starting data migration...");
|
||||
|
||||
// 1. copy value of name to key for all action classes where type is code
|
||||
const codeActionClasses = await tx.actionClass.findMany({
|
||||
where: {
|
||||
type: "code",
|
||||
},
|
||||
});
|
||||
console.log(`Found ${codeActionClasses.length} saved code action classes to update.`);
|
||||
|
||||
await Promise.all(
|
||||
codeActionClasses.map((codeActionClass) => {
|
||||
return tx.actionClass.update({
|
||||
where: {
|
||||
id: codeActionClass.id,
|
||||
},
|
||||
data: {
|
||||
key: codeActionClass.name,
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
console.log("Updated keys for saved code action classes.");
|
||||
|
||||
// 2. find all surveys with inlineTriggers and create action classes for them
|
||||
const surveysWithInlineTriggers = await tx.survey.findMany({
|
||||
where: {
|
||||
inlineTriggers: {
|
||||
not: Prisma.JsonNull,
|
||||
},
|
||||
},
|
||||
});
|
||||
console.log(`Found ${surveysWithInlineTriggers.length} surveys with inline triggers to process.`);
|
||||
|
||||
// 3. Create action classes for inlineTriggers and update survey to use the newly created action classes
|
||||
const getActionClassIdByCode = async (code: string, environmentId: string): Promise<string> => {
|
||||
const existingActionClass = await tx.actionClass.findFirst({
|
||||
where: {
|
||||
type: "code",
|
||||
key: code,
|
||||
environmentId: environmentId,
|
||||
},
|
||||
});
|
||||
|
||||
let codeActionId = "";
|
||||
|
||||
if (existingActionClass) {
|
||||
codeActionId = existingActionClass.id;
|
||||
} else {
|
||||
let codeActionClassName = code;
|
||||
|
||||
// check if there is an existing noCode action class with this name
|
||||
const existingNoCodeActionClass = await tx.actionClass.findFirst({
|
||||
where: {
|
||||
name: code,
|
||||
environmentId: environmentId,
|
||||
NOT: {
|
||||
type: "code",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (existingNoCodeActionClass) {
|
||||
codeActionClassName = `${code}-${createId()}`;
|
||||
}
|
||||
|
||||
// create a new private action for codeConfig
|
||||
const codeActionClass = await tx.actionClass.create({
|
||||
data: {
|
||||
name: codeActionClassName,
|
||||
key: code,
|
||||
type: "code",
|
||||
environment: {
|
||||
connect: {
|
||||
id: environmentId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
codeActionId = codeActionClass.id;
|
||||
}
|
||||
|
||||
return codeActionId;
|
||||
};
|
||||
|
||||
for (const survey of surveysWithInlineTriggers) {
|
||||
const { codeConfig, noCodeConfig } = survey.inlineTriggers ?? {};
|
||||
|
||||
if (
|
||||
noCodeConfig &&
|
||||
Object.keys(noCodeConfig).length > 0 &&
|
||||
(!codeConfig || codeConfig.identifier === "")
|
||||
) {
|
||||
// surveys with only noCodeConfig
|
||||
|
||||
// create a new private action for noCodeConfig
|
||||
const noCodeActionClass = await tx.actionClass.create({
|
||||
data: {
|
||||
name: `Custom Action-${createId()}`,
|
||||
noCodeConfig,
|
||||
type: "noCode",
|
||||
environment: {
|
||||
connect: {
|
||||
id: survey.environmentId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// update survey to use the newly created action class
|
||||
await tx.survey.update({
|
||||
where: {
|
||||
id: survey.id,
|
||||
},
|
||||
data: {
|
||||
triggers: {
|
||||
create: {
|
||||
actionClassId: noCodeActionClass.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} else if ((!noCodeConfig || Object.keys(noCodeConfig).length === 0) && codeConfig?.identifier) {
|
||||
const codeActionId = await getActionClassIdByCode(codeConfig.identifier, survey.environmentId);
|
||||
|
||||
await tx.survey.update({
|
||||
where: {
|
||||
id: survey.id,
|
||||
},
|
||||
data: {
|
||||
triggers: {
|
||||
create: {
|
||||
actionClassId: codeActionId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} else if (codeConfig?.identifier && noCodeConfig) {
|
||||
// create a new private action for noCodeConfig
|
||||
|
||||
const noCodeActionClass = await tx.actionClass.create({
|
||||
data: {
|
||||
name: `Custom Action-${createId()}`,
|
||||
noCodeConfig,
|
||||
type: "noCode",
|
||||
environment: {
|
||||
connect: {
|
||||
id: survey.environmentId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const codeActionId = await getActionClassIdByCode(codeConfig.identifier, survey.environmentId);
|
||||
|
||||
// update survey to use the newly created action classes
|
||||
await tx.survey.update({
|
||||
where: {
|
||||
id: survey.id,
|
||||
},
|
||||
data: {
|
||||
triggers: {
|
||||
createMany: {
|
||||
data: [
|
||||
{
|
||||
actionClassId: noCodeActionClass.id,
|
||||
},
|
||||
{
|
||||
actionClassId: codeActionId,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const endTime = Date.now();
|
||||
console.log(`Data migration completed. Total time: ${(endTime - startTime) / 1000}s`);
|
||||
},
|
||||
{
|
||||
timeout: 180000, // 3 minutes
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e: Error) => {
|
||||
console.error("Error during migration: ", e.message);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
@@ -6,7 +6,6 @@ import { TBaseFilters } from "@formbricks/types/segment";
|
||||
import {
|
||||
TSurveyClosedMessage,
|
||||
TSurveyHiddenFields,
|
||||
TSurveyInlineTriggers,
|
||||
TSurveyProductOverwrites,
|
||||
TSurveyQuestions,
|
||||
TSurveySingleUse,
|
||||
@@ -38,7 +37,6 @@ declare global {
|
||||
export type TeamBilling = TTeamBilling;
|
||||
export type UserNotificationSettings = TUserNotificationSettings;
|
||||
export type SegmentFilter = TBaseFilters;
|
||||
export type SurveyInlineTriggers = TSurveyInlineTriggers;
|
||||
export type Styling = TProductStyling;
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[key,environmentId]` on the table `ActionClass` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "ActionClass" ADD COLUMN "key" TEXT;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ActionClass_key_environmentId_key" ON "ActionClass"("key", "environmentId");
|
||||
@@ -31,6 +31,7 @@
|
||||
"data-migration:mls-fix": "ts-node ./data-migrations/20240318050527_add_languages_and_survey_languages/data-migration-fix.ts",
|
||||
"data-migration:mls-range-fix": "ts-node ./data-migrations/20240318050527_add_languages_and_survey_languages/data-migration-range-fix.ts",
|
||||
"data-migration:userId": "ts-node ./data-migrations/20240408123456_userid_migration/data-migration.ts",
|
||||
"data-migration:refactor-actions": "ts-node ./data-migrations/20240501111944_refactors_actions_and_removes_inline_triggers/data-migration.ts",
|
||||
"data-migration:mls-welcomeCard-fix": "ts-node ./data-migrations/20240318050527_add_languages_and_survey_languages/data-migration-welcomeCard-fix.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -332,6 +332,7 @@ model ActionClass {
|
||||
name String
|
||||
description String?
|
||||
type ActionType
|
||||
key String?
|
||||
/// @zod.custom(imports.ZActionClassNoCodeConfig)
|
||||
/// [ActionClassNoCodeConfig]
|
||||
noCodeConfig Json?
|
||||
@@ -340,6 +341,7 @@ model ActionClass {
|
||||
surveys SurveyTrigger[]
|
||||
actions Action[]
|
||||
|
||||
@@unique([key, environmentId])
|
||||
@@unique([name, environmentId])
|
||||
@@index([environmentId, createdAt])
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import z from "zod";
|
||||
|
||||
export { ZProductStyling } from "@formbricks/types/styling";
|
||||
|
||||
export const ZActionProperties = z.record(z.string());
|
||||
export { ZActionClassNoCodeConfig } from "@formbricks/types/actionClasses";
|
||||
export { ZIntegrationConfig } from "@formbricks/types/integration";
|
||||
@@ -28,5 +26,4 @@ export {
|
||||
|
||||
export { ZSegmentFilters } from "@formbricks/types/segment";
|
||||
export { ZTeamBilling } from "@formbricks/types/teams";
|
||||
export { ZLanguages } from "@formbricks/types/product";
|
||||
export { ZUserNotificationSettings } from "@formbricks/types/user";
|
||||
|
||||
Reference in New Issue
Block a user