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:
Piyush Gupta
2024-05-07 19:17:41 +05:30
committed by GitHub
parent b016d80cb2
commit 6bfd02794d
64 changed files with 1260 additions and 1381 deletions
@@ -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();
});
-2
View File
@@ -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;
}
}
@@ -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");
+1
View File
@@ -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": {
+2
View File
@@ -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])
}
-3
View File
@@ -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";