diff --git a/namae.code-workspace b/namae.code-workspace
index 2cb7696..64b4f15 100644
--- a/namae.code-workspace
+++ b/namae.code-workspace
@@ -1,9 +1,5 @@
{
"folders": [
- {
- "name": "Root",
- "path": "."
- },
{
"name": "API",
"path": "api"
diff --git a/web/package.json b/web/package.json
index e30ea4a..a073e36 100644
--- a/web/package.json
+++ b/web/package.json
@@ -5,11 +5,12 @@
"build": "NODE_ENV=production react-scripts build",
"eject": "react-scripts eject",
"now-build": "yarn build",
- "now-dev": "BROWSER=none react-scripts start",
- "start": "react-scripts start",
+ "now-dev": "NODE_ENV=development BROWSER=none react-scripts start",
+ "start": "NODE_ENV=development react-scripts start",
"test": "react-scripts test"
},
"dependencies": {
+ "@sentry/browser": "^5.6.3",
"fetch-suspense": "^1.2.0",
"i18next": ">=17.0.12",
"i18next-browser-languagedetector": "^3.0.3",
@@ -33,7 +34,7 @@
},
"devDependencies": {
"@testing-library/jest-dom": "^4.1.0",
- "@testing-library/react": "^9.1.3",
+ "@testing-library/react": "^9.1.4",
"@types/i18next-node-fs-backend": "^2.1.0",
"@types/jest": "^24.0.18",
"@types/node": "^12.7.3",
diff --git a/web/src/App.tsx b/web/src/App.tsx
index b1b5d98..a213e00 100644
--- a/web/src/App.tsx
+++ b/web/src/App.tsx
@@ -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 (
diff --git a/web/src/components/cards/core.tsx b/web/src/components/cards/core.tsx
index d2db96c..fe713a5 100644
--- a/web/src/components/cards/core.tsx
+++ b/web/src/components/cards/core.tsx
@@ -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}) => {
{title}
-
-
-
-
- }>
- {children}
-
-
+ {children}
);
@@ -55,12 +47,12 @@ export const Repeater: React.FC<{
return (
<>
{items.map((name) => (
- {children(name)}
+ {children(name)}
))}
{revealAlternatives
? moreItems.map((name) => (
- {children(name)}
+ {children(name)}
))
: 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 (
(
+const ErrorHandler: React.FC = ({children}) => (
(
+initSentry();
+
+ReactDOM.render(
-
+ ,
+ document.getElementById('root'),
);
-ReactDOM.render(, 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({});
diff --git a/web/src/util/analytics.ts b/web/src/util/analytics.ts
new file mode 100644
index 0000000..00906c6
--- /dev/null
+++ b/web/src/util/analytics.ts
@@ -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 {
+ return new Promise((resolve) => {
+ if (isProduction) {
+ Sentry.withScope((scope) => {
+ scope.setExtras(errorInfo);
+ const eventId = Sentry.captureException(error);
+ resolve(eventId);
+ });
+ }
+ });
+}
diff --git a/web/yarn.lock b/web/yarn.lock
index f9cc0e8..98534bf 100644
--- a/web/yarn.lock
+++ b/web/yarn.lock
@@ -1091,6 +1091,58 @@
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==
+"@sentry/browser@^5.6.3":
+ version "5.6.3"
+ resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.6.3.tgz#5cc37b0443eba55ad13c13d34d6b95ff30dfbfe3"
+ integrity sha512-bP1LTbcKPOkkmfJOAM6c7WZ0Ov0ZEW6B9keVZ9wH9fw/lBPd9UyDMDCwJ+FAYKz9M9S5pxQeJ4Ebd7WUUrGVAQ==
+ dependencies:
+ "@sentry/core" "5.6.2"
+ "@sentry/types" "5.6.1"
+ "@sentry/utils" "5.6.1"
+ tslib "^1.9.3"
+
+"@sentry/core@5.6.2":
+ version "5.6.2"
+ resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.6.2.tgz#8c5477654a83ebe41a72e86a79215deb5025e418"
+ integrity sha512-grbjvNmyxP5WSPR6UobN2q+Nss7Hvz+BClBT8QTr7VTEG5q89TwNddn6Ej3bGkaUVbct/GpVlI3XflWYDsnU6Q==
+ dependencies:
+ "@sentry/hub" "5.6.1"
+ "@sentry/minimal" "5.6.1"
+ "@sentry/types" "5.6.1"
+ "@sentry/utils" "5.6.1"
+ tslib "^1.9.3"
+
+"@sentry/hub@5.6.1":
+ version "5.6.1"
+ resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.6.1.tgz#9f355c0abcc92327fbd10b9b939608aa4967bece"
+ integrity sha512-m+OhkIV5yTAL3R1+XfCwzUQka0UF/xG4py8sEfPXyYIcoOJ2ZTX+1kQJLy8QQJ4RzOBwZA+DzRKP0cgzPJ3+oQ==
+ dependencies:
+ "@sentry/types" "5.6.1"
+ "@sentry/utils" "5.6.1"
+ tslib "^1.9.3"
+
+"@sentry/minimal@5.6.1":
+ version "5.6.1"
+ resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.6.1.tgz#09d92b26de0b24555cd50c3c33ba4c3e566009a1"
+ integrity sha512-ercCKuBWHog6aS6SsJRuKhJwNdJ2oRQVWT2UAx1zqvsbHT9mSa8ZRjdPHYOtqY3DoXKk/pLUFW/fkmAnpdMqRw==
+ dependencies:
+ "@sentry/hub" "5.6.1"
+ "@sentry/types" "5.6.1"
+ tslib "^1.9.3"
+
+"@sentry/types@5.6.1":
+ version "5.6.1"
+ resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.6.1.tgz#5915e1ee4b7a678da3ac260c356b1cb91139a299"
+ integrity sha512-Kub8TETefHpdhvtnDj3kKfhCj0u/xn3Zi2zIC7PB11NJHvvPXENx97tciz4roJGp7cLRCJsFqCg4tHXniqDSnQ==
+
+"@sentry/utils@5.6.1":
+ version "5.6.1"
+ resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.6.1.tgz#69d9e151e50415bc91f2428e3bcca8beb9bc2815"
+ integrity sha512-rfgha+UsHW816GqlSRPlniKqAZylOmQWML2JsujoUP03nPu80zdN43DK9Poy/d9OxBxv0gd5K2n+bFdM2kqLQQ==
+ dependencies:
+ "@sentry/types" "5.6.1"
+ tslib "^1.9.3"
+
"@sheerun/mutationobserver-shim@^0.3.2":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz#8013f2af54a2b7d735f71560ff360d3a8176a87b"
@@ -1199,10 +1251,10 @@
"@svgr/plugin-svgo" "^4.3.1"
loader-utils "^1.2.3"
-"@testing-library/dom@^6.0.0":
- version "6.1.0"
- resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-6.1.0.tgz#8d5a954158e81ecd7c994907f4ec240296ed823b"
- integrity sha512-qivqFvnbVIH3DyArFofEU/jlOhkGIioIemOy9A9M/NQTpPyDDQmtVkAfoB18RKN581f0s/RJMRBbq9WfMIhFTw==
+"@testing-library/dom@^6.1.0":
+ version "6.4.0"
+ resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-6.4.0.tgz#aaf7fceba1272516fc7c5ac0716a24f63ea6cdaa"
+ integrity sha512-uQFwl+mIH9THk9Q9qVZKBgoL/6ahVEQu9bDeOmY5yB8uc62L2Z9eYs0g7zNTdMsg4I0bOdPPMs/sNETYP5+PEw==
dependencies:
"@babel/runtime" "^7.5.5"
"@sheerun/mutationobserver-shim" "^0.3.2"
@@ -1226,13 +1278,13 @@
pretty-format "^24.0.0"
redent "^3.0.0"
-"@testing-library/react@^9.1.3":
- version "9.1.3"
- resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-9.1.3.tgz#3fb495227322ea36cd817532441dabb552e0d6ce"
- integrity sha512-qFVo6TsEbpEFpOmKjIxMHDujOKVdvVpcYFcUfJeWBqMO8eja5pN9SZnt6W6AzW3a1MRvRfw3X0Fhx3eXnBJxjA==
+"@testing-library/react@^9.1.4":
+ version "9.1.4"
+ resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-9.1.4.tgz#4cc1a228a944c0f468ee501e7da1651d8bbd9902"
+ integrity sha512-fQ/PXZoLcmnS1W5ZiM3P7XBy2x6Hm9cJAT/ZDuZKzJ1fS1rN3j31p7ReAqUe3N1kJ46sNot0n1oiGbz7FPU+FA==
dependencies:
"@babel/runtime" "^7.5.5"
- "@testing-library/dom" "^6.0.0"
+ "@testing-library/dom" "^6.1.0"
"@types/testing-library__react" "^9.1.0"
"@types/babel__core@^7.1.0":
@@ -9829,7 +9881,7 @@ ts-pnp@^1.1.2:
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.1.4.tgz#ae27126960ebaefb874c6d7fa4729729ab200d90"
integrity sha512-1J/vefLC+BWSo+qe8OnJQfWTYRS6ingxjwqmHMqaMxXMj7kFtKLgAaYW3JeX3mktjgUL+etlU8/B4VUAUI9QGw==
-tslib@^1.8.1, tslib@^1.9.0:
+tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
version "1.10.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==