Add ability for users to send error reports
This commit is contained in:
@@ -235,6 +235,7 @@
|
|||||||
"react-responsive": "^10.0.1",
|
"react-responsive": "^10.0.1",
|
||||||
"react-textarea-autosize": "^8.5.3",
|
"react-textarea-autosize": "^8.5.3",
|
||||||
"setimmediate": "^1.0.5",
|
"setimmediate": "^1.0.5",
|
||||||
|
"slugify": "^1.6.9",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"sonner-native": "^0.21.0",
|
"sonner-native": "^0.21.0",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import {useState} from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
import {useLingui} from '@lingui/react/macro'
|
||||||
|
import {useMutation} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import {logger} from '#/logger'
|
||||||
|
import {sendErrorReport} from '#/logger/reporting/sendErrorReport'
|
||||||
|
import {useSession} from '#/state/session'
|
||||||
|
import {atoms as a, web} from '#/alf'
|
||||||
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
|
import * as Dialog from '#/components/Dialog'
|
||||||
|
import * as TextField from '#/components/forms/TextField'
|
||||||
|
import * as Toast from '#/components/Toast'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
|
export function SendErrorReportDialog({
|
||||||
|
control,
|
||||||
|
}: {
|
||||||
|
control: Dialog.DialogControlProps
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Dialog.Outer control={control}>
|
||||||
|
<Dialog.Handle />
|
||||||
|
<SendErrorReportDialogInner />
|
||||||
|
<Dialog.Close />
|
||||||
|
</Dialog.Outer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SendErrorReportDialogInner() {
|
||||||
|
const {t: l} = useLingui()
|
||||||
|
const control = Dialog.useDialogContext()
|
||||||
|
const {currentAccount} = useSession()
|
||||||
|
const [title, setTitle] = useState('')
|
||||||
|
const [description, setDescription] = useState('')
|
||||||
|
|
||||||
|
const {mutate: onSubmit, isPending} = useMutation({
|
||||||
|
mutationFn: async () => {
|
||||||
|
sendErrorReport({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
handle: currentAccount?.handle ?? '',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
control.close(() => {
|
||||||
|
Toast.show(l`Report sent`)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onError: error => {
|
||||||
|
logger.error('Error sending user report', {safeMessage: error})
|
||||||
|
Toast.show(l`Failed to send report`, {type: 'error'})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const canSubmit = title.trim().length > 0 && !isPending
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog.ScrollableInner
|
||||||
|
label={l`Send error report`}
|
||||||
|
style={web({maxWidth: 420})}>
|
||||||
|
<View style={[a.gap_lg]}>
|
||||||
|
<Text style={[a.text_2xl, a.font_semi_bold]}>
|
||||||
|
{l`Send error report`}
|
||||||
|
</Text>
|
||||||
|
<View style={[a.gap_md]}>
|
||||||
|
<View>
|
||||||
|
<TextField.LabelText>{l`Title`}</TextField.LabelText>
|
||||||
|
<TextField.Root>
|
||||||
|
<TextField.Input
|
||||||
|
label={l`Title (100 characters max)`}
|
||||||
|
value={title}
|
||||||
|
onChangeText={value => setTitle(value.slice(0, 100))}
|
||||||
|
/>
|
||||||
|
</TextField.Root>
|
||||||
|
</View>
|
||||||
|
<View>
|
||||||
|
<TextField.LabelText>{l`Description`}</TextField.LabelText>
|
||||||
|
<TextField.Input
|
||||||
|
multiline
|
||||||
|
numberOfLines={8}
|
||||||
|
label={l`Description (1000 characters max)`}
|
||||||
|
value={description}
|
||||||
|
onChangeText={value => setDescription(value.slice(0, 1000))}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View style={[a.gap_sm]}>
|
||||||
|
<Button
|
||||||
|
label={l`Submit`}
|
||||||
|
size="large"
|
||||||
|
color="primary"
|
||||||
|
disabled={!canSubmit}
|
||||||
|
onPress={() => onSubmit()}>
|
||||||
|
<ButtonText>{l`Submit`}</ButtonText>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
label={l`Cancel`}
|
||||||
|
size="large"
|
||||||
|
color="secondary"
|
||||||
|
onPress={() => control.close()}>
|
||||||
|
<ButtonText>{l`Cancel`}</ButtonText>
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Dialog.ScrollableInner>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import slugify from 'slugify'
|
||||||
|
|
||||||
|
import {getEntries} from '#/logger/logDump'
|
||||||
|
import {Sentry} from '#/logger/sentry/lib'
|
||||||
|
|
||||||
|
export function sendErrorReport({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
handle,
|
||||||
|
}: {
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
handle: string
|
||||||
|
}) {
|
||||||
|
const name = slugify(title, {lower: true, strict: true}).slice(0, 32)
|
||||||
|
Sentry.withScope(scope => {
|
||||||
|
scope.addAttachment({
|
||||||
|
filename: name + '.json',
|
||||||
|
data: JSON.stringify(getEntries()),
|
||||||
|
contentType: 'application/json',
|
||||||
|
// mimetype: 'application/json', // need to update Sentry
|
||||||
|
})
|
||||||
|
scope.setExtras({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
handle,
|
||||||
|
})
|
||||||
|
scope.captureMessage(`[USER REPORT] ${handle} ${name}`, 'error')
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -13,12 +13,15 @@ import {type CommonNavigatorParams} from '#/lib/routes/types'
|
|||||||
import * as SettingsList from '#/screens/Settings/components/SettingsList'
|
import * as SettingsList from '#/screens/Settings/components/SettingsList'
|
||||||
import {Atom_Stroke2_Corner0_Rounded as AtomIcon} from '#/components/icons/Atom'
|
import {Atom_Stroke2_Corner0_Rounded as AtomIcon} from '#/components/icons/Atom'
|
||||||
import {BroomSparkle_Stroke2_Corner2_Rounded as BroomSparkleIcon} from '#/components/icons/BroomSparkle'
|
import {BroomSparkle_Stroke2_Corner2_Rounded as BroomSparkleIcon} from '#/components/icons/BroomSparkle'
|
||||||
|
import {Bubbles_Stroke2_Corner2_Rounded as BubblesIcon} from '#/components/icons/Bubble'
|
||||||
import {CodeLines_Stroke2_Corner2_Rounded as CodeLinesIcon} from '#/components/icons/CodeLines'
|
import {CodeLines_Stroke2_Corner2_Rounded as CodeLinesIcon} from '#/components/icons/CodeLines'
|
||||||
import {Globe_Stroke2_Corner0_Rounded as GlobeIcon} from '#/components/icons/Globe'
|
import {Globe_Stroke2_Corner0_Rounded as GlobeIcon} from '#/components/icons/Globe'
|
||||||
import {Newspaper_Stroke2_Corner2_Rounded as NewspaperIcon} from '#/components/icons/Newspaper'
|
import {Newspaper_Stroke2_Corner2_Rounded as NewspaperIcon} from '#/components/icons/Newspaper'
|
||||||
import {Wrench_Stroke2_Corner2_Rounded as WrenchIcon} from '#/components/icons/Wrench'
|
import {Wrench_Stroke2_Corner2_Rounded as WrenchIcon} from '#/components/icons/Wrench'
|
||||||
import * as Layout from '#/components/Layout'
|
import * as Layout from '#/components/Layout'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
|
import * as Prompt from '#/components/Prompt'
|
||||||
|
import {SendErrorReportDialog} from '#/components/SendErrorReportDialog'
|
||||||
import * as Toast from '#/components/Toast'
|
import * as Toast from '#/components/Toast'
|
||||||
import {getDeviceId} from '#/analytics/identifiers'
|
import {getDeviceId} from '#/analytics/identifiers'
|
||||||
import * as env from '#/env'
|
import * as env from '#/env'
|
||||||
@@ -32,6 +35,7 @@ export function AboutSettingsScreen({}: Props) {
|
|||||||
const {_, i18n} = useLingui()
|
const {_, i18n} = useLingui()
|
||||||
const [devModeEnabled, setDevModeEnabled] = useDevMode()
|
const [devModeEnabled, setDevModeEnabled] = useDevMode()
|
||||||
const [demoModeEnabled, setDemoModeEnabled] = useDemoMode()
|
const [demoModeEnabled, setDemoModeEnabled] = useDemoMode()
|
||||||
|
const sendErrorReportControl = Prompt.usePromptControl()
|
||||||
|
|
||||||
const {mutate: onClearImageCache, isPending: isClearingImageCache} =
|
const {mutate: onClearImageCache, isPending: isClearingImageCache} =
|
||||||
useMutation({
|
useMutation({
|
||||||
@@ -109,6 +113,14 @@ export function AboutSettingsScreen({}: Props) {
|
|||||||
<Trans>System log</Trans>
|
<Trans>System log</Trans>
|
||||||
</SettingsList.ItemText>
|
</SettingsList.ItemText>
|
||||||
</SettingsList.LinkItem>
|
</SettingsList.LinkItem>
|
||||||
|
<SettingsList.PressableItem
|
||||||
|
onPress={() => sendErrorReportControl.open()}
|
||||||
|
label={_(msg`Send error report`)}>
|
||||||
|
<SettingsList.ItemIcon icon={BubblesIcon} />
|
||||||
|
<SettingsList.ItemText>
|
||||||
|
<Trans>Send error report</Trans>
|
||||||
|
</SettingsList.ItemText>
|
||||||
|
</SettingsList.PressableItem>
|
||||||
{IS_NATIVE && (
|
{IS_NATIVE && (
|
||||||
<SettingsList.PressableItem
|
<SettingsList.PressableItem
|
||||||
onPress={() => onClearImageCache()}
|
onPress={() => onClearImageCache()}
|
||||||
@@ -182,6 +194,7 @@ export function AboutSettingsScreen({}: Props) {
|
|||||||
)}
|
)}
|
||||||
</SettingsList.Container>
|
</SettingsList.Container>
|
||||||
</Layout.Content>
|
</Layout.Content>
|
||||||
|
<SendErrorReportDialog control={sendErrorReportControl} />
|
||||||
</Layout.Screen>
|
</Layout.Screen>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15119,6 +15119,11 @@ slugify@^1.3.4, slugify@^1.6.6:
|
|||||||
resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.6.6.tgz#2d4ac0eacb47add6af9e04d3be79319cbcc7924b"
|
resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.6.6.tgz#2d4ac0eacb47add6af9e04d3be79319cbcc7924b"
|
||||||
integrity sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==
|
integrity sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==
|
||||||
|
|
||||||
|
slugify@^1.6.9:
|
||||||
|
version "1.6.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.6.9.tgz#610957dea21e56b65e3a153215ef7b265715c8e8"
|
||||||
|
integrity sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg==
|
||||||
|
|
||||||
sockjs@^0.3.24:
|
sockjs@^0.3.24:
|
||||||
version "0.3.24"
|
version "0.3.24"
|
||||||
resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce"
|
resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce"
|
||||||
|
|||||||
Reference in New Issue
Block a user