mirror of
https://github.com/formbricks/formbricks.git
synced 2026-02-19 10:09:57 -06:00
Compare commits
4 Commits
feat/dashb
...
feat/crud-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2038d9770 | ||
|
|
3e7fc6610a | ||
|
|
d01dc80712 | ||
|
|
d32437b4a6 |
@@ -12,7 +12,10 @@ type HasFindMany =
|
||||
| Prisma.TeamFindManyArgs
|
||||
| Prisma.ProjectTeamFindManyArgs
|
||||
| Prisma.UserFindManyArgs
|
||||
| Prisma.ContactAttributeKeyFindManyArgs;
|
||||
| Prisma.ContactAttributeKeyFindManyArgs
|
||||
| Prisma.ContactAttributeKeyFindManyArgs
|
||||
| Prisma.ChartFindManyArgs
|
||||
| Prisma.DashboardFindManyArgs;
|
||||
|
||||
export function buildCommonFilterQuery<T extends HasFindMany>(query: T, params: TGetFilter): T {
|
||||
const { limit, skip, sortBy, order, startDate, endDate, filterDateField = "createdAt" } = params || {};
|
||||
|
||||
293
apps/web/modules/ee/analysis/charts/actions.ts
Normal file
293
apps/web/modules/ee/analysis/charts/actions.ts
Normal file
@@ -0,0 +1,293 @@
|
||||
"use server";
|
||||
|
||||
import { z } from "zod";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { ZChartConfig, ZChartQuery } from "@formbricks/types/dashboard";
|
||||
import { authenticatedActionClient } from "@/lib/utils/action-client";
|
||||
import { checkAuthorizationUpdated } from "@/lib/utils/action-client/action-client-middleware";
|
||||
import { AuthenticatedActionClientCtx } from "@/lib/utils/action-client/types/context";
|
||||
import { getOrganizationIdFromEnvironmentId, getProjectIdFromEnvironmentId } from "@/lib/utils/helper";
|
||||
import { withAuditLogging } from "@/modules/ee/audit-logs/lib/handler";
|
||||
import { ZChartType } from "../types/analysis";
|
||||
|
||||
const checkProjectAccess = async (
|
||||
userId: string,
|
||||
environmentId: string,
|
||||
minPermission: "read" | "readWrite" | "manage"
|
||||
) => {
|
||||
const organizationId = await getOrganizationIdFromEnvironmentId(environmentId);
|
||||
const projectId = await getProjectIdFromEnvironmentId(environmentId);
|
||||
|
||||
await checkAuthorizationUpdated({
|
||||
userId,
|
||||
organizationId,
|
||||
access: [
|
||||
{ type: "organization", roles: ["owner", "manager"] },
|
||||
{ type: "projectTeam", minPermission, projectId },
|
||||
],
|
||||
});
|
||||
|
||||
return { organizationId, projectId };
|
||||
};
|
||||
|
||||
const ZCreateChartAction = z.object({
|
||||
environmentId: ZId,
|
||||
name: z.string().min(1),
|
||||
type: ZChartType,
|
||||
query: ZChartQuery,
|
||||
config: ZChartConfig.optional().default({}),
|
||||
});
|
||||
|
||||
export const createChartAction = authenticatedActionClient.schema(ZCreateChartAction).action(
|
||||
withAuditLogging(
|
||||
"created",
|
||||
"chart",
|
||||
async ({
|
||||
ctx,
|
||||
parsedInput,
|
||||
}: {
|
||||
ctx: AuthenticatedActionClientCtx;
|
||||
parsedInput: z.infer<typeof ZCreateChartAction>;
|
||||
}) => {
|
||||
const { organizationId, projectId } = await checkProjectAccess(
|
||||
ctx.user.id,
|
||||
parsedInput.environmentId,
|
||||
"readWrite"
|
||||
);
|
||||
|
||||
const chart = await prisma.chart.create({
|
||||
data: {
|
||||
name: parsedInput.name,
|
||||
type: parsedInput.type,
|
||||
projectId,
|
||||
query: parsedInput.query,
|
||||
config: parsedInput.config || {},
|
||||
createdBy: ctx.user.id,
|
||||
},
|
||||
});
|
||||
|
||||
ctx.auditLoggingCtx.organizationId = organizationId;
|
||||
ctx.auditLoggingCtx.projectId = projectId;
|
||||
ctx.auditLoggingCtx.newObject = chart;
|
||||
return chart;
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const ZUpdateChartAction = z.object({
|
||||
environmentId: ZId,
|
||||
chartId: ZId,
|
||||
name: z.string().min(1).optional(),
|
||||
type: ZChartType.optional(),
|
||||
query: ZChartQuery.optional(),
|
||||
config: ZChartConfig.optional(),
|
||||
});
|
||||
|
||||
export const updateChartAction = authenticatedActionClient.schema(ZUpdateChartAction).action(
|
||||
withAuditLogging(
|
||||
"updated",
|
||||
"chart",
|
||||
async ({
|
||||
ctx,
|
||||
parsedInput,
|
||||
}: {
|
||||
ctx: AuthenticatedActionClientCtx;
|
||||
parsedInput: z.infer<typeof ZUpdateChartAction>;
|
||||
}) => {
|
||||
const { organizationId, projectId } = await checkProjectAccess(
|
||||
ctx.user.id,
|
||||
parsedInput.environmentId,
|
||||
"readWrite"
|
||||
);
|
||||
|
||||
const chart = await prisma.chart.findFirst({
|
||||
where: { id: parsedInput.chartId, projectId },
|
||||
});
|
||||
|
||||
if (!chart) {
|
||||
throw new Error("Chart not found");
|
||||
}
|
||||
|
||||
const updatedChart = await prisma.chart.update({
|
||||
where: { id: parsedInput.chartId },
|
||||
data: {
|
||||
...(parsedInput.name !== undefined && { name: parsedInput.name }),
|
||||
...(parsedInput.type !== undefined && { type: parsedInput.type }),
|
||||
...(parsedInput.query !== undefined && { query: parsedInput.query }),
|
||||
...(parsedInput.config !== undefined && { config: parsedInput.config }),
|
||||
},
|
||||
});
|
||||
|
||||
ctx.auditLoggingCtx.organizationId = organizationId;
|
||||
ctx.auditLoggingCtx.projectId = projectId;
|
||||
ctx.auditLoggingCtx.oldObject = chart;
|
||||
ctx.auditLoggingCtx.newObject = updatedChart;
|
||||
return updatedChart;
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const ZDuplicateChartAction = z.object({
|
||||
environmentId: ZId,
|
||||
chartId: ZId,
|
||||
});
|
||||
|
||||
export const duplicateChartAction = authenticatedActionClient.schema(ZDuplicateChartAction).action(
|
||||
withAuditLogging(
|
||||
"created",
|
||||
"chart",
|
||||
async ({
|
||||
ctx,
|
||||
parsedInput,
|
||||
}: {
|
||||
ctx: AuthenticatedActionClientCtx;
|
||||
parsedInput: z.infer<typeof ZDuplicateChartAction>;
|
||||
}) => {
|
||||
const { organizationId, projectId } = await checkProjectAccess(
|
||||
ctx.user.id,
|
||||
parsedInput.environmentId,
|
||||
"readWrite"
|
||||
);
|
||||
|
||||
const sourceChart = await prisma.chart.findFirst({
|
||||
where: { id: parsedInput.chartId, projectId },
|
||||
});
|
||||
|
||||
if (!sourceChart) {
|
||||
throw new Error("Chart not found");
|
||||
}
|
||||
|
||||
const duplicatedChart = await prisma.chart.create({
|
||||
data: {
|
||||
name: `${sourceChart.name} (copy)`,
|
||||
type: sourceChart.type,
|
||||
projectId,
|
||||
query: sourceChart.query as object,
|
||||
config: (sourceChart.config as object) || {},
|
||||
createdBy: ctx.user.id,
|
||||
},
|
||||
});
|
||||
|
||||
ctx.auditLoggingCtx.organizationId = organizationId;
|
||||
ctx.auditLoggingCtx.projectId = projectId;
|
||||
ctx.auditLoggingCtx.newObject = duplicatedChart;
|
||||
return duplicatedChart;
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const ZDeleteChartAction = z.object({
|
||||
environmentId: ZId,
|
||||
chartId: ZId,
|
||||
});
|
||||
|
||||
export const deleteChartAction = authenticatedActionClient.schema(ZDeleteChartAction).action(
|
||||
withAuditLogging(
|
||||
"deleted",
|
||||
"chart",
|
||||
async ({
|
||||
ctx,
|
||||
parsedInput,
|
||||
}: {
|
||||
ctx: AuthenticatedActionClientCtx;
|
||||
parsedInput: z.infer<typeof ZDeleteChartAction>;
|
||||
}) => {
|
||||
const { organizationId, projectId } = await checkProjectAccess(
|
||||
ctx.user.id,
|
||||
parsedInput.environmentId,
|
||||
"readWrite"
|
||||
);
|
||||
|
||||
const chart = await prisma.chart.findFirst({
|
||||
where: { id: parsedInput.chartId, projectId },
|
||||
});
|
||||
|
||||
if (!chart) {
|
||||
throw new Error("Chart not found");
|
||||
}
|
||||
|
||||
await prisma.chart.delete({
|
||||
where: { id: parsedInput.chartId },
|
||||
});
|
||||
|
||||
ctx.auditLoggingCtx.organizationId = organizationId;
|
||||
ctx.auditLoggingCtx.projectId = projectId;
|
||||
ctx.auditLoggingCtx.oldObject = chart;
|
||||
return { success: true };
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const ZGetChartAction = z.object({
|
||||
environmentId: ZId,
|
||||
chartId: ZId,
|
||||
});
|
||||
|
||||
export const getChartAction = authenticatedActionClient
|
||||
.schema(ZGetChartAction)
|
||||
.action(
|
||||
async ({
|
||||
ctx,
|
||||
parsedInput,
|
||||
}: {
|
||||
ctx: AuthenticatedActionClientCtx;
|
||||
parsedInput: z.infer<typeof ZGetChartAction>;
|
||||
}) => {
|
||||
const { projectId } = await checkProjectAccess(ctx.user.id, parsedInput.environmentId, "read");
|
||||
|
||||
const chart = await prisma.chart.findFirst({
|
||||
where: { id: parsedInput.chartId, projectId },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
type: true,
|
||||
query: true,
|
||||
config: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!chart) {
|
||||
throw new Error("Chart not found");
|
||||
}
|
||||
|
||||
return chart;
|
||||
}
|
||||
);
|
||||
|
||||
const ZGetChartsAction = z.object({
|
||||
environmentId: ZId,
|
||||
});
|
||||
|
||||
export const getChartsAction = authenticatedActionClient
|
||||
.schema(ZGetChartsAction)
|
||||
.action(
|
||||
async ({
|
||||
ctx,
|
||||
parsedInput,
|
||||
}: {
|
||||
ctx: AuthenticatedActionClientCtx;
|
||||
parsedInput: z.infer<typeof ZGetChartsAction>;
|
||||
}) => {
|
||||
const { projectId } = await checkProjectAccess(ctx.user.id, parsedInput.environmentId, "read");
|
||||
|
||||
return prisma.chart.findMany({
|
||||
where: { projectId },
|
||||
orderBy: { createdAt: "desc" },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
type: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
query: true,
|
||||
config: true,
|
||||
widgets: {
|
||||
select: { dashboardId: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
300
apps/web/modules/ee/analysis/dashboards/actions.ts
Normal file
300
apps/web/modules/ee/analysis/dashboards/actions.ts
Normal file
@@ -0,0 +1,300 @@
|
||||
"use server";
|
||||
|
||||
import { z } from "zod";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { ZWidgetLayout } from "@formbricks/types/dashboard";
|
||||
import { authenticatedActionClient } from "@/lib/utils/action-client";
|
||||
import { checkAuthorizationUpdated } from "@/lib/utils/action-client/action-client-middleware";
|
||||
import { AuthenticatedActionClientCtx } from "@/lib/utils/action-client/types/context";
|
||||
import { getOrganizationIdFromEnvironmentId, getProjectIdFromEnvironmentId } from "@/lib/utils/helper";
|
||||
import { withAuditLogging } from "@/modules/ee/audit-logs/lib/handler";
|
||||
|
||||
const checkProjectAccess = async (
|
||||
userId: string,
|
||||
environmentId: string,
|
||||
minPermission: "read" | "readWrite" | "manage"
|
||||
) => {
|
||||
const organizationId = await getOrganizationIdFromEnvironmentId(environmentId);
|
||||
const projectId = await getProjectIdFromEnvironmentId(environmentId);
|
||||
|
||||
await checkAuthorizationUpdated({
|
||||
userId,
|
||||
organizationId,
|
||||
access: [
|
||||
{ type: "organization", roles: ["owner", "manager"] },
|
||||
{ type: "projectTeam", minPermission, projectId },
|
||||
],
|
||||
});
|
||||
|
||||
return { organizationId, projectId };
|
||||
};
|
||||
|
||||
const ZCreateDashboardAction = z.object({
|
||||
environmentId: ZId,
|
||||
name: z.string().min(1),
|
||||
description: z.string().optional(),
|
||||
});
|
||||
|
||||
export const createDashboardAction = authenticatedActionClient.schema(ZCreateDashboardAction).action(
|
||||
withAuditLogging(
|
||||
"created",
|
||||
"dashboard",
|
||||
async ({
|
||||
ctx,
|
||||
parsedInput,
|
||||
}: {
|
||||
ctx: AuthenticatedActionClientCtx;
|
||||
parsedInput: z.infer<typeof ZCreateDashboardAction>;
|
||||
}) => {
|
||||
const { organizationId, projectId } = await checkProjectAccess(
|
||||
ctx.user.id,
|
||||
parsedInput.environmentId,
|
||||
"readWrite"
|
||||
);
|
||||
|
||||
const dashboard = await prisma.dashboard.create({
|
||||
data: {
|
||||
name: parsedInput.name,
|
||||
description: parsedInput.description,
|
||||
projectId,
|
||||
createdBy: ctx.user.id,
|
||||
},
|
||||
});
|
||||
|
||||
ctx.auditLoggingCtx.organizationId = organizationId;
|
||||
ctx.auditLoggingCtx.projectId = projectId;
|
||||
ctx.auditLoggingCtx.newObject = dashboard;
|
||||
return dashboard;
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const ZUpdateDashboardAction = z.object({
|
||||
environmentId: ZId,
|
||||
dashboardId: ZId,
|
||||
name: z.string().min(1).optional(),
|
||||
description: z.string().optional().nullable(),
|
||||
});
|
||||
|
||||
export const updateDashboardAction = authenticatedActionClient.schema(ZUpdateDashboardAction).action(
|
||||
withAuditLogging(
|
||||
"updated",
|
||||
"dashboard",
|
||||
async ({
|
||||
ctx,
|
||||
parsedInput,
|
||||
}: {
|
||||
ctx: AuthenticatedActionClientCtx;
|
||||
parsedInput: z.infer<typeof ZUpdateDashboardAction>;
|
||||
}) => {
|
||||
const { organizationId, projectId } = await checkProjectAccess(
|
||||
ctx.user.id,
|
||||
parsedInput.environmentId,
|
||||
"readWrite"
|
||||
);
|
||||
|
||||
const dashboard = await prisma.dashboard.findFirst({
|
||||
where: { id: parsedInput.dashboardId, projectId },
|
||||
});
|
||||
|
||||
if (!dashboard) {
|
||||
throw new Error("Dashboard not found");
|
||||
}
|
||||
|
||||
const updatedDashboard = await prisma.dashboard.update({
|
||||
where: { id: parsedInput.dashboardId },
|
||||
data: {
|
||||
...(parsedInput.name !== undefined && { name: parsedInput.name }),
|
||||
...(parsedInput.description !== undefined && { description: parsedInput.description }),
|
||||
},
|
||||
});
|
||||
|
||||
ctx.auditLoggingCtx.organizationId = organizationId;
|
||||
ctx.auditLoggingCtx.projectId = projectId;
|
||||
ctx.auditLoggingCtx.oldObject = dashboard;
|
||||
ctx.auditLoggingCtx.newObject = updatedDashboard;
|
||||
return updatedDashboard;
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const ZDeleteDashboardAction = z.object({
|
||||
environmentId: ZId,
|
||||
dashboardId: ZId,
|
||||
});
|
||||
|
||||
export const deleteDashboardAction = authenticatedActionClient.schema(ZDeleteDashboardAction).action(
|
||||
withAuditLogging(
|
||||
"deleted",
|
||||
"dashboard",
|
||||
async ({
|
||||
ctx,
|
||||
parsedInput,
|
||||
}: {
|
||||
ctx: AuthenticatedActionClientCtx;
|
||||
parsedInput: z.infer<typeof ZDeleteDashboardAction>;
|
||||
}) => {
|
||||
const { organizationId, projectId } = await checkProjectAccess(
|
||||
ctx.user.id,
|
||||
parsedInput.environmentId,
|
||||
"readWrite"
|
||||
);
|
||||
|
||||
const dashboard = await prisma.dashboard.findFirst({
|
||||
where: { id: parsedInput.dashboardId, projectId },
|
||||
});
|
||||
|
||||
if (!dashboard) {
|
||||
throw new Error("Dashboard not found");
|
||||
}
|
||||
|
||||
await prisma.dashboard.delete({
|
||||
where: { id: parsedInput.dashboardId },
|
||||
});
|
||||
|
||||
ctx.auditLoggingCtx.organizationId = organizationId;
|
||||
ctx.auditLoggingCtx.projectId = projectId;
|
||||
ctx.auditLoggingCtx.oldObject = dashboard;
|
||||
return { success: true };
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const ZGetDashboardsAction = z.object({
|
||||
environmentId: ZId,
|
||||
});
|
||||
|
||||
export const getDashboardsAction = authenticatedActionClient
|
||||
.schema(ZGetDashboardsAction)
|
||||
.action(
|
||||
async ({
|
||||
ctx,
|
||||
parsedInput,
|
||||
}: {
|
||||
ctx: AuthenticatedActionClientCtx;
|
||||
parsedInput: z.infer<typeof ZGetDashboardsAction>;
|
||||
}) => {
|
||||
const { projectId } = await checkProjectAccess(ctx.user.id, parsedInput.environmentId, "read");
|
||||
|
||||
return prisma.dashboard.findMany({
|
||||
where: { projectId },
|
||||
orderBy: { createdAt: "desc" },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
description: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
_count: { select: { widgets: true } },
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const ZGetDashboardAction = z.object({
|
||||
environmentId: ZId,
|
||||
dashboardId: ZId,
|
||||
});
|
||||
|
||||
export const getDashboardAction = authenticatedActionClient
|
||||
.schema(ZGetDashboardAction)
|
||||
.action(
|
||||
async ({
|
||||
ctx,
|
||||
parsedInput,
|
||||
}: {
|
||||
ctx: AuthenticatedActionClientCtx;
|
||||
parsedInput: z.infer<typeof ZGetDashboardAction>;
|
||||
}) => {
|
||||
const { projectId } = await checkProjectAccess(ctx.user.id, parsedInput.environmentId, "read");
|
||||
|
||||
const dashboard = await prisma.dashboard.findFirst({
|
||||
where: { id: parsedInput.dashboardId, projectId },
|
||||
include: {
|
||||
widgets: {
|
||||
orderBy: { order: "asc" },
|
||||
include: {
|
||||
chart: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
type: true,
|
||||
query: true,
|
||||
config: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!dashboard) {
|
||||
throw new Error("Dashboard not found");
|
||||
}
|
||||
|
||||
return dashboard;
|
||||
}
|
||||
);
|
||||
|
||||
const ZAddChartToDashboardAction = z.object({
|
||||
environmentId: ZId,
|
||||
dashboardId: ZId,
|
||||
chartId: ZId,
|
||||
title: z.string().optional(),
|
||||
layout: ZWidgetLayout.optional().default({ x: 0, y: 0, w: 4, h: 3 }),
|
||||
});
|
||||
|
||||
export const addChartToDashboardAction = authenticatedActionClient
|
||||
.schema(ZAddChartToDashboardAction)
|
||||
.action(
|
||||
withAuditLogging(
|
||||
"created",
|
||||
"dashboardWidget",
|
||||
async ({
|
||||
ctx,
|
||||
parsedInput,
|
||||
}: {
|
||||
ctx: AuthenticatedActionClientCtx;
|
||||
parsedInput: z.infer<typeof ZAddChartToDashboardAction>;
|
||||
}) => {
|
||||
const { organizationId, projectId } = await checkProjectAccess(
|
||||
ctx.user.id,
|
||||
parsedInput.environmentId,
|
||||
"readWrite"
|
||||
);
|
||||
|
||||
const [chart, dashboard] = await Promise.all([
|
||||
prisma.chart.findFirst({ where: { id: parsedInput.chartId, projectId } }),
|
||||
prisma.dashboard.findFirst({ where: { id: parsedInput.dashboardId, projectId } }),
|
||||
]);
|
||||
|
||||
if (!chart) {
|
||||
throw new Error("Chart not found");
|
||||
}
|
||||
if (!dashboard) {
|
||||
throw new Error("Dashboard not found");
|
||||
}
|
||||
|
||||
const maxOrder = await prisma.dashboardWidget.aggregate({
|
||||
where: { dashboardId: parsedInput.dashboardId },
|
||||
_max: { order: true },
|
||||
});
|
||||
|
||||
const widget = await prisma.dashboardWidget.create({
|
||||
data: {
|
||||
dashboardId: parsedInput.dashboardId,
|
||||
chartId: parsedInput.chartId,
|
||||
title: parsedInput.title,
|
||||
layout: parsedInput.layout,
|
||||
order: (maxOrder._max.order ?? -1) + 1,
|
||||
},
|
||||
});
|
||||
|
||||
ctx.auditLoggingCtx.organizationId = organizationId;
|
||||
ctx.auditLoggingCtx.projectId = projectId;
|
||||
ctx.auditLoggingCtx.newObject = widget;
|
||||
return widget;
|
||||
}
|
||||
)
|
||||
);
|
||||
4
apps/web/modules/ee/analysis/types/analysis.ts
Normal file
4
apps/web/modules/ee/analysis/types/analysis.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const ZChartType = z.enum(["area", "bar", "line", "pie", "big_number"]);
|
||||
export type TChartType = z.infer<typeof ZChartType>;
|
||||
@@ -25,6 +25,9 @@ export const ZAuditTarget = z.enum([
|
||||
"integration",
|
||||
"file",
|
||||
"quota",
|
||||
"chart",
|
||||
"dashboard",
|
||||
"dashboardWidget",
|
||||
]);
|
||||
export const ZAuditAction = z.enum([
|
||||
"created",
|
||||
|
||||
@@ -93,6 +93,9 @@ async function deleteData(): Promise<void> {
|
||||
"segment",
|
||||
"webhook",
|
||||
"integration",
|
||||
"dashboardWidget",
|
||||
"chart",
|
||||
"dashboard",
|
||||
"projectTeam",
|
||||
"teamUser",
|
||||
"team",
|
||||
@@ -570,8 +573,213 @@ async function main(): Promise<void> {
|
||||
await generateResponses(SEED_IDS.SURVEY_CSAT, 50);
|
||||
await generateResponses(SEED_IDS.SURVEY_COMPLETED, 50);
|
||||
|
||||
// Charts & Dashboards
|
||||
logger.info("Seeding charts and dashboards...");
|
||||
|
||||
const chartResponsesOverTime = await prisma.chart.upsert({
|
||||
where: { id: SEED_IDS.CHART_RESPONSES_OVER_TIME },
|
||||
update: {},
|
||||
create: {
|
||||
id: SEED_IDS.CHART_RESPONSES_OVER_TIME,
|
||||
name: "Responses Over Time",
|
||||
type: "line",
|
||||
projectId: project.id,
|
||||
createdBy: SEED_IDS.USER_ADMIN,
|
||||
query: {
|
||||
measures: ["FeedbackRecords.count"],
|
||||
timeDimensions: [
|
||||
{ dimension: "FeedbackRecords.createdAt", granularity: "week" },
|
||||
],
|
||||
},
|
||||
config: {
|
||||
xAxisLabel: "Week",
|
||||
yAxisLabel: "Responses",
|
||||
showGrid: true,
|
||||
showLegend: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const chartSatisfactionDist = await prisma.chart.upsert({
|
||||
where: { id: SEED_IDS.CHART_SATISFACTION_DIST },
|
||||
update: {},
|
||||
create: {
|
||||
id: SEED_IDS.CHART_SATISFACTION_DIST,
|
||||
name: "Satisfaction Distribution",
|
||||
type: "pie",
|
||||
projectId: project.id,
|
||||
createdBy: SEED_IDS.USER_ADMIN,
|
||||
query: {
|
||||
measures: ["FeedbackRecords.count"],
|
||||
dimensions: ["FeedbackRecords.rating"],
|
||||
},
|
||||
config: {
|
||||
showLegend: true,
|
||||
legendPosition: "right",
|
||||
colors: ["#ef4444", "#f97316", "#eab308", "#22c55e", "#10b981"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const chartNpsScore = await prisma.chart.upsert({
|
||||
where: { id: SEED_IDS.CHART_NPS_SCORE },
|
||||
update: {},
|
||||
create: {
|
||||
id: SEED_IDS.CHART_NPS_SCORE,
|
||||
name: "NPS Score",
|
||||
type: "big_number",
|
||||
projectId: project.id,
|
||||
createdBy: SEED_IDS.USER_ADMIN,
|
||||
query: {
|
||||
measures: ["FeedbackRecords.npsScore"],
|
||||
},
|
||||
config: {
|
||||
prefix: "",
|
||||
suffix: "",
|
||||
numberFormat: "0",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const chartCompletionRate = await prisma.chart.upsert({
|
||||
where: { id: SEED_IDS.CHART_COMPLETION_RATE },
|
||||
update: {},
|
||||
create: {
|
||||
id: SEED_IDS.CHART_COMPLETION_RATE,
|
||||
name: "Survey Completion Rate",
|
||||
type: "bar",
|
||||
projectId: project.id,
|
||||
createdBy: SEED_IDS.USER_MANAGER,
|
||||
query: {
|
||||
measures: ["FeedbackRecords.completionRate"],
|
||||
dimensions: ["FeedbackRecords.surveyName"],
|
||||
},
|
||||
config: {
|
||||
xAxisLabel: "Survey",
|
||||
yAxisLabel: "Completion %",
|
||||
showValues: true,
|
||||
showGrid: true,
|
||||
colors: ["#6366f1"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const chartTopChannels = await prisma.chart.upsert({
|
||||
where: { id: SEED_IDS.CHART_TOP_CHANNELS },
|
||||
update: {},
|
||||
create: {
|
||||
id: SEED_IDS.CHART_TOP_CHANNELS,
|
||||
name: "Responses by Channel",
|
||||
type: "area",
|
||||
projectId: project.id,
|
||||
createdBy: SEED_IDS.USER_ADMIN,
|
||||
query: {
|
||||
measures: ["FeedbackRecords.count"],
|
||||
dimensions: ["FeedbackRecords.channel"],
|
||||
timeDimensions: [
|
||||
{ dimension: "FeedbackRecords.createdAt", granularity: "month" },
|
||||
],
|
||||
},
|
||||
config: {
|
||||
stacked: true,
|
||||
showLegend: true,
|
||||
legendPosition: "bottom",
|
||||
showGrid: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Dashboard: Overview
|
||||
const dashboardOverview = await prisma.dashboard.upsert({
|
||||
where: { id: SEED_IDS.DASHBOARD_OVERVIEW },
|
||||
update: {},
|
||||
create: {
|
||||
id: SEED_IDS.DASHBOARD_OVERVIEW,
|
||||
name: "Overview",
|
||||
description: "High-level metrics across all surveys",
|
||||
projectId: project.id,
|
||||
createdBy: SEED_IDS.USER_ADMIN,
|
||||
},
|
||||
});
|
||||
|
||||
// Dashboard: Survey Performance
|
||||
const dashboardSurveyPerf = await prisma.dashboard.upsert({
|
||||
where: { id: SEED_IDS.DASHBOARD_SURVEY_PERF },
|
||||
update: {},
|
||||
create: {
|
||||
id: SEED_IDS.DASHBOARD_SURVEY_PERF,
|
||||
name: "Survey Performance",
|
||||
description: "Detailed survey completion and response metrics",
|
||||
projectId: project.id,
|
||||
createdBy: SEED_IDS.USER_MANAGER,
|
||||
},
|
||||
});
|
||||
|
||||
// Widgets for Overview dashboard
|
||||
await prisma.dashboardWidget.createMany({
|
||||
skipDuplicates: true,
|
||||
data: [
|
||||
{
|
||||
dashboardId: dashboardOverview.id,
|
||||
chartId: chartNpsScore.id,
|
||||
title: "Current NPS",
|
||||
layout: { x: 0, y: 0, w: 2, h: 2 },
|
||||
order: 0,
|
||||
},
|
||||
{
|
||||
dashboardId: dashboardOverview.id,
|
||||
chartId: chartResponsesOverTime.id,
|
||||
title: "Weekly Responses",
|
||||
layout: { x: 2, y: 0, w: 4, h: 3 },
|
||||
order: 1,
|
||||
},
|
||||
{
|
||||
dashboardId: dashboardOverview.id,
|
||||
chartId: chartSatisfactionDist.id,
|
||||
title: "Satisfaction Breakdown",
|
||||
layout: { x: 0, y: 3, w: 3, h: 3 },
|
||||
order: 2,
|
||||
},
|
||||
{
|
||||
dashboardId: dashboardOverview.id,
|
||||
chartId: chartTopChannels.id,
|
||||
title: "Channel Trends",
|
||||
layout: { x: 3, y: 3, w: 3, h: 3 },
|
||||
order: 3,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Widgets for Survey Performance dashboard
|
||||
await prisma.dashboardWidget.createMany({
|
||||
skipDuplicates: true,
|
||||
data: [
|
||||
{
|
||||
dashboardId: dashboardSurveyPerf.id,
|
||||
chartId: chartCompletionRate.id,
|
||||
title: "Completion by Survey",
|
||||
layout: { x: 0, y: 0, w: 6, h: 3 },
|
||||
order: 0,
|
||||
},
|
||||
{
|
||||
dashboardId: dashboardSurveyPerf.id,
|
||||
chartId: chartResponsesOverTime.id,
|
||||
title: "Response Volume",
|
||||
layout: { x: 0, y: 3, w: 4, h: 3 },
|
||||
order: 1,
|
||||
},
|
||||
{
|
||||
dashboardId: dashboardSurveyPerf.id,
|
||||
chartId: chartNpsScore.id,
|
||||
title: "NPS Tracker",
|
||||
layout: { x: 4, y: 3, w: 2, h: 2 },
|
||||
order: 2,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
logger.info(`\n${"=".repeat(50)}`);
|
||||
logger.info("🚀 SEEDING COMPLETED SUCCESSFULLY");
|
||||
logger.info("SEEDING COMPLETED SUCCESSFULLY");
|
||||
logger.info("=".repeat(50));
|
||||
logger.info("\nLog in with the following credentials:");
|
||||
logger.info(`\n Admin (Owner):`);
|
||||
|
||||
@@ -10,6 +10,13 @@ export const SEED_IDS = {
|
||||
SURVEY_CSAT: "clseedsurveycsat000000",
|
||||
SURVEY_DRAFT: "clseedsurveydraft00000",
|
||||
SURVEY_COMPLETED: "clseedsurveycomplete00",
|
||||
CHART_RESPONSES_OVER_TIME: "clseedchartresptime00",
|
||||
CHART_SATISFACTION_DIST: "clseedchartsatdist000",
|
||||
CHART_NPS_SCORE: "clseedchartnpsscore00",
|
||||
CHART_COMPLETION_RATE: "clseedchartcomplete00",
|
||||
CHART_TOP_CHANNELS: "clseedcharttopchann00",
|
||||
DASHBOARD_OVERVIEW: "clseeddashovervieww00",
|
||||
DASHBOARD_SURVEY_PERF: "clseeddashsurvperf000",
|
||||
} as const;
|
||||
|
||||
export const SEED_CREDENTIALS = {
|
||||
|
||||
Reference in New Issue
Block a user