mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-23 22:50:35 -06:00
chore: Enable Sentry integration (#5337)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
This commit is contained in:
@@ -219,3 +219,8 @@ UNKEY_ROOT_KEY=
|
||||
# PROMETHEUS_ENABLED=
|
||||
# PROMETHEUS_EXPORTER_PORT=
|
||||
|
||||
# The SENTRY_DSN is used for error tracking and performance monitoring with Sentry.
|
||||
# SENTRY_DSN=
|
||||
# The SENTRY_AUTH_TOKEN variable is picked up by the Sentry Build Plugin.
|
||||
# It's used automatically by Sentry during the build for authentication when uploading source maps.
|
||||
# SENTRY_AUTH_TOKEN=
|
||||
|
||||
2
apps/web/.gitignore
vendored
2
apps/web/.gitignore
vendored
@@ -50,4 +50,4 @@ uploads/
|
||||
.sentryclirc
|
||||
|
||||
# SAML Preloaded Connections
|
||||
saml-connection/
|
||||
saml-connection/
|
||||
|
||||
72
apps/web/app/error.test.tsx
Normal file
72
apps/web/app/error.test.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { cleanup, render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import ErrorBoundary from "./error";
|
||||
|
||||
vi.mock("@/modules/ui/components/button", () => ({
|
||||
Button: (props: any) => <button {...props}>{props.children}</button>,
|
||||
}));
|
||||
|
||||
vi.mock("@/modules/ui/components/error-component", () => ({
|
||||
ErrorComponent: () => <div data-testid="ErrorComponent">ErrorComponent</div>,
|
||||
}));
|
||||
|
||||
vi.mock("@sentry/nextjs", () => ({
|
||||
captureException: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("ErrorBoundary", () => {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
const dummyError = new Error("Test error");
|
||||
const resetMock = vi.fn();
|
||||
|
||||
test("logs error via console.error in development", async () => {
|
||||
(process.env as { [key: string]: string }).NODE_ENV = "development";
|
||||
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
vi.mocked(Sentry.captureException).mockImplementation((() => {}) as any);
|
||||
|
||||
render(<ErrorBoundary error={dummyError} reset={resetMock} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith("Test error");
|
||||
});
|
||||
expect(Sentry.captureException).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("captures error with Sentry in production", async () => {
|
||||
(process.env as { [key: string]: string }).NODE_ENV = "production";
|
||||
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
vi.mocked(Sentry.captureException).mockImplementation((() => {}) as any);
|
||||
|
||||
render(<ErrorBoundary error={{ ...dummyError }} reset={resetMock} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(Sentry.captureException).toHaveBeenCalled();
|
||||
});
|
||||
expect(consoleErrorSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("calls reset when try again button is clicked", async () => {
|
||||
render(<ErrorBoundary error={{ ...dummyError }} reset={resetMock} />);
|
||||
const tryAgainBtn = screen.getByRole("button", { name: "common.try_again" });
|
||||
userEvent.click(tryAgainBtn);
|
||||
await waitFor(() => expect(resetMock).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
test("sets window.location.href to '/' when dashboard button is clicked", async () => {
|
||||
const originalLocation = window.location;
|
||||
delete (window as any).location;
|
||||
(window as any).location = { href: "" };
|
||||
render(<ErrorBoundary error={{ ...dummyError }} reset={resetMock} />);
|
||||
const dashBtn = screen.getByRole("button", { name: "common.go_to_dashboard" });
|
||||
userEvent.click(dashBtn);
|
||||
await waitFor(() => {
|
||||
expect(window.location.href).toBe("/");
|
||||
});
|
||||
window.location = originalLocation;
|
||||
});
|
||||
});
|
||||
@@ -3,12 +3,15 @@
|
||||
// Error components must be Client components
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { ErrorComponent } from "@/modules/ui/components/error-component";
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { useTranslate } from "@tolgee/react";
|
||||
|
||||
const Error = ({ error, reset }: { error: Error; reset: () => void }) => {
|
||||
const ErrorBoundary = ({ error, reset }: { error: Error; reset: () => void }) => {
|
||||
const { t } = useTranslate();
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.error(error.message);
|
||||
} else {
|
||||
Sentry.captureException(error);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -24,4 +27,4 @@ const Error = ({ error, reset }: { error: Error; reset: () => void }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Error;
|
||||
export default ErrorBoundary;
|
||||
|
||||
41
apps/web/app/global-error.test.tsx
Normal file
41
apps/web/app/global-error.test.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { cleanup, render, waitFor } from "@testing-library/react";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import GlobalError from "./global-error";
|
||||
|
||||
vi.mock("@sentry/nextjs", () => ({
|
||||
captureException: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("GlobalError", () => {
|
||||
const dummyError = new Error("Test error");
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
test("logs error using console.error in development", async () => {
|
||||
(process.env as { [key: string]: string }).NODE_ENV = "development";
|
||||
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
vi.mocked(Sentry.captureException).mockImplementation((() => {}) as any);
|
||||
|
||||
render(<GlobalError error={dummyError} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith("Test error");
|
||||
});
|
||||
expect(Sentry.captureException).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("captures error with Sentry in production", async () => {
|
||||
(process.env as { [key: string]: string }).NODE_ENV = "production";
|
||||
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
|
||||
render(<GlobalError error={dummyError} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(Sentry.captureException).toHaveBeenCalled();
|
||||
});
|
||||
expect(consoleErrorSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
22
apps/web/app/global-error.tsx
Normal file
22
apps/web/app/global-error.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import NextError from "next/error";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function GlobalError({ error }: { error: Error & { digest?: string } }) {
|
||||
useEffect(() => {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.error(error.message);
|
||||
} else {
|
||||
Sentry.captureException(error);
|
||||
}
|
||||
}, [error]);
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<NextError statusCode={0} />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { getTolgee } from "@/tolgee/server";
|
||||
import { TolgeeStaticData } from "@tolgee/react";
|
||||
import { Metadata } from "next";
|
||||
import React from "react";
|
||||
import { SENTRY_DSN } from "@formbricks/lib/constants";
|
||||
import { IS_PRODUCTION, SENTRY_DSN } from "@formbricks/lib/constants";
|
||||
import "../modules/ui/globals.css";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -25,7 +25,7 @@ const RootLayout = async ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<html lang={locale} translate="no">
|
||||
<body className="flex h-dvh flex-col transition-all ease-in-out">
|
||||
<SentryProvider sentryDsn={SENTRY_DSN}>
|
||||
<SentryProvider sentryDsn={SENTRY_DSN} isEnabled={IS_PRODUCTION}>
|
||||
<TolgeeNextProvider language={locale} staticData={staticData as unknown as TolgeeStaticData}>
|
||||
{children}
|
||||
</TolgeeNextProvider>
|
||||
|
||||
@@ -17,17 +17,18 @@ vi.mock("@sentry/nextjs", async () => {
|
||||
};
|
||||
});
|
||||
|
||||
const sentryDsn = "https://examplePublicKey@o0.ingest.sentry.io/0";
|
||||
|
||||
describe("SentryProvider", () => {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
test("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}>
|
||||
<SentryProvider sentryDsn={sentryDsn} isEnabled>
|
||||
<div data-testid="child">Test Content</div>
|
||||
</SentryProvider>
|
||||
);
|
||||
@@ -59,22 +60,32 @@ describe("SentryProvider", () => {
|
||||
expect(initSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("renders children", () => {
|
||||
const sentryDsn = "https://examplePublicKey@o0.ingest.sentry.io/0";
|
||||
test("does not call Sentry.init when isEnabled is not provided", () => {
|
||||
const initSpy = vi.spyOn(Sentry, "init").mockImplementation(() => undefined);
|
||||
|
||||
render(
|
||||
<SentryProvider sentryDsn={sentryDsn}>
|
||||
<div data-testid="child">Test Content</div>
|
||||
</SentryProvider>
|
||||
);
|
||||
|
||||
expect(initSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("renders children", () => {
|
||||
render(
|
||||
<SentryProvider sentryDsn={sentryDsn} isEnabled>
|
||||
<div data-testid="child">Test Content</div>
|
||||
</SentryProvider>
|
||||
);
|
||||
expect(screen.getByTestId("child")).toHaveTextContent("Test Content");
|
||||
});
|
||||
|
||||
test("processes beforeSend correctly", () => {
|
||||
const sentryDsn = "https://examplePublicKey@o0.ingest.sentry.io/0";
|
||||
const initSpy = vi.spyOn(Sentry, "init").mockImplementation(() => undefined);
|
||||
|
||||
render(
|
||||
<SentryProvider sentryDsn={sentryDsn}>
|
||||
<SentryProvider sentryDsn={sentryDsn} isEnabled>
|
||||
<div data-testid="child">Test Content</div>
|
||||
</SentryProvider>
|
||||
);
|
||||
|
||||
@@ -6,11 +6,12 @@ import { useEffect } from "react";
|
||||
interface SentryProviderProps {
|
||||
children: React.ReactNode;
|
||||
sentryDsn?: string;
|
||||
isEnabled?: boolean;
|
||||
}
|
||||
|
||||
export const SentryProvider = ({ children, sentryDsn }: SentryProviderProps) => {
|
||||
export const SentryProvider = ({ children, sentryDsn, isEnabled }: SentryProviderProps) => {
|
||||
useEffect(() => {
|
||||
if (sentryDsn) {
|
||||
if (sentryDsn && isEnabled) {
|
||||
Sentry.init({
|
||||
dsn: sentryDsn,
|
||||
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { PROMETHEUS_ENABLED, SENTRY_DSN } from "@formbricks/lib/constants";
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { IS_PRODUCTION, PROMETHEUS_ENABLED, SENTRY_DSN } from "@formbricks/lib/constants";
|
||||
|
||||
export const onRequestError = Sentry.captureRequestError;
|
||||
|
||||
// instrumentation.ts
|
||||
export const register = async () => {
|
||||
if (process.env.NEXT_RUNTIME === "nodejs" && PROMETHEUS_ENABLED) {
|
||||
await import("./instrumentation-node");
|
||||
}
|
||||
if (process.env.NEXT_RUNTIME === "nodejs" && SENTRY_DSN) {
|
||||
if (process.env.NEXT_RUNTIME === "nodejs" && IS_PRODUCTION && SENTRY_DSN) {
|
||||
await import("./sentry.server.config");
|
||||
}
|
||||
if (process.env.NEXT_RUNTIME === "edge" && SENTRY_DSN) {
|
||||
if (process.env.NEXT_RUNTIME === "edge" && IS_PRODUCTION && SENTRY_DSN) {
|
||||
await import("./sentry.edge.config");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ApiErrorResponseV2 } from "@/modules/api/v2/types/api-error";
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
import { ZodError } from "zod";
|
||||
import { logger } from "@formbricks/logger";
|
||||
@@ -9,6 +10,16 @@ const mockRequest = new Request("http://localhost");
|
||||
// Add the request id header
|
||||
mockRequest.headers.set("x-request-id", "123");
|
||||
|
||||
vi.mock("@sentry/nextjs", () => ({
|
||||
captureException: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock SENTRY_DSN constant
|
||||
vi.mock("@formbricks/lib/constants", () => ({
|
||||
SENTRY_DSN: "mocked-sentry-dsn",
|
||||
IS_PRODUCTION: true,
|
||||
}));
|
||||
|
||||
describe("utils", () => {
|
||||
describe("handleApiError", () => {
|
||||
test('return bad request response for "bad_request" error', async () => {
|
||||
@@ -257,5 +268,45 @@ describe("utils", () => {
|
||||
// Restore the original method
|
||||
logger.withContext = originalWithContext;
|
||||
});
|
||||
|
||||
test("log API error details with SENTRY_DSN set", () => {
|
||||
// Mock the withContext method and its returned error method
|
||||
const errorMock = vi.fn();
|
||||
const withContextMock = vi.fn().mockReturnValue({
|
||||
error: errorMock,
|
||||
});
|
||||
|
||||
// Mock Sentry's captureException method
|
||||
vi.mocked(Sentry.captureException).mockImplementation((() => {}) as any);
|
||||
|
||||
// Replace the original withContext with our mock
|
||||
const originalWithContext = logger.withContext;
|
||||
logger.withContext = withContextMock;
|
||||
|
||||
const mockRequest = new Request("http://localhost/api/test");
|
||||
mockRequest.headers.set("x-request-id", "123");
|
||||
|
||||
const error: ApiErrorResponseV2 = {
|
||||
type: "internal_server_error",
|
||||
details: [{ field: "server", issue: "error occurred" }],
|
||||
};
|
||||
|
||||
logApiError(mockRequest, error);
|
||||
|
||||
// Verify withContext was called with the expected context
|
||||
expect(withContextMock).toHaveBeenCalledWith({
|
||||
correlationId: "123",
|
||||
error,
|
||||
});
|
||||
|
||||
// Verify error was called on the child logger
|
||||
expect(errorMock).toHaveBeenCalledWith("API Error Details");
|
||||
|
||||
// Verify Sentry.captureException was called
|
||||
expect(Sentry.captureException).toHaveBeenCalled();
|
||||
|
||||
// Restore the original method
|
||||
logger.withContext = originalWithContext;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
// @ts-nocheck // We can remove this when we update the prisma client and the typescript version
|
||||
// if we don't add this we get build errors with prisma due to type-nesting
|
||||
import { responses } from "@/modules/api/v2/lib/response";
|
||||
import { ApiErrorResponseV2 } from "@/modules/api/v2/types/api-error";
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { ZodCustomIssue, ZodIssue } from "zod";
|
||||
import { IS_PRODUCTION, SENTRY_DSN } from "@formbricks/lib/constants";
|
||||
import { logger } from "@formbricks/logger";
|
||||
|
||||
export const handleApiError = (request: Request, err: ApiErrorResponseV2): Response => {
|
||||
@@ -59,7 +63,6 @@ export const logApiRequest = (request: Request, responseStatus: number): void =>
|
||||
Object.entries(queryParams).filter(([key]) => !sensitiveParams.includes(key.toLowerCase()))
|
||||
);
|
||||
|
||||
// Info: Conveys general, operational messages about system progress and state.
|
||||
logger
|
||||
.withContext({
|
||||
method,
|
||||
@@ -73,7 +76,22 @@ export const logApiRequest = (request: Request, responseStatus: number): void =>
|
||||
};
|
||||
|
||||
export const logApiError = (request: Request, error: ApiErrorResponseV2): void => {
|
||||
const correlationId = request.headers.get("x-request-id") || "";
|
||||
const correlationId = request.headers.get("x-request-id") ?? "";
|
||||
|
||||
// Send the error to Sentry if the DSN is set and the error type is internal_server_error
|
||||
// This is useful for tracking down issues without overloading Sentry with errors
|
||||
if (SENTRY_DSN && IS_PRODUCTION && error.type === "internal_server_error") {
|
||||
const err = new Error(`API V2 error, id: ${correlationId}`);
|
||||
|
||||
Sentry.captureException(err, {
|
||||
extra: {
|
||||
details: error.details,
|
||||
type: error.type,
|
||||
correlationId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
logger
|
||||
.withContext({
|
||||
correlationId,
|
||||
|
||||
@@ -350,8 +350,13 @@ export const AddApiKeyModal = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>{t("environments.project.api_keys.organization_access")}</Label>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label>{t("environments.project.api_keys.organization_access")}</Label>
|
||||
<p className="text-sm text-slate-500">
|
||||
{t("environments.project.api_keys.organization_access_description")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="grid grid-cols-[auto_100px_100px] gap-4">
|
||||
<div></div>
|
||||
|
||||
@@ -300,36 +300,22 @@ nextConfig.images.remotePatterns.push({
|
||||
});
|
||||
|
||||
const sentryOptions = {
|
||||
// For all available options, see:
|
||||
// https://github.com/getsentry/sentry-webpack-plugin#options
|
||||
// For all available options, see:
|
||||
// https://www.npmjs.com/package/@sentry/webpack-plugin#options
|
||||
|
||||
// Suppresses source map uploading logs during build
|
||||
silent: true,
|
||||
org: "formbricks",
|
||||
project: "formbricks-cloud",
|
||||
|
||||
org: "formbricks",
|
||||
project: "formbricks-cloud",
|
||||
// Only print logs for uploading source maps in CI
|
||||
silent: true,
|
||||
|
||||
// Upload a larger set of source maps for prettier stack traces (increases build time)
|
||||
widenClientFileUpload: true,
|
||||
|
||||
// Automatically tree-shake Sentry logger statements to reduce bundle size
|
||||
disableLogger: true,
|
||||
};
|
||||
|
||||
const sentryConfig = {
|
||||
// For all available options, see:
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
|
||||
const exportConfig = (process.env.SENTRY_DSN && process.env.NODE_ENV === "production") ? withSentryConfig(nextConfig, sentryOptions) : nextConfig;
|
||||
|
||||
// Upload a larger set of source maps for prettier stack traces (increases build time)
|
||||
widenClientFileUpload: true,
|
||||
|
||||
// Transpiles SDK to be compatible with IE11 (increases bundle size)
|
||||
transpileClientSDK: true,
|
||||
|
||||
// Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers (increases server load)
|
||||
tunnelRoute: "/monitoring",
|
||||
|
||||
// Hides source maps from generated client bundles
|
||||
hideSourceMaps: true,
|
||||
|
||||
// Automatically tree-shake Sentry logger statements to reduce bundle size
|
||||
disableLogger: true,
|
||||
};
|
||||
|
||||
const exportConfig = process.env.SENTRY_DSN ? withSentryConfig(nextConfig, sentryOptions) : nextConfig;
|
||||
|
||||
export default nextConfig;
|
||||
export default exportConfig;
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { SENTRY_DSN } from "@formbricks/lib/constants";
|
||||
import { logger } from "@formbricks/logger";
|
||||
|
||||
if (SENTRY_DSN) {
|
||||
console.log("Sentry DSN found, enabling Sentry on the edge");
|
||||
logger.info("Sentry DSN found, enabling Sentry on the edge");
|
||||
|
||||
Sentry.init({
|
||||
dsn: SENTRY_DSN,
|
||||
@@ -18,5 +19,5 @@ if (SENTRY_DSN) {
|
||||
debug: false,
|
||||
});
|
||||
} else {
|
||||
console.warn("Sentry DSN not found, Sentry will be disabled on the edge");
|
||||
logger.warn("Sentry DSN not found, Sentry will be disabled on the edge");
|
||||
}
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { SENTRY_DSN } from "@formbricks/lib/constants";
|
||||
import { logger } from "@formbricks/logger";
|
||||
|
||||
if (SENTRY_DSN) {
|
||||
console.log("Sentry DSN found, enabling Sentry on the server");
|
||||
logger.info("Sentry DSN found, enabling Sentry on the server");
|
||||
|
||||
Sentry.init({
|
||||
dsn: SENTRY_DSN,
|
||||
@@ -31,5 +32,5 @@ if (SENTRY_DSN) {
|
||||
},
|
||||
});
|
||||
} else {
|
||||
console.warn("Sentry DSN not found, Sentry will be disabled on the server");
|
||||
logger.warn("Sentry DSN not found, Sentry will be disabled on the server");
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"exclude": ["../../.env", "node_modules"],
|
||||
|
||||
@@ -67,6 +67,8 @@ export default defineConfig({
|
||||
"modules/ee/contacts/segments/components/segment-settings.tsx",
|
||||
"modules/ee/contacts/api/v2/management/contacts/bulk/lib/contact.ts",
|
||||
"modules/ee/sso/components/**/*.tsx",
|
||||
"app/global-error.tsx",
|
||||
"app/error.tsx",
|
||||
"modules/account/**/*.tsx",
|
||||
"modules/account/**/*.ts",
|
||||
"modules/analysis/**/*.tsx",
|
||||
|
||||
@@ -79,8 +79,14 @@ x-environment: &environment
|
||||
# SURVEY_URL:
|
||||
|
||||
# Configure Formbricks usage within Formbricks.
|
||||
#FORMBRICKS_API_HOST:
|
||||
#FORMBRICKS_ENVIRONMENT_ID:
|
||||
# FORMBRICKS_API_HOST:
|
||||
# FORMBRICKS_ENVIRONMENT_ID:
|
||||
|
||||
# The SENTRY_DSN is used for error tracking and performance monitoring with Sentry.
|
||||
# SENTRY_DSN:
|
||||
# It's used for authentication when uploading source maps to Sentry, to make errors more readable.
|
||||
# SENTRY_AUTH_TOKEN:
|
||||
|
||||
|
||||
################################################### OPTIONAL (STORAGE) ###################################################
|
||||
|
||||
|
||||
@@ -65,6 +65,8 @@ These variables are present inside your machine’s docker-compose file. Restart
|
||||
| PROMETHEUS_ENABLED | Enables Prometheus metrics if set to 1. | optional | |
|
||||
| PROMETHEUS_EXPORTER_PORT | Port for Prometheus metrics. | optional | 9090 |
|
||||
| DOCKER_CRON_ENABLED | Controls whether cron jobs run in the Docker image. Set to 0 to disable (useful for cluster setups). | optional | 1 |
|
||||
| SURVEY_URL | Set this to change the domain of the survey. | optional | WEBAPP_URL |
|
||||
| SURVEY_URL | Set this to change the domain of the survey. | optional | WEBAPP_URL
|
||||
| SENTRY_DSN | Set this to track errors and monitor performance in Sentry. | optional |
|
||||
| SENTRY_AUTH_TOKEN | Set this if you want to make errors more readable in Sentry. | optional |
|
||||
|
||||
Note: If you want to configure something that is not possible via above, please open an issue on our GitHub repo here or reach out to us on Github Discussions and we’ll try our best to work out a solution with you.
|
||||
|
||||
@@ -789,6 +789,7 @@
|
||||
"no_api_keys_yet": "Du hast noch keine API-Schlüssel",
|
||||
"no_env_permissions_found": "Keine Umgebungsberechtigungen gefunden",
|
||||
"organization_access": "Organisationszugang",
|
||||
"organization_access_description": "Wähle Lese- oder Schreibrechte für organisationsweite Ressourcen aus.",
|
||||
"permissions": "Berechtigungen",
|
||||
"project_access": "Projektzugriff",
|
||||
"secret": "Geheimnis",
|
||||
|
||||
@@ -789,6 +789,7 @@
|
||||
"no_api_keys_yet": "You don't have any API keys yet",
|
||||
"no_env_permissions_found": "No environment permissions found",
|
||||
"organization_access": "Organization Access",
|
||||
"organization_access_description": "Select read or write privileges for organization-wide resources.",
|
||||
"permissions": "Permissions",
|
||||
"project_access": "Project Access",
|
||||
"secret": "Secret",
|
||||
|
||||
@@ -789,6 +789,7 @@
|
||||
"no_api_keys_yet": "Vous n'avez pas encore de clés API.",
|
||||
"no_env_permissions_found": "Aucune autorisation d'environnement trouvée",
|
||||
"organization_access": "Accès à l'organisation",
|
||||
"organization_access_description": "Sélectionnez les privilèges de lecture ou d'écriture pour les ressources de l'organisation.",
|
||||
"permissions": "Permissions",
|
||||
"project_access": "Accès au projet",
|
||||
"secret": "Secret",
|
||||
|
||||
@@ -789,6 +789,7 @@
|
||||
"no_api_keys_yet": "Você ainda não tem nenhuma chave de API",
|
||||
"no_env_permissions_found": "Nenhuma permissão de ambiente encontrada",
|
||||
"organization_access": "Acesso à Organização",
|
||||
"organization_access_description": "Selecione privilégios de leitura ou escrita para recursos de toda a organização.",
|
||||
"permissions": "Permissões",
|
||||
"project_access": "Acesso ao Projeto",
|
||||
"secret": "Segredo",
|
||||
|
||||
@@ -789,6 +789,7 @@
|
||||
"no_api_keys_yet": "Ainda não tem nenhuma chave API",
|
||||
"no_env_permissions_found": "Nenhuma permissão de ambiente encontrada",
|
||||
"organization_access": "Acesso à Organização",
|
||||
"organization_access_description": "Selecione privilégios de leitura ou escrita para recursos de toda a organização.",
|
||||
"permissions": "Permissões",
|
||||
"project_access": "Acesso ao Projeto",
|
||||
"secret": "Segredo",
|
||||
|
||||
@@ -789,6 +789,7 @@
|
||||
"no_api_keys_yet": "您還沒有任何 API 金鑰",
|
||||
"no_env_permissions_found": "找不到環境權限",
|
||||
"organization_access": "組織 Access",
|
||||
"organization_access_description": "選擇組織範圍資源的讀取或寫入權限。",
|
||||
"permissions": "權限",
|
||||
"project_access": "專案存取",
|
||||
"secret": "密碼",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"exclude": ["dist", "build", "node_modules", "../../packages/types/surveys.d.ts"],
|
||||
|
||||
2
pnpm-lock.yaml
generated
2
pnpm-lock.yaml
generated
@@ -21319,7 +21319,7 @@ snapshots:
|
||||
dependencies:
|
||||
ansi-align: 3.0.1
|
||||
camelcase: 7.0.1
|
||||
chalk: 5.0.1
|
||||
chalk: 5.4.1
|
||||
cli-boxes: 3.0.0
|
||||
string-width: 5.1.2
|
||||
type-fest: 2.19.0
|
||||
|
||||
Reference in New Issue
Block a user