Skip to content

Commit

Permalink
Add sentry router tracing and new ErrorBoundary component (#670)
Browse files Browse the repository at this point in the history
* Add sentry router tracing and new ErrorBoundary component

* Remove update to dev button on ErrorBoundary for stable server

* Fix sentry init

* Add mascot to error boundary, make it mobile "friendly"

* Make requested changes.

---------

Co-authored-by: ElementalCrisis <[email protected]>
  • Loading branch information
harshithmohan and ElementalCrisis authored Nov 11, 2023
1 parent 21c89a1 commit bab4aff
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 48 deletions.
Binary file added images/shoko_mascot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
104 changes: 104 additions & 0 deletions src/components/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { useNavigate, useRouteError } from 'react-router-dom';

import ShokoMascot from '@/../images/shoko_mascot.png';
import Button from '@/components/Input/Button';
import { useGetInitVersionQuery } from '@/core/rtkQuery/splitV3Api/initApi';
import { usePostWebuiUpdateMutation } from '@/core/rtkQuery/splitV3Api/webuiApi';
import { unsetDetails } from '@/core/slices/apiSession';

const ErrorBoundary = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const error = useRouteError() as Error; // There is no type definition provided.

const version = useGetInitVersionQuery();
const [webuiUpdateTrigger, webuiUpdateResult] = usePostWebuiUpdateMutation();

const [updateChannel, setUpdateChannel] = useState<'Stable' | 'Dev'>('Stable');

const handleLogout = () => {
dispatch(unsetDetails());
navigate('/webui/login');
};

const handleWebUiUpdate = (channel: 'Stable' | 'Dev') => {
setUpdateChannel(channel);
webuiUpdateTrigger(channel).unwrap().then(() => handleLogout(), err => console.error(err));
};

return (
<div className="relative flex grow items-center justify-center overflow-hidden p-8">
<div className="z-20 flex h-full max-w-[56.4375rem] flex-col items-center justify-center gap-y-4 overflow-y-auto md:gap-y-8">
<div className="text-4xl text-panel-text md:text-7xl">Congratulations!</div>
<div className="text-2xl text-panel-text md:text-5xl">You Broke The Web UI!</div>
<pre className="flex max-h-[25rem] max-w-full flex-col overflow-y-auto whitespace-pre-wrap rounded-lg border border-panel-border bg-panel-input p-4 md:p-8">
The information below is absolutely (maybe) useless!
<br />
<br />
{error.stack}
</pre>
<div className="flex flex-col gap-y-4">
<div>Lets get you back into the Web UI.</div>
<div>
If you are seeing this page after updating, select&nbsp;
<span className="font-semibold text-panel-text-important">Force Update to Stable Web UI</span>
, otherwise select&nbsp;
<span className="font-semibold text-panel-text-important">Logout of Web UI</span>
&nbsp;to clear local storage.
</div>
<div>
Still need help? Hop on our&nbsp;
<a
href="https://discord.gg/vpeHDsg"
target="_blank"
rel="noopener noreferrer"
className="font-semibold text-panel-text-primary"
>
Discord
</a>
&nbsp;server and provide the above error.
</div>
</div>
<div className="flex flex-col gap-y-2 md:flex-row md:gap-x-4">
<Button
onClick={() => handleWebUiUpdate('Stable')}
className="px-4 py-2 drop-shadow-md"
buttonType="primary"
loading={updateChannel === 'Stable' && webuiUpdateResult.isLoading}
>
Force update to Stable Web UI
</Button>

{version.data?.Server.ReleaseChannel !== 'Stable' && (
<Button
onClick={() => handleWebUiUpdate('Dev')}
className="px-4 py-2 drop-shadow-md"
buttonType="primary"
loading={updateChannel === 'Dev' && webuiUpdateResult.isLoading}
>
Force update to Dev Web UI
</Button>
)}

<Button
onClick={() => handleLogout()}
className="px-4 py-2 drop-shadow-md"
buttonType="primary"
>
Logout of Web UI
</Button>
</div>
</div>

<img
src={ShokoMascot}
alt="mascot"
className="absolute -bottom-40 -right-36 z-10 opacity-30"
/>
</div>
);
};

export default ErrorBoundary;
7 changes: 3 additions & 4 deletions src/core/app.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import React from 'react';
import { Provider } from 'react-redux';

import ErrorBoundary from '@/pages/error/ErrorPage';
import * as Sentry from '@sentry/react';

import Router from './router';
import store from './store';

const App = () => (
<Provider store={store}>
<ErrorBoundary>
<Sentry.ErrorBoundary>
<Router />
</ErrorBoundary>
</Sentry.ErrorBoundary>
</Provider>
);

Expand Down
9 changes: 6 additions & 3 deletions src/core/router/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import React, { useEffect, useRef } from 'react';
import { useSelector } from 'react-redux';
import { Navigate, Route } from 'react-router';
import { RouterProvider, createBrowserRouter, createRoutesFromElements } from 'react-router-dom';
import * as Sentry from '@sentry/react';

import ErrorBoundary from '@/components/ErrorBoundary';
import { useGetSettingsQuery } from '@/core/rtkQuery/splitV3Api/settingsApi';
import Collection from '@/pages/collection/Collection';
import Series from '@/pages/collection/Series';
Expand All @@ -25,7 +27,6 @@ import StartServer from '@/pages/firstrun/StartServer';
import LoginPage from '@/pages/login/LoginPage';
import LogsPage from '@/pages/logs/LogsPage';
import MainPage from '@/pages/main/MainPage';
import NoMatchPage from '@/pages/nomatch';
import SettingsPage, { initialSettings } from '@/pages/settings/SettingsPage';
import AniDBSettings from '@/pages/settings/tabs/AniDBSettings';
import GeneralSettings from '@/pages/settings/tabs/GeneralSettings';
Expand All @@ -44,9 +45,11 @@ import AuthenticatedRoute from './AuthenticatedRoute';

import type { RootState } from '@/core/store';

const router = createBrowserRouter(
const sentryCreateBrowserRouter = Sentry.wrapCreateBrowserRouter(createBrowserRouter);

const router = sentryCreateBrowserRouter(
createRoutesFromElements(
<Route path="/" errorElement={<NoMatchPage />}>
<Route path="/" errorElement={<ErrorBoundary />}>
<Route index element={<Navigate to="/webui" replace />} />
<Route path="index.html" element={<Navigate to="/webui" replace />} />
<Route path="webui">
Expand Down
11 changes: 10 additions & 1 deletion src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { createRoutesFromChildren, matchRoutes, useLocation, useNavigationType } from 'react-router-dom';
import * as Sentry from '@sentry/react';
import { createRoot } from 'react-dom/client';

Expand All @@ -12,7 +13,15 @@ if (!isDebug()) {
environment: 'production',
release: uiVersion(),
integrations: [
new Sentry.BrowserTracing(),
new Sentry.BrowserTracing({
routingInstrumentation: Sentry.reactRouterV6Instrumentation(
React.useEffect,
useLocation,
useNavigationType,
createRoutesFromChildren,
matchRoutes,
),
}),
new Sentry.Replay({
maskAllText: true,
blockAllMedia: false,
Expand Down
40 changes: 0 additions & 40 deletions src/pages/error/ErrorPage.tsx

This file was deleted.

0 comments on commit bab4aff

Please sign in to comment.