mirror of
https://github.com/uetchy/namae.git
synced 2025-08-20 09:58:13 +09:00
feat: add sentry
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import React, {useState} from 'react';
|
||||
import React, {useState, useEffect} from 'react';
|
||||
import styled, {createGlobalStyle} from 'styled-components';
|
||||
import {Helmet} from 'react-helmet';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
import {initGA, sendQueryStatistics} from './util/analytics';
|
||||
|
||||
import Welcome from './components/Welcome';
|
||||
import Form from './components/Form';
|
||||
@@ -15,8 +16,13 @@ export default function App() {
|
||||
const [query, setQuery] = useState('');
|
||||
const {t} = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
initGA();
|
||||
}, []);
|
||||
|
||||
function onQuery(query: string) {
|
||||
setQuery(query);
|
||||
sendQueryStatistics(query.length);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@@ -6,6 +6,7 @@ import 'react-tippy/dist/tippy.css';
|
||||
import BarLoader from 'react-spinners/BarLoader';
|
||||
import {GoInfo} from 'react-icons/go';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
import {sendError} from '../../util/analytics';
|
||||
|
||||
import {mobile} from '../../util/css';
|
||||
import {ExternalLink} from '../Links';
|
||||
@@ -21,16 +22,7 @@ export const Card: React.FC<{title: string}> = ({title, children}) => {
|
||||
<CardContainer>
|
||||
<CardTitle>{title}</CardTitle>
|
||||
<CardContent>
|
||||
<ErrorBoundary>
|
||||
<Suspense
|
||||
fallback={
|
||||
<ResultContainer>
|
||||
<BarLoader />
|
||||
</ResultContainer>
|
||||
}>
|
||||
{children}
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
<ErrorHandler>{children}</ErrorHandler>
|
||||
</CardContent>
|
||||
</CardContainer>
|
||||
);
|
||||
@@ -55,12 +47,12 @@ export const Repeater: React.FC<{
|
||||
return (
|
||||
<>
|
||||
{items.map((name) => (
|
||||
<CellError key={name}>{children(name)}</CellError>
|
||||
<ErrorHandler key={name}>{children(name)}</ErrorHandler>
|
||||
))}
|
||||
|
||||
{revealAlternatives
|
||||
? moreItems.map((name) => (
|
||||
<CellError key={name}>{children(name)}</CellError>
|
||||
<ErrorHandler key={name}>{children(name)}</ErrorHandler>
|
||||
))
|
||||
: null}
|
||||
{moreItems.length > 0 && !revealAlternatives ? (
|
||||
@@ -208,24 +200,37 @@ export const Result: React.FC<{
|
||||
);
|
||||
};
|
||||
|
||||
// 1. getDerivedStateFromError
|
||||
// 2. render()
|
||||
// 3. componentDidCatch() send errorInfo to Sentry
|
||||
// 4. render(), now with eventId provided from Sentry
|
||||
class ErrorBoundary extends React.Component<
|
||||
{},
|
||||
{hasError: boolean; message: string}
|
||||
{hasError: boolean; message: string; eventId?: string}
|
||||
> {
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
this.state = {hasError: false, message: ''};
|
||||
this.state = {hasError: false, message: '', eventId: undefined};
|
||||
}
|
||||
|
||||
// used in SSR
|
||||
static getDerivedStateFromError(error: Error) {
|
||||
return {hasError: true, message: error.message};
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: any) {
|
||||
sendError(error, errorInfo).then((eventId) => {
|
||||
this.setState({eventId});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<Tooltip
|
||||
title={this.state.message}
|
||||
title={`${this.state.message}${
|
||||
this.state.eventId ? ` (${this.state.eventId})` : ''
|
||||
}`}
|
||||
position="bottom"
|
||||
arrow={true}
|
||||
animation="shift"
|
||||
@@ -245,7 +250,7 @@ class ErrorBoundary extends React.Component<
|
||||
}
|
||||
}
|
||||
|
||||
const CellError: React.FC = ({children}) => (
|
||||
const ErrorHandler: React.FC = ({children}) => (
|
||||
<ErrorBoundary>
|
||||
<Suspense
|
||||
fallback={
|
||||
|
@@ -3,22 +3,16 @@ import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
import * as serviceWorker from './serviceWorker';
|
||||
import {FullScreenSuspense} from './util/suspense';
|
||||
import {initSentry} from './util/analytics';
|
||||
import './util/i18n';
|
||||
|
||||
const Container = () => (
|
||||
initSentry();
|
||||
|
||||
ReactDOM.render(
|
||||
<FullScreenSuspense>
|
||||
<App />
|
||||
</FullScreenSuspense>
|
||||
</FullScreenSuspense>,
|
||||
document.getElementById('root'),
|
||||
);
|
||||
|
||||
ReactDOM.render(<Container />, document.getElementById('root'));
|
||||
|
||||
// register Google Analytics
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
import('react-ga').then((ReactGA) => {
|
||||
ReactGA.initialize('UA-28919359-15');
|
||||
ReactGA.pageview(window.location.pathname + window.location.search);
|
||||
});
|
||||
}
|
||||
|
||||
serviceWorker.register({});
|
||||
|
41
web/src/util/analytics.ts
Normal file
41
web/src/util/analytics.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import ReactGA from 'react-ga';
|
||||
import * as Sentry from '@sentry/browser';
|
||||
|
||||
const isProduction = process.env.NODE_ENV !== 'development';
|
||||
|
||||
export function initGA() {
|
||||
if (isProduction) {
|
||||
ReactGA.initialize('UA-28919359-15');
|
||||
ReactGA.pageview(window.location.pathname + window.location.search);
|
||||
}
|
||||
}
|
||||
|
||||
export function sendQueryStatistics(queryLength: number) {
|
||||
if (isProduction) {
|
||||
ReactGA.event({
|
||||
category: 'Search',
|
||||
action: 'New search invoked',
|
||||
value: queryLength,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function initSentry() {
|
||||
if (isProduction) {
|
||||
Sentry.init({
|
||||
dsn: 'https://7ab2df74aead499b950ebef190cc40b7@sentry.io/1759299',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function sendError(error: Error, errorInfo: any): Promise<string> {
|
||||
return new Promise((resolve) => {
|
||||
if (isProduction) {
|
||||
Sentry.withScope((scope) => {
|
||||
scope.setExtras(errorInfo);
|
||||
const eventId = Sentry.captureException(error);
|
||||
resolve(eventId);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user