feat: sentry feedback form (#388)
This commit is contained in:
@@ -20,6 +20,8 @@ jobs:
|
|||||||
REACT_APP_BEE_HOST: https://api.test-node.staging.ethswarm.org/
|
REACT_APP_BEE_HOST: https://api.test-node.staging.ethswarm.org/
|
||||||
REACT_APP_BEE_DEBUG_HOST: https://debug.test-node.staging.ethswarm.org/
|
REACT_APP_BEE_DEBUG_HOST: https://debug.test-node.staging.ethswarm.org/
|
||||||
REACT_APP_DEV_MODE: 1
|
REACT_APP_DEV_MODE: 1
|
||||||
|
REACT_APP_SENTRY_KEY: ${{ secrets.SENTRY_KEY }}
|
||||||
|
REACT_APP_SENTRY_ENVIRONMENT: 'preview'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { ReactElement, useEffect, useState } from 'react'
|
||||||
|
import * as Sentry from '@sentry/react'
|
||||||
|
import { Link } from '@material-ui/core'
|
||||||
|
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
|
||||||
|
import { MessageSquare } from 'react-feather'
|
||||||
|
|
||||||
|
import config from '../config'
|
||||||
|
import SideBarItem from './SideBarItem'
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
link: {
|
||||||
|
color: '#9f9f9f',
|
||||||
|
textDecoration: 'none',
|
||||||
|
'&:hover': {
|
||||||
|
textDecoration: 'none',
|
||||||
|
|
||||||
|
// https://github.com/mui-org/material-ui/issues/22543
|
||||||
|
'@media (hover: none)': {
|
||||||
|
textDecoration: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
icon: {
|
||||||
|
height: theme.spacing(4),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses Sentry DNS so it could be transformed into API call
|
||||||
|
* Sentry DNS like https://1asfasdf2312asdf3@o132123.ingest.sentry.io/13123123
|
||||||
|
*/
|
||||||
|
const SENTRY_PARSING_REGEX = /^https:\/\/(?<key>\w+)@(?<sub>\w+)\.ingest\.sentry\.io\/(?<path>\d+)$/gm
|
||||||
|
|
||||||
|
async function isSentryReachable(): Promise<boolean> {
|
||||||
|
const key = config.SENTRY_KEY
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = SENTRY_PARSING_REGEX.exec(key)
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `https://${match.groups?.sub}.ingest.sentry.io/api/${match.groups?.path}/envelope/?sentry_key=${match.groups?.key}`
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fetch(url, { method: 'POST' })
|
||||||
|
|
||||||
|
// Since we got some reply (even though most probably with some error) that means Sentry is reachable ==> lets provide the Feedback form
|
||||||
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
// If an error was thrown than the request was blocked by the browser so Sentry is not accessible to us
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showFeedbackForm(): void {
|
||||||
|
const eventId = Sentry.captureMessage('User feedback')
|
||||||
|
Sentry.showReportDialog({
|
||||||
|
eventId,
|
||||||
|
title: 'Provide us feedback!',
|
||||||
|
subtitle: 'Share with us what you like and/or dislike.',
|
||||||
|
subtitle2: 'We will be very happy.',
|
||||||
|
labelComments: 'What is your impression about this app?',
|
||||||
|
labelSubmit: 'Send Feedback',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Feedback(): ReactElement {
|
||||||
|
const [sentryEnabled, setSentryEnabled] = useState(false)
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
// Run this only on component mount to verify once that Sentry is reachable
|
||||||
|
useEffect(() => {
|
||||||
|
isSentryReachable().then(result => {
|
||||||
|
setSentryEnabled(result)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (sentryEnabled) {
|
||||||
|
return (
|
||||||
|
<Link onClick={showFeedbackForm} className={classes.link}>
|
||||||
|
<SideBarItem iconStart={<MessageSquare className={classes.icon} />} label={<span>Send feedback</span>} />
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import { Context } from '../providers/Bee'
|
|||||||
import { ROUTES } from '../routes'
|
import { ROUTES } from '../routes'
|
||||||
import SideBarItem from './SideBarItem'
|
import SideBarItem from './SideBarItem'
|
||||||
import SideBarStatus from './SideBarStatus'
|
import SideBarStatus from './SideBarStatus'
|
||||||
|
import Feedback from './Feedback'
|
||||||
|
|
||||||
const navBarItems = [
|
const navBarItems = [
|
||||||
{
|
{
|
||||||
@@ -60,6 +61,7 @@ const useStyles = makeStyles((theme: Theme) =>
|
|||||||
drawerPaper: {
|
drawerPaper: {
|
||||||
width: drawerWidth,
|
width: drawerWidth,
|
||||||
backgroundColor: '#212121',
|
backgroundColor: '#212121',
|
||||||
|
zIndex: 988,
|
||||||
},
|
},
|
||||||
logo: {
|
logo: {
|
||||||
marginLeft: theme.spacing(8),
|
marginLeft: theme.spacing(8),
|
||||||
@@ -131,9 +133,12 @@ export default function SideBar(): ReactElement {
|
|||||||
</List>
|
</List>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Link to={ROUTES.STATUS} className={classes.link}>
|
<List>
|
||||||
<SideBarStatus path={ROUTES.STATUS} />
|
<Link to={ROUTES.STATUS} className={classes.link}>
|
||||||
</Link>
|
<SideBarStatus path={ROUTES.STATUS} />
|
||||||
|
</Link>
|
||||||
|
<Feedback />
|
||||||
|
</List>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ class Config {
|
|||||||
public readonly GITHUB_REPO_URL: string
|
public readonly GITHUB_REPO_URL: string
|
||||||
public readonly BEE_DESKTOP_URL: string
|
public readonly BEE_DESKTOP_URL: string
|
||||||
public readonly SENTRY_KEY: string | undefined
|
public readonly SENTRY_KEY: string | undefined
|
||||||
|
public readonly SENTRY_ENVIRONMENT: string | undefined
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.BEE_API_HOST = sessionStorage.getItem('api_host') ?? process.env.REACT_APP_BEE_HOST ?? 'http://localhost:1633'
|
this.BEE_API_HOST = sessionStorage.getItem('api_host') ?? process.env.REACT_APP_BEE_HOST ?? 'http://localhost:1633'
|
||||||
this.SENTRY_KEY = process.env.REACT_APP_SENTRY_KEY
|
this.SENTRY_KEY = process.env.REACT_APP_SENTRY_KEY
|
||||||
|
this.SENTRY_ENVIRONMENT = process.env.REACT_APP_SENTRY_ENVIRONMENT
|
||||||
this.BEE_DEBUG_API_HOST =
|
this.BEE_DEBUG_API_HOST =
|
||||||
sessionStorage.getItem('debug_api_host') ?? process.env.REACT_APP_BEE_DEBUG_HOST ?? 'http://localhost:1635'
|
sessionStorage.getItem('debug_api_host') ?? process.env.REACT_APP_BEE_DEBUG_HOST ?? 'http://localhost:1635'
|
||||||
this.BLOCKCHAIN_EXPLORER_URL =
|
this.BLOCKCHAIN_EXPLORER_URL =
|
||||||
|
|||||||
@@ -17,3 +17,10 @@ body {
|
|||||||
code {
|
code {
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sentry-error-embed * {
|
||||||
|
font-family: 'Work Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||||
|
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif !important;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export async function initSentry(): Promise<void> {
|
|||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: config.SENTRY_KEY,
|
dsn: config.SENTRY_KEY,
|
||||||
release: packageJson.version,
|
release: packageJson.version,
|
||||||
|
environment: config.SENTRY_ENVIRONMENT,
|
||||||
tunnel: tunnelAvailable ? `${config.BEE_DESKTOP_URL}/sentry` : undefined,
|
tunnel: tunnelAvailable ? `${config.BEE_DESKTOP_URL}/sentry` : undefined,
|
||||||
integrations: [new BrowserTracing({ tracingOrigins: ['localhost'] })],
|
integrations: [new BrowserTracing({ tracingOrigins: ['localhost'] })],
|
||||||
tracesSampleRate: 0.3,
|
tracesSampleRate: 0.3,
|
||||||
|
|||||||
Reference in New Issue
Block a user