Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom error boundary #25946

Merged
merged 5 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 5 additions & 11 deletions apps/studio/components/interfaces/Home/ServiceStatus.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import { useParams } from 'common'
import { AlertTriangle, CheckCircle2 } from 'lucide-react'
import { AlertTriangle, CheckCircle2, Loader2 } from 'lucide-react'
import { useState } from 'react'
import {
Button,
IconLoader,
PopoverContent_Shadcn_,
PopoverTrigger_Shadcn_,
Popover_Shadcn_,
} from 'ui'
import { Button, PopoverContent_Shadcn_, PopoverTrigger_Shadcn_, Popover_Shadcn_ } from 'ui'

import { useEdgeFunctionServiceStatusQuery } from 'data/service-status/edge-functions-status-query'
import { usePostgresServiceStatusQuery } from 'data/service-status/postgres-service-status-query'
import { useProjectServiceStatusQuery } from 'data/service-status/service-status-query'
import { useIsFeatureEnabled, useSelectedProject } from 'hooks'
import { useEdgeFunctionServiceStatusQuery } from 'data/service-status/edge-functions-status-query'

const ServiceStatus = () => {
const { ref } = useParams()
Expand Down Expand Up @@ -125,7 +119,7 @@ const ServiceStatus = () => {
type="default"
icon={
isLoadingChecks ? (
<IconLoader className="animate-spin" />
<Loader2 className="animate-spin" />
) : (
<div
className={`w-2 h-2 rounded-full ${
Expand Down Expand Up @@ -155,7 +149,7 @@ const ServiceStatus = () => {
</p>
</div>
{service.isLoading ? (
<IconLoader className="animate-spin" size="tiny" />
<Loader2 className="animate-spin text-foreground-light" size={18} />
) : service.isSuccess ? (
<CheckCircle2 className="text-brand" size={18} strokeWidth={1.5} />
) : (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import * as Sentry from '@sentry/nextjs'
import { BASE_PATH } from 'lib/constants'
import { auth, buildPathWithParams } from 'lib/gotrue'
import { useState } from 'react'
Expand Down
32 changes: 13 additions & 19 deletions apps/studio/components/interfaces/Support/SupportForm.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CLIENT_LIBRARIES } from 'common/constants'
import { HelpCircle } from 'lucide-react'
import { AlertCircle, ExternalLink, HelpCircle, Loader2, Mail, Plus, X } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { ChangeEvent, useEffect, useRef, useState } from 'react'
Expand All @@ -25,12 +25,6 @@ import {
Button,
Checkbox,
Form,
IconAlertCircle,
IconExternalLink,
IconLoader,
IconMail,
IconPlus,
IconX,
Input,
Listbox,
Separator,
Expand Down Expand Up @@ -357,7 +351,7 @@ const SupportForm = ({ setSentCategory }: SupportFormProps) => {
<div className="space-y-2">
<p className="text-sm prose">Which project is affected?</p>
<div className="border rounded-md px-4 py-2 flex items-center space-x-2">
<IconAlertCircle strokeWidth={2} className="text-foreground-light" />
<AlertCircle size={16} strokeWidth={2} className="text-foreground-light" />
<p className="text-sm prose">Failed to retrieve projects</p>
</div>
</div>
Expand Down Expand Up @@ -413,7 +407,7 @@ const SupportForm = ({ setSentCategory }: SupportFormProps) => {
</p>
) : isLoadingSubscription && selectedProjectRef !== 'no-project' ? (
<div className="flex items-center space-x-2 mt-2">
<IconLoader size={14} className="animate-spin" />
<Loader2 size={14} className="animate-spin" />
<p className="text-sm text-foreground-light">Checking project's plan</p>
</div>
) : (
Expand Down Expand Up @@ -443,7 +437,7 @@ const SupportForm = ({ setSentCategory }: SupportFormProps) => {
<div className="space-y-2">
<p className="text-sm prose">Which organization is affected?</p>
<div className="border rounded-md px-4 py-2 flex items-center space-x-2">
<IconAlertCircle strokeWidth={2} className="text-foreground-light" />
<AlertCircle size={16} strokeWidth={2} className="text-foreground-light" />
<p className="text-sm prose">Failed to retrieve organizations</p>
</div>
</div>
Expand Down Expand Up @@ -473,7 +467,7 @@ const SupportForm = ({ setSentCategory }: SupportFormProps) => {
{subscription?.plan.id !== 'enterprise' && values.category !== 'Login_issues' && (
<div className="px-6">
<InformationBox
icon={<IconAlertCircle strokeWidth={2} />}
icon={<AlertCircle size={18} strokeWidth={2} />}
defaultVisibility={true}
hideCollapse={true}
title="Expected response times are based on your project's plan"
Expand Down Expand Up @@ -516,7 +510,7 @@ const SupportForm = ({ setSentCategory }: SupportFormProps) => {
Upgrade project
</Link>
</Button>
<Button asChild type="default" icon={<IconExternalLink size={14} />}>
<Button asChild type="default" icon={<ExternalLink />}>
<Link
href="https://supabase.com/contact/enterprise"
target="_blank"
Expand Down Expand Up @@ -561,7 +555,7 @@ const SupportForm = ({ setSentCategory }: SupportFormProps) => {
className="flex items-center space-x-2 text-foreground-light underline hover:text-foreground transition"
>
Github discussions
<IconExternalLink size={14} strokeWidth={2} className="ml-1" />
<ExternalLink size={14} strokeWidth={2} className="ml-1" />
</Link>
<span> for a quick answer</span>
</p>
Expand Down Expand Up @@ -629,7 +623,7 @@ const SupportForm = ({ setSentCategory }: SupportFormProps) => {
<Button
asChild
type="default"
icon={<IconExternalLink size={14} strokeWidth={1.5} />}
icon={<ExternalLink size={14} strokeWidth={1.5} />}
>
<Link href={library.url} target="_blank" rel="noreferrer">
View Github issues
Expand All @@ -655,7 +649,7 @@ const SupportForm = ({ setSentCategory }: SupportFormProps) => {
<Button
asChild
type="default"
icon={<IconExternalLink size={14} strokeWidth={1.5} />}
icon={<ExternalLink size={14} strokeWidth={1.5} />}
>
<Link
href="https://github.com/supabase/supabase"
Expand Down Expand Up @@ -712,7 +706,7 @@ const SupportForm = ({ setSentCategory }: SupportFormProps) => {
<Button
asChild
type="default"
icon={<IconExternalLink strokeWidth={1.5} />}
icon={<ExternalLink strokeWidth={1.5} />}
>
<Link
href="https://github.com/orgs/supabase/discussions/17817"
Expand Down Expand Up @@ -771,7 +765,7 @@ const SupportForm = ({ setSentCategory }: SupportFormProps) => {
].join(' ')}
onClick={() => removeUploadedFile(idx)}
>
<IconX size={12} strokeWidth={2} />
<X size={12} strokeWidth={2} />
</div>
</div>
))}
Expand All @@ -785,7 +779,7 @@ const SupportForm = ({ setSentCategory }: SupportFormProps) => {
if (uploadButtonRef.current) (uploadButtonRef.current as any).click()
}}
>
<IconPlus strokeWidth={2} size={20} />
<Plus strokeWidth={2} size={20} />
</div>
)}
</div>
Expand All @@ -804,7 +798,7 @@ const SupportForm = ({ setSentCategory }: SupportFormProps) => {
<Button
htmlType="submit"
size="small"
icon={<IconMail />}
icon={<Mail />}
disabled={isSubmitting}
loading={isSubmitting}
>
Expand Down
41 changes: 41 additions & 0 deletions apps/studio/components/ui/ErrorBoundaryState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { ExternalLink } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import type { FallbackProps } from 'react-error-boundary'

import { Button } from 'ui'

export const ErrorBoundaryState = ({ error, resetErrorBoundary }: FallbackProps) => {
const router = useRouter()
const message = `Path name: ${router.pathname}\n\n${error.stack}`

return (
<div className="w-screen h-screen flex items-center justify-center flex-col gap-y-3">
<div className="flex items-center flex-col gap-y-1">
<p className="text-sm">
Application error: a client-side exception has occured (see browser console for more
joshenlim marked this conversation as resolved.
Show resolved Hide resolved
joshenlim marked this conversation as resolved.
Show resolved Hide resolved
information)
</p>
<p className="text-sm text-foreground-light">Error: {error.message}</p>
</div>

<div className="flex items-center justify-center gap-x-2">
<Button asChild type="default" icon={<ExternalLink />}>
<Link
href={`/support/new?category=dashboard_bug&subject=Client%20side%20exception%20occured%20on%20dashboard&message=${encodeURI(message)}`}
target="_blank"
>
Report to support
</Link>
</Button>
{/* [Joshen] For local and staging, allow us to escape the error boundary */}
{/* We could actually investigate how to make this available on prod, but without being able to reliably test this, I'm not keen to do it now */}
{process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod' && (
<Button type="outline" onClick={() => resetErrorBoundary()}>
Return to dashboard
</Button>
)}
</div>
</div>
)
}
5 changes: 3 additions & 2 deletions apps/studio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.13",
"react-grid-layout": "^1.4.2",
"react-hot-toast": "^2.4.1",
"react-inlinesvg": "^4.0.4",
Expand Down Expand Up @@ -135,6 +136,7 @@
"@types/sqlstring": "^2.3.0",
"@types/uuid": "^8.3.4",
"@types/zxcvbn": "^4.4.1",
"api-types": "*",
"autoprefixer": "^10.4.14",
"common": "*",
"config": "*",
Expand All @@ -146,7 +148,6 @@
"prettier": "^4.0.0-alpha.8",
"storybook-dark-mode": "^3.0.1",
"tailwindcss": "^3.4.1",
"typescript": "^5.4.3",
"api-types": "*"
"typescript": "^5.4.3"
}
}
88 changes: 49 additions & 39 deletions apps/studio/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'styles/ui.scss'
import 'ui/build/css/themes/dark.css'
import 'ui/build/css/themes/light.css'

import * as Sentry from '@sentry/nextjs'
import { loader } from '@monaco-editor/react'
import { TooltipProvider } from '@radix-ui/react-tooltip'
import { SessionContextProvider } from '@supabase/auth-helpers-react'
Expand All @@ -29,7 +30,8 @@ import relativeTime from 'dayjs/plugin/relativeTime'
import timezone from 'dayjs/plugin/timezone'
import utc from 'dayjs/plugin/utc'
import Head from 'next/head'
import { useEffect, useMemo, useRef, useState } from 'react'
import { ErrorInfo, useEffect, useMemo, useRef, useState } from 'react'
import { ErrorBoundary } from 'react-error-boundary'
import toast from 'react-hot-toast'
import { PortalToast, Toaster } from 'ui'
import { ConsentToast } from 'ui-patterns/ConsentToast'
Expand All @@ -43,6 +45,7 @@ import {
import { AppBannerContextProvider } from 'components/interfaces/App/AppBannerWrapperContext'
import { FeaturePreviewContextProvider } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import FeaturePreviewModal from 'components/interfaces/App/FeaturePreview/FeaturePreviewModal'
import { ErrorBoundaryState } from 'components/ui/ErrorBoundaryState'
import FlagProvider from 'components/ui/Flag/FlagProvider'
import PageTelemetry from 'components/ui/PageTelemetry'
import { useRootQueryClient } from 'data/query-client'
Expand Down Expand Up @@ -107,6 +110,11 @@ function CustomApp({ Component, pageProps }: AppPropsWithLayout) {
[supabase]
)

const errorBoundaryHandler = (error: Error, info: ErrorInfo) => {
console.error(error.stack)
Sentry.captureMessage('Full page crash caught by error boundary')
}

useEffect(() => {
// Check for telemetry consent
if (typeof window !== 'undefined') {
Expand Down Expand Up @@ -140,44 +148,46 @@ function CustomApp({ Component, pageProps }: AppPropsWithLayout) {
const isTestEnv = process.env.NEXT_PUBLIC_NODE_ENV === 'test'

return (
<QueryClientProvider client={queryClient}>
<Hydrate state={pageProps.dehydratedState}>
<AuthContainer>
<ProfileProvider>
<FlagProvider>
<Head>
<title>Supabase</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
<MetaFaviconsPagesRouter applicationName="Supabase Studio" />
<PageTelemetry>
<TooltipProvider>
<RouteValidationWrapper>
<ThemeProvider defaultTheme="system" enableSystem disableTransitionOnChange>
<AppBannerContextProvider>
<CommandMenuWrapper>
<AppBannerWrapper>
<FeaturePreviewContextProvider>
{getLayout(<Component {...pageProps} />)}
<FeaturePreviewModal />
</FeaturePreviewContextProvider>
</AppBannerWrapper>
</CommandMenuWrapper>
</AppBannerContextProvider>
</ThemeProvider>
</RouteValidationWrapper>
</TooltipProvider>
</PageTelemetry>

<HCaptchaLoadedStore />
<Toaster />
<PortalToast />
{!isTestEnv && <ReactQueryDevtools initialIsOpen={false} position="bottom-right" />}
</FlagProvider>
</ProfileProvider>
</AuthContainer>
</Hydrate>
</QueryClientProvider>
<ErrorBoundary FallbackComponent={ErrorBoundaryState} onError={errorBoundaryHandler}>
<QueryClientProvider client={queryClient}>
<Hydrate state={pageProps.dehydratedState}>
<AuthContainer>
<ProfileProvider>
<FlagProvider>
<Head>
<title>Supabase</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
<MetaFaviconsPagesRouter applicationName="Supabase Studio" />
<PageTelemetry>
<TooltipProvider>
<RouteValidationWrapper>
<ThemeProvider defaultTheme="system" enableSystem disableTransitionOnChange>
<AppBannerContextProvider>
<CommandMenuWrapper>
<AppBannerWrapper>
<FeaturePreviewContextProvider>
{getLayout(<Component {...pageProps} />)}
<FeaturePreviewModal />
</FeaturePreviewContextProvider>
</AppBannerWrapper>
</CommandMenuWrapper>
</AppBannerContextProvider>
</ThemeProvider>
</RouteValidationWrapper>
</TooltipProvider>
</PageTelemetry>

<HCaptchaLoadedStore />
<Toaster />
<PortalToast />
{!isTestEnv && <ReactQueryDevtools initialIsOpen={false} position="bottom-right" />}
</FlagProvider>
</ProfileProvider>
</AuthContainer>
</Hydrate>
</QueryClientProvider>
</ErrorBoundary>
)
}

Expand Down
7 changes: 4 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.