Skip to content

Commit

Permalink
feat(core): implement basic error reporting (#6914)
Browse files Browse the repository at this point in the history
* refactor(core): use current package version as fallback version

* feat(core): implement basic error reporting

* refactor(core): upgrade `@sentry/react`, adjust typings to match

* refactor(core): clean up client initialization scenarios

* refactor(core): only enable error reporting in browser

* refactor(core): only enable error reporting for auto-updating studios
  • Loading branch information
rexxars authored Jun 18, 2024
1 parent b90820c commit 02dab2a
Show file tree
Hide file tree
Showing 12 changed files with 656 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {Button, Card, Flex, Stack} from '@sanity/ui'
import {useCallback, useState} from 'react'

function triggerCustomErrorOnEvent() {
throw new Error('Custom error triggered')
}

function triggerTypeErrorOnEvent(evt: any) {
evt.someFunctionThatDoesntExist()
}

function triggerTimeoutError() {
setTimeout(() => {
throw new Error('Custom error in setTimeout')
}, 1000)
}

function triggerPromiseError() {
return new Promise((resolve, reject) => {
requestAnimationFrame(() => {
reject(new Error('Custom error in promise'))
})
})
}

export function ErrorReportingTest() {
const [doRenderError, setRenderError] = useState(false)
const handleShouldRenderWithError = useCallback(() => setRenderError(true), [])

return (
<Card padding={5}>
<Flex>
<Stack space={4}>
<Button
text="Trigger custom error on event handler"
onClick={triggerCustomErrorOnEvent}
tone="primary"
/>

<Button
text="Trigger type error on event handler"
onClick={triggerTypeErrorOnEvent}
tone="primary"
/>

<Button
text="Trigger async background error (timeout)"
onClick={triggerTimeoutError}
tone="primary"
/>

<Button
text="Trigger unhandled rejection error"
onClick={triggerPromiseError}
tone="primary"
/>

<Button
text="Trigger React render error"
onClick={handleShouldRenderWithError}
tone="primary"
/>
</Stack>
</Flex>

{doRenderError && <WithRenderError />}
</Card>
)
}

function WithRenderError({text}: any) {
return <div>{text.toUpperCase()}</div>
}
1 change: 1 addition & 0 deletions dev/test-studio/plugins/error-reporting-test/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './plugin'
20 changes: 20 additions & 0 deletions dev/test-studio/plugins/error-reporting-test/plugin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {AsteriskIcon} from '@sanity/icons'
import {definePlugin} from 'sanity'
import {route} from 'sanity/router'

import {ErrorReportingTest} from './ErrorReportingTest'

export const errorReportingTestPlugin = definePlugin(() => {
return {
name: 'error-reporting-test',
tools: [
{
name: 'error-reporting-test',
title: 'Errors test',
icon: AsteriskIcon,
component: ErrorReportingTest,
router: route.create('/'),
},
],
}
})
2 changes: 2 additions & 0 deletions dev/test-studio/sanity.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {pasteAction} from './fieldActions/pasteAction'
import {resolveInitialValueTemplates} from './initialValueTemplates'
import {customInspector} from './inspectors/custom'
import {testStudioLocaleBundles} from './locales'
import {errorReportingTestPlugin} from './plugins/error-reporting-test'
import {languageFilter} from './plugins/language-filter'
import {presenceTool} from './plugins/presence'
import {routerDebugTool} from './plugins/router-debug'
Expand Down Expand Up @@ -135,6 +136,7 @@ const sharedSettings = definePlugin({
imageHotspotArrayPlugin(),
presenceTool(),
routerDebugTool(),
errorReportingTestPlugin(),
tsdoc(),
],
})
Expand Down
1 change: 1 addition & 0 deletions packages/sanity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@
"@sanity/ui": "^2.4.0",
"@sanity/util": "3.46.1",
"@sanity/uuid": "^3.0.1",
"@sentry/react": "^8.7.0",
"@tanstack/react-table": "^8.16.0",
"@tanstack/react-virtual": "3.0.0-beta.54",
"@types/react-copy-to-clipboard": "^5.0.2",
Expand Down
36 changes: 36 additions & 0 deletions packages/sanity/src/core/error/errorReporter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {type ErrorInfo as ReactErrorInfo} from 'react'

import {getSentryErrorReporter} from './sentry/sentryErrorReporter'

/**
* @internal
*/
export interface ErrorInfo {
reactErrorInfo?: ReactErrorInfo
errorBoundary?: string
}

/**
* @internal
*/
export interface ErrorReporter {
/** Call to prepare the error reporter for use */
initialize: () => void

/**
* Reports an error, as caught by an error handler or a React boundary.
*
* @param error - The error that is caught. Note that while typed as `Error` by Reacts `componentDidCatch`, it can also be invoked with non-error objects.
* @param options - Additional options for the error report
* @returns An object containing information on the reported error, or `null` if ignored
*/
reportError: (error: Error, options?: ErrorInfo) => {eventId: string} | null
}

/**
* Singleton instance of an error reporter, that will send errors encountered during execution or
* rendering to Sanity (potentially to a third party error tracking service).
*
* @internal
*/
export const errorReporter = getSentryErrorReporter()
3 changes: 3 additions & 0 deletions packages/sanity/src/core/error/sentry/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This may be moved into a separate package in the future, in order for us to provide a more generic error reporter that may be swapped for a different service if need be.

For now, we keep it in-module to keep things simple.
Loading

0 comments on commit 02dab2a

Please sign in to comment.