mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-20 22:42:53 -05:00
Added Sentry integration to Admin X (#18288)
no issue
- Adds Sentry to Admin X settings to be able to monitor uncaught errors, etc.
---
<!-- Leave the line below if you'd like GitHub Copilot to generate a
summary from your commit -->
<!--
copilot:summary
-->
### <samp>🤖 Generated by Copilot at 76de125</samp>
Added Sentry integration to the admin-x-settings app to improve error
monitoring and reporting. Updated the `ErrorBoundary` and `HtmlEditor`
components, the `useFetchApi` hook, and the app component to use the
`@sentry/react` module. Passed the `sentryDSN` prop from the parent
component to the app component and the services context.
This commit is contained in:
parent
2bc1392bff
commit
f7b50456ea
9 changed files with 92 additions and 51 deletions
|
@ -43,6 +43,7 @@
|
|||
"@dnd-kit/core": "6.0.8",
|
||||
"@dnd-kit/sortable": "7.0.2",
|
||||
"@ebay/nice-modal-react": "1.2.10",
|
||||
"@sentry/react": "7.70.0",
|
||||
"@tanstack/react-query": "4.35.3",
|
||||
"@tryghost/color-utils": "0.1.24",
|
||||
"@tryghost/limit-service": "^1.2.10",
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
import * as Sentry from '@sentry/react';
|
||||
import GlobalDataProvider from './components/providers/GlobalDataProvider';
|
||||
import MainContent from './MainContent';
|
||||
import NiceModal from '@ebay/nice-modal-react';
|
||||
import RoutingProvider, {ExternalLink} from './components/providers/RoutingProvider';
|
||||
import clsx from 'clsx';
|
||||
import {DefaultHeaderTypes} from './utils/unsplash/UnsplashTypes';
|
||||
import {ErrorBoundary} from '@sentry/react';
|
||||
import {GlobalDirtyStateProvider} from './hooks/useGlobalDirtyState';
|
||||
import {OfficialTheme, ServicesProvider} from './components/providers/ServiceProvider';
|
||||
import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
|
||||
import {Toaster} from 'react-hot-toast';
|
||||
import {ZapierTemplate} from './components/settings/advanced/integrations/ZapierModal';
|
||||
import {useEffect} from 'react';
|
||||
|
||||
interface AppProps {
|
||||
ghostVersion: string;
|
||||
|
@ -18,6 +21,7 @@ interface AppProps {
|
|||
toggleFeatureFlag: (flag: string, enabled: boolean) => void;
|
||||
darkMode?: boolean;
|
||||
unsplashConfig: DefaultHeaderTypes
|
||||
sentryDSN: string | null;
|
||||
}
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
|
@ -30,33 +34,56 @@ const queryClient = new QueryClient({
|
|||
}
|
||||
});
|
||||
|
||||
function App({ghostVersion, officialThemes, zapierTemplates, externalNavigate, toggleFeatureFlag, darkMode = false, unsplashConfig}: AppProps) {
|
||||
function SentryErrorBoundary({children}: {children: React.ReactNode}) {
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
{children}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
function App({ghostVersion, officialThemes, zapierTemplates, externalNavigate, toggleFeatureFlag, darkMode = false, unsplashConfig, sentryDSN}: AppProps) {
|
||||
const appClassName = clsx(
|
||||
'admin-x-settings h-[100vh] w-full overflow-y-auto overflow-x-hidden',
|
||||
darkMode && 'dark'
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (sentryDSN) {
|
||||
Sentry.init({
|
||||
dsn: sentryDSN,
|
||||
release: ghostVersion,
|
||||
integrations: [
|
||||
new Sentry.BrowserTracing({
|
||||
})
|
||||
]
|
||||
});
|
||||
}
|
||||
}, [sentryDSN, ghostVersion]);
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ServicesProvider ghostVersion={ghostVersion} officialThemes={officialThemes} toggleFeatureFlag={toggleFeatureFlag} unsplashConfig={unsplashConfig} zapierTemplates={zapierTemplates}>
|
||||
<GlobalDataProvider>
|
||||
<RoutingProvider externalNavigate={externalNavigate}>
|
||||
<GlobalDirtyStateProvider>
|
||||
<div className={appClassName} id="admin-x-root" style={{
|
||||
height: '100vh',
|
||||
width: '100%'
|
||||
}}
|
||||
>
|
||||
<Toaster />
|
||||
<NiceModal.Provider>
|
||||
<MainContent />
|
||||
</NiceModal.Provider>
|
||||
</div>
|
||||
</GlobalDirtyStateProvider>
|
||||
</RoutingProvider>
|
||||
</GlobalDataProvider>
|
||||
</ServicesProvider>
|
||||
</QueryClientProvider>
|
||||
<SentryErrorBoundary>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ServicesProvider ghostVersion={ghostVersion} officialThemes={officialThemes} sentryDSN={sentryDSN} toggleFeatureFlag={toggleFeatureFlag} unsplashConfig={unsplashConfig} zapierTemplates={zapierTemplates}>
|
||||
<GlobalDataProvider>
|
||||
<RoutingProvider externalNavigate={externalNavigate}>
|
||||
<GlobalDirtyStateProvider>
|
||||
<div className={appClassName} id="admin-x-root" style={{
|
||||
height: '100vh',
|
||||
width: '100%'
|
||||
}}
|
||||
>
|
||||
<Toaster />
|
||||
<NiceModal.Provider>
|
||||
<MainContent />
|
||||
</NiceModal.Provider>
|
||||
</div>
|
||||
</GlobalDirtyStateProvider>
|
||||
</RoutingProvider>
|
||||
</GlobalDataProvider>
|
||||
</ServicesProvider>
|
||||
</QueryClientProvider>
|
||||
</SentryErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import * as Sentry from '@sentry/react';
|
||||
import Banner from './Banner';
|
||||
import React, {ComponentType, ErrorInfo, ReactNode} from 'react';
|
||||
|
||||
|
@ -17,7 +18,10 @@ class ErrorBoundary extends React.Component<{children: ReactNode, name: ReactNod
|
|||
}
|
||||
|
||||
componentDidCatch(error: unknown, info: ErrorInfo) {
|
||||
// TODO: Log to Sentry
|
||||
Sentry.withScope((scope) => {
|
||||
scope.setTag('adminX settings component-', info.componentStack);
|
||||
Sentry.captureException(error);
|
||||
});
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
// eslint-disable-next-line no-console
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import * as Sentry from '@sentry/react';
|
||||
import React, {ReactNode, Suspense, useCallback, useMemo} from 'react';
|
||||
|
||||
export interface HtmlEditorProps {
|
||||
|
@ -90,21 +91,21 @@ const KoenigWrapper: React.FC<HtmlEditorProps & { editor: EditorResource }> = ({
|
|||
nodes
|
||||
}) => {
|
||||
const onError = useCallback((error: unknown) => {
|
||||
// ensure we're still showing errors in development
|
||||
try {
|
||||
Sentry.captureException({
|
||||
error,
|
||||
tags: {lexical: true},
|
||||
contexts: {
|
||||
koenig: {
|
||||
version: window['@tryghost/koenig-lexical']?.version
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
// if this fails, Sentry is probably not initialized
|
||||
console.error(e); // eslint-disable-line
|
||||
}
|
||||
console.error(error); // eslint-disable-line
|
||||
|
||||
// Pass down Sentry from Ember?
|
||||
// if (this.config.sentry_dsn) {
|
||||
// Sentry.captureException(error, {
|
||||
// tags: {lexical: true},
|
||||
// contexts: {
|
||||
// koenig: {
|
||||
// version: window['@tryghost/koenig-lexical']?.version
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// don't rethrow, Lexical will attempt to gracefully recover
|
||||
}, []);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
|
|
@ -19,6 +19,7 @@ interface ServicesContextProps {
|
|||
search: SearchService;
|
||||
unsplashConfig: DefaultHeaderTypes;
|
||||
toggleFeatureFlag: (flag: string, enabled: boolean) => void;
|
||||
sentryDSN: string | null;
|
||||
}
|
||||
|
||||
interface ServicesProviderProps {
|
||||
|
@ -28,6 +29,7 @@ interface ServicesProviderProps {
|
|||
officialThemes: OfficialTheme[];
|
||||
toggleFeatureFlag: (flag: string, enabled: boolean) => void;
|
||||
unsplashConfig: DefaultHeaderTypes;
|
||||
sentryDSN: string | null;
|
||||
}
|
||||
|
||||
const ServicesContext = createContext<ServicesContextProps>({
|
||||
|
@ -42,10 +44,11 @@ const ServicesContext = createContext<ServicesContextProps>({
|
|||
'Content-Type': '',
|
||||
'App-Pragma': '',
|
||||
'X-Unsplash-Cache': true
|
||||
}
|
||||
},
|
||||
sentryDSN: null
|
||||
});
|
||||
|
||||
const ServicesProvider: React.FC<ServicesProviderProps> = ({children, ghostVersion, zapierTemplates, officialThemes, toggleFeatureFlag, unsplashConfig}) => {
|
||||
const ServicesProvider: React.FC<ServicesProviderProps> = ({children, ghostVersion, zapierTemplates, officialThemes, toggleFeatureFlag, unsplashConfig, sentryDSN}) => {
|
||||
const search = useSearchService();
|
||||
|
||||
return (
|
||||
|
@ -55,7 +58,8 @@ const ServicesProvider: React.FC<ServicesProviderProps> = ({children, ghostVersi
|
|||
zapierTemplates,
|
||||
search,
|
||||
unsplashConfig,
|
||||
toggleFeatureFlag
|
||||
toggleFeatureFlag,
|
||||
sentryDSN
|
||||
}}>
|
||||
{children}
|
||||
</ServicesContext.Provider>
|
||||
|
@ -69,3 +73,5 @@ export const useServices = () => useContext(ServicesContext);
|
|||
export const useOfficialThemes = () => useServices().officialThemes;
|
||||
|
||||
export const useSearch = () => useServices().search;
|
||||
|
||||
export const useSentryDSN = () => useServices().sentryDSN;
|
||||
|
|
|
@ -30,6 +30,7 @@ ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
|||
ref: 'TryGhost/Edition',
|
||||
image: 'assets/img/themes/Edition.png'
|
||||
}]}
|
||||
sentryDSN={'' as string | null}
|
||||
toggleFeatureFlag={() => {}}
|
||||
unsplashConfig={{} as DefaultHeaderTypes}
|
||||
zapierTemplates={[]}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import * as Sentry from '@sentry/react';
|
||||
import handleError from './handleError';
|
||||
import handleResponse from './handleResponse';
|
||||
import {APIError, MaintenanceError, ServerUnreachableError, TimeoutError} from './errors';
|
||||
|
@ -5,7 +6,7 @@ import {QueryClient, UseInfiniteQueryOptions, UseQueryOptions, useInfiniteQuery,
|
|||
import {getGhostPaths} from './helpers';
|
||||
import {useEffect, useMemo} from 'react';
|
||||
import {usePage, usePagination} from '../hooks/usePagination';
|
||||
import {useServices} from '../components/providers/ServiceProvider';
|
||||
import {useSentryDSN, useServices} from '../components/providers/ServiceProvider';
|
||||
|
||||
export interface Meta {
|
||||
pagination: {
|
||||
|
@ -30,6 +31,7 @@ interface RequestOptions {
|
|||
|
||||
export const useFetchApi = () => {
|
||||
const {ghostVersion} = useServices();
|
||||
const sentrydsn = useSentryDSN();
|
||||
|
||||
return async (endpoint: string | URL, options: RequestOptions = {}) => {
|
||||
// By default, we set the Content-Type header to application/json
|
||||
|
@ -85,10 +87,9 @@ export const useFetchApi = () => {
|
|||
...options
|
||||
});
|
||||
|
||||
// TODO: Add Sentry integration
|
||||
// if (attempts !== 0 && config.sentry_dsn) {
|
||||
// captureMessage('Request took multiple attempts', {extra: getErrorData()});
|
||||
// }
|
||||
if (attempts !== 0 && sentrydsn) {
|
||||
Sentry.captureMessage('Request took multiple attempts', {extra: {attempts, retryingMs, endpoint: endpoint.toString()}});
|
||||
}
|
||||
|
||||
return handleResponse(response);
|
||||
} catch (error) {
|
||||
|
@ -102,10 +103,9 @@ export const useFetchApi = () => {
|
|||
continue;
|
||||
}
|
||||
|
||||
// TODO: Add Sentry integration
|
||||
// if (attempts > 0 && config.sentry_dsn) {
|
||||
// captureMessage('Request failed after multiple attempts', {extra: getErrorData()});
|
||||
// }
|
||||
if (attempts !== 0 && sentrydsn) {
|
||||
Sentry.captureMessage('Request failed after multiple attempts', {extra: {attempts, retryingMs, endpoint: endpoint.toString()}});
|
||||
}
|
||||
|
||||
if (error && typeof error === 'object' && 'name' in error && error.name === 'AbortError') {
|
||||
throw new TimeoutError();
|
||||
|
|
|
@ -325,6 +325,7 @@ export default class AdminXSettings extends Component {
|
|||
toggleFeatureFlag={this.toggleFeatureFlag}
|
||||
darkMode={this.feature.nightShift}
|
||||
unsplashConfig={defaultUnsplashHeaders}
|
||||
sentryDSN={this.config.sentry_dsn}
|
||||
/>
|
||||
</Suspense>
|
||||
</ErrorHandler>
|
||||
|
|
|
@ -2637,9 +2637,9 @@
|
|||
tslib "^2.4.0"
|
||||
|
||||
"@elastic/transport@^8.2.0":
|
||||
version "8.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@elastic/transport/-/transport-8.3.3.tgz#06c5b1b9566796775ac96d17959dafc269da5ec1"
|
||||
integrity sha512-g5nc//dq/RQUTMkJUB8Ui8KJa/WflWmUa7yLl4SRZd67PPxIp3cn+OvGMNIhpiLRcfz1upanzgZHb/7Po2eEdQ==
|
||||
version "8.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@elastic/transport/-/transport-8.3.4.tgz#43c852e848dc8502bbd7f23f2d61bd5665cded99"
|
||||
integrity sha512-+0o8o74sbzu3BO7oOZiP9ycjzzdOt4QwmMEjFc1zfO7M0Fh7QX1xrpKqZbSd8vBwihXNlSq/EnMPfgD2uFEmFg==
|
||||
dependencies:
|
||||
debug "^4.3.4"
|
||||
hpagent "^1.0.0"
|
||||
|
|
Loading…
Add table
Reference in a new issue