mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-05 00:49:57 -06:00
feat: adds product config (#2760)
Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
@@ -101,7 +101,13 @@ export const POST = async (request: Request) => {
|
||||
if (isMultiOrgEnabled) {
|
||||
const organization = await createOrganization({ name: user.name + "'s Organization" });
|
||||
await createMembership(organization.id, user.id, { role: "owner", accepted: true });
|
||||
const product = await createProduct(organization.id, { name: "My Product" });
|
||||
const product = await createProduct(organization.id, {
|
||||
name: "My Product",
|
||||
config: {
|
||||
channel: null,
|
||||
industry: null,
|
||||
},
|
||||
});
|
||||
|
||||
const updatedNotificationSettings = {
|
||||
...user.notificationSettings,
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import { PrismaClient, SurveyType } from "@prisma/client";
|
||||
import { TProductConfigChannel } from "@formbricks/types/product";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
const main = async () => {
|
||||
await prisma.$transaction(
|
||||
async (tx) => {
|
||||
const startTime = Date.now();
|
||||
console.log("Starting data migration...");
|
||||
|
||||
// Fetch all products
|
||||
const products = await tx.product.findMany({
|
||||
include: {
|
||||
environments: {
|
||||
select: {
|
||||
surveys: {
|
||||
select: {
|
||||
type: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`Found ${products.length} products to migrate...\n`);
|
||||
|
||||
const channelStatusCounts = {
|
||||
[SurveyType.app]: 0,
|
||||
[SurveyType.link]: 0,
|
||||
[SurveyType.website]: 0,
|
||||
null: 0,
|
||||
};
|
||||
|
||||
const updatePromises = products.map((product) => {
|
||||
const surveyTypes = new Set<SurveyType>();
|
||||
|
||||
// Collect all unique survey types for the product
|
||||
for (const environment of product.environments) {
|
||||
for (const survey of environment.surveys) {
|
||||
surveyTypes.add(survey.type);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the channel based on the survey types, default to null
|
||||
let channel: TProductConfigChannel = null;
|
||||
|
||||
if (surveyTypes.size === 0 || surveyTypes.size === 3) {
|
||||
// if there are no surveys or all 3 types of surveys (website, app, and link) are present, set channel to null
|
||||
channel = null;
|
||||
} else if (surveyTypes.size === 1) {
|
||||
// if there is only one type of survey, set channel to that type
|
||||
const type = Array.from(surveyTypes)[0];
|
||||
if (type === SurveyType.web) {
|
||||
// if the survey type is web, set channel to null, since web is a legacy type and will be removed
|
||||
channel = null;
|
||||
} else {
|
||||
// if the survey type is not web, set channel to that type
|
||||
channel = type;
|
||||
}
|
||||
} else if (surveyTypes.has(SurveyType.link) && surveyTypes.has(SurveyType.app)) {
|
||||
// if both link and app surveys are present, set channel to app
|
||||
channel = SurveyType.app;
|
||||
} else if (surveyTypes.has(SurveyType.link) && surveyTypes.has(SurveyType.website)) {
|
||||
// if both link and website surveys are present, set channel to website
|
||||
channel = SurveyType.website;
|
||||
}
|
||||
|
||||
// Increment the count for the determined channel
|
||||
channelStatusCounts[channel ?? "null"]++;
|
||||
|
||||
// Update the product with the determined channel and set industry to null
|
||||
return tx.product.update({
|
||||
where: { id: product.id },
|
||||
data: {
|
||||
config: {
|
||||
channel,
|
||||
industry: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(updatePromises);
|
||||
|
||||
console.log(
|
||||
`Channel status counts: ${Object.entries(channelStatusCounts).map(
|
||||
([channel, count]) => `\n${channel}: ${count}`
|
||||
)}\n`
|
||||
);
|
||||
|
||||
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();
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TActionClassNoCodeConfig } from "@formbricks/types/actionClasses";
|
||||
import { TIntegrationConfig } from "@formbricks/types/integration";
|
||||
import { TOrganizationBilling } from "@formbricks/types/organizations";
|
||||
import { TProductStyling } from "@formbricks/types/product";
|
||||
import { TProductConfig, TProductStyling } from "@formbricks/types/product";
|
||||
import { TResponseData, TResponseMeta, TResponsePersonAttributes } from "@formbricks/types/responses";
|
||||
import { TBaseFilters } from "@formbricks/types/segment";
|
||||
import {
|
||||
@@ -22,6 +22,7 @@ declare global {
|
||||
export type ActionProperties = { [key: string]: string };
|
||||
export type ActionClassNoCodeConfig = TActionClassNoCodeConfig;
|
||||
export type IntegrationConfig = TIntegrationConfig;
|
||||
export type ProductConfig = TProductConfig;
|
||||
export type ResponseData = TResponseData;
|
||||
export type ResponseMeta = TResponseMeta;
|
||||
export type ResponsePersonAttributes = TResponsePersonAttributes;
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Product" ADD COLUMN "config" JSONB NOT NULL DEFAULT '{}';
|
||||
@@ -34,7 +34,8 @@
|
||||
"data-migration:mls-welcomeCard-fix": "ts-node ./data-migrations/20240318050527_add_languages_and_survey_languages/data-migration-welcomeCard-fix.ts",
|
||||
"data-migration:v2.0": "pnpm data-migration:mls && pnpm data-migration:styling && pnpm data-migration:styling-fix && pnpm data-migration:website-surveys && pnpm data-migration:userId && pnpm data-migration:mls-welcomeCard-fix && pnpm data-migration:refactor-actions",
|
||||
"data-migration:extended-noCodeActions": "ts-node ./data-migrations/20240524053239_extends_no_code_action_schema/data-migration.ts",
|
||||
"data-migration:v2.1": "pnpm data-migration:extended-noCodeActions"
|
||||
"data-migration:v2.1": "pnpm data-migration:extended-noCodeActions",
|
||||
"data-migration:product-config": "ts-node ./data-migrations/20240612115151_adds_product_config/data-migration.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^5.14.0",
|
||||
|
||||
@@ -433,6 +433,9 @@ model Product {
|
||||
/// @zod.custom(imports.ZProductStyling)
|
||||
/// [Styling]
|
||||
styling Json @default("{\"allowStyleOverwrite\":true}")
|
||||
/// @zod.custom(imports.ZProductConfig)
|
||||
/// [ProductConfig]
|
||||
config Json @default("{}")
|
||||
recontactDays Int @default(7)
|
||||
linkSurveyBranding Boolean @default(true) // Determines if the survey branding should be displayed in link surveys
|
||||
inAppSurveyBranding Boolean @default(true) // Determines if the survey branding should be displayed in in-app surveys
|
||||
|
||||
@@ -25,6 +25,7 @@ const selectProduct = {
|
||||
recontactDays: true,
|
||||
linkSurveyBranding: true,
|
||||
inAppSurveyBranding: true,
|
||||
config: true,
|
||||
placement: true,
|
||||
clickOutsideClose: true,
|
||||
darkOverlay: true,
|
||||
|
||||
@@ -9,6 +9,16 @@ export const ZProductStyling = ZBaseStyling.extend({
|
||||
|
||||
export type TProductStyling = z.infer<typeof ZProductStyling>;
|
||||
|
||||
export const ZProductConfigChannel = z.enum(["link", "app", "website"]).nullable();
|
||||
export type TProductConfigChannel = z.infer<typeof ZProductConfigChannel>;
|
||||
|
||||
export const ZProductConfig = z.object({
|
||||
channel: ZProductConfigChannel,
|
||||
industry: z.enum(["eCommerce", "saas"]).nullable(),
|
||||
});
|
||||
|
||||
export type TProductConfig = z.infer<typeof ZProductConfig>;
|
||||
|
||||
export const ZLanguage = z.object({
|
||||
id: z.string().cuid2(),
|
||||
createdAt: z.date(),
|
||||
@@ -50,6 +60,7 @@ export const ZProduct = z.object({
|
||||
.max(365, { message: "Must be less than 365" }),
|
||||
inAppSurveyBranding: z.boolean(),
|
||||
linkSurveyBranding: z.boolean(),
|
||||
config: ZProductConfig,
|
||||
placement: ZPlacement,
|
||||
clickOutsideClose: z.boolean(),
|
||||
darkOverlay: z.boolean(),
|
||||
@@ -70,6 +81,7 @@ export const ZProductUpdateInput = z.object({
|
||||
recontactDays: z.number().int().optional(),
|
||||
inAppSurveyBranding: z.boolean().optional(),
|
||||
linkSurveyBranding: z.boolean().optional(),
|
||||
config: ZProductConfig.optional(),
|
||||
placement: ZPlacement.optional(),
|
||||
clickOutsideClose: z.boolean().optional(),
|
||||
darkOverlay: z.boolean().optional(),
|
||||
|
||||
Reference in New Issue
Block a user