mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-04 16:30:33 -06:00
feat: group events by environmentId in posthog (#2036)
Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
committed by
GitHub
parent
07f28d0971
commit
14b162aabc
@@ -8,14 +8,21 @@ import { env } from "@formbricks/lib/env.mjs";
|
||||
|
||||
const posthogEnabled = env.NEXT_PUBLIC_POSTHOG_API_KEY && env.NEXT_PUBLIC_POSTHOG_API_HOST;
|
||||
|
||||
export default function PosthogIdentify({ session }: { session: Session }) {
|
||||
export default function PosthogIdentify({
|
||||
session,
|
||||
environmentId,
|
||||
}: {
|
||||
session: Session;
|
||||
environmentId: string;
|
||||
}) {
|
||||
const posthog = usePostHog();
|
||||
|
||||
useEffect(() => {
|
||||
if (posthogEnabled && session.user && posthog) {
|
||||
posthog.identify(session.user.id, { name: session.user.name, email: session.user.email });
|
||||
posthog.group("environment", environmentId, { name: environmentId });
|
||||
}
|
||||
}, [session, posthog]);
|
||||
}, [session, environmentId, posthog]);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import { AuthorizationError } from "@formbricks/types/errors";
|
||||
import ToasterClient from "@formbricks/ui/ToasterClient";
|
||||
|
||||
import FormbricksClient from "../../components/FormbricksClient";
|
||||
import PosthogIdentify from "./components/PosthogIdentify";
|
||||
|
||||
export default async function EnvironmentLayout({ children, params }) {
|
||||
const session = await getServerSession(authOptions);
|
||||
@@ -24,6 +25,7 @@ export default async function EnvironmentLayout({ children, params }) {
|
||||
return (
|
||||
<>
|
||||
<ResponseFilterProvider>
|
||||
<PosthogIdentify session={session} environmentId={params.environmentId} />
|
||||
<FormbricksClient session={session} />
|
||||
<ToasterClient />
|
||||
<EnvironmentsNavbar
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
import FormbricksClient from "@/app/(app)/components/FormbricksClient";
|
||||
import { getServerSession } from "next-auth";
|
||||
// import { redirect } from "next/navigation";
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { NoMobileOverlay } from "@formbricks/ui/NoMobileOverlay";
|
||||
import { PHProvider, PostHogPageview } from "@formbricks/ui/PostHogClient";
|
||||
|
||||
import PosthogIdentify from "./components/PosthogIdentify";
|
||||
|
||||
export default async function AppLayout({ children }) {
|
||||
const session = await getServerSession(authOptions);
|
||||
// if (!session) {
|
||||
// return redirect(`/auth/login`);
|
||||
// }
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -23,13 +17,7 @@ export default async function AppLayout({ children }) {
|
||||
</Suspense>
|
||||
<PHProvider>
|
||||
<>
|
||||
{session ? (
|
||||
<>
|
||||
<PosthogIdentify session={session} />
|
||||
<FormbricksClient session={session} />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{session ? <FormbricksClient session={session} /> : null}
|
||||
{children}
|
||||
</>
|
||||
</PHProvider>
|
||||
|
||||
@@ -3,9 +3,8 @@ import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { createDisplayLegacy } from "@formbricks/lib/display/service";
|
||||
import { capturePosthogEvent } from "@formbricks/lib/posthogServer";
|
||||
import { capturePosthogEnvironmentEvent } from "@formbricks/lib/posthogServer";
|
||||
import { getSurvey } from "@formbricks/lib/survey/service";
|
||||
import { getTeamDetails } from "@formbricks/lib/teamDetail/service";
|
||||
import { TDisplay, ZDisplayLegacyCreateInput } from "@formbricks/types/displays";
|
||||
import { InvalidInputError } from "@formbricks/types/errors";
|
||||
|
||||
@@ -45,9 +44,6 @@ export async function POST(request: Request): Promise<NextResponse> {
|
||||
}
|
||||
}
|
||||
|
||||
// find teamId & teamOwnerId from environmentId
|
||||
const teamDetails = await getTeamDetails(survey.environmentId);
|
||||
|
||||
// create display
|
||||
let display: TDisplay;
|
||||
try {
|
||||
@@ -65,13 +61,9 @@ export async function POST(request: Request): Promise<NextResponse> {
|
||||
}
|
||||
}
|
||||
|
||||
if (teamDetails?.teamOwnerId) {
|
||||
await capturePosthogEvent(teamDetails.teamOwnerId, "display created", teamDetails.teamId, {
|
||||
surveyId,
|
||||
});
|
||||
} else {
|
||||
console.warn("Posthog capture not possible. No team owner found");
|
||||
}
|
||||
await capturePosthogEnvironmentEvent(survey.environmentId, "display created", {
|
||||
surveyId,
|
||||
});
|
||||
|
||||
return responses.successResponse({ id: display.id }, true);
|
||||
}
|
||||
|
||||
@@ -5,10 +5,9 @@ import { headers } from "next/headers";
|
||||
import { NextResponse } from "next/server";
|
||||
import { UAParser } from "ua-parser-js";
|
||||
|
||||
import { capturePosthogEvent } from "@formbricks/lib/posthogServer";
|
||||
import { capturePosthogEnvironmentEvent } from "@formbricks/lib/posthogServer";
|
||||
import { createResponseLegacy } from "@formbricks/lib/response/service";
|
||||
import { getSurvey } from "@formbricks/lib/survey/service";
|
||||
import { getTeamDetails } from "@formbricks/lib/teamDetail/service";
|
||||
import { InvalidInputError } from "@formbricks/types/errors";
|
||||
import { TResponse, ZResponseLegacyInput } from "@formbricks/types/responses";
|
||||
import { TSurvey } from "@formbricks/types/surveys";
|
||||
@@ -54,8 +53,6 @@ export async function POST(request: Request): Promise<NextResponse> {
|
||||
}
|
||||
}
|
||||
|
||||
const teamDetails = await getTeamDetails(survey.environmentId);
|
||||
|
||||
let response: TResponse;
|
||||
try {
|
||||
const meta = {
|
||||
@@ -104,14 +101,10 @@ export async function POST(request: Request): Promise<NextResponse> {
|
||||
});
|
||||
}
|
||||
|
||||
if (teamDetails?.teamOwnerId) {
|
||||
await capturePosthogEvent(teamDetails.teamOwnerId, "response created", teamDetails.teamId, {
|
||||
surveyId: response.surveyId,
|
||||
surveyType: survey.type,
|
||||
});
|
||||
} else {
|
||||
console.warn("Posthog capture not possible. No team owner found");
|
||||
}
|
||||
await capturePosthogEnvironmentEvent(survey.environmentId, "response created", {
|
||||
surveyId: response.surveyId,
|
||||
surveyType: survey.type,
|
||||
});
|
||||
|
||||
return responses.successResponse({ id: response.id }, true);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@ import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { createDisplay } from "@formbricks/lib/display/service";
|
||||
import { capturePosthogEvent } from "@formbricks/lib/posthogServer";
|
||||
import { getTeamDetails } from "@formbricks/lib/teamDetail/service";
|
||||
import { capturePosthogEnvironmentEvent } from "@formbricks/lib/posthogServer";
|
||||
import { ZDisplayCreateInput } from "@formbricks/types/displays";
|
||||
import { InvalidInputError } from "@formbricks/types/errors";
|
||||
|
||||
@@ -33,8 +32,6 @@ export async function POST(request: Request, context: Context): Promise<NextResp
|
||||
);
|
||||
}
|
||||
|
||||
// find teamId & teamOwnerId from environmentId
|
||||
const teamDetails = await getTeamDetails(inputValidation.data.environmentId);
|
||||
let response = {};
|
||||
|
||||
// create display
|
||||
@@ -50,11 +47,7 @@ export async function POST(request: Request, context: Context): Promise<NextResp
|
||||
}
|
||||
}
|
||||
|
||||
if (teamDetails?.teamOwnerId) {
|
||||
await capturePosthogEvent(teamDetails.teamOwnerId, "display created", teamDetails.teamId);
|
||||
} else {
|
||||
console.warn("Posthog capture not possible. No team owner found");
|
||||
}
|
||||
await capturePosthogEnvironmentEvent(inputValidation.data.environmentId, "display created");
|
||||
|
||||
return responses.successResponse(response, true);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { unstable_cache } from "next/cache";
|
||||
|
||||
import { capturePosthogEvent } from "@formbricks/lib/posthogServer";
|
||||
import { getTeamDetails } from "@formbricks/lib/teamDetail/service";
|
||||
import { capturePosthogEnvironmentEvent } from "@formbricks/lib/posthogServer";
|
||||
|
||||
export const sendFreeLimitReachedEventToPosthogBiWeekly = async (
|
||||
environmentId: string,
|
||||
@@ -9,12 +8,9 @@ export const sendFreeLimitReachedEventToPosthogBiWeekly = async (
|
||||
): Promise<string> =>
|
||||
unstable_cache(
|
||||
async () => {
|
||||
const teamDetails = await getTeamDetails(environmentId);
|
||||
if (teamDetails?.teamOwnerId) {
|
||||
await capturePosthogEvent(teamDetails.teamOwnerId, "free limit reached", teamDetails.teamId, {
|
||||
plan,
|
||||
});
|
||||
}
|
||||
await capturePosthogEnvironmentEvent(environmentId, "free limit reached", {
|
||||
plan,
|
||||
});
|
||||
return "success";
|
||||
},
|
||||
[`posthog-${plan}-limitReached-${environmentId}`],
|
||||
|
||||
@@ -6,10 +6,9 @@ import { NextResponse } from "next/server";
|
||||
import { UAParser } from "ua-parser-js";
|
||||
|
||||
import { getPerson } from "@formbricks/lib/person/service";
|
||||
import { capturePosthogEvent } from "@formbricks/lib/posthogServer";
|
||||
import { capturePosthogEnvironmentEvent } from "@formbricks/lib/posthogServer";
|
||||
import { createResponse } from "@formbricks/lib/response/service";
|
||||
import { getSurvey } from "@formbricks/lib/survey/service";
|
||||
import { getTeamDetails } from "@formbricks/lib/teamDetail/service";
|
||||
import { ZId } from "@formbricks/types/environment";
|
||||
import { InvalidInputError } from "@formbricks/types/errors";
|
||||
import { TResponse, ZResponseInput } from "@formbricks/types/responses";
|
||||
@@ -77,8 +76,6 @@ export async function POST(request: Request, context: Context): Promise<NextResp
|
||||
);
|
||||
}
|
||||
|
||||
const teamDetails = await getTeamDetails(survey.environmentId);
|
||||
|
||||
let response: TResponse;
|
||||
try {
|
||||
const meta = {
|
||||
@@ -121,14 +118,10 @@ export async function POST(request: Request, context: Context): Promise<NextResp
|
||||
});
|
||||
}
|
||||
|
||||
if (teamDetails?.teamOwnerId) {
|
||||
await capturePosthogEvent(teamDetails.teamOwnerId, "response created", teamDetails.teamId, {
|
||||
surveyId: response.surveyId,
|
||||
surveyType: survey.type,
|
||||
});
|
||||
} else {
|
||||
console.warn("Posthog capture not possible. No team owner found");
|
||||
}
|
||||
await capturePosthogEnvironmentEvent(survey.environmentId, "response created", {
|
||||
surveyId: response.surveyId,
|
||||
surveyType: survey.type,
|
||||
});
|
||||
|
||||
return responses.successResponse({ id: response.id }, true);
|
||||
}
|
||||
|
||||
1
apps/web/next-env.d.ts
vendored
1
apps/web/next-env.d.ts
vendored
@@ -1,6 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference types="next/navigation-types/compat/navigation" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
const environmentId = req.query.environmentId?.toString();
|
||||
|
||||
if (!environmentId) {
|
||||
return res.status(400).json({ message: "Missing environmentId" });
|
||||
}
|
||||
|
||||
const displayId = req.query.displayId?.toString();
|
||||
|
||||
if (!displayId) {
|
||||
return res.status(400).json({ message: "Missing displayId" });
|
||||
}
|
||||
|
||||
// CORS
|
||||
if (req.method === "OPTIONS") {
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
// POST
|
||||
else if (req.method === "POST") {
|
||||
// create new response
|
||||
await prisma.display.update({
|
||||
where: {
|
||||
id: displayId,
|
||||
},
|
||||
data: {
|
||||
status: "responded",
|
||||
},
|
||||
});
|
||||
|
||||
return res.status(201).end();
|
||||
}
|
||||
|
||||
// Unknown HTTP Method
|
||||
else {
|
||||
throw new Error(`The HTTP ${req.method} method is not supported by this route.`);
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { capturePosthogEvent } from "@formbricks/lib/posthogServer";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
const environmentId = req.query.environmentId?.toString();
|
||||
|
||||
if (!environmentId) {
|
||||
return res.status(400).json({ message: "Missing environmentId" });
|
||||
}
|
||||
|
||||
// CORS
|
||||
if (req.method === "OPTIONS") {
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
// POST
|
||||
else if (req.method === "POST") {
|
||||
const { surveyId, personId } = req.body;
|
||||
|
||||
if (!surveyId) {
|
||||
return res.status(400).json({ message: "Missing surveyId" });
|
||||
}
|
||||
|
||||
// get teamId from environment
|
||||
const environment = await prisma.environment.findUnique({
|
||||
where: {
|
||||
id: environmentId,
|
||||
},
|
||||
select: {
|
||||
product: {
|
||||
select: {
|
||||
team: {
|
||||
select: {
|
||||
id: true,
|
||||
memberships: {
|
||||
select: {
|
||||
userId: true,
|
||||
role: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!environment) {
|
||||
return res.status(404).json({ message: "Environment not found" });
|
||||
}
|
||||
|
||||
const teamId = environment.product.team.id;
|
||||
// find team owner
|
||||
const teamOwnerId = environment.product.team.memberships.find((m) => m.role === "owner")?.userId;
|
||||
|
||||
const createBody: any = {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
data: {
|
||||
survey: {
|
||||
connect: {
|
||||
id: surveyId,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (personId) {
|
||||
createBody.data.person = {
|
||||
connect: {
|
||||
id: personId,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// create new display
|
||||
const displayData = await prisma.display.create(createBody);
|
||||
|
||||
if (teamOwnerId) {
|
||||
await capturePosthogEvent(teamOwnerId, "display created", teamId, {
|
||||
surveyId,
|
||||
});
|
||||
} else {
|
||||
console.warn("Posthog capture not possible. No team owner found");
|
||||
}
|
||||
|
||||
return res.json(displayData);
|
||||
}
|
||||
|
||||
// Unknown HTTP Method
|
||||
else {
|
||||
throw new Error(`The HTTP ${req.method} method is not supported by this route.`);
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { TActionClassType } from "@formbricks/types/actionClasses";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
const environmentId = req.query.environmentId?.toString();
|
||||
|
||||
if (!environmentId) {
|
||||
return res.status(400).json({ message: "Missing environmentId" });
|
||||
}
|
||||
|
||||
// CORS
|
||||
if (req.method === "OPTIONS") {
|
||||
res.status(200).end();
|
||||
}
|
||||
// POST
|
||||
else if (req.method === "POST") {
|
||||
const { personId, eventName, properties } = req.body;
|
||||
|
||||
if (!personId) {
|
||||
return res.status(400).json({ message: "Missing personId" });
|
||||
}
|
||||
if (!eventName) {
|
||||
return res.status(400).json({ message: "Missing eventName" });
|
||||
}
|
||||
|
||||
let eventType: TActionClassType = "code";
|
||||
if (eventName === "Exit Intent (Desktop)" || eventName === "50% Scroll") {
|
||||
eventType = "automatic";
|
||||
}
|
||||
|
||||
const eventData = await prisma.action.create({
|
||||
data: {
|
||||
properties,
|
||||
person: {
|
||||
connect: {
|
||||
id: personId,
|
||||
},
|
||||
},
|
||||
actionClass: {
|
||||
connectOrCreate: {
|
||||
where: {
|
||||
name_environmentId: {
|
||||
name: eventName,
|
||||
environmentId,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
name: eventName,
|
||||
type: eventType,
|
||||
environment: {
|
||||
connect: {
|
||||
id: environmentId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
return res.json(eventData);
|
||||
}
|
||||
|
||||
// Unknown HTTP Method
|
||||
else {
|
||||
throw new Error(`The HTTP ${req.method} method is not supported by this route.`);
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
import { getSettings } from "@/app/lib/api/clientSettings";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { personCache } from "@formbricks/lib/person/cache";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
const environmentId = req.query.environmentId?.toString();
|
||||
|
||||
if (!environmentId) {
|
||||
return res.status(400).json({ message: "Missing environmentId" });
|
||||
}
|
||||
|
||||
const personId = req.query.personId?.toString();
|
||||
|
||||
if (!personId) {
|
||||
return res.status(400).json({ message: "Missing personId" });
|
||||
}
|
||||
|
||||
// CORS
|
||||
if (req.method === "OPTIONS") {
|
||||
res.status(200).end();
|
||||
}
|
||||
// POST
|
||||
else if (req.method === "POST") {
|
||||
const { key, value } = req.body;
|
||||
if (!key || !value) {
|
||||
return res.status(400).json({ message: "Missing key or value" });
|
||||
}
|
||||
const currentPerson = await prisma.person.findUnique({
|
||||
where: {
|
||||
id: personId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
environmentId: true,
|
||||
attributes: {
|
||||
select: {
|
||||
id: true,
|
||||
attributeClass: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!currentPerson) {
|
||||
return res.status(400).json({ message: "Person not found" });
|
||||
}
|
||||
|
||||
// find attribute class
|
||||
let attributeClass = await prisma.attributeClass.findUnique({
|
||||
where: {
|
||||
name_environmentId: {
|
||||
name: key,
|
||||
environmentId,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
// create new attribute class if not found
|
||||
if (attributeClass === null) {
|
||||
attributeClass = await prisma.attributeClass.create({
|
||||
data: {
|
||||
name: key,
|
||||
type: "code",
|
||||
environment: {
|
||||
connect: {
|
||||
id: environmentId,
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// upsert attribute (update or create)
|
||||
const attribute = await prisma.attribute.upsert({
|
||||
where: {
|
||||
attributeClassId_personId: {
|
||||
attributeClassId: attributeClass.id,
|
||||
personId,
|
||||
},
|
||||
},
|
||||
update: {
|
||||
value,
|
||||
},
|
||||
create: {
|
||||
attributeClass: {
|
||||
connect: {
|
||||
id: attributeClass.id,
|
||||
},
|
||||
},
|
||||
person: {
|
||||
connect: {
|
||||
id: personId,
|
||||
},
|
||||
},
|
||||
value,
|
||||
},
|
||||
select: {
|
||||
person: {
|
||||
select: {
|
||||
id: true,
|
||||
environmentId: true,
|
||||
attributes: {
|
||||
select: {
|
||||
id: true,
|
||||
value: true,
|
||||
attributeClass: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const person = attribute.person;
|
||||
|
||||
personCache.revalidate({
|
||||
id: person.id,
|
||||
environmentId: person.environmentId,
|
||||
});
|
||||
|
||||
const settings = await getSettings(environmentId, person.id);
|
||||
|
||||
// return updated person
|
||||
return res.json({ person, settings });
|
||||
}
|
||||
|
||||
// Unknown HTTP Method
|
||||
else {
|
||||
throw new Error(`The HTTP ${req.method} method is not supported by this route.`);
|
||||
}
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
import { sendToPipeline } from "@/app/lib/pipelines";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { INTERNAL_SECRET, WEBAPP_URL } from "@formbricks/lib/constants";
|
||||
import { transformPrismaPerson } from "@formbricks/lib/person/service";
|
||||
import { responseCache } from "@formbricks/lib/response/cache";
|
||||
import { TPipelineInput } from "@formbricks/types/pipelines";
|
||||
import { TResponse } from "@formbricks/types/responses";
|
||||
import { TTag } from "@formbricks/types/tags";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
const environmentId = req.query.environmentId?.toString();
|
||||
|
||||
if (!environmentId) {
|
||||
return res.status(400).json({ message: "Missing environmentId" });
|
||||
}
|
||||
|
||||
const responseId = req.query.responseId?.toString();
|
||||
|
||||
if (!responseId) {
|
||||
return res.status(400).json({ message: "Missing responseId" });
|
||||
}
|
||||
|
||||
// CORS
|
||||
if (req.method === "OPTIONS") {
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
// POST
|
||||
else if (req.method === "PUT") {
|
||||
const { response } = req.body;
|
||||
|
||||
const currentResponse = await prisma.response.findUnique({
|
||||
where: {
|
||||
id: responseId,
|
||||
},
|
||||
select: {
|
||||
data: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!currentResponse) {
|
||||
return res.status(400).json({ message: "Response not found" });
|
||||
}
|
||||
|
||||
const newResponseData = {
|
||||
...JSON.parse(JSON.stringify(currentResponse?.data)),
|
||||
...response.data,
|
||||
};
|
||||
|
||||
const responsePrisma = await prisma.response.update({
|
||||
where: {
|
||||
id: responseId,
|
||||
},
|
||||
data: {
|
||||
...response,
|
||||
data: newResponseData,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
surveyId: true,
|
||||
finished: true,
|
||||
data: true,
|
||||
ttc: true,
|
||||
meta: true,
|
||||
personAttributes: true,
|
||||
singleUseId: true,
|
||||
person: {
|
||||
select: {
|
||||
id: true,
|
||||
userId: true,
|
||||
environmentId: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
attributes: {
|
||||
select: {
|
||||
value: true,
|
||||
attributeClass: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
notes: {
|
||||
select: {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
text: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
isResolved: true,
|
||||
isEdited: true,
|
||||
},
|
||||
},
|
||||
tags: {
|
||||
select: {
|
||||
tag: {
|
||||
select: {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
name: true,
|
||||
environmentId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// update response cache
|
||||
responseCache.revalidate({
|
||||
id: responseId,
|
||||
surveyId: responsePrisma.surveyId,
|
||||
environmentId,
|
||||
});
|
||||
|
||||
const responseData: TResponse = {
|
||||
...responsePrisma,
|
||||
person: responsePrisma.person ? transformPrismaPerson(responsePrisma.person) : null,
|
||||
tags: responsePrisma.tags.map((tagPrisma: { tag: TTag }) => tagPrisma.tag),
|
||||
};
|
||||
|
||||
// send response update to pipeline
|
||||
// don't await to not block the response
|
||||
fetch(`${WEBAPP_URL}/api/pipeline`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
internalSecret: INTERNAL_SECRET,
|
||||
environmentId,
|
||||
surveyId: responseData.surveyId,
|
||||
event: "responseUpdated",
|
||||
response: responseData,
|
||||
} as TPipelineInput),
|
||||
});
|
||||
|
||||
if (response.finished) {
|
||||
// send response to pipeline
|
||||
// don't await to not block the response
|
||||
sendToPipeline({
|
||||
environmentId,
|
||||
surveyId: responseData.surveyId,
|
||||
event: "responseFinished",
|
||||
response: responseData,
|
||||
});
|
||||
}
|
||||
|
||||
return res.json({ message: "Response updated" });
|
||||
}
|
||||
|
||||
// Unknown HTTP Method
|
||||
else {
|
||||
throw new Error(`The HTTP ${req.method} method is not supported by this route.`);
|
||||
}
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
import { sendToPipeline } from "@/app/lib/pipelines";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { transformPrismaPerson } from "@formbricks/lib/person/service";
|
||||
import { capturePosthogEvent } from "@formbricks/lib/posthogServer";
|
||||
import { captureTelemetry } from "@formbricks/lib/telemetry";
|
||||
import { TResponse } from "@formbricks/types/responses";
|
||||
import { TTag } from "@formbricks/types/tags";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
const environmentId = req.query.environmentId?.toString();
|
||||
|
||||
if (!environmentId) {
|
||||
return res.status(400).json({ message: "Missing environmentId" });
|
||||
}
|
||||
|
||||
// CORS
|
||||
if (req.method === "OPTIONS") {
|
||||
return res.status(200).end();
|
||||
}
|
||||
|
||||
// POST
|
||||
else if (req.method === "POST") {
|
||||
const { surveyId, personId, response } = req.body;
|
||||
|
||||
if (!surveyId) {
|
||||
return res.status(400).json({ message: "Missing surveyId" });
|
||||
}
|
||||
if (!response) {
|
||||
return res.status(400).json({ message: "Missing data" });
|
||||
}
|
||||
// personId can be null, e.g. for link surveys
|
||||
|
||||
// check if survey exists
|
||||
const survey = await prisma.survey.findUnique({
|
||||
where: {
|
||||
id: surveyId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
type: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!survey) {
|
||||
return res.status(404).json({ message: "Survey not found" });
|
||||
}
|
||||
|
||||
// get teamId from environment
|
||||
const environment = await prisma.environment.findUnique({
|
||||
where: {
|
||||
id: environmentId,
|
||||
},
|
||||
select: {
|
||||
product: {
|
||||
select: {
|
||||
team: {
|
||||
select: {
|
||||
id: true,
|
||||
memberships: {
|
||||
select: {
|
||||
userId: true,
|
||||
role: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!environment) {
|
||||
return res.status(404).json({ message: "Environment not found" });
|
||||
}
|
||||
|
||||
const teamId = environment.product.team.id;
|
||||
// find team owner
|
||||
const teamOwnerId = environment.product.team.memberships.find((m) => m.role === "owner")?.userId;
|
||||
|
||||
const responseInput = {
|
||||
survey: {
|
||||
connect: {
|
||||
id: surveyId,
|
||||
},
|
||||
},
|
||||
...response,
|
||||
};
|
||||
|
||||
if (personId) {
|
||||
responseInput.data.person = {
|
||||
connect: {
|
||||
id: personId,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// create new response
|
||||
const responsePrisma = await prisma.response.create({
|
||||
data: {
|
||||
...responseInput,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
surveyId: true,
|
||||
finished: true,
|
||||
data: true,
|
||||
ttc: true,
|
||||
meta: true,
|
||||
personAttributes: true,
|
||||
singleUseId: true,
|
||||
person: {
|
||||
select: {
|
||||
id: true,
|
||||
userId: true,
|
||||
environmentId: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
attributes: {
|
||||
select: {
|
||||
value: true,
|
||||
attributeClass: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
notes: {
|
||||
select: {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
text: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
isResolved: true,
|
||||
isEdited: true,
|
||||
},
|
||||
},
|
||||
tags: {
|
||||
select: {
|
||||
tag: {
|
||||
select: {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
name: true,
|
||||
environmentId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const responseData: TResponse = {
|
||||
...responsePrisma,
|
||||
person: responsePrisma.person ? transformPrismaPerson(responsePrisma.person) : null,
|
||||
tags: responsePrisma.tags.map((tagPrisma: { tag: TTag }) => tagPrisma.tag),
|
||||
};
|
||||
|
||||
// send response to pipeline
|
||||
// don't await to not block the response
|
||||
sendToPipeline({
|
||||
environmentId,
|
||||
surveyId,
|
||||
event: "responseCreated",
|
||||
response: responseData,
|
||||
});
|
||||
|
||||
if (response.finished) {
|
||||
// send response to pipeline
|
||||
// don't await to not block the response
|
||||
sendToPipeline({
|
||||
environmentId,
|
||||
surveyId,
|
||||
event: "responseFinished",
|
||||
response: responseData,
|
||||
});
|
||||
}
|
||||
|
||||
captureTelemetry("response created");
|
||||
if (teamOwnerId) {
|
||||
await capturePosthogEvent(teamOwnerId, "response created", teamId, {
|
||||
surveyId,
|
||||
surveyType: survey.type,
|
||||
});
|
||||
} else {
|
||||
console.warn("Posthog capture not possible. No team owner found");
|
||||
}
|
||||
|
||||
return res.json({ id: responseData.id });
|
||||
}
|
||||
|
||||
// Unknown HTTP Method
|
||||
else {
|
||||
throw new Error(`The HTTP ${req.method} method is not supported by this route.`);
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { getSettings } from "@/app/lib/api/clientSettings";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
const environmentId = req.query.environmentId?.toString();
|
||||
|
||||
if (!environmentId) {
|
||||
return res.status(400).json({ message: "Missing environmentId" });
|
||||
}
|
||||
|
||||
// CORS
|
||||
if (req.method === "OPTIONS") {
|
||||
res.status(200).end();
|
||||
}
|
||||
// GET
|
||||
else if (req.method === "POST") {
|
||||
const { personId } = req.body;
|
||||
|
||||
if (!personId) {
|
||||
return res.status(400).json({ message: "Missing sessionId" });
|
||||
}
|
||||
|
||||
const settings = await getSettings(environmentId, personId);
|
||||
|
||||
return res.json(settings);
|
||||
}
|
||||
|
||||
// Unknown HTTP Method
|
||||
else {
|
||||
throw new Error(`The HTTP ${req.method} method is not supported by this route.`);
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
const surveyId = req.query.surveyId?.toString();
|
||||
|
||||
if (!surveyId) {
|
||||
return res.status(400).json({ message: "Missing surveyId" });
|
||||
}
|
||||
|
||||
// CORS
|
||||
if (req.method === "OPTIONS") {
|
||||
res.status(200).end();
|
||||
}
|
||||
// GET
|
||||
else if (req.method === "GET") {
|
||||
// get survey
|
||||
const survey = await prisma.survey.findFirst({
|
||||
where: {
|
||||
id: surveyId,
|
||||
type: "link",
|
||||
// status: "inProgress",
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
questions: true,
|
||||
thankYouCard: true,
|
||||
environmentId: true,
|
||||
status: true,
|
||||
redirectUrl: true,
|
||||
surveyClosedMessage: true,
|
||||
},
|
||||
});
|
||||
|
||||
// if survey does not exist, return 404
|
||||
if (!survey) {
|
||||
return res.status(404).json({ message: "Survey not found" });
|
||||
}
|
||||
|
||||
// get brandColor from product using environmentId
|
||||
const product = await prisma.product.findFirst({
|
||||
where: {
|
||||
environments: {
|
||||
some: {
|
||||
id: survey.environmentId,
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
brandColor: true,
|
||||
linkSurveyBranding: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (survey.status !== "inProgress") {
|
||||
return res.status(403).json({
|
||||
message: "Survey not running",
|
||||
reason: survey.status,
|
||||
brandColor: product?.brandColor,
|
||||
formbricksSignature: product?.linkSurveyBranding,
|
||||
surveyClosedMessage: survey?.surveyClosedMessage,
|
||||
});
|
||||
}
|
||||
|
||||
// if survey exists, return survey
|
||||
return res.status(200).json({
|
||||
...survey,
|
||||
brandColor: product?.brandColor,
|
||||
formbricksSignature: product?.linkSurveyBranding,
|
||||
});
|
||||
}
|
||||
|
||||
// Unknown HTTP Method
|
||||
else {
|
||||
throw new Error(`The HTTP ${req.method} method is not supported by this route.`);
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,9 @@ const enabled =
|
||||
process.env.NEXT_PUBLIC_POSTHOG_API_HOST &&
|
||||
process.env.NEXT_PUBLIC_POSTHOG_API_KEY;
|
||||
|
||||
export const capturePosthogEvent = async (
|
||||
userId: string,
|
||||
export const capturePosthogEnvironmentEvent = async (
|
||||
environmentId: string,
|
||||
eventName: string,
|
||||
teamId?: string,
|
||||
properties: any = {}
|
||||
) => {
|
||||
if (
|
||||
@@ -23,12 +22,11 @@ export const capturePosthogEvent = async (
|
||||
host: process.env.NEXT_PUBLIC_POSTHOG_API_HOST,
|
||||
});
|
||||
client.capture({
|
||||
distinctId: environmentId,
|
||||
event: eventName,
|
||||
distinctId: userId,
|
||||
groups: teamId ? { company: teamId } : {},
|
||||
groups: { environment: environmentId },
|
||||
properties,
|
||||
});
|
||||
|
||||
await client.shutdownAsync();
|
||||
} catch (error) {
|
||||
console.error("error sending posthog event:", error);
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
import "server-only";
|
||||
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { unstable_cache } from "next/cache";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { ZId } from "@formbricks/types/environment";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
|
||||
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||
import { teamCache } from "../team/cache";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
|
||||
export const getTeamDetails = async (
|
||||
environmentId: string
|
||||
): Promise<{ teamId: string; teamOwnerId: string | undefined }> =>
|
||||
unstable_cache(
|
||||
async () => {
|
||||
validateInputs([environmentId, ZId]);
|
||||
|
||||
try {
|
||||
const environment = await prisma.environment.findUnique({
|
||||
where: {
|
||||
id: environmentId,
|
||||
},
|
||||
select: {
|
||||
product: {
|
||||
select: {
|
||||
team: {
|
||||
select: {
|
||||
id: true,
|
||||
memberships: {
|
||||
select: {
|
||||
userId: true,
|
||||
role: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!environment) {
|
||||
throw new ResourceNotFoundError("Environment", environmentId);
|
||||
}
|
||||
|
||||
const teamId: string = environment.product.team.id;
|
||||
// find team owner
|
||||
const teamOwnerId: string | undefined = environment.product.team.memberships.find(
|
||||
(m) => m.role === "owner"
|
||||
)?.userId;
|
||||
|
||||
return {
|
||||
teamId: teamId,
|
||||
teamOwnerId: teamOwnerId,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[`getTeamDetails-${environmentId}`],
|
||||
{
|
||||
tags: [teamCache.tag.byEnvironmentId(environmentId)],
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
Reference in New Issue
Block a user