From 28514487e08462c8f2bd1234d7151a2a97431ffd Mon Sep 17 00:00:00 2001
From: Piyush Gupta <56182734+gupta-piyush19@users.noreply.github.com>
Date: Thu, 24 Jul 2025 17:31:39 +0530
Subject: [PATCH] chore: sunset weekly summary (#6282)
---
.../environments/[environmentId]/actions.ts | 4 -
.../components/EnvironmentLayout.test.tsx | 2 +-
.../components/MainNavigation.test.tsx | 2 +-
.../components/EditAlerts.test.tsx | 1 -
.../components/EditWeeklySummary.test.tsx | 166 -----
.../components/EditWeeklySummary.tsx | 59 --
.../components/NotificationSwitch.test.tsx | 39 --
.../components/NotificationSwitch.tsx | 2 +-
.../(account)/notifications/loading.test.tsx | 12 -
.../(account)/notifications/loading.tsx | 5 -
.../(account)/notifications/page.test.tsx | 32 +-
.../settings/(account)/notifications/page.tsx | 10 -
.../components/AccountSecurity.test.tsx | 2 +-
.../profile/components/DeleteAccount.test.tsx | 2 +-
.../EditProfileDetailsForm.test.tsx | 2 +-
.../settings/(account)/profile/page.test.tsx | 2 +-
.../(organization)/enterprise/page.test.tsx | 2 +-
.../components/ResponseCardModal.test.tsx | 2 +-
.../components/SurveyAnalysisCTA.test.tsx | 4 -
.../components/share-survey-modal.test.tsx | 1 -
.../lib/notificationResponse.test.ts | 276 ---------
.../lib/notificationResponse.ts | 78 ---
.../weekly-summary/lib/organization.test.ts | 48 --
.../cron/weekly-summary/lib/organization.ts | 10 -
.../cron/weekly-summary/lib/project.test.ts | 570 ------------------
.../api/cron/weekly-summary/lib/project.ts | 111 ----
apps/web/app/api/cron/weekly-summary/route.ts | 70 ---
apps/web/app/page.test.tsx | 4 -
apps/web/lib/organization/service.test.ts | 4 +-
apps/web/lib/organization/service.ts | 2 +-
apps/web/lib/survey/__mock__/survey.mock.ts | 2 +-
apps/web/lib/user/service.test.ts | 2 +-
apps/web/modules/auth/invite/page.tsx | 1 -
apps/web/modules/auth/lib/mock-data.ts | 2 +-
apps/web/modules/auth/signup/actions.ts | 4 +-
.../modules/ee/audit-logs/lib/utils.test.ts | 2 -
.../components/response-timeline.test.tsx | 1 -
apps/web/modules/ee/sso/lib/sso-handlers.ts | 3 -
.../lib/tests/__mock__/sso-handlers.mock.ts | 2 +-
.../create-reminder-notification-body.tsx | 41 --
.../live-survey-notification.tsx | 123 ----
.../no-live-survey-notification-email.tsx | 33 -
.../weekly-summary/notification-footer.tsx | 34 --
.../weekly-summary/notification-header.tsx | 51 --
.../weekly-summary/notification-insight.tsx | 46 --
.../weekly-summary-notification-email.tsx | 44 --
apps/web/modules/email/index.tsx | 70 ---
apps/web/modules/organization/actions.ts | 7 +-
.../removed-from-organization.test.tsx | 1 -
.../setup/organization/create/page.test.tsx | 10 -
docker/cronjobs | 2 +-
.../values-staging.yaml.gotmpl | 24 +-
.../formbricks-cloud-helm/values.yaml.gotmpl | 24 +-
.../migration.ts | 15 +
packages/types/user.ts | 1 -
packages/types/weekly-summary.ts | 104 ----
56 files changed, 38 insertions(+), 2135 deletions(-)
delete mode 100644 apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/EditWeeklySummary.test.tsx
delete mode 100644 apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/EditWeeklySummary.tsx
delete mode 100644 apps/web/app/api/cron/weekly-summary/lib/notificationResponse.test.ts
delete mode 100644 apps/web/app/api/cron/weekly-summary/lib/notificationResponse.ts
delete mode 100644 apps/web/app/api/cron/weekly-summary/lib/organization.test.ts
delete mode 100644 apps/web/app/api/cron/weekly-summary/lib/organization.ts
delete mode 100644 apps/web/app/api/cron/weekly-summary/lib/project.test.ts
delete mode 100644 apps/web/app/api/cron/weekly-summary/lib/project.ts
delete mode 100644 apps/web/app/api/cron/weekly-summary/route.ts
delete mode 100644 apps/web/modules/email/emails/weekly-summary/create-reminder-notification-body.tsx
delete mode 100644 apps/web/modules/email/emails/weekly-summary/live-survey-notification.tsx
delete mode 100644 apps/web/modules/email/emails/weekly-summary/no-live-survey-notification-email.tsx
delete mode 100644 apps/web/modules/email/emails/weekly-summary/notification-footer.tsx
delete mode 100644 apps/web/modules/email/emails/weekly-summary/notification-header.tsx
delete mode 100644 apps/web/modules/email/emails/weekly-summary/notification-insight.tsx
delete mode 100644 apps/web/modules/email/emails/weekly-summary/weekly-summary-notification-email.tsx
create mode 100644 packages/database/migration/20250722075349_sunset_weekly_summary/migration.ts
delete mode 100644 packages/types/weekly-summary.ts
diff --git a/apps/web/app/(app)/environments/[environmentId]/actions.ts b/apps/web/app/(app)/environments/[environmentId]/actions.ts
index ce866a868b..a533636e7f 100644
--- a/apps/web/app/(app)/environments/[environmentId]/actions.ts
+++ b/apps/web/app/(app)/environments/[environmentId]/actions.ts
@@ -71,10 +71,6 @@ export const createProjectAction = authenticatedActionClient.schema(ZCreateProje
alert: {
...user.notificationSettings?.alert,
},
- weeklySummary: {
- ...user.notificationSettings?.weeklySummary,
- [project.id]: true,
- },
};
await updateUser(user.id, {
diff --git a/apps/web/app/(app)/environments/[environmentId]/components/EnvironmentLayout.test.tsx b/apps/web/app/(app)/environments/[environmentId]/components/EnvironmentLayout.test.tsx
index 012eeb650f..959e7fcff7 100644
--- a/apps/web/app/(app)/environments/[environmentId]/components/EnvironmentLayout.test.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/components/EnvironmentLayout.test.tsx
@@ -104,7 +104,7 @@ const mockUser = {
identityProvider: "email",
createdAt: new Date(),
updatedAt: new Date(),
- notificationSettings: { alert: {}, weeklySummary: {} },
+ notificationSettings: { alert: {} },
} as unknown as TUser;
const mockOrganization = {
diff --git a/apps/web/app/(app)/environments/[environmentId]/components/MainNavigation.test.tsx b/apps/web/app/(app)/environments/[environmentId]/components/MainNavigation.test.tsx
index 56df681c75..916bd50a49 100644
--- a/apps/web/app/(app)/environments/[environmentId]/components/MainNavigation.test.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/components/MainNavigation.test.tsx
@@ -106,7 +106,7 @@ const mockUser = {
identityProvider: "email",
createdAt: new Date(),
updatedAt: new Date(),
- notificationSettings: { alert: {}, weeklySummary: {} },
+ notificationSettings: { alert: {} },
role: "project_manager",
objective: "other",
} as unknown as TUser;
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/EditAlerts.test.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/EditAlerts.test.tsx
index d1804af298..3f9b56d579 100644
--- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/EditAlerts.test.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/EditAlerts.test.tsx
@@ -49,7 +49,6 @@ const mockUser = {
email: "test@example.com",
notificationSettings: {
alert: {},
- weeklySummary: {},
unsubscribedOrganizationIds: [],
},
role: "project_manager",
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/EditWeeklySummary.test.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/EditWeeklySummary.test.tsx
deleted file mode 100644
index b02933b958..0000000000
--- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/EditWeeklySummary.test.tsx
+++ /dev/null
@@ -1,166 +0,0 @@
-import { cleanup, render, screen } from "@testing-library/react";
-import { afterEach, describe, expect, test, vi } from "vitest";
-import { TUser } from "@formbricks/types/user";
-import { Membership } from "../types";
-import { EditWeeklySummary } from "./EditWeeklySummary";
-
-vi.mock("lucide-react", () => ({
- UsersIcon: () =>
,
-}));
-
-vi.mock("next/link", () => ({
- default: ({ children, href }: { children: React.ReactNode; href: string }) => (
-
- {children}
-
- ),
-}));
-
-const mockNotificationSwitch = vi.fn();
-vi.mock("./NotificationSwitch", () => ({
- NotificationSwitch: (props: any) => {
- mockNotificationSwitch(props);
- return (
-
- NotificationSwitch
-
- );
- },
-}));
-
-const mockT = vi.fn((key) => key);
-vi.mock("@tolgee/react", () => ({
- useTranslate: () => ({
- t: mockT,
- }),
-}));
-
-const mockUser = {
- id: "user1",
- name: "Test User",
- email: "test@example.com",
- notificationSettings: {
- alert: {},
- weeklySummary: {
- proj1: true,
- proj3: false,
- },
- unsubscribedOrganizationIds: [],
- },
- role: "project_manager",
- objective: "other",
- emailVerified: new Date(),
- createdAt: new Date(),
- updatedAt: new Date(),
- identityProvider: "email",
- twoFactorEnabled: false,
-} as unknown as TUser;
-
-const mockMemberships: Membership[] = [
- {
- organization: {
- id: "org1",
- name: "Organization 1",
- projects: [
- { id: "proj1", name: "Project 1", environments: [] },
- { id: "proj2", name: "Project 2", environments: [] },
- ],
- },
- },
- {
- organization: {
- id: "org2",
- name: "Organization 2",
- projects: [{ id: "proj3", name: "Project 3", environments: [] }],
- },
- },
-];
-
-const environmentId = "test-env-id";
-
-describe("EditWeeklySummary", () => {
- afterEach(() => {
- cleanup();
- vi.clearAllMocks();
- });
-
- test("renders correctly with multiple memberships and projects", () => {
- render();
-
- expect(screen.getByText("Organization 1")).toBeInTheDocument();
- expect(screen.getByText("Project 1")).toBeInTheDocument();
- expect(screen.getByText("Project 2")).toBeInTheDocument();
- expect(screen.getByText("Organization 2")).toBeInTheDocument();
- expect(screen.getByText("Project 3")).toBeInTheDocument();
-
- expect(mockNotificationSwitch).toHaveBeenCalledWith(
- expect.objectContaining({
- surveyOrProjectOrOrganizationId: "proj1",
- notificationSettings: mockUser.notificationSettings,
- notificationType: "weeklySummary",
- })
- );
- expect(screen.getByTestId("notification-switch-proj1")).toBeInTheDocument();
-
- expect(mockNotificationSwitch).toHaveBeenCalledWith(
- expect.objectContaining({
- surveyOrProjectOrOrganizationId: "proj2",
- notificationSettings: mockUser.notificationSettings,
- notificationType: "weeklySummary",
- })
- );
- expect(screen.getByTestId("notification-switch-proj2")).toBeInTheDocument();
-
- expect(mockNotificationSwitch).toHaveBeenCalledWith(
- expect.objectContaining({
- surveyOrProjectOrOrganizationId: "proj3",
- notificationSettings: mockUser.notificationSettings,
- notificationType: "weeklySummary",
- })
- );
- expect(screen.getByTestId("notification-switch-proj3")).toBeInTheDocument();
-
- const inviteLinks = screen.getAllByTestId("link");
- expect(inviteLinks.length).toBe(mockMemberships.length);
- inviteLinks.forEach((link) => {
- expect(link).toHaveAttribute("href", `/environments/${environmentId}/settings/general`);
- expect(link).toHaveTextContent("common.invite_them");
- });
-
- expect(screen.getAllByTestId("users-icon").length).toBe(mockMemberships.length);
-
- expect(screen.getAllByText("common.project")[0]).toBeInTheDocument();
- expect(screen.getAllByText("common.weekly_summary")[0]).toBeInTheDocument();
- expect(
- screen.getAllByText("environments.settings.notifications.want_to_loop_in_organization_mates?").length
- ).toBe(mockMemberships.length);
- });
-
- test("renders correctly with no memberships", () => {
- render();
- expect(screen.queryByText("Organization 1")).not.toBeInTheDocument();
- expect(screen.queryByTestId("users-icon")).not.toBeInTheDocument();
- });
-
- test("renders correctly when an organization has no projects", () => {
- const membershipsWithNoProjects: Membership[] = [
- {
- organization: {
- id: "org3",
- name: "Organization No Projects",
- projects: [],
- },
- },
- ];
- render(
-
- );
- expect(screen.getByText("Organization No Projects")).toBeInTheDocument();
- expect(screen.queryByText("Project 1")).not.toBeInTheDocument(); // Check that no projects are listed under it
- expect(mockNotificationSwitch).not.toHaveBeenCalled(); // No projects, so no switches for projects
- });
-});
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/EditWeeklySummary.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/EditWeeklySummary.tsx
deleted file mode 100644
index 5f99be8309..0000000000
--- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/EditWeeklySummary.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-"use client";
-
-import { useTranslate } from "@tolgee/react";
-import { UsersIcon } from "lucide-react";
-import Link from "next/link";
-import { TUser } from "@formbricks/types/user";
-import { Membership } from "../types";
-import { NotificationSwitch } from "./NotificationSwitch";
-
-interface EditAlertsProps {
- memberships: Membership[];
- user: TUser;
- environmentId: string;
-}
-
-export const EditWeeklySummary = ({ memberships, user, environmentId }: EditAlertsProps) => {
- const { t } = useTranslate();
- return (
- <>
- {memberships.map((membership) => (
-
-
-
-
-
{membership.organization.name}
-
-
-
-
{t("common.project")}
-
{t("common.weekly_summary")}
-
-
- {membership.organization.projects.map((project) => (
-
-
{project?.name}
-
-
-
-
- ))}
-
-
- {t("environments.settings.notifications.want_to_loop_in_organization_mates")}?{" "}
-
- {t("common.invite_them")}
-
-
-
-
- ))}
- >
- );
-};
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/NotificationSwitch.test.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/NotificationSwitch.test.tsx
index de40d91042..7143498be2 100644
--- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/NotificationSwitch.test.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/NotificationSwitch.test.tsx
@@ -29,7 +29,6 @@ const organizationId = "org1";
const baseNotificationSettings: TUserNotificationSettings = {
alert: {},
- weeklySummary: {},
unsubscribedOrganizationIds: [],
};
@@ -68,19 +67,6 @@ describe("NotificationSwitch", () => {
expect(switchInput.checked).toBe(false);
});
- test("renders with initial checked state for 'weeklySummary' (true)", () => {
- const settings = { ...baseNotificationSettings, weeklySummary: { [projectId]: true } };
- renderSwitch({
- surveyOrProjectOrOrganizationId: projectId,
- notificationSettings: settings,
- notificationType: "weeklySummary",
- });
- const switchInput = screen.getByLabelText(
- "toggle notification settings for weeklySummary"
- ) as HTMLInputElement;
- expect(switchInput.checked).toBe(true);
- });
-
test("renders with initial checked state for 'unsubscribedOrganizationIds' (subscribed initially, so checked is true)", () => {
const settings = { ...baseNotificationSettings, unsubscribedOrganizationIds: [] };
renderSwitch({
@@ -268,31 +254,6 @@ describe("NotificationSwitch", () => {
expect(toast.success).not.toHaveBeenCalled();
});
- test("shows error toast when updateNotificationSettingsAction fails for 'weeklySummary' type", async () => {
- const mockErrorResponse = { serverError: "Database connection failed" };
- vi.mocked(updateNotificationSettingsAction).mockResolvedValueOnce(mockErrorResponse);
-
- const initialSettings = { ...baseNotificationSettings, weeklySummary: { [projectId]: true } };
- renderSwitch({
- surveyOrProjectOrOrganizationId: projectId,
- notificationSettings: initialSettings,
- notificationType: "weeklySummary",
- });
- const switchInput = screen.getByLabelText("toggle notification settings for weeklySummary");
-
- await act(async () => {
- await user.click(switchInput);
- });
-
- expect(updateNotificationSettingsAction).toHaveBeenCalledWith({
- notificationSettings: { ...initialSettings, weeklySummary: { [projectId]: false } },
- });
- expect(toast.error).toHaveBeenCalledWith("Database connection failed", {
- id: "notification-switch",
- });
- expect(toast.success).not.toHaveBeenCalled();
- });
-
test("shows error toast when updateNotificationSettingsAction fails for 'unsubscribedOrganizationIds' type", async () => {
const mockErrorResponse = { serverError: "Permission denied" };
vi.mocked(updateNotificationSettingsAction).mockResolvedValueOnce(mockErrorResponse);
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/NotificationSwitch.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/NotificationSwitch.tsx
index 9deeb3d6ad..deedc049b5 100644
--- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/NotificationSwitch.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/NotificationSwitch.tsx
@@ -12,7 +12,7 @@ import { updateNotificationSettingsAction } from "../actions";
interface NotificationSwitchProps {
surveyOrProjectOrOrganizationId: string;
notificationSettings: TUserNotificationSettings;
- notificationType: "alert" | "weeklySummary" | "unsubscribedOrganizationIds";
+ notificationType: "alert" | "unsubscribedOrganizationIds";
autoDisableNotificationType?: string;
autoDisableNotificationElementId?: string;
}
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/loading.test.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/loading.test.tsx
index 7cac2f1c36..ea2fbf0cd0 100644
--- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/loading.test.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/loading.test.tsx
@@ -34,17 +34,5 @@ describe("Loading Notifications Settings", () => {
.getByText("environments.settings.notifications.email_alerts_surveys")
.closest("div[class*='rounded-xl']"); // Find parent card
expect(alertsCard).toBeInTheDocument();
-
- // Check for Weekly Summary LoadingCard
- expect(
- screen.getByText("environments.settings.notifications.weekly_summary_projects")
- ).toBeInTheDocument();
- expect(
- screen.getByText("environments.settings.notifications.stay_up_to_date_with_a_Weekly_every_Monday")
- ).toBeInTheDocument();
- const weeklySummaryCard = screen
- .getByText("environments.settings.notifications.weekly_summary_projects")
- .closest("div[class*='rounded-xl']"); // Find parent card
- expect(weeklySummaryCard).toBeInTheDocument();
});
});
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/loading.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/loading.tsx
index d636572eb1..2b074d0963 100644
--- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/loading.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/loading.tsx
@@ -14,11 +14,6 @@ const Loading = () => {
description: t("environments.settings.notifications.set_up_an_alert_to_get_an_email_on_new_responses"),
skeletonLines: [{ classes: "h-6 w-28" }, { classes: "h-10 w-128" }, { classes: "h-10 w-128" }],
},
- {
- title: t("environments.settings.notifications.weekly_summary_projects"),
- description: t("environments.settings.notifications.stay_up_to_date_with_a_Weekly_every_Monday"),
- skeletonLines: [{ classes: "h-6 w-28" }, { classes: "h-10 w-128" }, { classes: "h-10 w-128" }],
- },
];
return (
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/page.test.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/page.test.tsx
index 93075bfcfa..40a86c30cd 100644
--- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/page.test.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/page.test.tsx
@@ -5,7 +5,6 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { prisma } from "@formbricks/database";
import { TUser } from "@formbricks/types/user";
import { EditAlerts } from "./components/EditAlerts";
-import { EditWeeklySummary } from "./components/EditWeeklySummary";
import Page from "./page";
import { Membership } from "./types";
@@ -58,9 +57,7 @@ vi.mock("@formbricks/database", () => ({
vi.mock("./components/EditAlerts", () => ({
EditAlerts: vi.fn(() => EditAlertsComponent
),
}));
-vi.mock("./components/EditWeeklySummary", () => ({
- EditWeeklySummary: vi.fn(() => EditWeeklySummaryComponent
),
-}));
+
vi.mock("./components/IntegrationsTip", () => ({
IntegrationsTip: () => IntegrationsTipComponent
,
}));
@@ -71,7 +68,6 @@ const mockUser: Partial = {
email: "test@example.com",
notificationSettings: {
alert: { "survey-old": true },
- weeklySummary: { "project-old": true },
unsubscribedOrganizationIds: ["org-unsubscribed"],
},
};
@@ -137,13 +133,6 @@ describe("NotificationsPage", () => {
).toBeInTheDocument();
expect(screen.getByText("EditAlertsComponent")).toBeInTheDocument();
expect(screen.getByText("IntegrationsTipComponent")).toBeInTheDocument();
- expect(
- screen.getByText("environments.settings.notifications.weekly_summary_projects")
- ).toBeInTheDocument();
- expect(
- screen.getByText("environments.settings.notifications.stay_up_to_date_with_a_Weekly_every_Monday")
- ).toBeInTheDocument();
- expect(screen.getByText("EditWeeklySummaryComponent")).toBeInTheDocument();
// The actual `user.notificationSettings` passed to EditAlerts will be a new object
// after `setCompleteNotificationSettings` processes it.
@@ -157,16 +146,12 @@ describe("NotificationsPage", () => {
// It iterates memberships, then projects, then environments, then surveys.
// `newNotificationSettings.alert[survey.id] = notificationSettings[survey.id]?.responseFinished || (notificationSettings.alert && notificationSettings.alert[survey.id]) || false;`
// This means only survey IDs found in memberships will be in the new `alert` object.
- // `newNotificationSettings.weeklySummary[project.id]` also only adds project IDs from memberships.
const finalExpectedSettings = {
alert: {
"survey-1": false,
"survey-2": false,
},
- weeklySummary: {
- "project-1": false,
- },
unsubscribedOrganizationIds: ["org-unsubscribed"],
};
@@ -175,11 +160,6 @@ describe("NotificationsPage", () => {
expect(editAlertsCall.environmentId).toBe(mockParams.environmentId);
expect(editAlertsCall.autoDisableNotificationType).toBe(mockSearchParams.type);
expect(editAlertsCall.autoDisableNotificationElementId).toBe(mockSearchParams.elementId);
-
- const editWeeklySummaryCall = vi.mocked(EditWeeklySummary).mock.calls[0][0];
- expect(editWeeklySummaryCall.user.notificationSettings).toEqual(finalExpectedSettings);
- expect(editWeeklySummaryCall.memberships).toEqual(mockMemberships);
- expect(editWeeklySummaryCall.environmentId).toBe(mockParams.environmentId);
});
test("throws error if session is not found", async () => {
@@ -207,21 +187,15 @@ describe("NotificationsPage", () => {
render(PageComponent);
expect(screen.getByText("EditAlertsComponent")).toBeInTheDocument();
- expect(screen.getByText("EditWeeklySummaryComponent")).toBeInTheDocument();
const expectedEmptySettings = {
alert: {},
- weeklySummary: {},
unsubscribedOrganizationIds: [],
};
const editAlertsCall = vi.mocked(EditAlerts).mock.calls[0][0];
expect(editAlertsCall.user.notificationSettings).toEqual(expectedEmptySettings);
expect(editAlertsCall.memberships).toEqual([]);
-
- const editWeeklySummaryCall = vi.mocked(EditWeeklySummary).mock.calls[0][0];
- expect(editWeeklySummaryCall.user.notificationSettings).toEqual(expectedEmptySettings);
- expect(editWeeklySummaryCall.memberships).toEqual([]);
});
test("handles legacy notification settings correctly", async () => {
@@ -229,7 +203,6 @@ describe("NotificationsPage", () => {
id: "user-legacy",
notificationSettings: {
"survey-1": { responseFinished: true }, // Legacy alert for survey-1
- weeklySummary: { "project-1": true },
unsubscribedOrganizationIds: [],
} as any, // To allow legacy structure
};
@@ -246,9 +219,6 @@ describe("NotificationsPage", () => {
"survey-1": true, // Should be true due to legacy setting
"survey-2": false, // Default for other surveys in membership
},
- weeklySummary: {
- "project-1": true, // From user's weeklySummary
- },
unsubscribedOrganizationIds: [],
};
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/page.tsx
index e536e64d0c..8cc08c7b02 100644
--- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/page.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/page.tsx
@@ -9,7 +9,6 @@ import { getServerSession } from "next-auth";
import { prisma } from "@formbricks/database";
import { TUserNotificationSettings } from "@formbricks/types/user";
import { EditAlerts } from "./components/EditAlerts";
-import { EditWeeklySummary } from "./components/EditWeeklySummary";
import { IntegrationsTip } from "./components/IntegrationsTip";
import type { Membership } from "./types";
@@ -19,14 +18,10 @@ const setCompleteNotificationSettings = (
): TUserNotificationSettings => {
const newNotificationSettings = {
alert: {},
- weeklySummary: {},
unsubscribedOrganizationIds: notificationSettings.unsubscribedOrganizationIds || [],
};
for (const membership of memberships) {
for (const project of membership.organization.projects) {
- // set default values for weekly summary
- newNotificationSettings.weeklySummary[project.id] =
- (notificationSettings.weeklySummary && notificationSettings.weeklySummary[project.id]) || false;
// set default values for alerts
for (const environment of project.environments) {
for (const survey of environment.surveys) {
@@ -183,11 +178,6 @@ const Page = async (props) => {
/>
-
-
-
);
};
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/AccountSecurity.test.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/AccountSecurity.test.tsx
index 3bd5c28285..835e669e5f 100644
--- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/AccountSecurity.test.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/AccountSecurity.test.tsx
@@ -20,7 +20,7 @@ const mockUser = {
email: "test@example.com",
notificationSettings: {
alert: {},
- weeklySummary: {},
+
unsubscribedOrganizationIds: [],
},
twoFactorEnabled: false,
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/DeleteAccount.test.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/DeleteAccount.test.tsx
index 230dbbd1f2..156be7ee0c 100644
--- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/DeleteAccount.test.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/DeleteAccount.test.tsx
@@ -15,7 +15,7 @@ const mockUser = {
id: "user1",
name: "Test User",
email: "test@example.com",
- notificationSettings: { alert: {}, weeklySummary: {}, unsubscribedOrganizationIds: [] },
+ notificationSettings: { alert: {}, unsubscribedOrganizationIds: [] },
twoFactorEnabled: false,
identityProvider: "email",
createdAt: new Date(),
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/EditProfileDetailsForm.test.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/EditProfileDetailsForm.test.tsx
index a9a779e55b..bbfb31327d 100644
--- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/EditProfileDetailsForm.test.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/EditProfileDetailsForm.test.tsx
@@ -13,7 +13,7 @@ const mockUser = {
locale: "en-US",
notificationSettings: {
alert: {},
- weeklySummary: {},
+
unsubscribedOrganizationIds: [],
},
twoFactorEnabled: false,
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/page.test.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/page.test.tsx
index feed9087f1..05fe5f59a3 100644
--- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/page.test.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/page.test.tsx
@@ -76,7 +76,7 @@ const mockUser = {
imageUrl: "http://example.com/avatar.png",
twoFactorEnabled: false,
identityProvider: "email",
- notificationSettings: { alert: {}, weeklySummary: {}, unsubscribedOrganizationIds: [] },
+ notificationSettings: { alert: {}, unsubscribedOrganizationIds: [] },
createdAt: new Date(),
updatedAt: new Date(),
role: "project_manager",
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/page.test.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/page.test.tsx
index d15b0a58da..8d1a82d43b 100644
--- a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/page.test.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/page.test.tsx
@@ -129,7 +129,7 @@ const mockUser = {
imageUrl: "",
twoFactorEnabled: false,
identityProvider: "email",
- notificationSettings: { alert: {}, weeklySummary: {} },
+ notificationSettings: { alert: {} },
role: "project_manager",
objective: "other",
} as unknown as TUser;
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseCardModal.test.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseCardModal.test.tsx
index 991084dbe2..54744c6655 100644
--- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseCardModal.test.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseCardModal.test.tsx
@@ -139,7 +139,7 @@ const mockUser = {
updatedAt: new Date(),
role: "project_manager",
objective: "increase_conversion",
- notificationSettings: { alert: {}, weeklySummary: {}, unsubscribedOrganizationIds: [] },
+ notificationSettings: { alert: {}, unsubscribedOrganizationIds: [] },
} as unknown as TUser;
const mockEnvironmentTags: TTag[] = [
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SurveyAnalysisCTA.test.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SurveyAnalysisCTA.test.tsx
index 5f03cddf57..ceeda6638a 100644
--- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SurveyAnalysisCTA.test.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SurveyAnalysisCTA.test.tsx
@@ -305,12 +305,8 @@ const mockUser: TUser = {
isActive: true,
notificationSettings: {
alert: {
- weeklySummary: true,
responseFinished: true,
},
- weeklySummary: {
- test: true,
- },
unsubscribedOrganizationIds: [],
},
};
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/share-survey-modal.test.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/share-survey-modal.test.tsx
index 3d656ccce9..4855b9efbd 100644
--- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/share-survey-modal.test.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/share-survey-modal.test.tsx
@@ -253,7 +253,6 @@ const mockUser: TUser = {
isActive: true,
notificationSettings: {
alert: {},
- weeklySummary: {},
unsubscribedOrganizationIds: [],
},
};
diff --git a/apps/web/app/api/cron/weekly-summary/lib/notificationResponse.test.ts b/apps/web/app/api/cron/weekly-summary/lib/notificationResponse.test.ts
deleted file mode 100644
index 9bdcc87cbd..0000000000
--- a/apps/web/app/api/cron/weekly-summary/lib/notificationResponse.test.ts
+++ /dev/null
@@ -1,276 +0,0 @@
-import { convertResponseValue } from "@/lib/responses";
-import { cleanup } from "@testing-library/react";
-import { afterEach, describe, expect, test, vi } from "vitest";
-import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys/types";
-import {
- TWeeklyEmailResponseData,
- TWeeklySummaryEnvironmentData,
- TWeeklySummarySurveyData,
-} from "@formbricks/types/weekly-summary";
-import { getNotificationResponse } from "./notificationResponse";
-
-vi.mock("@/lib/responses", () => ({
- convertResponseValue: vi.fn(),
-}));
-
-vi.mock("@/lib/utils/recall", () => ({
- replaceHeadlineRecall: vi.fn((survey) => survey),
-}));
-
-describe("getNotificationResponse", () => {
- afterEach(() => {
- cleanup();
- });
-
- test("should return a notification response with calculated insights and survey data when provided with an environment containing multiple surveys", () => {
- const mockSurveys = [
- {
- id: "survey1",
- name: "Survey 1",
- status: "inProgress",
- questions: [
- {
- id: "question1",
- headline: { default: "Question 1" },
- type: "text",
- } as unknown as TSurveyQuestion,
- ],
- displays: [{ id: "display1" }],
- responses: [
- { id: "response1", finished: true, data: { question1: "Answer 1" } },
- { id: "response2", finished: false, data: { question1: "Answer 2" } },
- ],
- } as unknown as TSurvey & { responses: TWeeklyEmailResponseData[] },
- {
- id: "survey2",
- name: "Survey 2",
- status: "inProgress",
- questions: [
- {
- id: "question2",
- headline: { default: "Question 2" },
- type: "text",
- } as unknown as TSurveyQuestion,
- ],
- displays: [{ id: "display2" }],
- responses: [
- { id: "response3", finished: true, data: { question2: "Answer 3" } },
- { id: "response4", finished: true, data: { question2: "Answer 4" } },
- { id: "response5", finished: false, data: { question2: "Answer 5" } },
- ],
- } as unknown as TSurvey & { responses: TWeeklyEmailResponseData[] },
- ] as unknown as TWeeklySummarySurveyData[];
-
- const mockEnvironment = {
- id: "env1",
- surveys: mockSurveys,
- } as unknown as TWeeklySummaryEnvironmentData;
-
- const projectName = "Project Name";
-
- const notificationResponse = getNotificationResponse(mockEnvironment, projectName);
-
- expect(notificationResponse).toBeDefined();
- expect(notificationResponse.environmentId).toBe("env1");
- expect(notificationResponse.projectName).toBe(projectName);
- expect(notificationResponse.surveys).toHaveLength(2);
-
- expect(notificationResponse.insights.totalCompletedResponses).toBe(3);
- expect(notificationResponse.insights.totalDisplays).toBe(2);
- expect(notificationResponse.insights.totalResponses).toBe(5);
- expect(notificationResponse.insights.completionRate).toBe(60);
- expect(notificationResponse.insights.numLiveSurvey).toBe(2);
-
- expect(notificationResponse.surveys[0].id).toBe("survey1");
- expect(notificationResponse.surveys[0].name).toBe("Survey 1");
- expect(notificationResponse.surveys[0].status).toBe("inProgress");
- expect(notificationResponse.surveys[0].responseCount).toBe(2);
-
- expect(notificationResponse.surveys[1].id).toBe("survey2");
- expect(notificationResponse.surveys[1].name).toBe("Survey 2");
- expect(notificationResponse.surveys[1].status).toBe("inProgress");
- expect(notificationResponse.surveys[1].responseCount).toBe(3);
- });
-
- test("should calculate the correct completion rate and other insights when surveys have responses with varying statuses", () => {
- const mockSurveys = [
- {
- id: "survey1",
- name: "Survey 1",
- status: "inProgress",
- questions: [
- {
- id: "question1",
- headline: { default: "Question 1" },
- type: "text",
- } as unknown as TSurveyQuestion,
- ],
- displays: [{ id: "display1" }],
- responses: [
- { id: "response1", finished: true, data: { question1: "Answer 1" } },
- { id: "response2", finished: false, data: { question1: "Answer 2" } },
- ],
- } as unknown as TSurvey & { responses: TWeeklyEmailResponseData[] },
- {
- id: "survey2",
- name: "Survey 2",
- status: "inProgress",
- questions: [
- {
- id: "question2",
- headline: { default: "Question 2" },
- type: "text",
- } as unknown as TSurveyQuestion,
- ],
- displays: [{ id: "display2" }],
- responses: [
- { id: "response3", finished: true, data: { question2: "Answer 3" } },
- { id: "response4", finished: true, data: { question2: "Answer 4" } },
- { id: "response5", finished: false, data: { question2: "Answer 5" } },
- ],
- } as unknown as TSurvey & { responses: TWeeklyEmailResponseData[] },
- {
- id: "survey3",
- name: "Survey 3",
- status: "inProgress",
- questions: [
- {
- id: "question3",
- headline: { default: "Question 3" },
- type: "text",
- } as unknown as TSurveyQuestion,
- ],
- displays: [{ id: "display3" }],
- responses: [{ id: "response6", finished: false, data: { question3: "Answer 6" } }],
- } as unknown as TSurvey & { responses: TWeeklyEmailResponseData[] },
- ] as unknown as TWeeklySummarySurveyData[];
-
- const mockEnvironment = {
- id: "env1",
- surveys: mockSurveys,
- } as unknown as TWeeklySummaryEnvironmentData;
-
- const projectName = "Project Name";
-
- const notificationResponse = getNotificationResponse(mockEnvironment, projectName);
-
- expect(notificationResponse).toBeDefined();
- expect(notificationResponse.environmentId).toBe("env1");
- expect(notificationResponse.projectName).toBe(projectName);
- expect(notificationResponse.surveys).toHaveLength(3);
-
- expect(notificationResponse.insights.totalCompletedResponses).toBe(3);
- expect(notificationResponse.insights.totalDisplays).toBe(3);
- expect(notificationResponse.insights.totalResponses).toBe(6);
- expect(notificationResponse.insights.completionRate).toBe(50);
- expect(notificationResponse.insights.numLiveSurvey).toBe(3);
-
- expect(notificationResponse.surveys[0].id).toBe("survey1");
- expect(notificationResponse.surveys[0].name).toBe("Survey 1");
- expect(notificationResponse.surveys[0].status).toBe("inProgress");
- expect(notificationResponse.surveys[0].responseCount).toBe(2);
-
- expect(notificationResponse.surveys[1].id).toBe("survey2");
- expect(notificationResponse.surveys[1].name).toBe("Survey 2");
- expect(notificationResponse.surveys[1].status).toBe("inProgress");
- expect(notificationResponse.surveys[1].responseCount).toBe(3);
-
- expect(notificationResponse.surveys[2].id).toBe("survey3");
- expect(notificationResponse.surveys[2].name).toBe("Survey 3");
- expect(notificationResponse.surveys[2].status).toBe("inProgress");
- expect(notificationResponse.surveys[2].responseCount).toBe(1);
- });
-
- test("should return default insights and an empty surveys array when the environment contains no surveys", () => {
- const mockEnvironment = {
- id: "env1",
- surveys: [],
- } as unknown as TWeeklySummaryEnvironmentData;
-
- const projectName = "Project Name";
-
- const notificationResponse = getNotificationResponse(mockEnvironment, projectName);
-
- expect(notificationResponse).toBeDefined();
- expect(notificationResponse.environmentId).toBe("env1");
- expect(notificationResponse.projectName).toBe(projectName);
- expect(notificationResponse.surveys).toHaveLength(0);
-
- expect(notificationResponse.insights.totalCompletedResponses).toBe(0);
- expect(notificationResponse.insights.totalDisplays).toBe(0);
- expect(notificationResponse.insights.totalResponses).toBe(0);
- expect(notificationResponse.insights.completionRate).toBe(0);
- expect(notificationResponse.insights.numLiveSurvey).toBe(0);
- });
-
- test("should handle missing response data gracefully when a response doesn't contain data for a question ID", () => {
- const mockSurveys = [
- {
- id: "survey1",
- name: "Survey 1",
- status: "inProgress",
- questions: [
- {
- id: "question1",
- headline: { default: "Question 1" },
- type: "text",
- } as unknown as TSurveyQuestion,
- ],
- displays: [{ id: "display1" }],
- responses: [
- { id: "response1", finished: true, data: {} }, // Response missing data for question1
- ],
- } as unknown as TSurvey & { responses: TWeeklyEmailResponseData[] },
- ] as unknown as TWeeklySummarySurveyData[];
-
- const mockEnvironment = {
- id: "env1",
- surveys: mockSurveys,
- } as unknown as TWeeklySummaryEnvironmentData;
-
- const projectName = "Project Name";
-
- // Mock the convertResponseValue function to handle the missing data case
- vi.mocked(convertResponseValue).mockReturnValue("");
-
- const notificationResponse = getNotificationResponse(mockEnvironment, projectName);
-
- expect(notificationResponse).toBeDefined();
- expect(notificationResponse.surveys).toHaveLength(1);
- expect(notificationResponse.surveys[0].responses).toHaveLength(1);
- expect(notificationResponse.surveys[0].responses[0].responseValue).toBe("");
- });
-
- test("should handle unsupported question types gracefully", () => {
- const mockSurveys = [
- {
- id: "survey1",
- name: "Survey 1",
- status: "inProgress",
- questions: [
- {
- id: "question1",
- headline: { default: "Question 1" },
- type: "unsupported",
- } as unknown as TSurveyQuestion,
- ],
- displays: [{ id: "display1" }],
- responses: [{ id: "response1", finished: true, data: { question1: "Answer 1" } }],
- } as unknown as TSurvey & { responses: TWeeklyEmailResponseData[] },
- ] as unknown as TWeeklySummarySurveyData[];
-
- const mockEnvironment = {
- id: "env1",
- surveys: mockSurveys,
- } as unknown as TWeeklySummaryEnvironmentData;
-
- const projectName = "Project Name";
-
- vi.mocked(convertResponseValue).mockReturnValue("Unsupported Response");
-
- const notificationResponse = getNotificationResponse(mockEnvironment, projectName);
-
- expect(notificationResponse).toBeDefined();
- expect(notificationResponse.surveys[0].responses[0].responseValue).toBe("Unsupported Response");
- });
-});
diff --git a/apps/web/app/api/cron/weekly-summary/lib/notificationResponse.ts b/apps/web/app/api/cron/weekly-summary/lib/notificationResponse.ts
deleted file mode 100644
index b4a35ea41f..0000000000
--- a/apps/web/app/api/cron/weekly-summary/lib/notificationResponse.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import { getLocalizedValue } from "@/lib/i18n/utils";
-import { convertResponseValue } from "@/lib/responses";
-import { replaceHeadlineRecall } from "@/lib/utils/recall";
-import { TSurvey } from "@formbricks/types/surveys/types";
-import {
- TWeeklyEmailResponseData,
- TWeeklySummaryEnvironmentData,
- TWeeklySummaryNotificationDataSurvey,
- TWeeklySummaryNotificationResponse,
- TWeeklySummarySurveyResponseData,
-} from "@formbricks/types/weekly-summary";
-
-export const getNotificationResponse = (
- environment: TWeeklySummaryEnvironmentData,
- projectName: string
-): TWeeklySummaryNotificationResponse => {
- const insights = {
- totalCompletedResponses: 0,
- totalDisplays: 0,
- totalResponses: 0,
- completionRate: 0,
- numLiveSurvey: 0,
- };
-
- const surveys: TWeeklySummaryNotificationDataSurvey[] = [];
- // iterate through the surveys and calculate the overall insights
- for (const survey of environment.surveys) {
- const parsedSurvey = replaceHeadlineRecall(survey as unknown as TSurvey, "default") as TSurvey & {
- responses: TWeeklyEmailResponseData[];
- };
- const surveyData: TWeeklySummaryNotificationDataSurvey = {
- id: parsedSurvey.id,
- name: parsedSurvey.name,
- status: parsedSurvey.status,
- responseCount: parsedSurvey.responses.length,
- responses: [],
- };
- // iterate through the responses and calculate the survey insights
- for (const response of parsedSurvey.responses) {
- // only take the first 3 responses
- if (surveyData.responses.length >= 3) {
- break;
- }
- const surveyResponses: TWeeklySummarySurveyResponseData[] = [];
- for (const question of parsedSurvey.questions) {
- const headline = question.headline;
- const responseValue = convertResponseValue(response.data[question.id], question);
- const surveyResponse: TWeeklySummarySurveyResponseData = {
- headline: getLocalizedValue(headline, "default"),
- responseValue,
- questionType: question.type,
- };
- surveyResponses.push(surveyResponse);
- }
- surveyData.responses = surveyResponses;
- }
- surveys.push(surveyData);
- // calculate the overall insights
- if (survey.status == "inProgress") {
- insights.numLiveSurvey += 1;
- }
- insights.totalCompletedResponses += survey.responses.filter((r) => r.finished).length;
- insights.totalDisplays += survey.displays.length;
- insights.totalResponses += survey.responses.length;
- insights.completionRate = Math.round((insights.totalCompletedResponses / insights.totalResponses) * 100);
- }
- // build the notification response needed for the emails
- const lastWeekDate = new Date();
- lastWeekDate.setDate(lastWeekDate.getDate() - 7);
- return {
- environmentId: environment.id,
- currentDate: new Date(),
- lastWeekDate,
- projectName: projectName,
- surveys,
- insights,
- };
-};
diff --git a/apps/web/app/api/cron/weekly-summary/lib/organization.test.ts b/apps/web/app/api/cron/weekly-summary/lib/organization.test.ts
deleted file mode 100644
index 4fe250acd9..0000000000
--- a/apps/web/app/api/cron/weekly-summary/lib/organization.test.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { cleanup } from "@testing-library/react";
-import { afterEach, describe, expect, test, vi } from "vitest";
-import { prisma } from "@formbricks/database";
-import { getOrganizationIds } from "./organization";
-
-vi.mock("@formbricks/database", () => ({
- prisma: {
- organization: {
- findMany: vi.fn(),
- },
- },
-}));
-
-describe("Organization", () => {
- afterEach(() => {
- cleanup();
- });
-
- test("getOrganizationIds should return an array of organization IDs when the database contains multiple organizations", async () => {
- const mockOrganizations = [{ id: "org1" }, { id: "org2" }, { id: "org3" }];
-
- vi.mocked(prisma.organization.findMany).mockResolvedValue(mockOrganizations);
-
- const organizationIds = await getOrganizationIds();
-
- expect(organizationIds).toEqual(["org1", "org2", "org3"]);
- expect(prisma.organization.findMany).toHaveBeenCalledTimes(1);
- expect(prisma.organization.findMany).toHaveBeenCalledWith({
- select: {
- id: true,
- },
- });
- });
-
- test("getOrganizationIds should return an empty array when the database contains no organizations", async () => {
- vi.mocked(prisma.organization.findMany).mockResolvedValue([]);
-
- const organizationIds = await getOrganizationIds();
-
- expect(organizationIds).toEqual([]);
- expect(prisma.organization.findMany).toHaveBeenCalledTimes(1);
- expect(prisma.organization.findMany).toHaveBeenCalledWith({
- select: {
- id: true,
- },
- });
- });
-});
diff --git a/apps/web/app/api/cron/weekly-summary/lib/organization.ts b/apps/web/app/api/cron/weekly-summary/lib/organization.ts
deleted file mode 100644
index bae8b74bb5..0000000000
--- a/apps/web/app/api/cron/weekly-summary/lib/organization.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { prisma } from "@formbricks/database";
-
-export const getOrganizationIds = async (): Promise => {
- const organizations = await prisma.organization.findMany({
- select: {
- id: true,
- },
- });
- return organizations.map((organization) => organization.id);
-};
diff --git a/apps/web/app/api/cron/weekly-summary/lib/project.test.ts b/apps/web/app/api/cron/weekly-summary/lib/project.test.ts
deleted file mode 100644
index c3de4eefe5..0000000000
--- a/apps/web/app/api/cron/weekly-summary/lib/project.test.ts
+++ /dev/null
@@ -1,570 +0,0 @@
-import { cleanup } from "@testing-library/react";
-import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
-import { prisma } from "@formbricks/database";
-import { getProjectsByOrganizationId } from "./project";
-
-const mockProjects = [
- {
- id: "project1",
- name: "Project 1",
- environments: [
- {
- id: "env1",
- type: "production",
- surveys: [],
- attributeKeys: [],
- },
- ],
- organization: {
- memberships: [
- {
- user: {
- id: "user1",
- email: "test@example.com",
- notificationSettings: {
- weeklySummary: {
- project1: true,
- },
- },
- locale: "en",
- },
- },
- ],
- },
- },
-];
-
-const sevenDaysAgo = new Date();
-sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 6); // Set to 6 days ago to be within the last 7 days
-
-const mockProjectsWithNoEnvironments = [
- {
- id: "project3",
- name: "Project 3",
- environments: [],
- organization: {
- memberships: [
- {
- user: {
- id: "user1",
- email: "test@example.com",
- notificationSettings: {
- weeklySummary: {
- project3: true,
- },
- },
- locale: "en",
- },
- },
- ],
- },
- },
-];
-
-vi.mock("@formbricks/database", () => ({
- prisma: {
- project: {
- findMany: vi.fn(),
- },
- },
-}));
-
-describe("Project Management", () => {
- beforeEach(() => {
- vi.clearAllMocks();
- });
-
- afterEach(() => {
- cleanup();
- });
-
- describe("getProjectsByOrganizationId", () => {
- test("retrieves projects with environments, surveys, and organization memberships for a valid organization ID", async () => {
- vi.mocked(prisma.project.findMany).mockResolvedValueOnce(mockProjects);
-
- const organizationId = "testOrgId";
- const projects = await getProjectsByOrganizationId(organizationId);
-
- expect(projects).toEqual(mockProjects);
- expect(prisma.project.findMany).toHaveBeenCalledWith({
- where: {
- organizationId: organizationId,
- },
- select: {
- id: true,
- name: true,
- environments: {
- where: {
- type: "production",
- },
- select: {
- id: true,
- surveys: {
- where: {
- NOT: {
- AND: [
- { status: "completed" },
- {
- responses: {
- none: {
- createdAt: {
- gte: expect.any(Date),
- },
- },
- },
- },
- ],
- },
- status: {
- not: "draft",
- },
- },
- select: {
- id: true,
- name: true,
- questions: true,
- status: true,
- responses: {
- where: {
- createdAt: {
- gte: expect.any(Date),
- },
- },
- select: {
- id: true,
- createdAt: true,
- updatedAt: true,
- finished: true,
- data: true,
- },
- orderBy: {
- createdAt: "desc",
- },
- },
- displays: {
- where: {
- createdAt: {
- gte: expect.any(Date),
- },
- },
- select: {
- id: true,
- },
- },
- hiddenFields: true,
- },
- },
- attributeKeys: {
- select: {
- id: true,
- createdAt: true,
- updatedAt: true,
- name: true,
- description: true,
- type: true,
- environmentId: true,
- key: true,
- isUnique: true,
- },
- },
- },
- },
- organization: {
- select: {
- memberships: {
- select: {
- user: {
- select: {
- id: true,
- email: true,
- notificationSettings: true,
- locale: true,
- },
- },
- },
- },
- },
- },
- },
- });
- });
-
- test("handles date calculations correctly across DST boundaries", async () => {
- const mockDate = new Date(2024, 10, 3, 0, 0, 0); // November 3, 2024, 00:00:00 (example DST boundary)
- const sevenDaysAgo = new Date(mockDate);
- sevenDaysAgo.setDate(mockDate.getDate() - 7);
-
- vi.useFakeTimers();
- vi.setSystemTime(mockDate);
-
- vi.mocked(prisma.project.findMany).mockResolvedValueOnce(mockProjects);
-
- const organizationId = "testOrgId";
- await getProjectsByOrganizationId(organizationId);
-
- expect(prisma.project.findMany).toHaveBeenCalledWith(
- expect.objectContaining({
- where: {
- organizationId: organizationId,
- },
- select: expect.objectContaining({
- environments: expect.objectContaining({
- select: expect.objectContaining({
- surveys: expect.objectContaining({
- where: expect.objectContaining({
- NOT: expect.objectContaining({
- AND: expect.arrayContaining([
- expect.objectContaining({ status: "completed" }),
- expect.objectContaining({
- responses: expect.objectContaining({
- none: expect.objectContaining({
- createdAt: expect.objectContaining({
- gte: sevenDaysAgo,
- }),
- }),
- }),
- }),
- ]),
- }),
- }),
- }),
- }),
- }),
- }),
- })
- );
-
- vi.useRealTimers();
- });
-
- test("includes surveys with 'completed' status but responses within the last 7 days", async () => {
- vi.mocked(prisma.project.findMany).mockResolvedValueOnce(mockProjects);
-
- const organizationId = "testOrgId";
- const projects = await getProjectsByOrganizationId(organizationId);
-
- expect(projects).toEqual(mockProjects);
- expect(prisma.project.findMany).toHaveBeenCalledWith({
- where: {
- organizationId: organizationId,
- },
- select: {
- id: true,
- name: true,
- environments: {
- where: {
- type: "production",
- },
- select: {
- id: true,
- surveys: {
- where: {
- NOT: {
- AND: [
- { status: "completed" },
- {
- responses: {
- none: {
- createdAt: {
- gte: expect.any(Date),
- },
- },
- },
- },
- ],
- },
- status: {
- not: "draft",
- },
- },
- select: {
- id: true,
- name: true,
- questions: true,
- status: true,
- responses: {
- where: {
- createdAt: {
- gte: expect.any(Date),
- },
- },
- select: {
- id: true,
- createdAt: true,
- updatedAt: true,
- finished: true,
- data: true,
- },
- orderBy: {
- createdAt: "desc",
- },
- },
- displays: {
- where: {
- createdAt: {
- gte: expect.any(Date),
- },
- },
- select: {
- id: true,
- },
- },
- hiddenFields: true,
- },
- },
- attributeKeys: {
- select: {
- id: true,
- createdAt: true,
- updatedAt: true,
- name: true,
- description: true,
- type: true,
- environmentId: true,
- key: true,
- isUnique: true,
- },
- },
- },
- },
- organization: {
- select: {
- memberships: {
- select: {
- user: {
- select: {
- id: true,
- email: true,
- notificationSettings: true,
- locale: true,
- },
- },
- },
- },
- },
- },
- },
- });
- });
-
- test("returns an empty array when an invalid organization ID is provided", async () => {
- vi.mocked(prisma.project.findMany).mockResolvedValueOnce([]);
-
- const invalidOrganizationId = "invalidOrgId";
- const projects = await getProjectsByOrganizationId(invalidOrganizationId);
-
- expect(projects).toEqual([]);
- expect(prisma.project.findMany).toHaveBeenCalledWith({
- where: {
- organizationId: invalidOrganizationId,
- },
- select: {
- id: true,
- name: true,
- environments: {
- where: {
- type: "production",
- },
- select: {
- id: true,
- surveys: {
- where: {
- NOT: {
- AND: [
- { status: "completed" },
- {
- responses: {
- none: {
- createdAt: {
- gte: expect.any(Date),
- },
- },
- },
- },
- ],
- },
- status: {
- not: "draft",
- },
- },
- select: {
- id: true,
- name: true,
- questions: true,
- status: true,
- responses: {
- where: {
- createdAt: {
- gte: expect.any(Date),
- },
- },
- select: {
- id: true,
- createdAt: true,
- updatedAt: true,
- finished: true,
- data: true,
- },
- orderBy: {
- createdAt: "desc",
- },
- },
- displays: {
- where: {
- createdAt: {
- gte: expect.any(Date),
- },
- },
- select: {
- id: true,
- },
- },
- hiddenFields: true,
- },
- },
- attributeKeys: {
- select: {
- id: true,
- createdAt: true,
- updatedAt: true,
- name: true,
- description: true,
- type: true,
- environmentId: true,
- key: true,
- isUnique: true,
- },
- },
- },
- },
- organization: {
- select: {
- memberships: {
- select: {
- user: {
- select: {
- id: true,
- email: true,
- notificationSettings: true,
- locale: true,
- },
- },
- },
- },
- },
- },
- },
- });
- });
-
- test("handles projects with no environments", async () => {
- vi.mocked(prisma.project.findMany).mockResolvedValueOnce(mockProjectsWithNoEnvironments);
-
- const organizationId = "testOrgId";
- const projects = await getProjectsByOrganizationId(organizationId);
-
- expect(projects).toEqual(mockProjectsWithNoEnvironments);
- expect(prisma.project.findMany).toHaveBeenCalledWith({
- where: {
- organizationId: organizationId,
- },
- select: {
- id: true,
- name: true,
- environments: {
- where: {
- type: "production",
- },
- select: {
- id: true,
- surveys: {
- where: {
- NOT: {
- AND: [
- { status: "completed" },
- {
- responses: {
- none: {
- createdAt: {
- gte: expect.any(Date),
- },
- },
- },
- },
- ],
- },
- status: {
- not: "draft",
- },
- },
- select: {
- id: true,
- name: true,
- questions: true,
- status: true,
- responses: {
- where: {
- createdAt: {
- gte: expect.any(Date),
- },
- },
- select: {
- id: true,
- createdAt: true,
- updatedAt: true,
- finished: true,
- data: true,
- },
- orderBy: {
- createdAt: "desc",
- },
- },
- displays: {
- where: {
- createdAt: {
- gte: expect.any(Date),
- },
- },
- select: {
- id: true,
- },
- },
- hiddenFields: true,
- },
- },
- attributeKeys: {
- select: {
- id: true,
- createdAt: true,
- updatedAt: true,
- name: true,
- description: true,
- type: true,
- environmentId: true,
- key: true,
- isUnique: true,
- },
- },
- },
- },
- organization: {
- select: {
- memberships: {
- select: {
- user: {
- select: {
- id: true,
- email: true,
- notificationSettings: true,
- locale: true,
- },
- },
- },
- },
- },
- },
- },
- });
- });
- });
-});
diff --git a/apps/web/app/api/cron/weekly-summary/lib/project.ts b/apps/web/app/api/cron/weekly-summary/lib/project.ts
deleted file mode 100644
index d8c51561e0..0000000000
--- a/apps/web/app/api/cron/weekly-summary/lib/project.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-import { prisma } from "@formbricks/database";
-import { TWeeklySummaryProjectData } from "@formbricks/types/weekly-summary";
-
-export const getProjectsByOrganizationId = async (
- organizationId: string
-): Promise => {
- const sevenDaysAgo = new Date();
- sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
-
- return await prisma.project.findMany({
- where: {
- organizationId: organizationId,
- },
- select: {
- id: true,
- name: true,
- environments: {
- where: {
- type: "production",
- },
- select: {
- id: true,
- surveys: {
- where: {
- NOT: {
- AND: [
- { status: "completed" },
- {
- responses: {
- none: {
- createdAt: {
- gte: sevenDaysAgo,
- },
- },
- },
- },
- ],
- },
- status: {
- not: "draft",
- },
- },
- select: {
- id: true,
- name: true,
- questions: true,
- status: true,
- responses: {
- where: {
- createdAt: {
- gte: sevenDaysAgo,
- },
- },
- select: {
- id: true,
- createdAt: true,
- updatedAt: true,
- finished: true,
- data: true,
- },
- orderBy: {
- createdAt: "desc",
- },
- },
- displays: {
- where: {
- createdAt: {
- gte: sevenDaysAgo,
- },
- },
- select: {
- id: true,
- },
- },
- hiddenFields: true,
- },
- },
- attributeKeys: {
- select: {
- id: true,
- createdAt: true,
- updatedAt: true,
- name: true,
- description: true,
- type: true,
- environmentId: true,
- key: true,
- isUnique: true,
- },
- },
- },
- },
- organization: {
- select: {
- memberships: {
- select: {
- user: {
- select: {
- id: true,
- email: true,
- notificationSettings: true,
- locale: true,
- },
- },
- },
- },
- },
- },
- },
- });
-};
diff --git a/apps/web/app/api/cron/weekly-summary/route.ts b/apps/web/app/api/cron/weekly-summary/route.ts
deleted file mode 100644
index 785db9ff8c..0000000000
--- a/apps/web/app/api/cron/weekly-summary/route.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { responses } from "@/app/lib/api/response";
-import { CRON_SECRET } from "@/lib/constants";
-import { hasUserEnvironmentAccess } from "@/lib/environment/auth";
-import { sendNoLiveSurveyNotificationEmail, sendWeeklySummaryNotificationEmail } from "@/modules/email";
-import { headers } from "next/headers";
-import { getNotificationResponse } from "./lib/notificationResponse";
-import { getOrganizationIds } from "./lib/organization";
-import { getProjectsByOrganizationId } from "./lib/project";
-
-const BATCH_SIZE = 500;
-
-export const POST = async (): Promise => {
- const headersList = await headers();
- // Check authentication
- if (headersList.get("x-api-key") !== CRON_SECRET) {
- return responses.notAuthenticatedResponse();
- }
-
- const emailSendingPromises: Promise[] = [];
-
- // Fetch all organization IDs
- const organizationIds = await getOrganizationIds();
-
- // Paginate through organizations
- for (let i = 0; i < organizationIds.length; i += BATCH_SIZE) {
- const batchedOrganizationIds = organizationIds.slice(i, i + BATCH_SIZE);
- // Fetch projects for batched organizations asynchronously
- const batchedProjectsPromises = batchedOrganizationIds.map((organizationId) =>
- getProjectsByOrganizationId(organizationId)
- );
-
- const batchedProjects = await Promise.all(batchedProjectsPromises);
- for (const projects of batchedProjects) {
- for (const project of projects) {
- const organizationMembers = project.organization.memberships;
- const organizationMembersWithNotificationEnabled = organizationMembers.filter(
- (member) =>
- member.user.notificationSettings?.weeklySummary &&
- member.user.notificationSettings.weeklySummary[project.id]
- );
-
- if (organizationMembersWithNotificationEnabled.length === 0) continue;
-
- const notificationResponse = getNotificationResponse(project.environments[0], project.name);
-
- if (notificationResponse.insights.numLiveSurvey === 0) {
- for (const organizationMember of organizationMembersWithNotificationEnabled) {
- if (await hasUserEnvironmentAccess(organizationMember.user.id, project.environments[0].id)) {
- emailSendingPromises.push(
- sendNoLiveSurveyNotificationEmail(organizationMember.user.email, notificationResponse)
- );
- }
- }
- continue;
- }
-
- for (const organizationMember of organizationMembersWithNotificationEnabled) {
- if (await hasUserEnvironmentAccess(organizationMember.user.id, project.environments[0].id)) {
- emailSendingPromises.push(
- sendWeeklySummaryNotificationEmail(organizationMember.user.email, notificationResponse)
- );
- }
- }
- }
- }
- }
-
- await Promise.all(emailSendingPromises);
- return responses.successResponse({}, true);
-};
diff --git a/apps/web/app/page.test.tsx b/apps/web/app/page.test.tsx
index a117a07ff3..796bb2f137 100644
--- a/apps/web/app/page.test.tsx
+++ b/apps/web/app/page.test.tsx
@@ -127,7 +127,6 @@ describe("Page", () => {
objective: null,
notificationSettings: {
alert: {},
- weeklySummary: {},
unsubscribedOrganizationIds: [],
},
locale: "en-US",
@@ -171,7 +170,6 @@ describe("Page", () => {
objective: null,
notificationSettings: {
alert: {},
- weeklySummary: {},
unsubscribedOrganizationIds: [],
},
locale: "en-US",
@@ -261,7 +259,6 @@ describe("Page", () => {
objective: null,
notificationSettings: {
alert: {},
- weeklySummary: {},
unsubscribedOrganizationIds: [],
},
locale: "en-US",
@@ -351,7 +348,6 @@ describe("Page", () => {
objective: null,
notificationSettings: {
alert: {},
- weeklySummary: {},
unsubscribedOrganizationIds: [],
},
locale: "en-US",
diff --git a/apps/web/lib/organization/service.test.ts b/apps/web/lib/organization/service.test.ts
index d0129587f0..546253983a 100644
--- a/apps/web/lib/organization/service.test.ts
+++ b/apps/web/lib/organization/service.test.ts
@@ -273,7 +273,6 @@ describe("Organization Service", () => {
id: "user-123",
notificationSettings: {
alert: { "existing-survey-id": true },
- weeklySummary: {},
unsubscribedOrganizationIds: [], // User is subscribed to all organizations
},
} as any;
@@ -296,7 +295,7 @@ describe("Organization Service", () => {
"existing-survey-id": true,
"survey-123": true,
},
- weeklySummary: {},
+
unsubscribedOrganizationIds: [],
},
});
@@ -307,7 +306,6 @@ describe("Organization Service", () => {
id: "user-123",
notificationSettings: {
alert: { "existing-survey-id": true },
- weeklySummary: {},
unsubscribedOrganizationIds: ["org-123"], // User has unsubscribed from this organization
},
} as any;
diff --git a/apps/web/lib/organization/service.ts b/apps/web/lib/organization/service.ts
index 160a1d1317..0e2570a337 100644
--- a/apps/web/lib/organization/service.ts
+++ b/apps/web/lib/organization/service.ts
@@ -296,7 +296,7 @@ export const subscribeOrganizationMembersToSurveyResponses = async (
return;
}
- const defaultSettings = { alert: {}, weeklySummary: {} };
+ const defaultSettings = { alert: {} };
const updatedNotificationSettings: TUserNotificationSettings = {
...defaultSettings,
...surveyCreator.notificationSettings,
diff --git a/apps/web/lib/survey/__mock__/survey.mock.ts b/apps/web/lib/survey/__mock__/survey.mock.ts
index 81347d0caa..316d7a53a4 100644
--- a/apps/web/lib/survey/__mock__/survey.mock.ts
+++ b/apps/web/lib/survey/__mock__/survey.mock.ts
@@ -130,7 +130,7 @@ export const mockUser: TUser = {
objective: "improve_user_retention",
notificationSettings: {
alert: {},
- weeklySummary: {},
+
unsubscribedOrganizationIds: [],
},
role: "other",
diff --git a/apps/web/lib/user/service.test.ts b/apps/web/lib/user/service.test.ts
index d986497917..457f746db0 100644
--- a/apps/web/lib/user/service.test.ts
+++ b/apps/web/lib/user/service.test.ts
@@ -48,7 +48,7 @@ describe("User Service", () => {
objective: Objective.increase_conversion,
notificationSettings: {
alert: {},
- weeklySummary: {},
+
unsubscribedOrganizationIds: [],
},
locale: "en-US" as TUserLocale,
diff --git a/apps/web/modules/auth/invite/page.tsx b/apps/web/modules/auth/invite/page.tsx
index 21bfe6ab31..91d6d5b282 100644
--- a/apps/web/modules/auth/invite/page.tsx
+++ b/apps/web/modules/auth/invite/page.tsx
@@ -107,7 +107,6 @@ export const InvitePage = async (props: InvitePageProps) => {
notificationSettings: {
...user.notificationSettings,
alert: user.notificationSettings.alert ?? {},
- weeklySummary: user.notificationSettings.weeklySummary ?? {},
unsubscribedOrganizationIds: Array.from(
new Set([
...(user.notificationSettings?.unsubscribedOrganizationIds || []),
diff --git a/apps/web/modules/auth/lib/mock-data.ts b/apps/web/modules/auth/lib/mock-data.ts
index 3dfd70c011..07227762c3 100644
--- a/apps/web/modules/auth/lib/mock-data.ts
+++ b/apps/web/modules/auth/lib/mock-data.ts
@@ -13,7 +13,7 @@ export const mockUser: TUser = {
objective: "improve_user_retention",
notificationSettings: {
alert: {},
- weeklySummary: {},
+
unsubscribedOrganizationIds: [],
},
role: "other",
diff --git a/apps/web/modules/auth/signup/actions.ts b/apps/web/modules/auth/signup/actions.ts
index 8ed15a9ae9..d72bad55ba 100644
--- a/apps/web/modules/auth/signup/actions.ts
+++ b/apps/web/modules/auth/signup/actions.ts
@@ -124,7 +124,7 @@ async function handleInviteAcceptance(
await updateUser(user.id, {
notificationSettings: {
alert: {},
- weeklySummary: {},
+
unsubscribedOrganizationIds: [invite.organizationId],
},
});
@@ -149,7 +149,7 @@ async function handleOrganizationCreation(ctx: ActionClientCtx, user: TCreatedUs
notificationSettings: {
...user.notificationSettings,
alert: { ...user.notificationSettings?.alert },
- weeklySummary: { ...user.notificationSettings?.weeklySummary },
+
unsubscribedOrganizationIds: Array.from(
new Set([...(user.notificationSettings?.unsubscribedOrganizationIds ?? []), organization.id])
),
diff --git a/apps/web/modules/ee/audit-logs/lib/utils.test.ts b/apps/web/modules/ee/audit-logs/lib/utils.test.ts
index 6dffdd8b98..c68cd84a8a 100644
--- a/apps/web/modules/ee/audit-logs/lib/utils.test.ts
+++ b/apps/web/modules/ee/audit-logs/lib/utils.test.ts
@@ -127,7 +127,6 @@ describe("withAuditLogging", () => {
objective: null,
notificationSettings: {
alert: {},
- weeklySummary: {},
},
},
organizationId: "org1",
@@ -167,7 +166,6 @@ describe("withAuditLogging", () => {
objective: null,
notificationSettings: {
alert: {},
- weeklySummary: {},
},
},
organizationId: "org1",
diff --git a/apps/web/modules/ee/contacts/[contactId]/components/response-timeline.test.tsx b/apps/web/modules/ee/contacts/[contactId]/components/response-timeline.test.tsx
index f40265db61..c938ee0c0e 100644
--- a/apps/web/modules/ee/contacts/[contactId]/components/response-timeline.test.tsx
+++ b/apps/web/modules/ee/contacts/[contactId]/components/response-timeline.test.tsx
@@ -38,7 +38,6 @@ describe("ResponseTimeline", () => {
isActive: true,
notificationSettings: {
alert: {},
- weeklySummary: {},
},
locale: "en-US",
lastLoginAt: new Date(),
diff --git a/apps/web/modules/ee/sso/lib/sso-handlers.ts b/apps/web/modules/ee/sso/lib/sso-handlers.ts
index ed12d14e65..895742a49f 100644
--- a/apps/web/modules/ee/sso/lib/sso-handlers.ts
+++ b/apps/web/modules/ee/sso/lib/sso-handlers.ts
@@ -216,9 +216,6 @@ export const handleSsoCallback = async ({
unsubscribedOrganizationIds: Array.from(
new Set([...(userProfile.notificationSettings?.unsubscribedOrganizationIds || []), organization.id])
),
- weeklySummary: {
- ...userProfile.notificationSettings?.weeklySummary,
- },
};
await updateUser(userProfile.id, {
diff --git a/apps/web/modules/ee/sso/lib/tests/__mock__/sso-handlers.mock.ts b/apps/web/modules/ee/sso/lib/tests/__mock__/sso-handlers.mock.ts
index eb319298f7..2c02097ff5 100644
--- a/apps/web/modules/ee/sso/lib/tests/__mock__/sso-handlers.mock.ts
+++ b/apps/web/modules/ee/sso/lib/tests/__mock__/sso-handlers.mock.ts
@@ -9,7 +9,7 @@ export const mockUser: TUser = {
name: "Test User",
notificationSettings: {
alert: {},
- weeklySummary: {},
+
unsubscribedOrganizationIds: [],
},
emailVerified: new Date(),
diff --git a/apps/web/modules/email/emails/weekly-summary/create-reminder-notification-body.tsx b/apps/web/modules/email/emails/weekly-summary/create-reminder-notification-body.tsx
deleted file mode 100644
index 3fbe96f2b0..0000000000
--- a/apps/web/modules/email/emails/weekly-summary/create-reminder-notification-body.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import { WEBAPP_URL } from "@/lib/constants";
-import { getTranslate } from "@/tolgee/server";
-import { Container, Text } from "@react-email/components";
-import React from "react";
-import type { TWeeklySummaryNotificationResponse } from "@formbricks/types/weekly-summary";
-import { EmailButton } from "../../components/email-button";
-import { NotificationFooter } from "./notification-footer";
-
-interface CreateReminderNotificationBodyProps {
- notificationData: TWeeklySummaryNotificationResponse;
-}
-
-export async function CreateReminderNotificationBody({
- notificationData,
-}: CreateReminderNotificationBodyProps): Promise {
- const t = await getTranslate();
- return (
-
-
- {t("emails.weekly_summary_create_reminder_notification_body_text", {
- projectName: notificationData.projectName,
- })}
-
-
- {t("emails.weekly_summary_create_reminder_notification_body_dont_let_a_week_pass")}
-
-
-
- {t("emails.weekly_summary_create_reminder_notification_body_need_help")}
-
- {t("emails.weekly_summary_create_reminder_notification_body_cal_slot")}
-
- {t("emails.weekly_summary_create_reminder_notification_body_reply_email")}
-
-
-
- );
-}
diff --git a/apps/web/modules/email/emails/weekly-summary/live-survey-notification.tsx b/apps/web/modules/email/emails/weekly-summary/live-survey-notification.tsx
deleted file mode 100644
index 3f31811ef7..0000000000
--- a/apps/web/modules/email/emails/weekly-summary/live-survey-notification.tsx
+++ /dev/null
@@ -1,123 +0,0 @@
-import { WEBAPP_URL } from "@/lib/constants";
-import { renderEmailResponseValue } from "@/modules/email/emails/lib/utils";
-import { getTranslate } from "@/tolgee/server";
-import { Container, Hr, Link, Tailwind, Text } from "@react-email/components";
-import { TFnType } from "@tolgee/react";
-import React, { type JSX } from "react";
-import type { TSurveyStatus } from "@formbricks/types/surveys/types";
-import type {
- TWeeklySummaryNotificationDataSurvey,
- TWeeklySummarySurveyResponseData,
-} from "@formbricks/types/weekly-summary";
-import { EmailButton } from "../../components/email-button";
-
-const getButtonLabel = (count: number, t: TFnType): string => {
- if (count === 1) {
- return t("emails.live_survey_notification_view_response");
- }
- return t("emails.live_survey_notification_view_more_responses", {
- responseCount: count > 2 ? (count - 1).toString() : "1",
- });
-};
-
-const convertSurveyStatus = (status: TSurveyStatus, t: TFnType): string => {
- const statusMap = {
- inProgress: t("emails.live_survey_notification_in_progress"),
- paused: t("emails.live_survey_notification_paused"),
- completed: t("emails.live_survey_notification_completed"),
- draft: t("emails.live_survey_notification_draft"),
- scheduled: t("emails.live_survey_notification_scheduled"),
- };
-
- return statusMap[status] || status;
-};
-
-interface LiveSurveyNotificationProps {
- environmentId: string;
- surveys: TWeeklySummaryNotificationDataSurvey[];
-}
-
-export async function LiveSurveyNotification({
- environmentId,
- surveys,
-}: LiveSurveyNotificationProps): Promise {
- const t = await getTranslate();
- const createSurveyFields = (
- surveyResponses: TWeeklySummarySurveyResponseData[]
- ): React.JSX.Element | React.JSX.Element[] => {
- if (surveyResponses.length === 0) {
- return (
-
-
- {t("emails.live_survey_notification_no_responses_yet")}
-
-
- );
- }
- const surveyFields: JSX.Element[] = [];
- const responseCount = surveyResponses.length;
-
- surveyResponses.forEach((surveyResponse, index) => {
- if (!surveyResponse.responseValue) {
- return;
- }
-
- surveyFields.push(
-
- {surveyResponse.headline}
- {renderEmailResponseValue(surveyResponse.responseValue, surveyResponse.questionType, t)}
-
- );
-
- // Add
only when there are 2 or more responses to display, and it's not the last response
- if (responseCount >= 2 && index < responseCount - 1) {
- surveyFields.push(
);
- }
- });
-
- return surveyFields;
- };
-
- if (!surveys.length) return [];
-
- return surveys.map((survey) => {
- const displayStatus = convertSurveyStatus(survey.status, t);
- const isInProgress = displayStatus === "In Progress";
- const noResponseLastWeek = isInProgress && survey.responses.length === 0;
- return (
-
-
-
-
- {survey.name}
-
-
-
-
- {displayStatus}
-
- {noResponseLastWeek ? (
- {t("emails.live_survey_notification_no_new_response")}
- ) : (
- createSurveyFields(survey.responses)
- )}
- {survey.responseCount > 0 && (
-
-
-
- )}
-
-
- );
- });
-}
diff --git a/apps/web/modules/email/emails/weekly-summary/no-live-survey-notification-email.tsx b/apps/web/modules/email/emails/weekly-summary/no-live-survey-notification-email.tsx
deleted file mode 100644
index 8f9d878855..0000000000
--- a/apps/web/modules/email/emails/weekly-summary/no-live-survey-notification-email.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import React from "react";
-import type { TWeeklySummaryNotificationResponse } from "@formbricks/types/weekly-summary";
-import { CreateReminderNotificationBody } from "./create-reminder-notification-body";
-import { NotificationHeader } from "./notification-header";
-
-interface NoLiveSurveyNotificationEmailProps {
- notificationData: TWeeklySummaryNotificationResponse;
- startDate: string;
- endDate: string;
- startYear: number;
- endYear: number;
-}
-
-export function NoLiveSurveyNotificationEmail({
- notificationData,
- startDate,
- endDate,
- startYear,
- endYear,
-}: NoLiveSurveyNotificationEmailProps): React.JSX.Element {
- return (
-
-
-
-
- );
-}
diff --git a/apps/web/modules/email/emails/weekly-summary/notification-footer.tsx b/apps/web/modules/email/emails/weekly-summary/notification-footer.tsx
deleted file mode 100644
index 19c8d417ef..0000000000
--- a/apps/web/modules/email/emails/weekly-summary/notification-footer.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { WEBAPP_URL } from "@/lib/constants";
-import { getTranslate } from "@/tolgee/server";
-import { Container, Link, Tailwind, Text } from "@react-email/components";
-import React from "react";
-
-interface NotificatonFooterProps {
- environmentId: string;
-}
-export async function NotificationFooter({
- environmentId,
-}: NotificatonFooterProps): Promise {
- const t = await getTranslate();
- return (
-
-
- {t("emails.notification_footer_all_the_best")}
- {t("emails.notification_footer_the_formbricks_team")}
-
-
- {t("emails.notification_footer_to_halt_weekly_updates")}
-
- {t("emails.notification_footer_please_turn_them_off")}
- {" "}
- {t("emails.notification_footer_in_your_settings")} 🙏
-
-
-
-
- );
-}
diff --git a/apps/web/modules/email/emails/weekly-summary/notification-header.tsx b/apps/web/modules/email/emails/weekly-summary/notification-header.tsx
deleted file mode 100644
index 542808d0a2..0000000000
--- a/apps/web/modules/email/emails/weekly-summary/notification-header.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { getTranslate } from "@/tolgee/server";
-import { Container, Heading, Text } from "@react-email/components";
-import React from "react";
-
-interface NotificationHeaderProps {
- projectName: string;
- startDate: string;
- endDate: string;
- startYear: number;
- endYear: number;
-}
-
-export async function NotificationHeader({
- projectName,
- startDate,
- endDate,
- startYear,
- endYear,
-}: NotificationHeaderProps): Promise {
- const t = await getTranslate();
- const getNotificationHeaderTimePeriod = (): React.JSX.Element => {
- if (startYear === endYear) {
- return (
-
- {startDate} - {endDate} {endYear}
-
- );
- }
-
- return (
-
- {startDate} {startYear} - {endDate} {endYear}
-
- );
- };
- return (
-
-
-
- {t("emails.notification_header_hey")}
-
-
-
- {t("emails.notification_header_weekly_report_for")} {projectName}
-
- {getNotificationHeaderTimePeriod()}
-
-
-
- );
-}
diff --git a/apps/web/modules/email/emails/weekly-summary/notification-insight.tsx b/apps/web/modules/email/emails/weekly-summary/notification-insight.tsx
deleted file mode 100644
index f9bce12bf2..0000000000
--- a/apps/web/modules/email/emails/weekly-summary/notification-insight.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import { getTranslate } from "@/tolgee/server";
-import { Column, Container, Row, Section, Text } from "@react-email/components";
-import React from "react";
-import type { TWeeklySummaryInsights } from "@formbricks/types/weekly-summary";
-
-interface NotificationInsightProps {
- insights: TWeeklySummaryInsights;
-}
-
-export async function NotificationInsight({
- insights,
-}: NotificationInsightProps): Promise {
- const t = await getTranslate();
- return (
-
-
-
-
- {t("emails.notification_insight_surveys")}
- {insights.numLiveSurvey}
-
-
- {t("emails.notification_insight_displays")}
- {insights.totalDisplays}
-
-
- {t("emails.notification_insight_responses")}
- {insights.totalResponses}
-
-
- {t("emails.notification_insight_completed")}
- {insights.totalCompletedResponses}
-
- {insights.totalDisplays !== 0 ? (
-
- {t("emails.notification_insight_completion_rate")}
- {Math.round(insights.completionRate)}%
-
- ) : (
- ""
- )}
-
-
-
- );
-}
diff --git a/apps/web/modules/email/emails/weekly-summary/weekly-summary-notification-email.tsx b/apps/web/modules/email/emails/weekly-summary/weekly-summary-notification-email.tsx
deleted file mode 100644
index df6b8348c6..0000000000
--- a/apps/web/modules/email/emails/weekly-summary/weekly-summary-notification-email.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import { TFnType } from "@tolgee/react";
-import React from "react";
-import type { TWeeklySummaryNotificationResponse } from "@formbricks/types/weekly-summary";
-import { EmailTemplate } from "../../components/email-template";
-import { LiveSurveyNotification } from "./live-survey-notification";
-import { NotificationFooter } from "./notification-footer";
-import { NotificationHeader } from "./notification-header";
-import { NotificationInsight } from "./notification-insight";
-
-interface WeeklySummaryNotificationEmailProps {
- notificationData: TWeeklySummaryNotificationResponse;
- startDate: string;
- endDate: string;
- startYear: number;
- endYear: number;
- t: TFnType;
-}
-
-export function WeeklySummaryNotificationEmail({
- notificationData,
- startDate,
- endDate,
- startYear,
- endYear,
- t,
-}: WeeklySummaryNotificationEmailProps): React.JSX.Element {
- return (
-
-
-
-
-
-
- );
-}
diff --git a/apps/web/modules/email/index.tsx b/apps/web/modules/email/index.tsx
index a75e673556..d8d1637b38 100644
--- a/apps/web/modules/email/index.tsx
+++ b/apps/web/modules/email/index.tsx
@@ -26,7 +26,6 @@ import { InvalidInputError } from "@formbricks/types/errors";
import type { TResponse } from "@formbricks/types/responses";
import type { TSurvey } from "@formbricks/types/surveys/types";
import { TUserEmail, TUserLocale } from "@formbricks/types/user";
-import type { TWeeklySummaryNotificationResponse } from "@formbricks/types/weekly-summary";
import { ForgotPasswordEmail } from "./emails/auth/forgot-password-email";
import { PasswordResetNotifyEmail } from "./emails/auth/password-reset-notify-email";
import { VerificationEmail } from "./emails/auth/verification-email";
@@ -35,8 +34,6 @@ import { InviteEmail } from "./emails/invite/invite-email";
import { EmbedSurveyPreviewEmail } from "./emails/survey/embed-survey-preview-email";
import { LinkSurveyEmail } from "./emails/survey/link-survey-email";
import { ResponseFinishedEmail } from "./emails/survey/response-finished-email";
-import { NoLiveSurveyNotificationEmail } from "./emails/weekly-summary/no-live-survey-notification-email";
-import { WeeklySummaryNotificationEmail } from "./emails/weekly-summary/weekly-summary-notification-email";
export const IS_SMTP_CONFIGURED = Boolean(SMTP_HOST && SMTP_PORT);
@@ -290,70 +287,3 @@ export const sendLinkSurveyToVerifiedEmail = async (data: TLinkSurveyEmailData):
html,
});
};
-
-export const sendWeeklySummaryNotificationEmail = async (
- email: string,
- notificationData: TWeeklySummaryNotificationResponse
-): Promise => {
- const startDate = `${notificationData.lastWeekDate.getDate().toString()} ${notificationData.lastWeekDate.toLocaleString(
- "default",
- { month: "short" }
- )}`;
- const endDate = `${notificationData.currentDate.getDate().toString()} ${notificationData.currentDate.toLocaleString(
- "default",
- { month: "short" }
- )}`;
- const startYear = notificationData.lastWeekDate.getFullYear();
- const endYear = notificationData.currentDate.getFullYear();
- const t = await getTranslate();
- const html = await render(
- WeeklySummaryNotificationEmail({
- notificationData,
- startDate,
- endDate,
- startYear,
- endYear,
- t,
- })
- );
- await sendEmail({
- to: email,
- subject: t("emails.weekly_summary_email_subject", {
- projectName: notificationData.projectName,
- }),
- html,
- });
-};
-
-export const sendNoLiveSurveyNotificationEmail = async (
- email: string,
- notificationData: TWeeklySummaryNotificationResponse
-): Promise => {
- const startDate = `${notificationData.lastWeekDate.getDate().toString()} ${notificationData.lastWeekDate.toLocaleString(
- "default",
- { month: "short" }
- )}`;
- const endDate = `${notificationData.currentDate.getDate().toString()} ${notificationData.currentDate.toLocaleString(
- "default",
- { month: "short" }
- )}`;
- const startYear = notificationData.lastWeekDate.getFullYear();
- const endYear = notificationData.currentDate.getFullYear();
- const t = await getTranslate();
- const html = await render(
- NoLiveSurveyNotificationEmail({
- notificationData,
- startDate,
- endDate,
- startYear,
- endYear,
- })
- );
- await sendEmail({
- to: email,
- subject: t("emails.weekly_summary_email_subject", {
- projectName: notificationData.projectName,
- }),
- html,
- });
-};
diff --git a/apps/web/modules/organization/actions.ts b/apps/web/modules/organization/actions.ts
index dba70c0f9a..9f95190999 100644
--- a/apps/web/modules/organization/actions.ts
+++ b/apps/web/modules/organization/actions.ts
@@ -36,7 +36,7 @@ export const createOrganizationAction = authenticatedActionClient.schema(ZCreate
accepted: true,
});
- const project = await createProject(newOrganization.id, {
+ await createProject(newOrganization.id, {
name: "My Project",
});
@@ -45,10 +45,7 @@ export const createOrganizationAction = authenticatedActionClient.schema(ZCreate
alert: {
...ctx.user.notificationSettings?.alert,
},
- weeklySummary: {
- ...ctx.user.notificationSettings?.weeklySummary,
- [project.id]: true,
- },
+
unsubscribedOrganizationIds: Array.from(
new Set([...(ctx.user.notificationSettings?.unsubscribedOrganizationIds || []), newOrganization.id]) // NOSONAR // We want to check for empty strings too
),
diff --git a/apps/web/modules/setup/organization/create/components/removed-from-organization.test.tsx b/apps/web/modules/setup/organization/create/components/removed-from-organization.test.tsx
index 9e8ff9c2d6..b5a51a6756 100644
--- a/apps/web/modules/setup/organization/create/components/removed-from-organization.test.tsx
+++ b/apps/web/modules/setup/organization/create/components/removed-from-organization.test.tsx
@@ -63,7 +63,6 @@ const mockUser = {
updatedAt: new Date(),
notificationSettings: {
alert: {},
- weeklySummary: {},
},
role: "other",
} as TUser;
diff --git a/apps/web/modules/setup/organization/create/page.test.tsx b/apps/web/modules/setup/organization/create/page.test.tsx
index 7124f5ed8a..785d9ebb39 100644
--- a/apps/web/modules/setup/organization/create/page.test.tsx
+++ b/apps/web/modules/setup/organization/create/page.test.tsx
@@ -160,16 +160,6 @@ describe("CreateOrganizationPage", () => {
surveyUpdated: true,
surveyCreated: true,
},
- weeklySummary: {
- surveyInvite: true,
- surveyResponse: true,
- surveyClosed: true,
- surveyPaused: true,
- surveyCompleted: true,
- surveyDeleted: true,
- surveyUpdated: true,
- surveyCreated: true,
- },
unsubscribedOrganizationIds: [],
},
locale: "en-US" as const,
diff --git a/docker/cronjobs b/docker/cronjobs
index 919421c1d4..e118a434e7 100644
--- a/docker/cronjobs
+++ b/docker/cronjobs
@@ -1,3 +1,3 @@
0 0 * * * curl $WEBAPP_URL/api/cron/survey-status -X POST -H 'content-type: application/json' -H 'x-api-key: '"$CRON_SECRET"''
-0 8 * * 1 curl $WEBAPP_URL/api/cron/weekly-summary -X POST -H 'content-type: application/json' -H 'x-api-key: '"$CRON_SECRET"''
+
0 9 * * * curl $WEBAPP_URL/api/cron/ping -X POST -H 'content-type: application/json' -H 'x-api-key: '"$CRON_SECRET"''
diff --git a/infra/formbricks-cloud-helm/values-staging.yaml.gotmpl b/infra/formbricks-cloud-helm/values-staging.yaml.gotmpl
index def821965d..5d3b0b1aa4 100644
--- a/infra/formbricks-cloud-helm/values-staging.yaml.gotmpl
+++ b/infra/formbricks-cloud-helm/values-staging.yaml.gotmpl
@@ -48,29 +48,7 @@ cronJob:
tag: latest
schedule: 0 0 * * *
successfulJobsHistoryLimit: 0
- weekly-summary:
- args:
- - /bin/sh
- - -c
- - 'curl -X POST -H "content-type: application/json" -H "x-api-key: $CRON_SECRET"
- "$WEBAPP_URL/api/cron/weekly-summary"'
- env:
- CRON_SECRET:
- valueFrom:
- secretKeyRef:
- key: CRON_SECRET
- name: formbricks-stage-app-env
- WEBAPP_URL:
- valueFrom:
- secretKeyRef:
- key: WEBAPP_URL
- name: formbricks-stage-app-env
- image:
- imagePullPolicy: IfNotPresent
- repository: quay.io/curl/curl
- tag: latest
- schedule: 0 8 * * 1
- successfulJobsHistoryLimit: 0
+
## Deployment & Autoscaling
deployment:
diff --git a/infra/formbricks-cloud-helm/values.yaml.gotmpl b/infra/formbricks-cloud-helm/values.yaml.gotmpl
index 514906e78e..0d05350022 100644
--- a/infra/formbricks-cloud-helm/values.yaml.gotmpl
+++ b/infra/formbricks-cloud-helm/values.yaml.gotmpl
@@ -47,29 +47,7 @@ cronJob:
tag: latest
schedule: 0 0 * * *
successfulJobsHistoryLimit: 0
- weekely-summary:
- args:
- - /bin/sh
- - -c
- - 'curl -X POST -H "content-type: application/json" -H "x-api-key: $CRON_SECRET"
- "$WEBAPP_URL/api/cron/weekly-summary"'
- env:
- CRON_SECRET:
- valueFrom:
- secretKeyRef:
- key: CRON_SECRET
- name: formbricks-app-env
- WEBAPP_URL:
- valueFrom:
- secretKeyRef:
- key: WEBAPP_URL
- name: formbricks-app-env
- image:
- imagePullPolicy: IfNotPresent
- repository: quay.io/curl/curl
- tag: latest
- schedule: 0 8 * * 1
- successfulJobsHistoryLimit: 0
+
## Deployment & Autoscaling
deployment:
diff --git a/packages/database/migration/20250722075349_sunset_weekly_summary/migration.ts b/packages/database/migration/20250722075349_sunset_weekly_summary/migration.ts
new file mode 100644
index 0000000000..2ca949c30f
--- /dev/null
+++ b/packages/database/migration/20250722075349_sunset_weekly_summary/migration.ts
@@ -0,0 +1,15 @@
+import type { MigrationScript } from "../../src/scripts/migration-runner";
+
+export const sunsetWeeklySummary: MigrationScript = {
+ type: "data",
+ id: "v0ruecekavlfu8410htz4mly",
+ name: "20250722075349_sunset_weekly_summary",
+ run: async ({ tx }) => {
+ // Remove weeklySummary from notificationSettings for all users where it is set
+ await tx.$queryRaw`
+ UPDATE "User"
+ SET "notificationSettings" = "notificationSettings" - 'weeklySummary'
+ WHERE "notificationSettings"->>'weeklySummary' IS NOT NULL
+ `;
+ },
+};
diff --git a/packages/types/user.ts b/packages/types/user.ts
index f591078dae..7892b3938e 100644
--- a/packages/types/user.ts
+++ b/packages/types/user.ts
@@ -18,7 +18,6 @@ export type TUserObjective = z.infer;
export const ZUserNotificationSettings = z.object({
alert: z.record(z.boolean()),
- weeklySummary: z.record(z.boolean()),
unsubscribedOrganizationIds: z.array(z.string()).optional(),
});
diff --git a/packages/types/weekly-summary.ts b/packages/types/weekly-summary.ts
deleted file mode 100644
index f2c7d95312..0000000000
--- a/packages/types/weekly-summary.ts
+++ /dev/null
@@ -1,104 +0,0 @@
-import { z } from "zod";
-import { ZContactAttributeKey } from "./contact-attribute-key";
-import { ZResponseData } from "./responses";
-import { ZSurveyHiddenFields, ZSurveyQuestion, ZSurveyQuestionType, ZSurveyStatus } from "./surveys/types";
-import { ZUserNotificationSettings } from "./user";
-
-const ZWeeklySummaryInsights = z.object({
- totalCompletedResponses: z.number(),
- totalDisplays: z.number(),
- totalResponses: z.number(),
- completionRate: z.number(),
- numLiveSurvey: z.number(),
-});
-
-export type TWeeklySummaryInsights = z.infer;
-
-export const ZWeeklySummarySurveyResponseData = z.object({
- headline: z.string(),
- responseValue: z.union([z.string(), z.array(z.string())]),
- questionType: ZSurveyQuestionType,
-});
-
-export type TWeeklySummarySurveyResponseData = z.infer;
-
-export const ZWeeklySummaryNotificationDataSurvey = z.object({
- id: z.string(),
- name: z.string(),
- responses: z.array(ZWeeklySummarySurveyResponseData),
- responseCount: z.number(),
- status: ZSurveyStatus,
-});
-
-export type TWeeklySummaryNotificationDataSurvey = z.infer;
-
-export const ZWeeklySummaryNotificationResponse = z.object({
- environmentId: z.string(),
- currentDate: z.date(),
- lastWeekDate: z.date(),
- projectName: z.string(),
- surveys: z.array(ZWeeklySummaryNotificationDataSurvey),
- insights: ZWeeklySummaryInsights,
-});
-
-export type TWeeklySummaryNotificationResponse = z.infer;
-
-export const ZWeeklyEmailResponseData = z.object({
- id: z.string(),
- createdAt: z.date(),
- updatedAt: z.date(),
- finished: z.boolean(),
- data: ZResponseData,
-});
-
-export type TWeeklyEmailResponseData = z.infer;
-
-export const ZWeeklySummarySurveyData = z.object({
- id: z.string(),
- name: z.string(),
- questions: z.array(ZSurveyQuestion),
- status: ZSurveyStatus,
- responses: z.array(ZWeeklyEmailResponseData),
- displays: z.array(z.object({ id: z.string() })),
- hiddenFields: ZSurveyHiddenFields,
-});
-
-export type TWeeklySummarySurveyData = z.infer;
-
-export const ZWeeklySummaryEnvironmentData = z.object({
- id: z.string(),
- surveys: z.array(ZWeeklySummarySurveyData),
- attributeKeys: z.array(ZContactAttributeKey),
-});
-
-export type TWeeklySummaryEnvironmentData = z.infer;
-
-export const ZWeeklySummaryUserData = z.object({
- id: z.string(),
- email: z.string(),
- notificationSettings: ZUserNotificationSettings,
- locale: z.string(),
-});
-
-export type TWeeklySummaryUserData = z.infer;
-
-export const ZWeeklySummaryMembershipData = z.object({
- user: ZWeeklySummaryUserData,
-});
-
-export type TWeeklySummaryMembershipData = z.infer;
-
-export const ZWeeklyEmailOrganizationData = z.object({
- memberships: z.array(ZWeeklySummaryMembershipData),
-});
-
-export type TWeeklyEmailOrganizationData = z.infer;
-
-export const ZWeeklySummaryProjectData = z.object({
- id: z.string(),
- name: z.string(),
- environments: z.array(ZWeeklySummaryEnvironmentData),
- organization: ZWeeklyEmailOrganizationData,
-});
-
-export type TWeeklySummaryProjectData = z.infer;