diff --git a/npm-packages/common/config/rush/pnpm-lock.yaml b/npm-packages/common/config/rush/pnpm-lock.yaml index 3ab2af62..187d7f80 100644 --- a/npm-packages/common/config/rush/pnpm-lock.yaml +++ b/npm-packages/common/config/rush/pnpm-lock.yaml @@ -612,9 +612,6 @@ importers: formik: specifier: ~2.2.9 version: 2.2.10(react@18.3.1) - js-base64: - specifier: ~3.7.4 - version: 3.7.7 launchdarkly-react-client-sdk: specifier: ^3.0.4 version: 3.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -633,9 +630,6 @@ importers: posthog-js: specifier: ~1.211.3 version: 1.211.3 - prop-types: - specifier: ~15.8.1 - version: 15.8.1 react: specifier: ^18.0.0 version: 18.3.1 @@ -651,9 +645,6 @@ importers: react-sparklines: specifier: ~1.7.0 version: 1.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-table: - specifier: 7.8.0 - version: 7.8.0(react@18.3.1) react-use: specifier: ~17.6.0 version: 17.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -901,9 +892,6 @@ importers: '@radix-ui/react-tooltip': specifier: ~1.1.0 version: 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@sentry/nextjs': - specifier: 7.120.2 - version: 7.120.2(encoding@0.1.13)(next@14.2.21(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(supports-color@10.0.0)(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))) '@tanstack/react-table': specifier: ~8.20.6 version: 8.20.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -26929,7 +26917,7 @@ snapshots: '@sentry/types': 7.120.2 '@sentry/utils': 7.120.2 - '@sentry/cli@1.77.3(encoding@0.1.13)(supports-color@10.0.0)': + '@sentry/cli@1.77.3(encoding@0.1.13)': dependencies: https-proxy-agent: 5.0.1(supports-color@10.0.0) mkdirp: 0.5.6 @@ -26968,7 +26956,7 @@ snapshots: '@sentry/types': 7.120.2 '@sentry/utils': 7.120.2 '@sentry/vercel-edge': 7.120.2 - '@sentry/webpack-plugin': 1.21.0(encoding@0.1.13)(supports-color@10.0.0) + '@sentry/webpack-plugin': 1.21.0(encoding@0.1.13) chalk: 3.0.0 next: 14.2.21(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 @@ -26981,29 +26969,6 @@ snapshots: - encoding - supports-color - '@sentry/nextjs@7.120.2(encoding@0.1.13)(next@14.2.21(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(supports-color@10.0.0)(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15)))': - dependencies: - '@rollup/plugin-commonjs': 24.0.0(rollup@2.79.2) - '@sentry/core': 7.120.2 - '@sentry/integrations': 7.120.2 - '@sentry/node': 7.120.2 - '@sentry/react': 7.120.2(react@18.3.1) - '@sentry/types': 7.120.2 - '@sentry/utils': 7.120.2 - '@sentry/vercel-edge': 7.120.2 - '@sentry/webpack-plugin': 1.21.0(encoding@0.1.13)(supports-color@10.0.0) - chalk: 3.0.0 - next: 14.2.21(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - resolve: 1.22.8 - rollup: 2.79.2 - stacktrace-parser: 0.1.10 - optionalDependencies: - webpack: 5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15)) - transitivePeerDependencies: - - encoding - - supports-color - '@sentry/node@7.120.2': dependencies: '@sentry-internal/tracing': 7.120.2 @@ -27052,9 +27017,9 @@ snapshots: '@sentry/types': 7.120.2 '@sentry/utils': 7.120.2 - '@sentry/webpack-plugin@1.21.0(encoding@0.1.13)(supports-color@10.0.0)': + '@sentry/webpack-plugin@1.21.0(encoding@0.1.13)': dependencies: - '@sentry/cli': 1.77.3(encoding@0.1.13)(supports-color@10.0.0) + '@sentry/cli': 1.77.3(encoding@0.1.13) webpack-sources: 3.2.3 transitivePeerDependencies: - encoding diff --git a/npm-packages/dashboard-common/package.json b/npm-packages/dashboard-common/package.json index 890e051b..b53275a7 100644 --- a/npm-packages/dashboard-common/package.json +++ b/npm-packages/dashboard-common/package.json @@ -23,7 +23,6 @@ "@monaco-editor/react": "4.5.1", "@radix-ui/react-icons": "~1.3.0", "@radix-ui/react-tooltip": "~1.1.0", - "@sentry/nextjs": "7.120.2", "@tanstack/react-table": "~8.20.6", "acorn": "~8.8.1", "base64-js": "^1.5.1", diff --git a/npm-packages/dashboard-common/src/elements/BigChart.tsx b/npm-packages/dashboard-common/src/elements/BigChart.tsx index 78eb7c3c..b6bfb3c5 100644 --- a/npm-packages/dashboard-common/src/elements/BigChart.tsx +++ b/npm-packages/dashboard-common/src/elements/BigChart.tsx @@ -1,5 +1,4 @@ -import { captureException } from "@sentry/nextjs"; -import { useEffect, useState } from "react"; +import { useContext, useEffect, useState } from "react"; import { CartesianGrid, Legend, @@ -14,6 +13,7 @@ import { LoadingTransition } from "@common/elements/Loading"; import { ChartData, ChartDataSource } from "@common/lib/charts/types"; import { Callout } from "@common/elements/Callout"; import { ChartTooltip } from "@common/elements/ChartTooltip"; +import { DeploymentInfoContext } from "@common/lib/deploymentContext"; export function BigChart({ dataSources, @@ -33,6 +33,8 @@ export function BigChart({ const [startDate] = useState(initStartDate); const [endDate] = useState(initEndDate); + const { captureException } = useContext(DeploymentInfoContext); + useEffect(() => { async function getChartData() { if (startDate < endDate) { @@ -55,7 +57,7 @@ export function BigChart({ } } void getChartData(); - }, [dataSources, startDate, endDate]); + }, [dataSources, startDate, endDate, captureException]); return (
diff --git a/npm-packages/dashboard-common/src/elements/ObjectEditor/ObjectEditor.tsx b/npm-packages/dashboard-common/src/elements/ObjectEditor/ObjectEditor.tsx index b6fd76de..b20337c0 100644 --- a/npm-packages/dashboard-common/src/elements/ObjectEditor/ObjectEditor.tsx +++ b/npm-packages/dashboard-common/src/elements/ObjectEditor/ObjectEditor.tsx @@ -11,7 +11,6 @@ import React, { useCallback, useEffect, useRef, useState } from "react"; import isArray from "lodash/isArray"; import isPlainObject from "lodash/isPlainObject"; import { UNDEFINED_PLACEHOLDER } from "system-udfs/convex/_system/frontend/patchDocumentsFields"; -import { captureMessage } from "@sentry/nextjs"; import { useMount } from "react-use"; import { useRouter } from "next/router"; import { stringifyValue } from "@common/lib/stringifyValue"; @@ -333,7 +332,6 @@ function setErrorMarkers( .getModels() ?.find((m) => path.replace(":", "_") === m.uri.path.slice(1)); if (!model) { - captureMessage(`Model not found in Monaco editor for path: ${path}`); return; } monaco.editor.setModelMarkers(model, "", markers); diff --git a/npm-packages/dashboard-common/src/elements/ObjectEditor/useIdDecorations.tsx b/npm-packages/dashboard-common/src/elements/ObjectEditor/useIdDecorations.tsx index a764c187..61a4db9d 100644 --- a/npm-packages/dashboard-common/src/elements/ObjectEditor/useIdDecorations.tsx +++ b/npm-packages/dashboard-common/src/elements/ObjectEditor/useIdDecorations.tsx @@ -7,7 +7,6 @@ import { useRouter } from "next/router"; import { cn } from "@common/lib/cn"; import { GenericDocument } from "convex/server"; import { SourceLocation } from "acorn"; -import { captureMessage } from "@sentry/nextjs"; import type { editor } from "monaco-editor/esm/vs/editor/editor.api"; import { stringifyValue } from "@common/lib/stringifyValue"; import { @@ -69,7 +68,6 @@ export function useIdDecorations( .getModels() ?.find((m) => path.replace(":", "_") === m.uri.path.slice(1)); if (!model) { - captureMessage(`Model not found in Monaco editor for path: ${path}`); return; } diff --git a/npm-packages/dashboard-common/src/features/data/components/DataToolbar/ClearTableConfirmation.tsx b/npm-packages/dashboard-common/src/features/data/components/DataToolbar/ClearTableConfirmation.tsx index 244bf57f..dffccb9a 100644 --- a/npm-packages/dashboard-common/src/features/data/components/DataToolbar/ClearTableConfirmation.tsx +++ b/npm-packages/dashboard-common/src/features/data/components/DataToolbar/ClearTableConfirmation.tsx @@ -1,10 +1,10 @@ -import { captureException } from "@sentry/nextjs"; import { Cursor } from "convex/server"; -import { useState } from "react"; +import { useContext, useState } from "react"; import { useMountedState } from "react-use"; import { useInvalidateShapes } from "@common/features/data/lib/api"; import { ConfirmationDialog } from "@common/elements/ConfirmationDialog"; import { toast } from "@common/lib/utils"; +import { DeploymentInfoContext } from "@common/lib/deploymentContext"; export function ClearTableConfirmation({ numRows, @@ -49,6 +49,8 @@ export function ClearTableConfirmation({ const invalidateShapes = useInvalidateShapes(); + const { captureException } = useContext(DeploymentInfoContext); + return ( ({ - id: 0, - name: "Team", - slug: "team", - }), - useTeamMembers: () => [], - useTeamEntitlements: () => ({ - auditLogsEnabled: true, - }), - useCurrentUsageBanner: () => null, - useCurrentProject: () => ({ - id: 0, - name: "Project", - slug: "project", - teamId: 0, - }), - useLogDeploymentEvent: () => () => {}, - useCurrentDeployment: () => ({ - id: 0, - name: "local", - deploymentType: "prod", - projectId: 0, - kind: "local", - previewIdentifier: null, - }), - useHasProjectAdminPermissions: () => true, - useIsDeploymentPaused: () => false, - useProjectEnvironmentVariables: () => ({ configs: [] }), - CloudImport: ({ sourceCloudBackupId }: { sourceCloudBackupId: number }) => ( -
{sourceCloudBackupId}
- ), - TeamMemberLink: () =>
, - useTeamUsageState: () => "Default", - teamsURI: "/", - projectsURI: "/", - deploymentsURI: "/", - isSelfHosted: true, -}; +import { mockDeploymentInfo } from "@common/lib/mockDeploymentInfo"; jest.mock("convex/react", () => ({ useQuery: jest.fn(), @@ -121,7 +76,9 @@ describe("DataToolbar", () => { // @ts-expect-error useRouter.mockReturnValue({ query, replace: jest.fn() }); return render( - , + + , + , ); }; @@ -156,35 +113,34 @@ describe("DataToolbar", () => { activeSchema: null, ...hookProps, }); + return ( - - new Map(), [])}> - {popupState.popupEl} - - - + new Map(), [])}> + {popupState.popupEl} + + ); } diff --git a/npm-packages/dashboard-common/src/features/data/components/Table/TableContextMenu.test.tsx b/npm-packages/dashboard-common/src/features/data/components/Table/TableContextMenu.test.tsx index a5f6d158..b127b851 100644 --- a/npm-packages/dashboard-common/src/features/data/components/Table/TableContextMenu.test.tsx +++ b/npm-packages/dashboard-common/src/features/data/components/Table/TableContextMenu.test.tsx @@ -9,6 +9,8 @@ import { TableContextMenuProps, } from "@common/features/data/components/Table/TableContextMenu"; import { mockConvexReactClient } from "@common/lib/mockConvexReactClient"; +import { DeploymentInfoContext } from "@common/lib/deploymentContext"; +import { mockDeploymentInfo } from "@common/lib/mockDeploymentInfo"; jest.mock("next/router", () => jest.requireActual("next-router-mock")); @@ -72,9 +74,11 @@ describe("TableContextMenu", () => { const renderWithProvider = (props: Partial = {}) => render( - - - , + + + + + , ); beforeEach(() => { diff --git a/npm-packages/dashboard-common/src/features/data/components/Table/TableContextMenu.tsx b/npm-packages/dashboard-common/src/features/data/components/Table/TableContextMenu.tsx index 911490bb..8be5c6c1 100644 --- a/npm-packages/dashboard-common/src/features/data/components/Table/TableContextMenu.tsx +++ b/npm-packages/dashboard-common/src/features/data/components/Table/TableContextMenu.tsx @@ -1,4 +1,3 @@ -import * as Sentry from "@sentry/nextjs"; import { ClipboardCopyIcon, EnterFullScreenIcon, @@ -8,7 +7,7 @@ import { TrashIcon, } from "@radix-ui/react-icons"; import FunnelIcon from "@heroicons/react/24/outline/FunnelIcon"; -import React, { useCallback, useState } from "react"; +import React, { useCallback, useContext, useState } from "react"; import { GenericDocument } from "convex/server"; import { Value, convexToJson } from "convex/values"; import { @@ -28,6 +27,7 @@ import { } from "@common/features/data/components/Table/DataCell/utils/cellActions"; import { stringifyValue } from "@common/lib/stringifyValue"; import { useNents } from "@common/lib/useNents"; +import { DeploymentInfoContext } from "@common/lib/deploymentContext"; export function useTableContextMenuState(): { contextMenuState: TableContextMenuState | null; @@ -88,6 +88,8 @@ export function TableContextMenu({ selectedNent && selectedNent.state !== "active" ); + const { captureMessage } = useContext(DeploymentInfoContext); + const disableEditDoc = !canManageTable || isInUnmountedComponent; const disableEdit = state?.selectedCell?.column.startsWith("_") || disableEditDoc; @@ -113,9 +115,7 @@ export function TableContextMenu({ const selectedRowId = state.selectedCell?.rowId; const document = data.find((row) => row._id === selectedRowId); if (!document) { - Sentry.captureMessage( - "Can’t find the right-clicked document in data", - ); + captureMessage("Can’t find the right-clicked document in data"); return; } } @@ -383,8 +383,9 @@ function FilterWithSubmenu({ addDraftFilter: (newFilter: Filter) => void; defaultDocument: GenericDocument; }) { + const { captureMessage } = useContext(DeploymentInfoContext); if (!state.selectedCell) { - Sentry.captureMessage("No selected cell in FilterWithSubmenu"); + captureMessage("No selected cell in FilterWithSubmenu"); return null; } return ( diff --git a/npm-packages/dashboard-common/src/features/data/components/Table/utils/arrowKeyHandler.ts b/npm-packages/dashboard-common/src/features/data/components/Table/utils/arrowKeyHandler.ts index fcc364b1..6369d575 100644 --- a/npm-packages/dashboard-common/src/features/data/components/Table/utils/arrowKeyHandler.ts +++ b/npm-packages/dashboard-common/src/features/data/components/Table/utils/arrowKeyHandler.ts @@ -1,5 +1,3 @@ -import { captureMessage } from "@sentry/nextjs"; - function newDirection( { colIndex, rowIndex }: { colIndex: number; rowIndex: number }, key: string, @@ -14,7 +12,6 @@ function newDirection( case "ArrowDown": return { newColIndex: colIndex, newRowIndex: rowIndex + 1 }; default: - captureMessage(`Unexpected key ${key}`); return { newColIndex: colIndex, newRowIndex: rowIndex }; } } diff --git a/npm-packages/dashboard-common/src/features/data/lib/api.ts b/npm-packages/dashboard-common/src/features/data/lib/api.ts index 328c1d63..6aeee522 100644 --- a/npm-packages/dashboard-common/src/features/data/lib/api.ts +++ b/npm-packages/dashboard-common/src/features/data/lib/api.ts @@ -1,6 +1,5 @@ -import { useCallback } from "react"; +import { useCallback, useContext } from "react"; import useSWR, { useSWRConfig } from "swr"; -import { reportHttpError } from "@common/lib/utils"; import { useDeploymentUrl, useAdminKey, @@ -10,6 +9,7 @@ import { } from "@common/lib/deploymentApi"; import { deploymentFetch } from "@common/lib/fetching"; import { useNents } from "@common/lib/useNents"; +import { DeploymentInfoContext } from "@common/lib/deploymentContext"; export function useDeleteTables(): ( tableNames: string[], @@ -17,6 +17,7 @@ export function useDeleteTables(): ( ) => Promise<{ success: false; error: string } | { success: true }> { const deploymentUrl = useDeploymentUrl(); const adminKey = useAdminKey(); + const { reportHttpError } = useContext(DeploymentInfoContext); return async (tableNames: string[], componentId: string | null) => { const body = JSON.stringify({ tableNames, componentId }); diff --git a/npm-packages/dashboard-common/src/features/data/lib/useDataToolbarActions.ts b/npm-packages/dashboard-common/src/features/data/lib/useDataToolbarActions.ts index 4a67923c..c18ccfce 100644 --- a/npm-packages/dashboard-common/src/features/data/lib/useDataToolbarActions.ts +++ b/npm-packages/dashboard-common/src/features/data/lib/useDataToolbarActions.ts @@ -1,6 +1,5 @@ import { Cursor, GenericDocument } from "convex/server"; import { ConvexError } from "convex/values"; -import { captureException } from "@sentry/nextjs"; import { useMutation } from "convex/react"; import udfs from "udfs"; import { Id } from "system-udfs/convex/_generated/dataModel"; @@ -10,6 +9,8 @@ import { } from "@common/features/data/lib/api"; import { useNents } from "@common/lib/useNents"; import { toast } from "@common/lib/utils"; +import { useContext } from "react"; +import { DeploymentInfoContext } from "@common/lib/deploymentContext"; export function useDataToolbarActions({ tableName, @@ -33,6 +34,7 @@ export function useDataToolbarActions({ ) => Promise<{ continueCursor: Cursor; deleted: number; hasMore: boolean }>; deleteRows: (rowIds: Set) => Promise; } { + const { captureException } = useContext(DeploymentInfoContext); const invalidateShapes = useInvalidateShapes(); const documentAdd = useMutation(udfs.addDocument.default); diff --git a/npm-packages/dashboard-common/src/features/data/lib/useToolPopup.tsx b/npm-packages/dashboard-common/src/features/data/lib/useToolPopup.tsx index 1117862e..9d7befca 100644 --- a/npm-packages/dashboard-common/src/features/data/lib/useToolPopup.tsx +++ b/npm-packages/dashboard-common/src/features/data/lib/useToolPopup.tsx @@ -1,8 +1,7 @@ -import { captureMessage } from "@sentry/nextjs"; import { useMutation } from "convex/react"; import { Cursor, GenericDocument } from "convex/server"; import { ConvexError, ValidatorJSON } from "convex/values"; -import { useState } from "react"; +import { useContext, useState } from "react"; import udfs from "udfs"; import { SchemaJson } from "system-udfs/convex/_system/frontend/lib/filters"; import { useNents } from "@common/lib/useNents"; @@ -15,6 +14,7 @@ import { EditFieldsPanel } from "@common/features/data/components/Table/EditDocu import { TableMetrics } from "@common/features/data/components/TableMetrics"; import { TableSchemaAndIndexes } from "@common/features/data/components/TableSchemaAndIndexes"; import { useDefaultDocument } from "@common/features/data/lib/useDefaultDocument"; +import { DeploymentInfoContext } from "@common/lib/deploymentContext"; export type PopupState = ReturnType; @@ -197,6 +197,7 @@ function EditSingleDocumentPanel({ const replaceDocument = useMutation(udfs.replaceDocument.default); const invalidateShapes = useInvalidateShapes(); const { selectedNent } = useNents(); + const { captureMessage } = useContext(DeploymentInfoContext); return ( ({ - id: 0, - name: "Team", - slug: "team", - }), - useTeamMembers: () => [], - useTeamEntitlements: () => ({ - auditLogsEnabled: true, - }), - useCurrentUsageBanner: () => null, - useCurrentProject: () => ({ - id: 0, - name: "Project", - slug: "project", - teamId: 0, - }), - useLogDeploymentEvent: () => () => {}, - useCurrentDeployment: () => ({ - id: 0, - name: "local", - deploymentType: "prod", - projectId: 0, - kind: "local", - previewIdentifier: null, - }), - useHasProjectAdminPermissions: () => true, - useIsDeploymentPaused: () => false, - useProjectEnvironmentVariables: () => ({ configs: [] }), - CloudImport: ({ sourceCloudBackupId }: { sourceCloudBackupId: number }) => ( -
{sourceCloudBackupId}
- ), - TeamMemberLink: () =>
, - useTeamUsageState: () => "Default", - teamsURI: "/", - projectsURI: "/", - deploymentsURI: "/", - isSelfHosted: true, -}; +import { DeploymentInfoContext } from "@common/lib/deploymentContext"; +import { mockDeploymentInfo } from "@common/lib/mockDeploymentInfo"; const mockRouter = jest .fn() @@ -100,7 +55,7 @@ describe("FileStorageContent", () => { const setup = () => act(() => render( - + @@ -140,7 +95,7 @@ describe("FileStorageContent", () => { const setup = () => act(() => render( - + diff --git a/npm-packages/dashboard-common/src/features/files/components/FileStorageView.tsx b/npm-packages/dashboard-common/src/features/files/components/FileStorageView.tsx index d158d2da..ee463bfc 100644 --- a/npm-packages/dashboard-common/src/features/files/components/FileStorageView.tsx +++ b/npm-packages/dashboard-common/src/features/files/components/FileStorageView.tsx @@ -6,7 +6,6 @@ import { TrashIcon, UploadIcon, } from "@radix-ui/react-icons"; -import * as Sentry from "@sentry/nextjs"; import { useMutation, usePaginatedQuery, useQuery } from "convex/react"; import Link from "next/link"; import React, { useContext, useEffect, useMemo, useRef, useState } from "react"; @@ -240,7 +239,6 @@ export function useUploadFiles() { } return { status: "success", name: file.name }; } catch (err) { - Sentry.captureException(err); return { status: "failure", name: file.name }; } } diff --git a/npm-packages/dashboard-common/src/features/schedules/lib/CronsProvider.tsx b/npm-packages/dashboard-common/src/features/schedules/lib/CronsProvider.tsx index 9bf3220a..5bb7318e 100644 --- a/npm-packages/dashboard-common/src/features/schedules/lib/CronsProvider.tsx +++ b/npm-packages/dashboard-common/src/features/schedules/lib/CronsProvider.tsx @@ -1,5 +1,4 @@ -import * as Sentry from "@sentry/nextjs"; -import { ReactNode, useMemo } from "react"; +import { ReactNode, useContext, useMemo } from "react"; import { useQuery } from "convex/react"; import udfs from "udfs"; import { @@ -12,6 +11,7 @@ import { useInMemoryDocumentCache } from "@common/features/schedules/lib/useInMe import { useListModules } from "@common/lib/functions/useListModules"; import { createContextHook } from "@common/lib/createContextHook"; import { useNents } from "@common/lib/useNents"; +import { DeploymentInfoContext } from "@common/lib/deploymentContext"; type CronJobsContextType = { cronsModule: Module | undefined; @@ -30,6 +30,7 @@ export function CronJobsProviderWithCronHistory({ }: { children: ReactNode; }) { + const { captureMessage } = useContext(DeploymentInfoContext); // Get functions const modules = useListModules(); // Get cron jobs @@ -82,16 +83,14 @@ export function CronJobsProviderWithCronHistory({ .map((identifier) => { const cronJob = cronJobsMap.get(identifier)!; if (!cronJob) { - Sentry.captureMessage( - `No CronJob found for CronSpec ${identifier}`, - ); + captureMessage(`No CronJob found for CronSpec ${identifier}`); } return cronJob; }) .filter((x) => x), // remove empty cronsModuleInner, ]; - }, [modules, cronJobs, cronJobRuns]); + }, [cronJobs, modules, cronJobRuns, captureMessage]); return ( Promise { const deploymentUrl = useDeploymentUrl(); const adminKey = useAdminKey(); const { selectedNent } = useNents(); - const { deploymentsURI } = useContext(DeploymentInfoContext); + const { deploymentsURI, reportHttpError } = useContext(DeploymentInfoContext); return async (udfPath?: string) => { const body = JSON.stringify({ @@ -65,6 +65,7 @@ export function useCancelJob(): ( ) => Promise { const deploymentUrl = useDeploymentUrl(); const adminKey = useAdminKey(); + const { reportHttpError } = useContext(DeploymentInfoContext); return async (id: string, componentId: string | null) => { const body = JSON.stringify({ id, componentId }); diff --git a/npm-packages/dashboard-common/src/features/settings/components/DeploymentEnvironmentVariables.test.tsx b/npm-packages/dashboard-common/src/features/settings/components/DeploymentEnvironmentVariables.test.tsx index cbc2b281..1991f7b7 100644 --- a/npm-packages/dashboard-common/src/features/settings/components/DeploymentEnvironmentVariables.test.tsx +++ b/npm-packages/dashboard-common/src/features/settings/components/DeploymentEnvironmentVariables.test.tsx @@ -10,53 +10,10 @@ import { import { mockConvexReactClient } from "@common/lib/mockConvexReactClient"; import { ConnectedDeploymentContext, - DeploymentInfo, DeploymentInfoContext, } from "@common/lib/deploymentContext"; import { ProjectEnvVarConfig } from "@common/features/settings/lib/types"; - -const deploymentInfo: DeploymentInfo = { - ok: true, - deploymentUrl: process.env.NEXT_PUBLIC_DEPLOYMENT_URL!, - adminKey: process.env.NEXT_PUBLIC_ADMIN_KEY!, - useCurrentTeam: () => ({ - id: 0, - name: "Team", - slug: "team", - }), - useTeamMembers: () => [], - useTeamEntitlements: () => ({ - auditLogsEnabled: true, - }), - useCurrentUsageBanner: () => null, - useCurrentProject: () => ({ - id: 0, - name: "Project", - slug: "project", - teamId: 0, - }), - useCurrentDeployment: () => ({ - id: 0, - name: "local", - deploymentType: "prod", - projectId: 0, - kind: "local", - previewIdentifier: null, - }), - useHasProjectAdminPermissions: () => true, - useIsDeploymentPaused: () => false, - useProjectEnvironmentVariables: () => ({ configs: [] }), - CloudImport: ({ sourceCloudBackupId }: { sourceCloudBackupId: number }) => ( -
{sourceCloudBackupId}
- ), - useLogDeploymentEvent: () => () => {}, - TeamMemberLink: () =>
, - useTeamUsageState: () => "Default", - teamsURI: "/", - projectsURI: "/", - deploymentsURI: "/", - isSelfHosted: true, -}; +import { mockDeploymentInfo } from "@common/lib/mockDeploymentInfo"; const createEnvironmentVariable = ( name: string, @@ -221,7 +178,7 @@ describe("Prefilling env var name", () => { function renderUI() { render( - + Promise { const deploymentUrl = useDeploymentUrl(); const adminKey = useAdminKey(); + const { reportHttpError } = useContext(DeploymentInfoContext); return async (changes) => { const body = JSON.stringify({ changes }); const res = await fetch( @@ -40,6 +44,7 @@ export function useUpdateEnvVars(): ( export function useDeleteComponent() { const deploymentUrl = useDeploymentUrl(); const adminKey = useAdminKey(); + const { reportHttpError } = useContext(DeploymentInfoContext); return useCallback( async (id: Id<"_components">) => { const res = await fetch(`${deploymentUrl}/api/delete_component`, { @@ -56,7 +61,7 @@ export function useDeleteComponent() { toast("error", err.message); } }, - [deploymentUrl, adminKey], + [deploymentUrl, adminKey, reportHttpError], ); } @@ -69,6 +74,7 @@ export function useChangeDeploymentState(): ( } const deploymentUrl = useDeploymentUrl(); const authHeader = useDeploymentAuthHeader(); + const { reportHttpError } = useContext(DeploymentInfoContext); return async (newState) => { const body = JSON.stringify({ newState }); const res = await fetch(`${deploymentUrl}/api/change_deployment_state`, { diff --git a/npm-packages/dashboard-common/src/index.ts b/npm-packages/dashboard-common/src/index.ts index f4f49b91..55a5d82c 100644 --- a/npm-packages/dashboard-common/src/index.ts +++ b/npm-packages/dashboard-common/src/index.ts @@ -4,12 +4,7 @@ */ // General dashboard exports -export { - reportHttpError, - toast, - dismissToast, - backoffWithJitter, -} from "@common/lib/utils"; +export { toast, dismissToast, backoffWithJitter } from "@common/lib/utils"; export * from "@common/lib/fetching"; export * from "@common/lib/useGlobalLocalStorage"; export * from "@common/lib/useCopy"; diff --git a/npm-packages/dashboard-common/src/layouts/SidebarDetailLayout.tsx b/npm-packages/dashboard-common/src/layouts/SidebarDetailLayout.tsx index b5ca2053..bc837d35 100644 --- a/npm-packages/dashboard-common/src/layouts/SidebarDetailLayout.tsx +++ b/npm-packages/dashboard-common/src/layouts/SidebarDetailLayout.tsx @@ -5,7 +5,6 @@ import { ReactNode, useContext, useRef, useState } from "react"; import { useLocalStorage } from "react-use"; import { gt } from "semver"; import udfs from "udfs"; -import { ErrorBoundary } from "@sentry/nextjs"; import { useRouter } from "next/router"; import { ImperativePanelHandle, @@ -16,28 +15,12 @@ import { import { DragHandleDots2Icon } from "@radix-ui/react-icons"; import { cn } from "@common/lib/cn"; -import { Callout } from "@common/elements/Callout"; import { PageContent } from "@common/elements/PageContent"; import { DeploymentInfoContext } from "@common/lib/deploymentContext"; import { Tooltip } from "@common/elements/Tooltip"; import { ClosePanelButton } from "@common/elements/ClosePanelButton"; import { Button } from "@common/elements/Button"; -function Fallback({ error }: { error: Error | null }) { - return ( -
-
- -
-

We encountered an error loading this page.

- {error && {error.toString()}} -
-
-
-
- ); -} - export function SidebarDetailLayout({ sidebarComponent, contentComponent, @@ -54,6 +37,8 @@ export function SidebarDetailLayout({ const [collapsed, setCollapsed] = useState(false); const panelRef = useRef(null); + const { ErrorBoundary } = useContext(DeploymentInfoContext); + return (
- +
{contentComponent}
diff --git a/npm-packages/dashboard-common/src/lib/deploymentContext.tsx b/npm-packages/dashboard-common/src/lib/deploymentContext.tsx index e8a08cbc..1a222c0a 100644 --- a/npm-packages/dashboard-common/src/lib/deploymentContext.tsx +++ b/npm-packages/dashboard-common/src/lib/deploymentContext.tsx @@ -10,7 +10,6 @@ import { useState, } from "react"; import { useRouter } from "next/router"; -import { captureMessage } from "@sentry/nextjs"; import { cn } from "@common/lib/cn"; import { LoadingLogo } from "@common/elements/Loading"; import { ProjectEnvVarConfig } from "@common/features/settings/lib/types"; @@ -25,6 +24,13 @@ export type DeploymentInfo = ( } | { ok: false; errorCode: string; errorMessage: string } ) & { + captureMessage: (msg: string) => void; + captureException: (e: any) => void; + reportHttpError: ( + method: string, + url: string, + error: { code: string; message: string }, + ) => void; useCurrentTeam(): | { id: number; @@ -73,6 +79,7 @@ export type DeploymentInfo = ( memberId?: number | null; name: string; }): JSX.Element; + ErrorBoundary(props: { children: ReactNode }): JSX.Element; teamsURI: string; projectsURI: string; deploymentsURI: string; @@ -247,6 +254,8 @@ export function DeploymentApiProvider({ } }, [router.isReady, router.query, deploymentOverride]); + const deploymentInfoContext = useContext(DeploymentInfoContext); + const connected = useConnectedDeployment(deploymentName); // eslint-disable-next-line react/jsx-no-constructed-context-values let value: MaybeConnectedDeployment = { @@ -273,7 +282,7 @@ export function DeploymentApiProvider({ errorKind: "DoesNotExist", }; } else if (connected && !connected?.ok) { - captureMessage( + deploymentInfoContext?.captureMessage( `Can't connect to deployment ${connected?.errorCode} ${connected?.errorMessage}`, ); } diff --git a/npm-packages/dashboard-common/src/lib/functions/FunctionsProvider.tsx b/npm-packages/dashboard-common/src/lib/functions/FunctionsProvider.tsx index 7526705c..01b4c1a6 100644 --- a/npm-packages/dashboard-common/src/lib/functions/FunctionsProvider.tsx +++ b/npm-packages/dashboard-common/src/lib/functions/FunctionsProvider.tsx @@ -1,7 +1,6 @@ -import { ReactNode, useMemo } from "react"; +import { ReactNode, useContext, useMemo } from "react"; import { useRouter } from "next/router"; import { cn } from "@common/lib/cn"; -import { captureMessage } from "@sentry/nextjs"; import { Module } from "system-udfs/convex/_system/frontend/common"; import { createGlobalState } from "react-use"; import { @@ -15,6 +14,7 @@ import { useListModulesAllNents } from "@common/lib/functions/useListModules"; import { createContextHook } from "@common/lib/createContextHook"; import { ComponentId, Nent, useNents } from "@common/lib/useNents"; import { LoadingLogo } from "@common/elements/Loading"; +import { DeploymentInfoContext } from "@common/lib/deploymentContext"; const [FunctionsContext, useFunctions] = createContextHook< Map> @@ -149,6 +149,7 @@ export function useCurrentOpenFunction() { export function useModuleFunctions(): ModuleFunction[] { const modules = useFunctions(); const { nents } = useNents(); + const { captureMessage } = useContext(DeploymentInfoContext); return useMemo(() => { if (!nents) { @@ -159,7 +160,7 @@ export function useModuleFunctions(): ModuleFunction[] { } return modulesToModuleFunctions(modules, nents); - }, [modules, nents]); + }, [captureMessage, modules, nents]); } // Exported for testing only @@ -190,6 +191,7 @@ export function useRootEntries() { const modules = useFunctions(); const [searchTerm] = useFunctionSearchTerm(); const { selectedNent, nents } = useNents(); + const { captureMessage } = useContext(DeploymentInfoContext); const rootEntries = useMemo(() => { if (!nents) { @@ -221,7 +223,7 @@ export function useRootEntries() { } return rootDirectory?.children ?? []; - }, [modules, nents, searchTerm, selectedNent]); + }, [captureMessage, modules, nents, searchTerm, selectedNent]); return rootEntries; } diff --git a/npm-packages/dashboard-common/src/lib/mockDeploymentInfo.tsx b/npm-packages/dashboard-common/src/lib/mockDeploymentInfo.tsx new file mode 100644 index 00000000..f6957c7f --- /dev/null +++ b/npm-packages/dashboard-common/src/lib/mockDeploymentInfo.tsx @@ -0,0 +1,50 @@ +import { DeploymentInfo } from "@common/lib/deploymentContext"; + +export const mockDeploymentInfo: DeploymentInfo = { + ok: true, + reportHttpError: jest.fn(), + captureException: jest.fn(), + captureMessage: jest.fn(), + deploymentUrl: process.env.NEXT_PUBLIC_DEPLOYMENT_URL!, + adminKey: process.env.NEXT_PUBLIC_ADMIN_KEY!, + useCurrentTeam: () => ({ + id: 0, + name: "Team", + slug: "team", + }), + useTeamMembers: () => [], + useTeamEntitlements: () => ({ + auditLogsEnabled: true, + }), + useCurrentUsageBanner: () => null, + useCurrentProject: () => ({ + id: 0, + name: "Project", + slug: "project", + teamId: 0, + }), + useLogDeploymentEvent: () => () => {}, + useCurrentDeployment: () => ({ + id: 0, + name: "local", + deploymentType: "prod", + projectId: 0, + kind: "local", + previewIdentifier: null, + }), + useHasProjectAdminPermissions: () => true, + useIsDeploymentPaused: () => false, + useProjectEnvironmentVariables: () => ({ configs: [] }), + CloudImport: ({ sourceCloudBackupId }: { sourceCloudBackupId: number }) => ( +
{sourceCloudBackupId}
+ ), + ErrorBoundary: ({ children }: { children: React.ReactNode }) => ( + <>{children} + ), + TeamMemberLink: () =>
, + useTeamUsageState: () => "Default", + teamsURI: "/", + projectsURI: "/", + deploymentsURI: "/", + isSelfHosted: true, +}; diff --git a/npm-packages/dashboard-common/src/lib/useDeploymentAuditLog.ts b/npm-packages/dashboard-common/src/lib/useDeploymentAuditLog.ts index e84e7ff7..d4efe258 100644 --- a/npm-packages/dashboard-common/src/lib/useDeploymentAuditLog.ts +++ b/npm-packages/dashboard-common/src/lib/useDeploymentAuditLog.ts @@ -1,4 +1,3 @@ -import * as Sentry from "@sentry/nextjs"; import { usePaginatedQuery } from "convex/react"; import udfs from "udfs"; import { Doc } from "system-udfs/convex/_generated/dataModel"; @@ -50,12 +49,6 @@ function processDeploymentAuditLogEvent( case "snapshot_import": break; default: - // eslint-disable-next-line no-case-declarations, @typescript-eslint/no-unused-vars - Sentry.captureMessage( - `Unexpected deployment audit log with action ${ - (auditLogEvent as any).action - }`, - ); return null; } diff --git a/npm-packages/dashboard-common/src/lib/useLogs.ts b/npm-packages/dashboard-common/src/lib/useLogs.ts index 0793dbdf..2a1a8d29 100644 --- a/npm-packages/dashboard-common/src/lib/useLogs.ts +++ b/npm-packages/dashboard-common/src/lib/useLogs.ts @@ -1,5 +1,4 @@ import { useCallback, useEffect, useState } from "react"; -import { captureException } from "@sentry/nextjs"; import { FunctionExecution, UdfType, @@ -221,7 +220,6 @@ function queryFunctionLogs( if (e instanceof DOMException && e.code === DOMException.ABORT_ERR) { return; } - captureException(e); numFailures += 1; // Give it some time before we show an error to avoid looking extra broken due to transient // connectivity or backend errors (e.g. a backend restart during a push). diff --git a/npm-packages/dashboard-common/src/lib/utils.ts b/npm-packages/dashboard-common/src/lib/utils.ts index 201eafb9..d907101c 100644 --- a/npm-packages/dashboard-common/src/lib/utils.ts +++ b/npm-packages/dashboard-common/src/lib/utils.ts @@ -4,7 +4,6 @@ import { toast as sonnerToast } from "sonner"; import * as IdEncoding from "id-encoding"; import { NextRouter } from "next/router"; import { FilterExpression } from "system-udfs/convex/_system/frontend/lib/filters"; -import { captureMessage } from "@sentry/nextjs"; export function dismissToast(id: string) { sonnerToast.dismiss(id); @@ -105,13 +104,3 @@ export function documentHref( }, }; } - -export const reportHttpError = ( - method: string, - url: string, - error: { code: string; message: string }, -) => { - captureMessage( - `failed to request ${method} ${url}: ${error.code} - ${error.message} `, - ); -}; diff --git a/npm-packages/dashboard-self-hosted/src/components/ErrorBoundary.tsx b/npm-packages/dashboard-self-hosted/src/components/ErrorBoundary.tsx new file mode 100644 index 00000000..a4bd51fe --- /dev/null +++ b/npm-packages/dashboard-self-hosted/src/components/ErrorBoundary.tsx @@ -0,0 +1,69 @@ +import { ExitIcon } from "@radix-ui/react-icons"; +import React, { ReactNode, ErrorInfo } from "react"; +import { Button } from "@common/elements/Button"; +import { Sheet } from "@common/elements/Sheet"; + +interface ErrorBoundaryProps { + children: ReactNode; +} + +interface ErrorBoundaryState { + error?: Error; +} + +export class ErrorBoundary extends React.Component< + ErrorBoundaryProps, + ErrorBoundaryState +> { + constructor(props: ErrorBoundaryProps) { + super(props); + this.state = {}; + } + + static getDerivedStateFromError(e: Error): ErrorBoundaryState { + return { error: e }; + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + console.error("Uncaught error:", error, errorInfo); + } + + render() { + const { error } = this.state; + const { children } = this.props; + if (error) { + return ( +
+

Something went wrong

+
+ {error.message.includes("not permitted") && ( +

+ Your admin key may be invalid. Please try logging in again. +

+ )} + +
+ + {error.message} +
+              {error.stack}
+            
+
+
+ ); + } + + return children; + } +} diff --git a/npm-packages/dashboard-self-hosted/src/pages/_app.tsx b/npm-packages/dashboard-self-hosted/src/pages/_app.tsx index c3394ed6..9210de5b 100644 --- a/npm-packages/dashboard-self-hosted/src/pages/_app.tsx +++ b/npm-packages/dashboard-self-hosted/src/pages/_app.tsx @@ -28,15 +28,9 @@ import { MenuItem, TextInput, Button, - Sheet, } from "dashboard-common"; -import React, { - ErrorInfo, - ReactNode, - useEffect, - useMemo, - useState, -} from "react"; +import React, { useEffect, useMemo, useState } from "react"; +import { ErrorBoundary } from "components/ErrorBoundary"; function App({ Component, @@ -101,6 +95,17 @@ export default App; const deploymentInfo: Omit = { ok: true, + captureMessage: console.error, + captureException: console.error, + reportHttpError: ( + method: string, + url: string, + error: { code: string; message: string }, + ) => { + console.error( + `failed to request ${method} ${url}: ${error.code} - ${error.message} `, + ); + }, useCurrentTeam: () => ({ id: 0, name: "Team", @@ -137,6 +142,9 @@ const deploymentInfo: Omit = {
{sourceCloudBackupId}
), TeamMemberLink: () =>
, + ErrorBoundary: ({ children }: { children: React.ReactNode }) => ( + {children} + ), useTeamUsageState: () => "Default", teamsURI: "/", projectsURI: "/", @@ -245,68 +253,3 @@ function Header({ onLogout }: { onLogout: () => void }) { ); } - -interface ErrorBoundaryProps { - children: ReactNode; -} - -interface ErrorBoundaryState { - error?: Error; -} - -class ErrorBoundary extends React.Component< - ErrorBoundaryProps, - ErrorBoundaryState -> { - constructor(props: ErrorBoundaryProps) { - super(props); - this.state = {}; - } - - static getDerivedStateFromError(e: Error): ErrorBoundaryState { - return { error: e }; - } - - componentDidCatch(error: Error, errorInfo: ErrorInfo) { - console.error("Uncaught error:", error, errorInfo); - } - - render() { - const { error } = this.state; - const { children } = this.props; - if (error) { - return ( -
-

Something went wrong

-
- {error.message.includes("not permitted") && ( -

- Your admin key may be invalid. Please try logging in again. -

- )} - -
- - {error.message} -
-              {error.stack}
-            
-
-
- ); - } - - return children; - } -} diff --git a/npm-packages/dashboard/package.json b/npm-packages/dashboard/package.json index f68acb4e..113388e2 100644 --- a/npm-packages/dashboard/package.json +++ b/npm-packages/dashboard/package.json @@ -39,17 +39,14 @@ "date-fns": "^2.29.3", "fetch-retry": "~6.0.0", "formik": "~2.2.9", - "js-base64": "~3.7.4", "launchdarkly-react-client-sdk": "^3.0.4", "lodash": "~4.17.21", "next": "14.2.21", "nprogress": "~0.2.0", - "prop-types": "~15.8.1", "react": "^18.0.0", "react-day-picker": "~8.10.1", "react-dom": "^18.0.0", "react-popper": "^2.3.0", - "react-table": "7.8.0", "react-use": "~17.6.0", "react-window": "1.8.11", "recharts": "2.15.0", diff --git a/npm-packages/dashboard/src/api/api.ts b/npm-packages/dashboard/src/api/api.ts index 3fb6b787..b05a7b76 100644 --- a/npm-packages/dashboard/src/api/api.ts +++ b/npm-packages/dashboard/src/api/api.ts @@ -8,14 +8,14 @@ import { PathsWithMethod } from "openapi-typescript-helpers"; import { createMutateHook, createQueryHook } from "swr-openapi"; import isMatch from "lodash/isMatch"; import { fireGoogleAnalyticsEvent } from "elements/GoogleAnalytics"; -import { reportHttpError, toast } from "dashboard-common"; +import { toast } from "dashboard-common"; import type { paths as BigBrainPaths } from "generatedApi"; import { SWRConfiguration } from "swr"; import { useAccessToken } from "hooks/useServerSideData"; import { useRouter } from "next/router"; import { useCallback, useEffect } from "react"; import { usePrevious } from "react-use"; -import { getGoogleAnalyticsClientId } from "../hooks/fetching"; +import { getGoogleAnalyticsClientId, reportHttpError } from "../hooks/fetching"; export const client = createClient({ baseUrl: `${process.env.NEXT_PUBLIC_BIG_BRAIN_URL}/api/dashboard`, diff --git a/npm-packages/dashboard/src/hooks/api.tsx b/npm-packages/dashboard/src/hooks/api.tsx index 1c9c4c29..0beb4e1b 100644 --- a/npm-packages/dashboard/src/hooks/api.tsx +++ b/npm-packages/dashboard/src/hooks/api.tsx @@ -53,7 +53,6 @@ export function useUpdateProjectEnvVars(projectId?: number) { url: `/api/dashboard/projects/${projectId}/environment_variables/update_batch`, mutateKey: `/api/dashboard/projects/${projectId}/environment_variables/list`, successToast: "Environment variables updated.", - method: "POST", }); } diff --git a/npm-packages/dashboard/src/hooks/debug.ts b/npm-packages/dashboard/src/hooks/debug.ts deleted file mode 100644 index be1c3fb6..00000000 --- a/npm-packages/dashboard/src/hooks/debug.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { useRef } from "react"; - -const firstRender = {}; -export function useWhatChanged(obj: Record, label?: string) { - const previous = useRef>(firstRender); - - if (previous.current === firstRender) { - previous.current = obj; - } - - if (Object.keys(obj).length !== Object.keys(previous.current).length) { - // eslint-disable-next-line no-console - console.log("changed size!"); - previous.current = obj; - return; - } - - const changes: Record = {}; - // eslint-disable-next-line no-restricted-syntax - for (const prop of Object.keys(obj)) { - const prev = previous.current[prop]; - const cur = obj[prop]; - if (cur !== prev) { - changes[prop] = [prev, cur]; - } - } - - if (Object.keys(changes).length) { - // eslint-disable-next-line no-console - console.log(`${label || "component"}rerendered from change:`, changes); - } - - previous.current = obj; -} diff --git a/npm-packages/dashboard/src/hooks/deploymentApi.tsx b/npm-packages/dashboard/src/hooks/deploymentApi.tsx index 4e19a22e..7fbbd944 100644 --- a/npm-packages/dashboard/src/hooks/deploymentApi.tsx +++ b/npm-packages/dashboard/src/hooks/deploymentApi.tsx @@ -12,7 +12,6 @@ import { ConnectedDeploymentContext, DeploymentInfoContext, DeploymentInfo, - reportHttpError, useAdminKey, useDeploymentAuthHeader, useDeploymentUrl, @@ -33,6 +32,13 @@ import { useIsDeploymentPaused } from "hooks/useIsDeploymentPaused"; import { CloudImport } from "elements/BackupIdentifier"; import { TeamMemberLink } from "elements/TeamMemberLink"; import { logDeploymentEvent } from "convex-analytics"; +import { + ErrorBoundary, + captureException, + captureMessage, +} from "@sentry/nextjs"; +import { reportHttpError } from "hooks/fetching"; +import { Fallback } from "pages/500"; import { useAccessToken } from "./useServerSideData"; import { useCurrentProject } from "../api/projects"; import { useTeamUsageState } from "./useTeamUsageState"; @@ -42,6 +48,10 @@ import { useProjectEnvironmentVariables } from "./api"; const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect; +function DeploymentErrorBoundary({ children }: { children: React.ReactNode }) { + return {children}; +} + export function DeploymentInfoProvider({ children, deploymentOverride, @@ -69,6 +79,9 @@ export function DeploymentInfoProvider({ ); setDeploymentInfo({ ...info, + captureMessage, + captureException, + reportHttpError, useCurrentTeam, useCurrentProject, useCurrentUsageBanner, @@ -82,6 +95,7 @@ export function DeploymentInfoProvider({ useLogDeploymentEvent, TeamMemberLink, CloudImport, + ErrorBoundary: DeploymentErrorBoundary, teamsURI, projectsURI, deploymentsURI, diff --git a/npm-packages/dashboard/src/hooks/fetching.ts b/npm-packages/dashboard/src/hooks/fetching.ts index 31624fd5..ff7d87b6 100644 --- a/npm-packages/dashboard/src/hooks/fetching.ts +++ b/npm-packages/dashboard/src/hooks/fetching.ts @@ -1,5 +1,5 @@ import { Middleware } from "swr"; -import { captureException } from "@sentry/nextjs"; +import { captureException, captureMessage } from "@sentry/nextjs"; import { deploymentFetch, translateResponse } from "dashboard-common"; import { useAccessToken } from "./useServerSideData"; @@ -100,3 +100,13 @@ export const bigBrainAuth: Middleware = return useSWRNext(swrKey, fetcher, { ...config, fallbackData }); }; + +export const reportHttpError = ( + method: string, + url: string, + error: { code: string; message: string }, +) => { + captureMessage( + `failed to request ${method} ${url}: ${error.code} - ${error.message} `, + ); +}; diff --git a/npm-packages/dashboard/src/hooks/useMutation.ts b/npm-packages/dashboard/src/hooks/useMutation.ts index 7af3f12a..e3c84df4 100644 --- a/npm-packages/dashboard/src/hooks/useMutation.ts +++ b/npm-packages/dashboard/src/hooks/useMutation.ts @@ -1,31 +1,22 @@ -import { reportHttpError, toast } from "dashboard-common"; +import { toast } from "dashboard-common"; import { useCallback } from "react"; import { useSWRConfig } from "swr"; -import { fireGoogleAnalyticsEvent } from "elements/GoogleAnalytics"; -import { useRouter } from "next/router"; -import { useAuthHeader } from "./fetching"; +import { reportHttpError, useAuthHeader } from "./fetching"; type MutateOptions = { url: string; mutateKey?: string; successToast?: string; - method?: "POST" | "PUT"; toastOnError?: boolean; - redirectTo?: string; - googleAnalyticsEvent?: string; }; // Makes a mutative API request, handling errors and toasts. export function useMutation({ url, - method = "POST", mutateKey, successToast, toastOnError = true, - googleAnalyticsEvent, - redirectTo, }: MutateOptions): (body: Request) => Promise { - const router = useRouter(); const { mutate } = useSWRConfig(); const authHeader = useAuthHeader(); return useCallback( @@ -41,7 +32,7 @@ export function useMutation({ const response = await fetch( `${process.env.NEXT_PUBLIC_BIG_BRAIN_URL}${url}`, { - method, + method: "POST", headers: { Authorization: authHeader, "Content-Type": "application/json", @@ -51,40 +42,15 @@ export function useMutation({ ); if (!response.ok) { const error = await response.json(); - reportHttpError(method, url, error); + reportHttpError("POST", url, error); toastOnError && toast("error", error.message, error.message); throw error; } else { - redirectTo && (await router.push(redirectTo)); mutateKey && (await mutate([mutateKey, authHeader])); successToast && toast("success", successToast); - googleAnalyticsEvent && fireGoogleAnalyticsEvent(googleAnalyticsEvent); return response; } }, - [ - authHeader, - googleAnalyticsEvent, - method, - mutate, - mutateKey, - redirectTo, - router, - successToast, - toastOnError, - url, - ], - ); -} - -// Convenience wrapper for converting the JSON response to a parameterized `Response` type. -export function useMutationWithResponse( - options: MutateOptions, -): (body: Request) => Promise { - const mutate = useMutation(options); - return useCallback( - async (request: Request): Promise => - (await mutate(request)).json(), - [mutate], + [authHeader, mutate, mutateKey, successToast, toastOnError, url], ); }