mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 18:30:32 -06:00
clean up and move to new action -> service structure
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { ResourceNotFoundError } from "@formbricks/errors";
|
||||
import { captureTelemetry } from "@formbricks/lib/telemetry";
|
||||
import { deleteSurvey, getSurvey } from "@formbricks/lib/services/survey";
|
||||
import { QuestionType } from "@formbricks/types/questions";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import { Team } from "@prisma/client";
|
||||
@@ -1376,46 +1376,13 @@ export async function addDemoData(teamId: string): Promise<void> {
|
||||
);
|
||||
}
|
||||
|
||||
export async function deleteSurveyAction(surveyId: string) {
|
||||
const deletedSurvey = await prisma.survey.delete({
|
||||
where: {
|
||||
id: surveyId,
|
||||
},
|
||||
});
|
||||
return deletedSurvey;
|
||||
}
|
||||
|
||||
export async function createSurveyAction(environmentId: string, surveyBody: any) {
|
||||
const survey = await prisma.survey.create({
|
||||
data: {
|
||||
...surveyBody,
|
||||
environment: {
|
||||
connect: {
|
||||
id: environmentId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
captureTelemetry("survey created");
|
||||
|
||||
return survey;
|
||||
}
|
||||
|
||||
export async function duplicateSurveyAction(environmentId: string, surveyId: string) {
|
||||
const existingSurvey = await prisma.survey.findFirst({
|
||||
where: {
|
||||
id: surveyId,
|
||||
environmentId,
|
||||
},
|
||||
include: {
|
||||
triggers: true,
|
||||
attributeFilters: true,
|
||||
},
|
||||
});
|
||||
const existingSurvey = await getSurvey(surveyId);
|
||||
|
||||
if (!existingSurvey) {
|
||||
throw new ResourceNotFoundError("Survey", surveyId);
|
||||
}
|
||||
|
||||
// create new survey with the data of the existing survey
|
||||
const newSurvey = await prisma.survey.create({
|
||||
data: {
|
||||
@@ -1428,7 +1395,7 @@ export async function duplicateSurveyAction(environmentId: string, surveyId: str
|
||||
thankYouCard: JSON.parse(JSON.stringify(existingSurvey.thankYouCard)),
|
||||
triggers: {
|
||||
create: existingSurvey.triggers.map((trigger) => ({
|
||||
eventClassId: trigger.eventClassId,
|
||||
eventClassId: trigger.id,
|
||||
})),
|
||||
},
|
||||
attributeFilters: {
|
||||
@@ -1575,3 +1542,7 @@ export async function copyToOtherEnvironmentAction(
|
||||
});
|
||||
return newSurvey;
|
||||
}
|
||||
|
||||
export const deleteSurveyAction = async (surveyId: string) => {
|
||||
await deleteSurvey(surveyId);
|
||||
};
|
||||
|
||||
@@ -14,21 +14,21 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/shared/DropdownMenu";
|
||||
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
||||
import type { TEnvironment } from "@formbricks/types/v1/environment";
|
||||
import type { TSurveyWithAnalytics } from "@formbricks/types/v1/surveys";
|
||||
import {
|
||||
ArrowUpOnSquareStackIcon,
|
||||
DocumentDuplicateIcon,
|
||||
EllipsisHorizontalIcon,
|
||||
EyeIcon,
|
||||
LinkIcon,
|
||||
PencilSquareIcon,
|
||||
EyeIcon,
|
||||
TrashIcon,
|
||||
ArrowUpOnSquareStackIcon,
|
||||
} from "@heroicons/react/24/solid";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import type { TEnvironment } from "@formbricks/types/v1/environment";
|
||||
|
||||
interface SurveyDropDownMenuProps {
|
||||
environmentId: string;
|
||||
@@ -151,7 +151,7 @@ export default function SurveyDropDownMenu({
|
||||
<DropdownMenuItem>
|
||||
<Link
|
||||
className="flex w-full items-center"
|
||||
href={`${window.location.protocol}//${window.location.host}/s/${survey.id}?preview=true`}
|
||||
href={`/s/${survey.id}?preview=true`}
|
||||
target="_blank">
|
||||
<EyeIcon className="mr-2 h-4 w-4" />
|
||||
Preview Survey
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
import { Template } from "@/../../packages/types/templates";
|
||||
import { createSurveyAction } from "@/app/(app)/environments/[environmentId]/actions";
|
||||
import { createSurveyAction } from "./actions";
|
||||
import TemplateList from "@/app/(app)/environments/[environmentId]/surveys/templates/TemplateList";
|
||||
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
||||
import type { TEnvironment } from "@formbricks/types/v1/environment";
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { IS_FORMBRICKS_CLOUD, RESPONSES_LIMIT_FREE } from "@formbricks/lib/constants";
|
||||
import { getSurveyResponses } from "@formbricks/lib/services/response";
|
||||
import { getSurvey } from "@formbricks/lib/services/survey";
|
||||
import { getSurveyWithAnalytics } from "@formbricks/lib/services/survey";
|
||||
import { getTeamByEnvironmentId } from "@formbricks/lib/services/team";
|
||||
|
||||
export const getAnalysisData = async (surveyId: string, environmentId: string) => {
|
||||
const [survey, team, allResponses] = await Promise.all([
|
||||
getSurvey(surveyId),
|
||||
getSurveyWithAnalytics(surveyId),
|
||||
getTeamByEnvironmentId(environmentId),
|
||||
getSurveyResponses(surveyId),
|
||||
]);
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
"use server";
|
||||
|
||||
import { createSurvey } from "@formbricks/lib/services/survey";
|
||||
|
||||
export async function createSurveyAction(environmentId: string, surveyBody: any) {
|
||||
return await createSurvey(environmentId, surveyBody);
|
||||
}
|
||||
@@ -1,21 +1,27 @@
|
||||
"use client";
|
||||
|
||||
import { createSurvey } from "@/lib/surveys/surveys";
|
||||
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
||||
import { useProfile } from "@/lib/profile";
|
||||
import { replacePresetPlaceholders } from "@/lib/templates";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import type { Template } from "@formbricks/types/templates";
|
||||
import { Button, ErrorComponent } from "@formbricks/ui";
|
||||
import { PlusCircleIcon } from "@heroicons/react/24/outline";
|
||||
import { SparklesIcon } from "@heroicons/react/24/solid";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import { customSurvey, templates } from "./templates";
|
||||
import { SplitIcon } from "lucide-react";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@formbricks/ui";
|
||||
import type { TEnvironment } from "@formbricks/types/v1/environment";
|
||||
import type { TProduct } from "@formbricks/types/v1/product";
|
||||
import { useProfile } from "@/lib/profile";
|
||||
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
||||
import {
|
||||
Button,
|
||||
ErrorComponent,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@formbricks/ui";
|
||||
import { PlusCircleIcon } from "@heroicons/react/24/outline";
|
||||
import { SparklesIcon } from "@heroicons/react/24/solid";
|
||||
import { SplitIcon } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import { createSurveyAction } from "./actions";
|
||||
import { customSurvey, templates } from "./templates";
|
||||
|
||||
type TemplateList = {
|
||||
environmentId: string;
|
||||
@@ -59,7 +65,7 @@ export default function TemplateList({ environmentId, onTemplateClick, product,
|
||||
...activeTemplate.preset,
|
||||
type: environment?.widgetSetupCompleted ? "web" : "link",
|
||||
};
|
||||
const survey = await createSurvey(environmentId, augmentedTemplate);
|
||||
const survey = await createSurveyAction(environmentId, augmentedTemplate);
|
||||
router.push(`/environments/${environmentId}/surveys/${survey.id}/edit`);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
"use server";
|
||||
|
||||
import { createSurvey } from "@formbricks/lib/services/survey";
|
||||
|
||||
export async function createSurveyAction(environmentId: string, surveyBody: any) {
|
||||
return await createSurvey(environmentId, surveyBody);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { select } from "@formbricks/lib/services/survey";
|
||||
import { selectSurvey } from "@formbricks/lib/services/survey";
|
||||
import { TPerson } from "@formbricks/types/v1/people";
|
||||
import { TSurvey } from "@formbricks/types/v1/surveys";
|
||||
|
||||
@@ -48,7 +48,7 @@ export const getSurveys = async (environmentId: string, person: TPerson): Promis
|
||||
],
|
||||
},
|
||||
select: {
|
||||
...select,
|
||||
...selectSurvey,
|
||||
attributeFilters: {
|
||||
select: {
|
||||
id: true,
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { z } from "zod";
|
||||
import { ValidationError } from "@formbricks/errors";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/errors";
|
||||
import {
|
||||
TSurvey,
|
||||
TSurveyWithAnalytics,
|
||||
ZSurvey,
|
||||
ZSurveyWithAnalytics
|
||||
} from "@formbricks/types/v1/surveys";
|
||||
import { DatabaseError, ResourceNotFoundError, ValidationError } from "@formbricks/errors";
|
||||
import { TSurvey, TSurveyWithAnalytics, ZSurvey, ZSurveyWithAnalytics } from "@formbricks/types/v1/surveys";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import "server-only";
|
||||
import { cache } from "react";
|
||||
import "server-only";
|
||||
import { z } from "zod";
|
||||
import { captureTelemetry } from "../telemetry";
|
||||
|
||||
export const select = {
|
||||
export const selectSurveyWithAnalytics = {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
@@ -53,31 +48,126 @@ export const select = {
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
displays:{
|
||||
select:{
|
||||
status:true,
|
||||
id:true
|
||||
}
|
||||
},
|
||||
_count:{
|
||||
select:{
|
||||
responses:true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const preloadSurvey = (surveyId: string) => {
|
||||
void getSurvey(surveyId);
|
||||
displays: {
|
||||
select: {
|
||||
status: true,
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
responses: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const getSurvey = cache(async (surveyId: string): Promise<TSurveyWithAnalytics | null> => {
|
||||
export const selectSurvey = {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
name: true,
|
||||
type: true,
|
||||
environmentId: true,
|
||||
status: true,
|
||||
questions: true,
|
||||
thankYouCard: true,
|
||||
displayOption: true,
|
||||
recontactDays: true,
|
||||
autoClose: true,
|
||||
closeOnDate: true,
|
||||
delay: true,
|
||||
autoComplete: true,
|
||||
redirectUrl: true,
|
||||
triggers: {
|
||||
select: {
|
||||
eventClass: {
|
||||
select: {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
environmentId: true,
|
||||
name: true,
|
||||
description: true,
|
||||
type: true,
|
||||
noCodeConfig: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
attributeFilters: {
|
||||
select: {
|
||||
id: true,
|
||||
attributeClassId: true,
|
||||
condition: true,
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const preloadSurveyWithAnalytics = (surveyId: string) => {
|
||||
void getSurveyWithAnalytics(surveyId);
|
||||
};
|
||||
|
||||
export const getSurveyWithAnalytics = cache(
|
||||
async (surveyId: string): Promise<TSurveyWithAnalytics | null> => {
|
||||
let surveyPrisma;
|
||||
try {
|
||||
surveyPrisma = await prisma.survey.findUnique({
|
||||
where: {
|
||||
id: surveyId,
|
||||
},
|
||||
select: selectSurveyWithAnalytics,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError("Database operation failed");
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!surveyPrisma) {
|
||||
throw new ResourceNotFoundError("Survey", surveyId);
|
||||
}
|
||||
|
||||
let { _count, displays, ...surveyPrismaFields } = surveyPrisma;
|
||||
|
||||
const numDisplays = displays.length;
|
||||
const numDisplaysResponded = displays.filter((item) => item.status === "responded").length;
|
||||
const numResponses = _count.responses;
|
||||
// responseRate, rounded to 2 decimal places
|
||||
const responseRate = numDisplays ? Math.round((numDisplaysResponded / numDisplays) * 100) / 100 : 0;
|
||||
|
||||
const transformedSurvey = {
|
||||
...surveyPrismaFields,
|
||||
triggers: surveyPrismaFields.triggers.map((trigger) => trigger.eventClass),
|
||||
analytics: {
|
||||
numDisplays,
|
||||
responseRate,
|
||||
numResponses,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const survey = ZSurveyWithAnalytics.parse(transformedSurvey);
|
||||
return survey;
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
console.error(JSON.stringify(error.errors, null, 2)); // log the detailed error information
|
||||
}
|
||||
throw new ValidationError("Data validation of survey failed");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const getSurvey = cache(async (surveyId: string): Promise<TSurvey | null> => {
|
||||
let surveyPrisma;
|
||||
try {
|
||||
surveyPrisma = await prisma.survey.findUnique({
|
||||
where: {
|
||||
id: surveyId,
|
||||
},
|
||||
select
|
||||
select: selectSurvey,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
@@ -88,29 +178,16 @@ export const getSurvey = cache(async (surveyId: string): Promise<TSurveyWithAnal
|
||||
}
|
||||
|
||||
if (!surveyPrisma) {
|
||||
throw new ResourceNotFoundError("Survey", surveyId);
|
||||
return null;
|
||||
}
|
||||
|
||||
let {_count,displays, ...surveyPrismaFields}=surveyPrisma;
|
||||
|
||||
const numDisplays=displays.length
|
||||
const numDisplaysResponded=displays.filter((item)=>item.status==='responded').length
|
||||
const numResponses=_count.responses
|
||||
// responseRate, rounded to 2 decimal places
|
||||
const responseRate = numDisplays ? Math.round((numDisplaysResponded / numDisplays) * 100) / 100 : 0;
|
||||
|
||||
const transformedSurvey = {
|
||||
...surveyPrismaFields,
|
||||
triggers: surveyPrismaFields.triggers.map((trigger) => trigger.eventClass),
|
||||
analytics: {
|
||||
numDisplays,
|
||||
responseRate,
|
||||
numResponses
|
||||
},
|
||||
...surveyPrisma,
|
||||
triggers: surveyPrisma.triggers.map((trigger) => trigger.eventClass),
|
||||
};
|
||||
|
||||
try {
|
||||
const survey = ZSurveyWithAnalytics.parse(transformedSurvey);
|
||||
const survey = ZSurvey.parse(transformedSurvey);
|
||||
return survey;
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
@@ -127,7 +204,7 @@ export const getSurveys = cache(async (environmentId: string): Promise<TSurvey[]
|
||||
where: {
|
||||
environmentId,
|
||||
},
|
||||
select,
|
||||
select: selectSurvey,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
@@ -138,16 +215,16 @@ export const getSurveys = cache(async (environmentId: string): Promise<TSurvey[]
|
||||
}
|
||||
|
||||
const surveys: TSurvey[] = [];
|
||||
for (const {_count,displays, ...surveyPrisma} of surveysPrisma) {
|
||||
const transformedSurvey = {
|
||||
...surveyPrisma,
|
||||
triggers: surveyPrisma.triggers.map((trigger) => trigger.eventClass),
|
||||
};
|
||||
const survey = ZSurvey.parse(transformedSurvey);
|
||||
surveys.push(survey);
|
||||
}
|
||||
|
||||
try {
|
||||
for (const surveyPrisma of surveysPrisma) {
|
||||
const transformedSurvey = {
|
||||
...surveyPrisma,
|
||||
triggers: surveyPrisma.triggers.map((trigger) => trigger.eventClass),
|
||||
};
|
||||
const survey = ZSurvey.parse(transformedSurvey);
|
||||
surveys.push(survey);
|
||||
}
|
||||
return surveys;
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
@@ -157,48 +234,75 @@ export const getSurveys = cache(async (environmentId: string): Promise<TSurvey[]
|
||||
}
|
||||
});
|
||||
|
||||
export const getSurveysWithAnalytics = cache(async (environmentId: string): Promise<TSurveyWithAnalytics[]> => {
|
||||
let surveysPrisma;
|
||||
try {
|
||||
surveysPrisma = await prisma.survey.findMany({
|
||||
where: {
|
||||
environmentId,
|
||||
},
|
||||
select
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError("Database operation failed");
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
const surveys: TSurveyWithAnalytics[] = [];
|
||||
for (const {_count,displays, ...surveyPrisma} of surveysPrisma) {
|
||||
const numDisplays=displays.length
|
||||
const numDisplaysResponded=displays.filter((item)=>item.status==='responded').length
|
||||
const responseRate = numDisplays ? Math.round((numDisplaysResponded / numDisplays) * 100) / 100 : 0;
|
||||
|
||||
const transformedSurvey = {
|
||||
...surveyPrisma,
|
||||
triggers: surveyPrisma.triggers.map((trigger) => trigger.eventClass),
|
||||
analytics:{
|
||||
numDisplays,
|
||||
responseRate,
|
||||
numResponses:_count.responses
|
||||
export const getSurveysWithAnalytics = cache(
|
||||
async (environmentId: string): Promise<TSurveyWithAnalytics[]> => {
|
||||
let surveysPrisma;
|
||||
try {
|
||||
surveysPrisma = await prisma.survey.findMany({
|
||||
where: {
|
||||
environmentId,
|
||||
},
|
||||
select: selectSurveyWithAnalytics,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError("Database operation failed");
|
||||
}
|
||||
};
|
||||
const survey = ZSurveyWithAnalytics.parse(transformedSurvey);
|
||||
surveys.push(survey);
|
||||
}
|
||||
|
||||
try {
|
||||
return surveys;
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
console.error(JSON.stringify(error.errors, null, 2)); // log the detailed error information
|
||||
throw error;
|
||||
}
|
||||
|
||||
try {
|
||||
const surveys: TSurveyWithAnalytics[] = [];
|
||||
for (const { _count, displays, ...surveyPrisma } of surveysPrisma) {
|
||||
const numDisplays = displays.length;
|
||||
const numDisplaysResponded = displays.filter((item) => item.status === "responded").length;
|
||||
const responseRate = numDisplays ? Math.round((numDisplaysResponded / numDisplays) * 100) / 100 : 0;
|
||||
|
||||
const transformedSurvey = {
|
||||
...surveyPrisma,
|
||||
triggers: surveyPrisma.triggers.map((trigger) => trigger.eventClass),
|
||||
analytics: {
|
||||
numDisplays,
|
||||
responseRate,
|
||||
numResponses: _count.responses,
|
||||
},
|
||||
};
|
||||
const survey = ZSurveyWithAnalytics.parse(transformedSurvey);
|
||||
surveys.push(survey);
|
||||
}
|
||||
return surveys;
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
console.error(JSON.stringify(error.errors, null, 2)); // log the detailed error information
|
||||
}
|
||||
throw new ValidationError("Data validation of survey failed");
|
||||
}
|
||||
throw new ValidationError("Data validation of survey failed");
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
export async function deleteSurvey(surveyId: string) {
|
||||
const deletedSurvey = await prisma.survey.delete({
|
||||
where: {
|
||||
id: surveyId,
|
||||
},
|
||||
select: selectSurvey,
|
||||
});
|
||||
return deletedSurvey;
|
||||
}
|
||||
|
||||
export async function createSurvey(environmentId: string, surveyBody: any) {
|
||||
const survey = await prisma.survey.create({
|
||||
data: {
|
||||
...surveyBody,
|
||||
environment: {
|
||||
connect: {
|
||||
id: environmentId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
captureTelemetry("survey created");
|
||||
|
||||
return survey;
|
||||
}
|
||||
|
||||
@@ -238,6 +238,7 @@ export const ZSurvey = z.object({
|
||||
delay: z.number(),
|
||||
autoComplete: z.union([z.number(), z.null()]),
|
||||
closeOnDate: z.date().nullable(),
|
||||
surveyClosedMessage: ZSurveyClosedMessage,
|
||||
});
|
||||
|
||||
export type TSurvey = z.infer<typeof ZSurvey>;
|
||||
@@ -248,7 +249,6 @@ export const ZSurveyWithAnalytics = ZSurvey.extend({
|
||||
responseRate: z.number(),
|
||||
numResponses: z.number(),
|
||||
}),
|
||||
surveyClosedMessage: ZSurveyClosedMessage,
|
||||
});
|
||||
|
||||
export type TSurveyWithAnalytics = z.infer<typeof ZSurveyWithAnalytics>;
|
||||
|
||||
Reference in New Issue
Block a user