Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions sentry.utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { ErrorEvent } from '@sentry/nextjs'
import { shouldIgnoreError } from './sentry.utils'

function eventWith(partial: { message?: string; type?: string; value?: string }): ErrorEvent {
return {
message: partial.message,
exception: { values: [{ type: partial.type, value: partial.value }] },
} as unknown as ErrorEvent
}

describe('shouldIgnoreError — alreadyReported (fetchWithSentry wrapper)', () => {
it('ignores a re-thrown ServiceUnavailableError (already captured at the fetch site)', () => {
expect(shouldIgnoreError(eventWith({ type: 'ServiceUnavailableError', value: 'upstream 503' }))).toBe(true)
})

it('does not ignore an unrelated application error', () => {
expect(shouldIgnoreError(eventWith({ type: 'TypeError', value: 'x is not a function' }))).toBe(false)
})
})
9 changes: 9 additions & 0 deletions sentry.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ const IGNORED_ERRORS = {
// Third-party scripts we don't control
thirdParty: ['googletagmanager', 'gtag', 'analytics', 'hotjar', 'clarity', 'intercom', 'crisp'],

// fetchWithSentry wrapper errors: the underlying timeout/network/HTTP
// failure is already captured at the fetch site with full context, so the
// re-thrown ServiceUnavailableError bubbling to global handlers (or being
// console.error'd by a consumer) would only double-count it (PEANUT-UI-QDJ).
// Substring-matching this pattern is safe only because ServiceUnavailableError
// is our own internal fetchWithSentry wrapper name, not a generic string that
// could appear in an unrelated third-party error message.
alreadyReported: ['ServiceUnavailableError'],

Comment on lines +41 to +49

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Narrow the ServiceUnavailableError ignore rule.

shouldIgnoreError applies patterns with substring matching across the message, exception value, type, and culprit. This suppresses unrelated events that merely mention ServiceUnavailableError, despite the comment describing an exact wrapper match. Match the exception type explicitly (or add regression tests covering both the wrapper and unrelated messages containing this text).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sentry.utils.ts` around lines 41 - 46, Restrict the alreadyReported rule in
sentry.utils.ts so shouldIgnoreError ignores only the fetchWithSentry wrapper’s
exact ServiceUnavailableError exception type, not events whose message, value,
type, or culprit merely contains that text. Update the rule using the existing
error-pattern configuration mechanism and add regression coverage for both the
wrapper match and unrelated containing messages if tests are available.

// Third-party SDK internal errors (not actionable)
thirdPartySdkErrors: [
'IndexedDB:Set:InternalError', // Vercel Analytics storage - fails in private browsing, not actionable
Expand Down
6 changes: 4 additions & 2 deletions src/components/Badges/BadgeStatusDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Drawer, DrawerContent } from '@/components/Global/Drawer'
import { Drawer, DrawerContent, DrawerTitle } from '@/components/Global/Drawer'
import Image from 'next/image'
import { useState } from 'react'
import { formatDate } from '@/utils/general.utils'
Expand Down Expand Up @@ -64,7 +64,9 @@ export const BadgeStatusDrawer = ({ isOpen, onClose, badge }: BadgeStatusDrawerP
<h2 className="flex items-center gap-2 text-xs font-medium text-grey-1">
Badge unlocked!
</h2>
<h1 className={`text-lg font-extrabold md:text-4xl`}>{displayName}</h1>
<DrawerTitle className="text-lg font-extrabold md:text-4xl">
{displayName}
</DrawerTitle>
</div>
</div>
</Card>
Expand Down
8 changes: 6 additions & 2 deletions src/hooks/useTokenPrice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,13 @@ export const useTokenPrice = ({

return null
} catch (error) {
// Preserve Sentry error reporting from original implementation
// fetchWithSentry already reported the underlying failure; this
// capture only surfaces non-fetch errors (beforeSend drops the
// ServiceUnavailableError wrapper). console.info stays out of
// captureConsoleIntegration (PEANUT-UI-MH5) — the fallback is
// graceful, not an error.
Sentry.captureException(error)
console.error('error fetching tokenPrice, falling back to tokenDenomination')
console.info('error fetching tokenPrice, falling back to tokenDenomination')
return null
}
},
Expand Down
5 changes: 4 additions & 1 deletion src/utils/sentry.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,10 @@ export const fetchWithSentry = async (
// fetch rejected (timeout / DNS / connection refused) — the request never
// reached the backend, so flag a connectivity failure.
reportNetworkError()
console.error(error)
// console.info, not error: captureConsoleIntegration would turn an
// error-level log into a second Sentry event on top of the explicit
// captures below.
console.info(error)

if (error instanceof Error && error.name === 'AbortError') {
const timeoutError = new Error(`Request to ${url} timed out after ${timeoutMs}ms`)
Expand Down
Loading