mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 10:19:51 -06:00
chore: Refactored the Sentry next public env variable and added test files (#4979)
Co-authored-by: Piyush Gupta <piyushguptaa2z123@gmail.com>
This commit is contained in:
@@ -40,7 +40,6 @@ RUN echo '#!/bin/sh' > /tmp/read-secrets.sh && \
|
||||
echo 'exec "$@"' >> /tmp/read-secrets.sh && \
|
||||
chmod +x /tmp/read-secrets.sh
|
||||
|
||||
ARG NEXT_PUBLIC_SENTRY_DSN
|
||||
ARG SENTRY_AUTH_TOKEN
|
||||
|
||||
# Increase Node.js memory limit as a regular build argument
|
||||
|
||||
@@ -29,6 +29,7 @@ vi.mock("@formbricks/lib/constants", () => ({
|
||||
OIDC_SIGNING_ALGORITHM: "test-oidc-signing-algorithm",
|
||||
WEBAPP_URL: "test-webapp-url",
|
||||
IS_PRODUCTION: false,
|
||||
SENTRY_DSN: "mock-sentry-dsn",
|
||||
}));
|
||||
|
||||
vi.mock("@/tolgee/language", () => ({
|
||||
@@ -69,6 +70,15 @@ vi.mock("@/tolgee/client", () => ({
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock("@/app/sentry/SentryProvider", () => ({
|
||||
SentryProvider: ({ children, sentryDsn }: { children: React.ReactNode; sentryDsn?: string }) => (
|
||||
<div data-testid="sentry-provider">
|
||||
SentryProvider: {sentryDsn}
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
describe("RootLayout", () => {
|
||||
beforeEach(() => {
|
||||
cleanup();
|
||||
@@ -97,6 +107,7 @@ describe("RootLayout", () => {
|
||||
expect(screen.getByTestId("speed-insights")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("ph-provider")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("tolgee-next-provider")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("sentry-provider")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("child")).toHaveTextContent("Child Content");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { SentryProvider } from "@/app/sentry/SentryProvider";
|
||||
import { PHProvider } from "@/modules/ui/components/post-hog-client";
|
||||
import { TolgeeNextProvider } from "@/tolgee/client";
|
||||
import { getLocale } from "@/tolgee/language";
|
||||
@@ -6,7 +7,7 @@ import { TolgeeStaticData } from "@tolgee/react";
|
||||
import { SpeedInsights } from "@vercel/speed-insights/next";
|
||||
import { Metadata } from "next";
|
||||
import React from "react";
|
||||
import { IS_POSTHOG_CONFIGURED } from "@formbricks/lib/constants";
|
||||
import { IS_POSTHOG_CONFIGURED, SENTRY_DSN } from "@formbricks/lib/constants";
|
||||
import "../modules/ui/globals.css";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -27,11 +28,13 @@ const RootLayout = async ({ children }: { children: React.ReactNode }) => {
|
||||
<html lang={locale} translate="no">
|
||||
<body className="flex h-dvh flex-col transition-all ease-in-out">
|
||||
{process.env.VERCEL === "1" && <SpeedInsights sampleRate={0.1} />}
|
||||
<PHProvider posthogEnabled={IS_POSTHOG_CONFIGURED}>
|
||||
<TolgeeNextProvider language={locale} staticData={staticData as unknown as TolgeeStaticData}>
|
||||
{children}
|
||||
</TolgeeNextProvider>
|
||||
</PHProvider>
|
||||
<SentryProvider sentryDsn={SENTRY_DSN}>
|
||||
<PHProvider posthogEnabled={IS_POSTHOG_CONFIGURED}>
|
||||
<TolgeeNextProvider language={locale} staticData={staticData as unknown as TolgeeStaticData}>
|
||||
{children}
|
||||
</TolgeeNextProvider>
|
||||
</PHProvider>
|
||||
</SentryProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
101
apps/web/app/sentry/SentryProvider.test.tsx
Normal file
101
apps/web/app/sentry/SentryProvider.test.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { cleanup, render, screen } from "@testing-library/react";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SentryProvider } from "./SentryProvider";
|
||||
|
||||
vi.mock("@sentry/nextjs", async () => {
|
||||
const actual = await vi.importActual<typeof import("@sentry/nextjs")>("@sentry/nextjs");
|
||||
return {
|
||||
...actual,
|
||||
replayIntegration: (options: any) => {
|
||||
return {
|
||||
name: "Replay",
|
||||
id: "Replay",
|
||||
options,
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe("SentryProvider", () => {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it("calls Sentry.init when sentryDsn is provided", () => {
|
||||
const sentryDsn = "https://examplePublicKey@o0.ingest.sentry.io/0";
|
||||
const initSpy = vi.spyOn(Sentry, "init").mockImplementation(() => undefined);
|
||||
|
||||
render(
|
||||
<SentryProvider sentryDsn={sentryDsn}>
|
||||
<div data-testid="child">Test Content</div>
|
||||
</SentryProvider>
|
||||
);
|
||||
|
||||
// The useEffect runs after mount, so Sentry.init should have been called.
|
||||
expect(initSpy).toHaveBeenCalled();
|
||||
expect(initSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
dsn: sentryDsn,
|
||||
tracesSampleRate: 1,
|
||||
debug: false,
|
||||
replaysOnErrorSampleRate: 1.0,
|
||||
replaysSessionSampleRate: 0.1,
|
||||
integrations: expect.any(Array),
|
||||
beforeSend: expect.any(Function),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("does not call Sentry.init when sentryDsn is not provided", () => {
|
||||
const initSpy = vi.spyOn(Sentry, "init").mockImplementation(() => undefined);
|
||||
|
||||
render(
|
||||
<SentryProvider>
|
||||
<div data-testid="child">Test Content</div>
|
||||
</SentryProvider>
|
||||
);
|
||||
|
||||
expect(initSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("renders children", () => {
|
||||
const sentryDsn = "https://examplePublicKey@o0.ingest.sentry.io/0";
|
||||
render(
|
||||
<SentryProvider sentryDsn={sentryDsn}>
|
||||
<div data-testid="child">Test Content</div>
|
||||
</SentryProvider>
|
||||
);
|
||||
expect(screen.getByTestId("child")).toHaveTextContent("Test Content");
|
||||
});
|
||||
|
||||
it("processes beforeSend correctly", () => {
|
||||
const sentryDsn = "https://examplePublicKey@o0.ingest.sentry.io/0";
|
||||
const initSpy = vi.spyOn(Sentry, "init").mockImplementation(() => undefined);
|
||||
|
||||
render(
|
||||
<SentryProvider sentryDsn={sentryDsn}>
|
||||
<div data-testid="child">Test Content</div>
|
||||
</SentryProvider>
|
||||
);
|
||||
|
||||
const config = initSpy.mock.calls[0][0];
|
||||
expect(config).toHaveProperty("beforeSend");
|
||||
const beforeSend = config.beforeSend;
|
||||
|
||||
if (!beforeSend) {
|
||||
throw new Error("beforeSend is not defined");
|
||||
}
|
||||
|
||||
const dummyEvent = { some: "event" } as unknown as Sentry.ErrorEvent;
|
||||
|
||||
const hintWithNextNotFound = { originalException: { digest: "NEXT_NOT_FOUND" } };
|
||||
expect(beforeSend(dummyEvent, hintWithNextNotFound)).toBeNull();
|
||||
|
||||
const hintWithOtherError = { originalException: { digest: "OTHER_ERROR" } };
|
||||
expect(beforeSend(dummyEvent, hintWithOtherError)).toEqual(dummyEvent);
|
||||
|
||||
const hintWithoutError = { originalException: undefined };
|
||||
expect(beforeSend(dummyEvent, hintWithoutError)).toEqual(dummyEvent);
|
||||
});
|
||||
});
|
||||
53
apps/web/app/sentry/SentryProvider.tsx
Normal file
53
apps/web/app/sentry/SentryProvider.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
"use client";
|
||||
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { useEffect } from "react";
|
||||
|
||||
interface SentryProviderProps {
|
||||
children: React.ReactNode;
|
||||
sentryDsn?: string;
|
||||
}
|
||||
|
||||
export const SentryProvider = ({ children, sentryDsn }: SentryProviderProps) => {
|
||||
useEffect(() => {
|
||||
if (sentryDsn) {
|
||||
Sentry.init({
|
||||
dsn: sentryDsn,
|
||||
|
||||
// Adjust this value in production, or use tracesSampler for greater control
|
||||
tracesSampleRate: 1,
|
||||
|
||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||
debug: false,
|
||||
|
||||
replaysOnErrorSampleRate: 1.0,
|
||||
|
||||
// This sets the sample rate to be 10%. You may want this to be 100% while
|
||||
// in development and sample at a lower rate in production
|
||||
replaysSessionSampleRate: 0.1,
|
||||
|
||||
// You can remove this option if you're not planning to use the Sentry Session Replay feature:
|
||||
integrations: [
|
||||
Sentry.replayIntegration({
|
||||
// Additional Replay configuration goes in here, for example:
|
||||
maskAllText: true,
|
||||
blockAllMedia: true,
|
||||
}),
|
||||
],
|
||||
|
||||
beforeSend(event, hint) {
|
||||
const error = hint.originalException as Error;
|
||||
|
||||
// @ts-expect-error
|
||||
if (error && error.digest === "NEXT_NOT_FOUND") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return event;
|
||||
},
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
@@ -1,8 +1,14 @@
|
||||
import { env } from "@formbricks/lib/env";
|
||||
import { PROMETHEUS_ENABLED, SENTRY_DSN } from "@formbricks/lib/constants";
|
||||
|
||||
// instrumentation.ts
|
||||
export const register = async () => {
|
||||
if (process.env.NEXT_RUNTIME === "nodejs" && env.PROMETHEUS_ENABLED) {
|
||||
if (process.env.NEXT_RUNTIME === "nodejs" && PROMETHEUS_ENABLED) {
|
||||
await import("./instrumentation-node");
|
||||
}
|
||||
if (process.env.NEXT_RUNTIME === "nodejs" && SENTRY_DSN) {
|
||||
await import("./sentry.server.config");
|
||||
}
|
||||
if (process.env.NEXT_RUNTIME === "edge" && SENTRY_DSN) {
|
||||
await import("./sentry.edge.config");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -321,7 +321,7 @@ const sentryConfig = {
|
||||
disableLogger: true,
|
||||
};
|
||||
|
||||
const exportConfig = process.env.NEXT_PUBLIC_SENTRY_DSN
|
||||
const exportConfig = process.env.SENTRY_DSN
|
||||
? withSentryConfig(nextConfig, sentryOptions)
|
||||
: nextConfig;
|
||||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
// This file configures the initialization of Sentry on the client.
|
||||
// The config you add here will be used whenever a users loads a page in their browser.
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
|
||||
Sentry.init({
|
||||
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||
|
||||
// Adjust this value in production, or use tracesSampler for greater control
|
||||
tracesSampleRate: 1,
|
||||
|
||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||
debug: false,
|
||||
|
||||
replaysOnErrorSampleRate: 1.0,
|
||||
|
||||
// This sets the sample rate to be 10%. You may want this to be 100% while
|
||||
// in development and sample at a lower rate in production
|
||||
replaysSessionSampleRate: 0.1,
|
||||
|
||||
// You can remove this option if you're not planning to use the Sentry Session Replay feature:
|
||||
integrations: [
|
||||
Sentry.replayIntegration({
|
||||
// Additional Replay configuration goes in here, for example:
|
||||
maskAllText: true,
|
||||
blockAllMedia: true,
|
||||
}),
|
||||
],
|
||||
|
||||
beforeSend(event, hint) {
|
||||
const error = hint.originalException as Error;
|
||||
|
||||
// @ts-expect-error
|
||||
if (error && error.digest === "NEXT_NOT_FOUND") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return event;
|
||||
},
|
||||
});
|
||||
@@ -3,13 +3,20 @@
|
||||
// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { SENTRY_DSN } from "@formbricks/lib/constants";
|
||||
|
||||
Sentry.init({
|
||||
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||
if (SENTRY_DSN) {
|
||||
console.log("Sentry DSN found, enabling Sentry on the edge");
|
||||
|
||||
// Adjust this value in production, or use tracesSampler for greater control
|
||||
tracesSampleRate: 1,
|
||||
Sentry.init({
|
||||
dsn: SENTRY_DSN,
|
||||
|
||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||
debug: false,
|
||||
});
|
||||
// Adjust this value in production, or use tracesSampler for greater control
|
||||
tracesSampleRate: 1,
|
||||
|
||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||
debug: false,
|
||||
});
|
||||
} else {
|
||||
console.warn("Sentry DSN not found, Sentry will be disabled on the edge");
|
||||
}
|
||||
|
||||
@@ -2,27 +2,34 @@
|
||||
// The config you add here will be used whenever the server handles a request.
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { SENTRY_DSN } from "@formbricks/lib/constants";
|
||||
|
||||
Sentry.init({
|
||||
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||
if (SENTRY_DSN) {
|
||||
console.log("Sentry DSN found, enabling Sentry on the server");
|
||||
|
||||
// Adjust this value in production, or use tracesSampler for greater control
|
||||
tracesSampleRate: 1,
|
||||
Sentry.init({
|
||||
dsn: SENTRY_DSN,
|
||||
|
||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||
debug: false,
|
||||
// Adjust this value in production, or use tracesSampler for greater control
|
||||
tracesSampleRate: 1,
|
||||
|
||||
// uncomment the line below to enable Spotlight (https://spotlightjs.com)
|
||||
// spotlight: process.env.NODE_ENV === 'development',
|
||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||
debug: false,
|
||||
|
||||
beforeSend(event, hint) {
|
||||
const error = hint.originalException as Error;
|
||||
// uncomment the line below to enable Spotlight (https://spotlightjs.com)
|
||||
// spotlight: process.env.NODE_ENV === 'development',
|
||||
|
||||
// @ts-expect-error
|
||||
if (error && error.digest === "NEXT_NOT_FOUND") {
|
||||
return null;
|
||||
}
|
||||
beforeSend(event, hint) {
|
||||
const error = hint.originalException as Error;
|
||||
|
||||
return event;
|
||||
},
|
||||
});
|
||||
// @ts-expect-error
|
||||
if (error && error.digest === "NEXT_NOT_FOUND") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return event;
|
||||
},
|
||||
});
|
||||
} else {
|
||||
console.warn("Sentry DSN not found, Sentry will be disabled on the server");
|
||||
}
|
||||
|
||||
@@ -32,14 +32,13 @@ export default defineConfig({
|
||||
"app/(app)/environments/**/components/PosthogIdentify.tsx",
|
||||
"app/(app)/(onboarding)/organizations/**/layout.tsx",
|
||||
"app/(app)/(survey-editor)/environments/**/layout.tsx",
|
||||
"modules/ee/sso/lib/**/*.ts",
|
||||
"modules/ee/contacts/lib/**/*.ts",
|
||||
"modules/survey/link/lib/**/*.ts",
|
||||
"app/(auth)/layout.tsx",
|
||||
"app/(app)/layout.tsx",
|
||||
"app/layout.tsx",
|
||||
"app/(app)/environments/**/surveys/**/(analysis)/summary/components/SurveyAnalysisCTA.tsx",
|
||||
"app/intercom/*.tsx",
|
||||
"app/sentry/*.tsx",
|
||||
"app/(app)/environments/**/surveys/**/(analysis)/summary/components/SurveyAnalysisCTA.tsx",
|
||||
"modules/ee/sso/lib/**/*.ts",
|
||||
"app/lib/**/*.ts",
|
||||
"app/api/(internal)/insights/lib/**/*.ts",
|
||||
"modules/ee/role-management/*.ts",
|
||||
|
||||
@@ -289,3 +289,7 @@ export const IS_TURNSTILE_CONFIGURED = Boolean(env.NEXT_PUBLIC_TURNSTILE_SITE_KE
|
||||
export const IS_PRODUCTION = env.NODE_ENV === "production";
|
||||
|
||||
export const IS_DEVELOPMENT = env.NODE_ENV === "development";
|
||||
|
||||
export const SENTRY_DSN = env.SENTRY_DSN;
|
||||
|
||||
export const PROMETHEUS_ENABLED = env.PROMETHEUS_ENABLED === "1";
|
||||
|
||||
@@ -81,6 +81,7 @@ export const env = createEnv({
|
||||
S3_ENDPOINT_URL: z.string().optional(),
|
||||
S3_FORCE_PATH_STYLE: z.enum(["1", "0"]).optional(),
|
||||
SAML_DATABASE_URL: z.string().optional(),
|
||||
SENTRY_DSN: z.string().optional(),
|
||||
SIGNUP_DISABLED: z.enum(["1", "0"]).optional(),
|
||||
SLACK_CLIENT_ID: z.string().optional(),
|
||||
SLACK_CLIENT_SECRET: z.string().optional(),
|
||||
@@ -126,7 +127,6 @@ export const env = createEnv({
|
||||
.or(z.string().refine((str) => str === "")),
|
||||
NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID: z.string().optional(),
|
||||
NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID: z.string().optional(),
|
||||
NEXT_PUBLIC_SENTRY_DSN: z.string().optional(),
|
||||
NEXT_PUBLIC_TURNSTILE_SITE_KEY: z.string().optional(),
|
||||
},
|
||||
/*
|
||||
@@ -184,9 +184,9 @@ export const env = createEnv({
|
||||
NEXT_PUBLIC_FORMBRICKS_API_HOST: process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST,
|
||||
NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID: process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID,
|
||||
NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID: process.env.NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID,
|
||||
SENTRY_DSN: process.env.SENTRY_DSN,
|
||||
POSTHOG_API_KEY: process.env.POSTHOG_API_KEY,
|
||||
POSTHOG_API_HOST: process.env.POSTHOG_API_HOST,
|
||||
NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||
NEXT_PUBLIC_TURNSTILE_SITE_KEY: process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY,
|
||||
OPENTELEMETRY_LISTENER_URL: process.env.OPENTELEMETRY_LISTENER_URL,
|
||||
INTERCOM_APP_ID: process.env.INTERCOM_APP_ID,
|
||||
|
||||
@@ -103,6 +103,9 @@ export const track = async (
|
||||
|
||||
return trackAction(actionClass.name, code);
|
||||
} catch (error) {
|
||||
const logger = Logger.getInstance();
|
||||
logger.error(`Error tracking action ${error as string}`);
|
||||
|
||||
return err({
|
||||
code: "error",
|
||||
message: "Error tracking action",
|
||||
|
||||
@@ -21,5 +21,5 @@ sonar.scm.exclusions.disabled=false
|
||||
sonar.sourceEncoding=UTF-8
|
||||
|
||||
# Coverage
|
||||
sonar.coverage.exclusions=**/*.test.*,**/*.spec.*,**/*.mdx,**/constants.ts,**/route.ts,modules/**/types/**,**/*.stories.*,**/mocks/**,**/openapi.ts,**/openapi-document.ts,playwright/**,scripts/merge-client-endpoints.ts
|
||||
sonar.cpd.exclusions=**/*.test.*,**/*.spec.*,**/*.mdx,**/constants.ts,**/route.ts,modules/**/types/**,**/openapi.ts,**/openapi-document.ts,scripts/merge-client-endpoints.ts
|
||||
sonar.coverage.exclusions=**/*.test.*,**/*.spec.*,**/*.mdx,**,**/*.config.mts,**/*.config.ts,**/constants.ts,**/route.ts,modules/**/types/**,**/*.stories.*,**/mocks/**,**/openapi.ts,**/openapi-document.ts,playwright/**, **/instrumentation.ts, scripts/merge-client-endpoints.ts
|
||||
sonar.cpd.exclusions=**/*.test.*,**/*.spec.*,**/*.mdx,**,**/*.config.mts,**/*.config.ts,**/constants.ts,**/route.ts,modules/**/types/**,**/openapi.ts,**/openapi-document.ts, **/instrumentation.ts, scripts/merge-client-endpoints.ts
|
||||
@@ -159,7 +159,6 @@
|
||||
"NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID",
|
||||
"NEXT_PUBLIC_FORMBRICKS_PMF_FORM_ID",
|
||||
"NEXT_PUBLIC_FORMBRICKS_URL",
|
||||
"NEXT_PUBLIC_SENTRY_DSN",
|
||||
"NEXT_PUBLIC_FORMBRICKS_COM_API_HOST",
|
||||
"NEXT_PUBLIC_FORMBRICKS_COM_ENVIRONMENT_ID",
|
||||
"NEXT_PUBLIC_FORMBRICKS_COM_DOCS_FEEDBACK_SURVEY_ID",
|
||||
|
||||
Reference in New Issue
Block a user