mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-24 03:21:20 -05:00
feat: survey follow ups (#4247)
Co-authored-by: Johannes <johannes@formbricks.com> Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com> Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
@@ -1,14 +1,12 @@
|
||||
/* eslint-disable import/no-relative-packages -- required for importing types */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-namespace -- using namespaces is required for prisma-json-types-generator */
|
||||
import { type TActionClassNoCodeConfig } from "@formbricks/types/action-classes";
|
||||
import { type TIntegrationConfig } from "@formbricks/types/integration";
|
||||
import { type TOrganizationBilling } from "@formbricks/types/organizations";
|
||||
import { type TProductConfig, type TProductStyling } from "@formbricks/types/product";
|
||||
import {
|
||||
type TResponseData,
|
||||
type TResponseMeta,
|
||||
type TResponsePersonAttributes,
|
||||
} from "@formbricks/types/responses";
|
||||
import { type TBaseFilters } from "@formbricks/types/segment";
|
||||
import { type TActionClassNoCodeConfig } from "../types/action-classes";
|
||||
import { type TIntegrationConfig } from "../types/integration";
|
||||
import { type TOrganizationBilling } from "../types/organizations";
|
||||
import { type TProductConfig, type TProductStyling } from "../types/product";
|
||||
import { type TResponseData, type TResponseMeta, type TResponsePersonAttributes } from "../types/responses";
|
||||
import { type TBaseFilters } from "../types/segment";
|
||||
import {
|
||||
type TSurveyClosedMessage,
|
||||
type TSurveyEnding,
|
||||
@@ -19,8 +17,9 @@ import {
|
||||
type TSurveyStyling,
|
||||
type TSurveyVariables,
|
||||
type TSurveyWelcomeCard,
|
||||
} from "@formbricks/types/surveys/types";
|
||||
import { type TUserLocale, type TUserNotificationSettings } from "@formbricks/types/user";
|
||||
} from "../types/surveys/types";
|
||||
import { type TUserLocale, type TUserNotificationSettings } from "../types/user";
|
||||
import type { TSurveyFollowUpAction, TSurveyFollowUpTrigger } from "./types/survey-follow-up";
|
||||
|
||||
declare global {
|
||||
namespace PrismaJson {
|
||||
@@ -45,5 +44,7 @@ declare global {
|
||||
export type SegmentFilter = TBaseFilters;
|
||||
export type Styling = TProductStyling;
|
||||
export type Locale = TUserLocale;
|
||||
export type SurveyFollowUpTrigger = TSurveyFollowUpTrigger;
|
||||
export type SurveyFollowUpAction = TSurveyFollowUpAction;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "SurveyFollowUp" (
|
||||
"id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
"surveyId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"trigger" JSONB NOT NULL,
|
||||
"action" JSONB NOT NULL,
|
||||
|
||||
CONSTRAINT "SurveyFollowUp_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "SurveyFollowUp" ADD CONSTRAINT "SurveyFollowUp_surveyId_fkey" FOREIGN KEY ("surveyId") REFERENCES "Survey"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "Response" ADD COLUMN "endingId" TEXT;
|
||||
@@ -65,9 +65,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formbricks/config-typescript": "workspace:*",
|
||||
"@formbricks/types": "workspace:*",
|
||||
"@paralleldrive/cuid2": "2.2.2",
|
||||
"@formbricks/eslint-config": "workspace:*",
|
||||
"@paralleldrive/cuid2": "2.2.2",
|
||||
"prisma": "5.20.0",
|
||||
"prisma-dbml-generator": "0.12.0",
|
||||
"prisma-json-types-generator": "3.1.1",
|
||||
|
||||
@@ -112,6 +112,7 @@ model Response {
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @updatedAt @map(name: "updated_at")
|
||||
finished Boolean @default(false)
|
||||
endingId String?
|
||||
survey Survey @relation(fields: [surveyId], references: [id], onDelete: Cascade)
|
||||
surveyId String
|
||||
person Person? @relation(fields: [personId], references: [id], onDelete: Cascade)
|
||||
@@ -333,11 +334,27 @@ model Survey {
|
||||
languages SurveyLanguage[]
|
||||
showLanguageSwitch Boolean?
|
||||
documents Document[]
|
||||
followUps SurveyFollowUp[]
|
||||
|
||||
@@index([environmentId, updatedAt])
|
||||
@@index([segmentId])
|
||||
}
|
||||
|
||||
model SurveyFollowUp {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @updatedAt @map(name: "updated_at")
|
||||
survey Survey @relation(fields: [surveyId], references: [id], onDelete: Cascade)
|
||||
surveyId String
|
||||
name String
|
||||
/// [SurveyFollowUpTrigger]
|
||||
/// @zod.custom(imports.ZSurveyFollowUpTrigger)
|
||||
trigger Json
|
||||
/// [SurveyFollowUpAction]
|
||||
/// @zod.custom(imports.ZSurveyFollowUpAction)
|
||||
action Json
|
||||
}
|
||||
|
||||
enum ActionType {
|
||||
code
|
||||
noCode
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const ZSurveyFollowUpTrigger = z
|
||||
.object({
|
||||
type: z.enum(["response", "endings"]),
|
||||
properties: z
|
||||
.object({
|
||||
endingIds: z.array(z.string().cuid2()),
|
||||
})
|
||||
.nullable(),
|
||||
})
|
||||
.superRefine((trigger, ctx) => {
|
||||
if (trigger.type === "response") {
|
||||
if (trigger.properties) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Properties should be null for response type",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (trigger.type === "endings") {
|
||||
if (!trigger.properties) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Properties must be defined for endings type",
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export type TSurveyFollowUpTrigger = z.infer<typeof ZSurveyFollowUpTrigger>;
|
||||
|
||||
export const ZSurveyFollowUpAction = z.object({
|
||||
type: z.literal("send-email"),
|
||||
properties: z.object({
|
||||
to: z.string(),
|
||||
from: z.string().email(),
|
||||
replyTo: z.array(z.string().email()),
|
||||
subject: z.string(),
|
||||
body: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export type TSurveyFollowUpAction = z.infer<typeof ZSurveyFollowUpAction>;
|
||||
|
||||
export const ZSurveyFollowUp = z.object({
|
||||
id: z.string().cuid2(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
name: z.string(),
|
||||
trigger: ZSurveyFollowUpTrigger,
|
||||
action: ZSurveyFollowUpAction,
|
||||
surveyId: z.string().cuid2(),
|
||||
});
|
||||
|
||||
export type TSurveyFollowUp = z.infer<typeof ZSurveyFollowUp>;
|
||||
@@ -1,15 +1,11 @@
|
||||
/* eslint-disable import/no-relative-packages -- required for importing types */
|
||||
import { z } from "zod";
|
||||
|
||||
export const ZActionProperties = z.record(z.string());
|
||||
export { ZActionClassNoCodeConfig } from "@formbricks/types/action-classes";
|
||||
export { ZIntegrationConfig } from "@formbricks/types/integration";
|
||||
export { ZActionClassNoCodeConfig } from "../types/action-classes";
|
||||
export { ZIntegrationConfig } from "../types/integration";
|
||||
|
||||
export {
|
||||
ZResponseData,
|
||||
ZResponsePersonAttributes,
|
||||
ZResponseMeta,
|
||||
ZResponseTtc,
|
||||
} from "@formbricks/types/responses";
|
||||
export { ZResponseData, ZResponsePersonAttributes, ZResponseMeta, ZResponseTtc } from "../types/responses";
|
||||
|
||||
export {
|
||||
ZSurveyWelcomeCard,
|
||||
@@ -22,8 +18,10 @@ export {
|
||||
ZSurveySingleUse,
|
||||
ZSurveyInlineTriggers,
|
||||
ZSurveyEnding,
|
||||
} from "@formbricks/types/surveys/types";
|
||||
} from "../types/surveys/types";
|
||||
|
||||
export { ZSegmentFilters } from "@formbricks/types/segment";
|
||||
export { ZOrganizationBilling } from "@formbricks/types/organizations";
|
||||
export { ZUserNotificationSettings } from "@formbricks/types/user";
|
||||
export { ZSurveyFollowUpAction, ZSurveyFollowUpTrigger } from "./types/survey-follow-up";
|
||||
|
||||
export { ZSegmentFilters } from "../types/segment";
|
||||
export { ZOrganizationBilling } from "../types/organizations";
|
||||
export { ZUserNotificationSettings } from "../types/user";
|
||||
|
||||
Reference in New Issue
Block a user