diff --git a/.changeset/light-socks-admire.md b/.changeset/light-socks-admire.md new file mode 100644 index 00000000..50193fa6 --- /dev/null +++ b/.changeset/light-socks-admire.md @@ -0,0 +1,7 @@ +--- +'@spotlightjs/overlay': patch +'@spotlightjs/electron': patch +'@spotlightjs/spotlight': patch +--- + +Overhaul React code for multiple fixes and performance improvements diff --git a/.vscode/settings.json b/.vscode/settings.json index 744146b9..81af5f75 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,6 +9,8 @@ "spotlightjs", "svgr", "tailwindcss", - "uuidv" + "ttfb", + "uuidv", + "webvitals" ] } diff --git a/packages/overlay/src/components/Overview.tsx b/packages/overlay/src/components/Overview.tsx index 4a694a52..69c2f234 100644 --- a/packages/overlay/src/components/Overview.tsx +++ b/packages/overlay/src/components/Overview.tsx @@ -51,21 +51,12 @@ export default function Overview({
- Not Found - How'd you manage to get here?

} key={'not-found'}>
- {tabs.map(tab => { - const TabContent = tab.content && tab.content; - - if (TabContent) { - return ( - - ); - } - return null; - })} + Not Found - How'd you manage to get here?

} key={'not-found'} /> + {tabs.map(({ content: TabContent, id, processedEvents }) => + TabContent ? ( + + ) : null, + )}
diff --git a/packages/overlay/src/components/Tabs.tsx b/packages/overlay/src/components/Tabs.tsx index 78cd8d97..eefc5a27 100644 --- a/packages/overlay/src/components/Tabs.tsx +++ b/packages/overlay/src/components/Tabs.tsx @@ -55,14 +55,14 @@ export default function Tabs({ tabs, nested, setOpen }: Props) { className="border-primary-800 bg-primary-800 hover:bg-primary-700 hover:border-primary-700 focus:bg-primary-800 text-primary-100 block w-full rounded-md py-2 pl-3 pr-10 focus:outline-none sm:text-sm" onChange={e => { const activeTab = tabs.find(tab => tab.id === e.target.value); - if (activeTab && activeTab.onSelect) { + if (activeTab?.onSelect) { activeTab.onSelect(); } navigate(`${nested ? '' : '/'}${activeTab?.id || 'not-found'}`); }} > - {tabs.map((tab, tabIdx) => ( - ))} @@ -83,7 +83,7 @@ export default function Tabs({ tabs, nested, setOpen }: Props) { '-m-y -mx-2 flex select-none whitespace-nowrap border-b-2 px-2 py-3 text-sm font-medium', ) } - onClick={() => tab.onSelect && tab.onSelect()} + onClick={() => tab.onSelect?.()} > {tab.title} {tab.notificationCount !== undefined ? ( diff --git a/packages/overlay/src/components/Trigger.tsx b/packages/overlay/src/components/Trigger.tsx index 6d58b4bc..6257a6b0 100644 --- a/packages/overlay/src/components/Trigger.tsx +++ b/packages/overlay/src/components/Trigger.tsx @@ -16,7 +16,7 @@ function getAnchorClasses(anchor: AnchorConfig) { return 'top-4 right-4'; case 'bottomLeft': return 'bottom-4 left-4'; - case 'bottomRight': + // case 'bottomRight': default: return 'bottom-4 right-4'; } @@ -35,7 +35,7 @@ function ToolbarItem({
{children} - {!!count && ( + {count && ( {sdkList.length !== 0 ? ( - {sdkList.map(sdk => { - return ( -
- + {sdkList.map(sdk => ( +
+ -
-
{sdk.name}
-
{sdk.version}
- -
+
+
{sdk.name}
+
{sdk.version}
+
- ); - })} +
+ ))} ) : (
Looks like there's no SDKs that have reported yet. 🤔
diff --git a/packages/overlay/src/integrations/sentry/components/SpanResizer.tsx b/packages/overlay/src/integrations/sentry/components/SpanResizer.tsx index c9631328..8f8eb7b7 100644 --- a/packages/overlay/src/integrations/sentry/components/SpanResizer.tsx +++ b/packages/overlay/src/integrations/sentry/components/SpanResizer.tsx @@ -1,4 +1,4 @@ -import { MouseEventHandler, useState } from 'react'; +import { useState, type MouseEventHandler } from 'react'; import classNames from '~/lib/classNames'; type SpanResizerProps = { diff --git a/packages/overlay/src/integrations/sentry/components/Tags.tsx b/packages/overlay/src/integrations/sentry/components/Tags.tsx index 78cbdfd5..2aaabcfc 100644 --- a/packages/overlay/src/integrations/sentry/components/Tags.tsx +++ b/packages/overlay/src/integrations/sentry/components/Tags.tsx @@ -3,9 +3,9 @@ import Tag from '~/ui/Tag'; export default function Tags({ tags }: { tags: { [key: string]: string } }) { return (
- {Object.keys(tags).map(tagKey => { - return ; - })} + {Object.keys(tags).map(tagKey => ( + + ))}
); } diff --git a/packages/overlay/src/integrations/sentry/components/developerInfo/EnvelopeDetails.tsx b/packages/overlay/src/integrations/sentry/components/developerInfo/EnvelopeDetails.tsx index d63e4640..3e7dbc62 100644 --- a/packages/overlay/src/integrations/sentry/components/developerInfo/EnvelopeDetails.tsx +++ b/packages/overlay/src/integrations/sentry/components/developerInfo/EnvelopeDetails.tsx @@ -1,6 +1,6 @@ -import { Envelope } from '@sentry/types'; +import type { Envelope } from '@sentry/types'; import { useState } from 'react'; -import { RawEventContext } from '~/integrations/integration'; +import type { RawEventContext } from '~/integrations/integration'; import SidePanel, { SidePanelHeader } from '~/ui/SidePanel'; import JsonViewer from './JsonViewer'; @@ -14,13 +14,11 @@ export default function EnvelopeDetails({ data }: { data: { envelope: Envelope; - {header.event_id && ( - <> - Event Id {header.event_id} - - )} - + header.event_id ? ( + <> + Event Id {header.event_id} + + ) : undefined } backto="/devInfo" /> @@ -33,8 +31,8 @@ export default function EnvelopeDetails({ data }: { data: { envelope: Envelope; onChange={() => setShowRawJSON(prev => !prev)} checked={showRawJSON} /> -
-
+
+
Show Raw Data diff --git a/packages/overlay/src/integrations/sentry/components/developerInfo/EnvelopeList.tsx b/packages/overlay/src/integrations/sentry/components/developerInfo/EnvelopeList.tsx index 9793d802..6fa43257 100644 --- a/packages/overlay/src/integrations/sentry/components/developerInfo/EnvelopeList.tsx +++ b/packages/overlay/src/integrations/sentry/components/developerInfo/EnvelopeList.tsx @@ -26,7 +26,7 @@ export default function EnvelopeList() { ? sentryDataCache.getEnvelopes().find(({ envelope: _env }) => _env[0].event_id === eventId) || null : null; - if (allEnvelopes && allEnvelopes.length) { + if (allEnvelopes?.length) { return ( <> {hiddenItemCount > 0 && !showAll && ( @@ -47,44 +47,43 @@ export default function EnvelopeList() { const envelopeEventId: string | unknown = header.event_id; const { trace_id } = (header?.trace as { trace_id?: string }) || {}; const envelopeItem = envelope[1].length > 0 ? (envelope[1][0] as EnvelopeItem) : null; - if (typeof envelopeEventId === 'string') { - return ( - -
- -
-

Event Id

-
-
{envelopeEventId.substring(0, 8)}
- {trace_id && helpers.isLocalToSession(trace_id) ? ( - Local - ) : null} -
+ if (typeof envelopeEventId !== 'string') { + return null; + } + return ( + +
+ +
+

Event Id

+
+
{envelopeEventId.substring(0, 8)}
+ {trace_id && helpers.isLocalToSession(trace_id) ? ( + Local + ) : null}
+
-
-

Type

- {envelopeItem && envelopeItem[0]?.type ? <>{envelopeItem[0].type} : '-'} -
-
-

Recieved

- {(header.sent_at as string | Date | number) ? ( - - ) : ( - '-' - )} -
+
+

Type

+ {envelopeItem?.[0]?.type ? envelopeItem[0].type : '-'}
- - ); - } - return null; +
+

Received

+ {(header.sent_at as string | Date | number) ? ( + + ) : ( + '-' + )} +
+
+ + ); })}
diff --git a/packages/overlay/src/integrations/sentry/components/events/Event.tsx b/packages/overlay/src/integrations/sentry/components/events/Event.tsx index 9e8c3f85..72ca707e 100644 --- a/packages/overlay/src/integrations/sentry/components/events/Event.tsx +++ b/packages/overlay/src/integrations/sentry/components/events/Event.tsx @@ -1,14 +1,16 @@ -import { SentryEvent } from '../../types'; -import { Error, ErrorSummary, ErrorTitle } from './error/Error'; +import type { SentryEvent } from '../../types'; +import { ErrorItem, ErrorSummary, ErrorTitle } from './error/Error'; function getEventMessage(event: SentryEvent) { if (typeof event.message === 'string') { return event.message; - } else if (event.message !== undefined && typeof event.message.formatted === 'string') { + } + + if (event.message !== undefined && typeof event.message.formatted === 'string') { return event.message.formatted; - } else { - return ''; } + + return ''; } export function EventTitle({ event }: { event: SentryEvent }) { @@ -16,11 +18,7 @@ export function EventTitle({ event }: { event: SentryEvent }) { return ; } - return ( - <> - {getEventMessage(event)} - - ); + return {getEventMessage(event)}; } export function EventSummary({ event }: { event: SentryEvent }) { @@ -38,7 +36,7 @@ export function EventSummary({ event }: { event: SentryEvent }) { export default function Event({ event }: { event: SentryEvent }) { if ('exception' in event) { - return ; + return ; } return ( diff --git a/packages/overlay/src/integrations/sentry/components/events/EventBreadcrumbs.tsx b/packages/overlay/src/integrations/sentry/components/events/EventBreadcrumbs.tsx index 733f376d..690c5a1d 100644 --- a/packages/overlay/src/integrations/sentry/components/events/EventBreadcrumbs.tsx +++ b/packages/overlay/src/integrations/sentry/components/events/EventBreadcrumbs.tsx @@ -35,10 +35,10 @@ export default function EventBreadcrumbs({ event }: { event: SentryEvent }) { } return (
- {breadcrumbs.map((crumb, crumbIdx) => { - if (!crumb.message) return null; - return ( - + {breadcrumbs + .filter(crumb => crumb.message) + .map((crumb, crumbIdx) => ( +
{crumb.category || ' '}
@@ -54,8 +54,7 @@ export default function EventBreadcrumbs({ event }: { event: SentryEvent }) { {crumb.message} - ); - })} + ))}
); } diff --git a/packages/overlay/src/integrations/sentry/components/events/EventContexts.tsx b/packages/overlay/src/integrations/sentry/components/events/EventContexts.tsx index 521ee1c4..4b85bd79 100644 --- a/packages/overlay/src/integrations/sentry/components/events/EventContexts.tsx +++ b/packages/overlay/src/integrations/sentry/components/events/EventContexts.tsx @@ -12,7 +12,7 @@ export default function EventContexts({ event }: { event: SentryEvent }) { const tags = event.tags; - if ((!contexts || !Object.values(contexts).find(v => !!v)) && !tags) { + if ((!contexts || !Object.values(contexts).some(v => v)) && !tags) { return (
@@ -31,32 +31,29 @@ export default function EventContexts({ event }: { event: SentryEvent }) {
)}
- {Object.entries(contexts).map(([ctxKey, ctxValues]) => { - if (!ctxValues) return null; - return ( + {Object.entries(contexts).map(([ctxKey, ctxValues]) => + ctxValues ? (

{ctxKey}

- {Object.entries(ctxValues).map(([key, value]) => { - return ( - - - - - ); - })} + {Object.entries(ctxValues).map(([key, value]) => ( + + + + + ))}
-
{key}
-
-
-                            {JSON.stringify(value, undefined, 2)}
-                          
-
+
{key}
+
+
+                          {JSON.stringify(value, undefined, 2)}
+                        
+
- ); - })} + ) : null, + )}
); diff --git a/packages/overlay/src/integrations/sentry/components/events/EventDetails.tsx b/packages/overlay/src/integrations/sentry/components/events/EventDetails.tsx index 249708cd..0ab72e28 100644 --- a/packages/overlay/src/integrations/sentry/components/events/EventDetails.tsx +++ b/packages/overlay/src/integrations/sentry/components/events/EventDetails.tsx @@ -57,7 +57,7 @@ export default function EventDetails() {

{renderEventTitle(event)}

- {!!traceCtx && ( + {traceCtx && (
T:{' '} diff --git a/packages/overlay/src/integrations/sentry/components/events/EventList.tsx b/packages/overlay/src/integrations/sentry/components/events/EventList.tsx index a4d87369..d84375c5 100644 --- a/packages/overlay/src/integrations/sentry/components/events/EventList.tsx +++ b/packages/overlay/src/integrations/sentry/components/events/EventList.tsx @@ -56,7 +56,7 @@ export default function EventList() { Local ) : null}
- +
{renderEvent(e)}
diff --git a/packages/overlay/src/integrations/sentry/components/events/error/Error.tsx b/packages/overlay/src/integrations/sentry/components/events/error/Error.tsx index 03863d0e..53cfb816 100644 --- a/packages/overlay/src/integrations/sentry/components/events/error/Error.tsx +++ b/packages/overlay/src/integrations/sentry/components/events/error/Error.tsx @@ -1,8 +1,8 @@ -import { EventException, EventExceptionValue, SentryErrorEvent } from '../../../types'; +import type { EventException, EventExceptionValue, SentryErrorEvent } from '../../../types'; import Frame from './Frame'; export function ErrorTitle({ event }: { event: SentryErrorEvent }) { - const values = arraifyValues(event.exception); + const values = valuesToArray(event.exception); return ( <> {values[0].type}: {values[0].value} @@ -11,7 +11,7 @@ export function ErrorTitle({ event }: { event: SentryErrorEvent }) { } export function ErrorSummary({ event }: { event: SentryErrorEvent }) { - const values = arraifyValues(event.exception); + const values = valuesToArray(event.exception); return (
@@ -23,8 +23,8 @@ export function ErrorSummary({ event }: { event: SentryErrorEvent }) { ); } -export function Error({ event }: { event: SentryErrorEvent }) { - const values = arraifyValues(event.exception); +export function ErrorItem({ event }: { event: SentryErrorEvent }) { + const values = valuesToArray(event.exception); return ( <> @@ -56,7 +56,7 @@ export function Error({ event }: { event: SentryErrorEvent }) { ); } -function arraifyValues(exception: EventException): EventExceptionValue[] { +function valuesToArray(exception: EventException): EventExceptionValue[] { if (exception.value) { return [exception.value]; } diff --git a/packages/overlay/src/integrations/sentry/components/events/error/Frame.tsx b/packages/overlay/src/integrations/sentry/components/events/error/Frame.tsx index 02346648..770245b7 100644 --- a/packages/overlay/src/integrations/sentry/components/events/error/Frame.tsx +++ b/packages/overlay/src/integrations/sentry/components/events/error/Frame.tsx @@ -96,20 +96,19 @@ export default function Frame({
{isOpen && (
- {frame.pre_context && - frame.pre_context.map((line, lineNo) => { - return ( -
- {frame.lineno !== undefined && ( -
- {frame.lineno - frame.pre_context!.length + lineNo} -
- )} -
{line}
-
- ); - })} - {!!frame.context_line && ( + {frame.pre_context?.map((line, relativeLineNo) => { + const lineNo = + frame.lineno != null + ? frame.lineno - (frame.pre_context as string[]).length + relativeLineNo + : relativeLineNo; + return ( +
+ {frame.lineno !== undefined &&
{lineNo}
} +
{line}
+
+ ); + })} + {frame.context_line && (
{frame.context_line}
)} - {frame.post_context && - frame.post_context.map((line, lineNo) => { - return ( -
- {frame.lineno !== undefined && ( -
{frame.lineno + 1 + lineNo}
- )} -
{line}
-
- ); - })} + {frame.post_context?.map((line, relativeLineNo) => { + const lineNo = frame.lineno != null ? frame.lineno + 1 + relativeLineNo : relativeLineNo; + return ( +
+ {frame.lineno !== undefined &&
{lineNo}
} +
{line}
+
+ ); + })} {frame.vars && }
)} diff --git a/packages/overlay/src/integrations/sentry/components/performance/PerformanceTabDetails.tsx b/packages/overlay/src/integrations/sentry/components/performance/PerformanceTabDetails.tsx index d5236760..71b24942 100644 --- a/packages/overlay/src/integrations/sentry/components/performance/PerformanceTabDetails.tsx +++ b/packages/overlay/src/integrations/sentry/components/performance/PerformanceTabDetails.tsx @@ -55,7 +55,7 @@ export default function PerformanceTabDetails() { } /> } /> - } /> + } /> } /> {/* Default tab */} } /> diff --git a/packages/overlay/src/integrations/sentry/components/performance/Queries.tsx b/packages/overlay/src/integrations/sentry/components/performance/Queries.tsx index ecc0e34a..ceccc327 100644 --- a/packages/overlay/src/integrations/sentry/components/performance/Queries.tsx +++ b/packages/overlay/src/integrations/sentry/components/performance/Queries.tsx @@ -1,11 +1,11 @@ -import { useEffect, useState } from 'react'; +import { useMemo, useState } from 'react'; import { Link } from 'react-router-dom'; import { ReactComponent as Sort } from '~/assets/sort.svg'; import { ReactComponent as SortDown } from '~/assets/sortDown.svg'; import classNames from '~/lib/classNames'; import { QUERIES_HEADERS, QUERIES_SORT_KEYS } from '../../constants'; import { useSentrySpans } from '../../data/useSentrySpans'; -import { Span } from '../../types'; +import type { Span } from '../../types'; import { getFormattedDuration } from '../../utils/duration'; const filterDBSpans = (spans: Span[], regex?: RegExp) => { @@ -35,67 +35,51 @@ const calculateQueryInfo = ({ query, spanData }: { query: string; spanData: Span }; }; +type QueryInfoComparator = (a: QueryInfo, b: QueryInfo) => number; +type QuerySortTypes = (typeof QUERIES_SORT_KEYS)[keyof typeof QUERIES_SORT_KEYS]; +const COMPARATORS: Record = { + [QUERIES_SORT_KEYS.queryDesc]: (a, b) => { + if (a.description < b.description) return -1; + if (a.description > b.description) return 1; + return 0; + }, + [QUERIES_SORT_KEYS.avgDuration]: (a, b) => a.avgDuration - b.avgDuration, + [QUERIES_SORT_KEYS.timeSpent]: (a, b) => a.timeSpent - b.timeSpent, +}; + const Queries = ({ showAll }: { showAll: boolean }) => { const [allSpans, localSpans] = useSentrySpans(); - const [queriesData, setQueriesData] = useState([]); const [sort, setSort] = useState({ active: QUERIES_SORT_KEYS.timeSpent, asc: false, }); - const toggleSortOrder = (type: string) => { - if (sort.active === type) { - setSort(prev => ({ - active: type, - asc: !prev.asc, - })); - } else { - setSort({ - active: type, - asc: false, - }); - } - }; + const toggleSortOrder = (type: string) => + setSort(prev => + prev.active === type + ? { + active: type, + asc: !prev.asc, + } + : { + active: type, + asc: false, + }, + ); - const compareQueryInfo = (a: QueryInfo, b: QueryInfo) => { - switch (sort.active) { - case QUERIES_SORT_KEYS.queryDesc: { - if (a.description < b.description) return -1; - if (a.description > b.description) return 1; - return 0; - } - case QUERIES_SORT_KEYS.avgDuration: - return a.avgDuration - b.avgDuration; - case QUERIES_SORT_KEYS.timeSpent: - return a.timeSpent - b.timeSpent; - default: - return a.timeSpent - b.timeSpent; - } - }; + const queriesData: QueryInfo[] = useMemo(() => { + const compareQueryInfo = COMPARATORS[sort.active] || COMPARATORS[QUERIES_SORT_KEYS.timeSpent]; - useEffect(() => { const onlyDBSpans = filterDBSpans(showAll ? allSpans : localSpans, /db\.[A-Za-z]+/); + const uniqueSpansSet = new Set(onlyDBSpans.map(span => String(span?.description).trim())); + // CLear out empty ones (they collapse as a single empty string since this is a set) + uniqueSpansSet.delete(''); + return [...uniqueSpansSet] + .map(query => calculateQueryInfo({ query, spanData: onlyDBSpans })) + .sort((a, b) => (sort.asc ? compareQueryInfo(a, b) : compareQueryInfo(b, a))); + }, [allSpans, localSpans, showAll, sort]); - if (onlyDBSpans.length > 0) { - const uniqueQueries: string[] = [ - ...new Set( - onlyDBSpans - .map(span => span?.description) - .map(String) - .filter(query => query.trim() !== ''), - ), - ]; - setQueriesData( - uniqueQueries - .map(query => calculateQueryInfo({ query, spanData: onlyDBSpans })) - .sort((a, b) => { - return sort.asc ? compareQueryInfo(a, b) : compareQueryInfo(b, a); - }), - ); - } - }, [showAll, sort]); - - if (queriesData && queriesData.length) { + if (queriesData?.length) { return ( diff --git a/packages/overlay/src/integrations/sentry/components/performance/QuerySummary.tsx b/packages/overlay/src/integrations/sentry/components/performance/QuerySummary.tsx index b2dcfb10..0ff366f2 100644 --- a/packages/overlay/src/integrations/sentry/components/performance/QuerySummary.tsx +++ b/packages/overlay/src/integrations/sentry/components/performance/QuerySummary.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useMemo, useState } from 'react'; import { Link, useParams } from 'react-router-dom'; import { ReactComponent as Sort } from '~/assets/sort.svg'; import { ReactComponent as SortDown } from '~/assets/sortDown.svg'; @@ -6,7 +6,7 @@ import classNames from '~/lib/classNames'; import Breadcrumbs from '~/ui/Breadcrumbs'; import { QUERY_SUMMARY_HEADERS, QUERY_SUMMARY_SORT_KEYS } from '../../constants'; import { useSentrySpans } from '../../data/useSentrySpans'; -import { Span } from '../../types'; +import type { Span } from '../../types'; import { getFormattedDuration } from '../../utils/duration'; const filterDBSpans = (spans: Span[], type?: string) => { @@ -17,141 +17,130 @@ const filterDBSpans = (spans: Span[], type?: string) => { return []; }; +type SpanInfoComparator = (a: Span, b: Span) => number; +type QuerySummarySortTypes = (typeof QUERY_SUMMARY_SORT_KEYS)[keyof typeof QUERY_SUMMARY_SORT_KEYS]; +const COMPARATORS: Record = { + [QUERY_SUMMARY_SORT_KEYS.foundIn]: (a, b) => { + if (a.trace_id < b.trace_id) return -1; + if (a.trace_id > b.trace_id) return 1; + return 0; + }, + [QUERY_SUMMARY_SORT_KEYS.spanId]: (a, b) => { + if (a.span_id < b.span_id) return -1; + if (a.span_id > b.span_id) return 1; + return 0; + }, + [QUERY_SUMMARY_SORT_KEYS.timeSpent]: (a, b) => a.timestamp - a.start_timestamp - (b.timestamp - b.start_timestamp), +}; + const QuerySummary = ({ showAll }: { showAll: boolean }) => { const [allSpans, localSpans] = useSentrySpans(); const { type } = useParams(); - const [filteredDBSpans, setFilteredDBSpans] = useState([]); const [sort, setSort] = useState({ active: QUERY_SUMMARY_SORT_KEYS.timeSpent, asc: false, }); - const toggleSortOrder = (type: string) => { - if (sort.active === type) { - setSort(prev => ({ - active: type, - asc: !prev.asc, - })); - } else { - setSort({ - active: type, - asc: false, - }); - } - }; + const toggleSortOrder = (type: string) => + setSort(prev => + prev.active === type + ? { + active: type, + asc: !prev.asc, + } + : { + active: type, + asc: false, + }, + ); - const compareSpanInfo = (a: Span, b: Span) => { - switch (sort.active) { - case QUERY_SUMMARY_SORT_KEYS.foundIn: { - if (a.trace_id < b.trace_id) return -1; - if (a.trace_id > b.trace_id) return 1; - return 0; - } - case QUERY_SUMMARY_SORT_KEYS.spanId: { - if (a.span_id < b.span_id) return -1; - if (a.span_id > b.span_id) return 1; - return 0; - } - case QUERY_SUMMARY_SORT_KEYS.timeSpent: { - const aTimeSpent = a.timestamp - a.start_timestamp; - const bTimeSpent = b.timestamp - b.start_timestamp; - return aTimeSpent - bTimeSpent; - } - default: { - const aTimeSpent = a.timestamp - a.start_timestamp; - const bTimeSpent = b.timestamp - b.start_timestamp; - return aTimeSpent - bTimeSpent; - } - } - }; + const filteredDBSpans: Span[] = useMemo(() => { + const compareSpanInfo = COMPARATORS[sort.active] || COMPARATORS[QUERY_SUMMARY_SORT_KEYS.timeSpent]; - useEffect(() => { - setFilteredDBSpans( - filterDBSpans(showAll ? allSpans : localSpans, type).sort((a, b) => { - return sort.asc ? compareSpanInfo(a, b) : compareSpanInfo(b, a); - }), + return filterDBSpans(showAll ? allSpans : localSpans, type).sort((a, b) => + sort.asc ? compareSpanInfo(a, b) : compareSpanInfo(b, a), ); - }, [showAll, sort]); + }, [allSpans, localSpans, showAll, sort, type]); - if (type && filteredDBSpans && filteredDBSpans.length) { - return ( - <> - -
- - - {QUERY_SUMMARY_HEADERS.map(header => ( - + + + {filteredDBSpans.map(span => ( + + + + + + ))} + +
Query not found.

; + } + return ( + <> + + + + + {QUERY_SUMMARY_HEADERS.map(header => ( + - ))} - - - - {filteredDBSpans.map(span => ( - - - - - + {header.title} + {sort.active === header.sortKey ? ( + + ) : ( + + )} + + ))} - -
+
toggleSortOrder(header.sortKey)} > -
toggleSortOrder(header.sortKey)} - > - {header.title} - {sort.active === header.sortKey ? ( - - ) : ( - - )} -
-
- - {span.trace_id} - - - - {span.span_id} - - - {getFormattedDuration(span.timestamp - span.start_timestamp)} -
- - ); - } - return

Query not found.

; +
+ + {span.trace_id} + + + + {span.span_id} + + + {getFormattedDuration(span.timestamp - span.start_timestamp)} +
+ + ); }; export default QuerySummary; diff --git a/packages/overlay/src/integrations/sentry/components/performance/Resources.tsx b/packages/overlay/src/integrations/sentry/components/performance/Resources.tsx index d10f22d6..cfe84af0 100644 --- a/packages/overlay/src/integrations/sentry/components/performance/Resources.tsx +++ b/packages/overlay/src/integrations/sentry/components/performance/Resources.tsx @@ -1,11 +1,11 @@ -import { useEffect, useState } from 'react'; +import { useMemo, useState } from 'react'; import { ReactComponent as Sort } from '~/assets/sort.svg'; import { ReactComponent as SortDown } from '~/assets/sortDown.svg'; import classNames from '~/lib/classNames'; import Tooltip from '~/ui/Tooltip'; import { RESOURCES_SORT_KEYS, RESOURCE_HEADERS } from '../../constants'; import { useSentrySpans } from '../../data/useSentrySpans'; -import { Span } from '../../types'; +import type { Span } from '../../types'; import { formatBytes } from '../../utils/bytes'; import { getFormattedDuration } from '../../utils/duration'; @@ -53,6 +53,19 @@ const getResourceSpans = (spans: Span[], options: { type?: string; regex?: RegEx return []; }; +type ResourceInfoComparator = (a: ResourceInfo, b: ResourceInfo) => number; +type ResourceSortTypes = (typeof RESOURCES_SORT_KEYS)[keyof typeof RESOURCES_SORT_KEYS]; +const COMPARATORS: Record = { + [RESOURCES_SORT_KEYS.description]: (a, b) => { + if (a.description < b.description) return -1; + if (a.description > b.description) return 1; + return 0; + }, + [RESOURCES_SORT_KEYS.avgEncodedSize]: (a, b) => a.avgEncodedSize - b.avgEncodedSize, + [RESOURCES_SORT_KEYS.avgDuration]: (a, b) => a.avgDuration - b.avgDuration, + [RESOURCES_SORT_KEYS.timeSpent]: (a, b) => a.timeSpent - b.timeSpent, +}; + const Resources = ({ showAll }: { showAll: boolean }) => { const [allSpans, localSpans] = useSentrySpans(); @@ -60,139 +73,113 @@ const Resources = ({ showAll }: { showAll: boolean }) => { active: RESOURCES_SORT_KEYS.timeSpent, asc: false, }); - const [resources, setResources] = useState([]); - - const compareResourceInfo = (a: ResourceInfo, b: ResourceInfo) => { - switch (sort.active) { - case RESOURCES_SORT_KEYS.description: { - if (a.description < b.description) return -1; - if (a.description > b.description) return 1; - return 0; - } - case RESOURCES_SORT_KEYS.avgEncodedSize: - return a.avgEncodedSize - b.avgEncodedSize; - case RESOURCES_SORT_KEYS.avgDuration: - return a.avgDuration - b.avgDuration; - case RESOURCES_SORT_KEYS.timeSpent: - return a.timeSpent - b.timeSpent; - default: - return a.timeSpent - b.timeSpent; - } - }; - const toggleSortOrder = (type: string) => { - if (sort.active === type) { - setSort(prev => ({ - active: type, - asc: !prev.asc, - })); - } else { - setSort({ - active: type, - asc: false, - }); - } - }; + const toggleSortOrder = (type: string) => + setSort(prev => + prev.active === type + ? { + active: type, + asc: !prev.asc, + } + : { + active: type, + asc: false, + }, + ); - useEffect(() => { + const resources = useMemo(() => { const filteredResourceSpans = getResourceSpans(showAll ? allSpans : localSpans, { regex: /resource\.[A-Za-z]+/ }); - if (filteredResourceSpans.length > 0) { - const uniqueResourceDescriptions: string[] = [ - ...new Set( - filteredResourceSpans - .map(span => span?.description) - .map(String) - .filter(resource => resource.trim() !== ''), - ), - ]; - setResources( - uniqueResourceDescriptions - .map(resource => calculateResourceInfo({ resource, spanData: filteredResourceSpans })) - .sort((a, b) => { - return sort.asc ? compareResourceInfo(a, b) : compareResourceInfo(b, a); - }), - ); - } - }, [sort, showAll]); + const uniqueResourceDescriptionsSet = new Set(filteredResourceSpans.map(span => String(span?.description).trim())); + // CLear out empty ones (they collapse as a single empty string since this is a set) + uniqueResourceDescriptionsSet.delete(''); + const uniqueResourceDescriptions: string[] = [...uniqueResourceDescriptionsSet]; + const compareResourceInfo = COMPARATORS[sort.active] || COMPARATORS[RESOURCES_SORT_KEYS.timeSpent]; + + return uniqueResourceDescriptions + .map(resource => calculateResourceInfo({ resource, spanData: filteredResourceSpans })) + .sort((a, b) => { + return sort.asc ? compareResourceInfo(a, b) : compareResourceInfo(b, a); + }); + }, [sort, showAll, allSpans, localSpans]); - if (resources && resources.length) { - return ( - - - - {RESOURCE_HEADERS.map(header => ( - + + + {resources.map((resource: ResourceInfo) => ( + + + + + + + ))} + +
No Resource found.

; + } + return ( + + + + {RESOURCE_HEADERS.map(header => ( + - ))} - - - - {resources.map((resource: ResourceInfo) => ( - - - - - - + {header.title} + {sort.active === header.sortKey ? ( + + ) : ( + + )} + + ))} - -
+
toggleSortOrder(header.sortKey)} > -
toggleSortOrder(header.sortKey)} - > - {header.title} - {sort.active === header.sortKey ? ( - - ) : ( - - )} -
-
- -

Preview

- preview - - ) - } - > -
{resource.description}
-
-
- {getFormattedDuration(resource.avgDuration)} - - {getFormattedDuration(resource.timeSpent)} - - {formatBytes(resource.avgEncodedSize)} -
- ); - } - return

No Resource found.

; +
+ +

Preview

+ preview + + ) + } + > +
{resource.description}
+
+
+ {getFormattedDuration(resource.avgDuration)} + + {getFormattedDuration(resource.timeSpent)} + + {formatBytes(resource.avgEncodedSize)} +
+ ); }; export default Resources; diff --git a/packages/overlay/src/integrations/sentry/components/performance/webVitals/PerformanceChart.tsx b/packages/overlay/src/integrations/sentry/components/performance/webVitals/PerformanceChart.tsx index c4c6c920..b025b505 100644 --- a/packages/overlay/src/integrations/sentry/components/performance/webVitals/PerformanceChart.tsx +++ b/packages/overlay/src/integrations/sentry/components/performance/webVitals/PerformanceChart.tsx @@ -1,9 +1,9 @@ import { useRef, useState } from 'react'; -import { MetricScoreProps, MetricWeightsProps } from '~/integrations/sentry/types'; +import type { MetricScoreProps, MetricWeightsProps } from '~/integrations/sentry/types'; import { calculateLabelCoordinates } from '~/integrations/sentry/utils/webVitals'; import classNames from '~/lib/classNames'; import { RingChart } from '~/ui/Chart'; -import { WebVitals } from '../../../constants'; +import type { WebVitals } from '../../../constants'; import useMouseTracking from '../../../hooks/useMouseTracking'; type Coordinates = { diff --git a/packages/overlay/src/integrations/sentry/components/performance/webVitals/WebVitalsDetail.tsx b/packages/overlay/src/integrations/sentry/components/performance/webVitals/WebVitalsDetail.tsx index be8e4106..cfd952c6 100644 --- a/packages/overlay/src/integrations/sentry/components/performance/webVitals/WebVitalsDetail.tsx +++ b/packages/overlay/src/integrations/sentry/components/performance/webVitals/WebVitalsDetail.tsx @@ -1,7 +1,7 @@ import { useParams } from 'react-router-dom'; import { PERFORMANCE_SCORE_PROFILES } from '~/integrations/sentry/constants'; import { useSentryEvents } from '~/integrations/sentry/data/useSentryEvents'; -import { MetricScoreProps, MetricWeightsProps, SentryEventWithPerformanceData } from '~/integrations/sentry/types'; +import type { MetricScoreProps, MetricWeightsProps, SentryEventWithPerformanceData } from '~/integrations/sentry/types'; import { getFormattedDuration } from '~/integrations/sentry/utils/duration'; import Breadcrumbs from '~/ui/Breadcrumbs'; import { normalizePerformanceScore } from '../../../utils/webVitals'; diff --git a/packages/overlay/src/integrations/sentry/components/performance/webVitals/index.tsx b/packages/overlay/src/integrations/sentry/components/performance/webVitals/index.tsx index 3f044745..5b17b85f 100644 --- a/packages/overlay/src/integrations/sentry/components/performance/webVitals/index.tsx +++ b/packages/overlay/src/integrations/sentry/components/performance/webVitals/index.tsx @@ -1,73 +1,55 @@ -import { useEffect, useState } from 'react'; +import { useMemo, useState } from 'react'; import { Link } from 'react-router-dom'; import { ReactComponent as Sort } from '~/assets/sort.svg'; import { ReactComponent as SortDown } from '~/assets/sortDown.svg'; import { PERFORMANCE_SCORE_PROFILES, WEB_VITALS_HEADERS, WEB_VITALS_SORT_KEYS } from '~/integrations/sentry/constants'; import { useSentryEvents } from '~/integrations/sentry/data/useSentryEvents'; -import { SentryEventWithPerformanceData } from '~/integrations/sentry/types'; +import type { SentryEventWithPerformanceData } from '~/integrations/sentry/types'; import { getFormattedDuration } from '~/integrations/sentry/utils/duration'; import classNames from '~/lib/classNames'; import { normalizePerformanceScore } from '../../../utils/webVitals'; -const WebVitals = ({ showAll }: { showAll: boolean }) => { +type SentryEventComparator = (a: SentryEventWithPerformanceData, b: SentryEventWithPerformanceData) => number; +type WebVitalsSortTypes = (typeof WEB_VITALS_SORT_KEYS)[keyof typeof WEB_VITALS_SORT_KEYS]; +const COMPARATORS: Record = { + [WEB_VITALS_SORT_KEYS.pages]: (a, b) => { + if (a.transaction && b.transaction && a.transaction < b.transaction) return -1; + if (a.transaction && b.transaction && a.transaction > b.transaction) return 1; + return 0; + }, + [WEB_VITALS_SORT_KEYS.lcp]: (a, b) => a.measurements['score.lcp'].value - b.measurements['score.lcp'].value, + [WEB_VITALS_SORT_KEYS.fid]: (a, b) => a.measurements['score.fid'].value - b.measurements['score.fid'].value, + [WEB_VITALS_SORT_KEYS.fcp]: (a, b) => a.measurements['score.fcp'].value - b.measurements['score.fcp'].value, + [WEB_VITALS_SORT_KEYS.cls]: (a, b) => a.measurements['score.cls'].value - b.measurements['score.cls'].value, + [WEB_VITALS_SORT_KEYS.ttfb]: (a, b) => a.measurements['score.ttfb'].value - b.measurements['score.ttfb'].value, + [WEB_VITALS_SORT_KEYS.score]: (a, b) => a.measurements['score.total'].value - b.measurements['score.total'].value, +}; + +const WebVitals = () => { const events = useSentryEvents(); - const [measurementEvents, setMeasurementEvents] = useState([]); const [sort, setSort] = useState({ active: WEB_VITALS_SORT_KEYS.score, asc: false, }); - const toggleSortOrder = (type: string) => { - if (sort.active === type) { - setSort(prev => ({ - active: type, - asc: !prev.asc, - })); - } else { - setSort({ - active: type, - asc: false, - }); - } - }; + const toggleSortOrder = (type: string) => + setSort(prev => + prev.active === type + ? { + active: type, + asc: !prev.asc, + } + : { + active: type, + asc: false, + }, + ); - const compareEvents = (a: SentryEventWithPerformanceData, b: SentryEventWithPerformanceData) => { - switch (sort.active) { - case WEB_VITALS_SORT_KEYS.pages: { - if (a.transaction && b.transaction && a.transaction < b.transaction) return -1; - if (a.transaction && b.transaction && a.transaction > b.transaction) return 1; - return 0; - } - case WEB_VITALS_SORT_KEYS.lcp: { - return a.measurements['score.lcp'].value - b.measurements['score.lcp'].value; - } - case WEB_VITALS_SORT_KEYS.fid: { - return a.measurements['score.fid'].value - b.measurements['score.fid'].value; - } - case WEB_VITALS_SORT_KEYS.fcp: { - return a.measurements['score.fcp'].value - b.measurements['score.fcp'].value; - } - case WEB_VITALS_SORT_KEYS.cls: { - return a.measurements['score.cls'].value - b.measurements['score.cls'].value; - } - case WEB_VITALS_SORT_KEYS.ttfb: { - return a.measurements['score.ttfb'].value - b.measurements['score.ttfb'].value; - } - case WEB_VITALS_SORT_KEYS.score: { - return a.measurements['score.total'].value - b.measurements['score.total'].value; - } - default: { - return a.measurements['score.total'].value - b.measurements['score.total'].value; - } - } - }; + const measurementEvents: SentryEventWithPerformanceData[] = useMemo(() => { + const compareEvents = COMPARATORS[sort.active] || COMPARATORS[WEB_VITALS_SORT_KEYS.score]; - useEffect(() => { - const _measurementEvents = []; - for (const event of events) { - if ( - event.measurements && - event?.contexts?.trace?.op === 'pageload' + return ( + events // TODO: Skipping this check because of not getting all required metrics // && !PERFORMANCE_SCORE_PROFILES.profiles[0].scoreComponents.some(c => { // return ( @@ -76,94 +58,91 @@ const WebVitals = ({ showAll }: { showAll: boolean }) => { // !c.optional // ); // }) - ) { - const updatedEvent = { ...event }; - normalizePerformanceScore(updatedEvent, PERFORMANCE_SCORE_PROFILES); - _measurementEvents.push(updatedEvent as unknown as SentryEventWithPerformanceData); - } - } - setMeasurementEvents( - _measurementEvents.sort((a, b) => { - return sort.asc ? compareEvents(a, b) : compareEvents(b, a); - }), + .filter(event => event.measurements && event?.contexts?.trace?.op === 'pageload') + .map(event => { + const updatedEvent = { ...event }; + normalizePerformanceScore(updatedEvent, PERFORMANCE_SCORE_PROFILES); + return updatedEvent as unknown as SentryEventWithPerformanceData; + }) + .sort((a, b) => (sort.asc ? compareEvents(a, b) : compareEvents(b, a))) ); - }, [showAll, sort]); + }, [events, sort]); - if (measurementEvents && measurementEvents.length) { - return ( - <> - - - - {WEB_VITALS_HEADERS.map(header => ( - + + + {measurementEvents.map(event => ( + + + + + + + + + + ))} + +
No Measurements found.

; + } + return ( + <> + + + + {WEB_VITALS_HEADERS.map(header => ( + - ))} - - - - {measurementEvents.map(event => ( - - - - - - - - - + {header.title} + {sort.active === header.sortKey ? ( + + ) : ( + + )} + + ))} - -
+
toggleSortOrder(header.sortKey)} > -
toggleSortOrder(header.sortKey)} - > - {header.title} - {sort.active === header.sortKey ? ( - - ) : ( - - )} -
-
- - {event.transaction} - - - {event.measurements?.lcp ? getFormattedDuration(event.measurements.lcp.value) : '-'} - - {event.measurements?.fcp ? getFormattedDuration(event.measurements.fcp.value) : '-'} - - {event.measurements?.fid ? getFormattedDuration(event.measurements.fid.value) : '-'} - - {event.measurements?.cls ? event.measurements.cls.value : '-'} - - {event.measurements?.ttfb ? getFormattedDuration(event.measurements.ttfb.value) : '-'} - - {event.measurements['score.total']?.value - ? Math.trunc(event.measurements['score.total'].value * 100) - : '-'} -
- - ); - } - return

No Measurements found.

; +
+ + {event.transaction} + + + {event.measurements?.lcp ? getFormattedDuration(event.measurements.lcp.value) : '-'} + + {event.measurements?.fcp ? getFormattedDuration(event.measurements.fcp.value) : '-'} + + {event.measurements?.fid ? getFormattedDuration(event.measurements.fid.value) : '-'} + + {event.measurements?.cls ? event.measurements.cls.value : '-'} + + {event.measurements?.ttfb ? getFormattedDuration(event.measurements.ttfb.value) : '-'} + + {event.measurements['score.total']?.value + ? Math.trunc(event.measurements['score.total'].value * 100) + : '-'} +
+ + ); }; export default WebVitals; diff --git a/packages/overlay/src/integrations/sentry/components/traces/TraceDetails.tsx b/packages/overlay/src/integrations/sentry/components/traces/TraceDetails.tsx index d915e865..3454b016 100644 --- a/packages/overlay/src/integrations/sentry/components/traces/TraceDetails.tsx +++ b/packages/overlay/src/integrations/sentry/components/traces/TraceDetails.tsx @@ -72,14 +72,14 @@ export default function TraceDetails() { onChange={handleSearch} placeholder="Search in Trace" /> - {query && ( + {query ? ( - )} + ) : null}
; - } else { - return ( -
- {platformsInTrace - .slice(0, 4) - .map((_platform, ind) => - ind < 3 ? ( - - ) : ( -
{`+${platformsInTrace.length - 3}`}
- ), - )} -
- ); } + + const dominantPlatforms = platformsInTrace.slice(0, 3); + const remainingPlatforms = platformsInTrace.slice(3); + return ( +
+ {dominantPlatforms.map(platform => ( + + ))} + {remainingPlatforms.length && ( +
{`+${remainingPlatforms.length}`}
+ )} +
+ ); } diff --git a/packages/overlay/src/integrations/sentry/components/traces/spans/SpanDetails.tsx b/packages/overlay/src/integrations/sentry/components/traces/spans/SpanDetails.tsx index 961b771e..86c06198 100644 --- a/packages/overlay/src/integrations/sentry/components/traces/spans/SpanDetails.tsx +++ b/packages/overlay/src/integrations/sentry/components/traces/spans/SpanDetails.tsx @@ -26,7 +26,7 @@ function formatValue(name: string, value: unknown): ReactNode { if (name.indexOf('size') !== -1 || name.indexOf('length') !== -1) return formatBytes(value); return value.toLocaleString(); } - return value as ReactNode; + return `${value}` as ReactNode; } export default function SpanDetails({ @@ -91,7 +91,7 @@ export default function SpanDetails({
- {!!errors.length && ( + {errors.length && (

Related Errors

{errors.map(event => ( @@ -130,18 +130,16 @@ export default function SpanDetails({ {span.tags && Object.keys(span.tags).length ? ( - {Object.entries(span.tags).map(([key, value]) => { - return ( - - - - - ); - })} + {Object.entries(span.tags).map(([key, value]) => ( + + + + + ))}
-
{key}
-
-
{JSON.stringify(value, undefined, 2)}
-
+
{key}
+
+
{JSON.stringify(value, undefined, 2)}
+
) : ( @@ -159,7 +157,11 @@ export default function SpanDetails({ [ 'parent', span.parent_span_id ? ( - + {span.parent_span_id} ) : ( @@ -167,18 +169,16 @@ export default function SpanDetails({ ), ], ['op', span.op], - ].map(([key, value]) => { - return ( - - -
{key}
- - -
{value}
- - - ); - })} + ].map(([key, value]) => ( + + +
{key}
+ + +
{value}
+ + + ))}
@@ -188,24 +188,22 @@ export default function SpanDetails({

Data

- {Object.entries(span.data).map(([key, value]) => { - return ( - - - - - ); - })} + {Object.entries(span.data).map(([key, value]) => ( + + + + + ))}
-
{key}
-
-
{formatValue(key, value)}
-
+
{key}
+
+
{formatValue(key, value)}
+
)} - {!!span.children?.length && ( + {span.children?.length && (

Sub-tree

|| '-'], ['Total Duration', `${getDuration(trace.start_timestamp, trace.timestamp).toLocaleString()} ms`], - ].map(([key, value]) => { - return ( - - -
{key}
- - -
{value}
- - - ); - })} + ].map(([key, value]) => ( + + +
{key}
+ + +
{value}
+ + + ))}
diff --git a/packages/overlay/src/integrations/sentry/components/traces/traceInfo/TraceTags.tsx b/packages/overlay/src/integrations/sentry/components/traces/traceInfo/TraceTags.tsx index 804893a4..d7571dfb 100644 --- a/packages/overlay/src/integrations/sentry/components/traces/traceInfo/TraceTags.tsx +++ b/packages/overlay/src/integrations/sentry/components/traces/traceInfo/TraceTags.tsx @@ -1,4 +1,4 @@ -import { Trace } from '~/integrations/sentry/types'; +import type { Trace } from '~/integrations/sentry/types'; type TraceTagsProps = { trace: Trace; @@ -8,7 +8,7 @@ export default function TraceTags({ trace }: TraceTagsProps) { const tags = trace.transactions .map(tsx => tsx.tags) .reduce((prev, current) => { - return { ...prev, ...current }; + return Object.assign(prev!, current); }, {}); return ( @@ -17,18 +17,16 @@ export default function TraceTags({ trace }: TraceTagsProps) { {tags && Object.keys(tags).length ? ( - {Object.entries(tags).map(([key, value]) => { - return ( - - - - - ); - })} + {Object.entries(tags).map(([key, value]) => ( + + + + + ))}
-
{key}
-
-
{JSON.stringify(value, undefined, 2)}
-
+
{key}
+
+
{JSON.stringify(value, undefined, 2)}
+
) : ( diff --git a/packages/overlay/src/integrations/sentry/components/traces/traceInfo/index.tsx b/packages/overlay/src/integrations/sentry/components/traces/traceInfo/index.tsx index 2835d10c..b85dd445 100644 --- a/packages/overlay/src/integrations/sentry/components/traces/traceInfo/index.tsx +++ b/packages/overlay/src/integrations/sentry/components/traces/traceInfo/index.tsx @@ -1,6 +1,6 @@ import SidePanel, { SidePanelHeader } from '~/ui/SidePanel'; import dataCache from '../../../data/sentryDataCache'; -import { TraceContext } from '../../../types'; +import type { TraceContext } from '../../../types'; import TraceGeneralInfo from './TraceGeneralInfo'; import TraceTags from './TraceTags'; diff --git a/packages/overlay/src/integrations/sentry/data/sentryEventsContext.tsx b/packages/overlay/src/integrations/sentry/data/sentryEventsContext.tsx index 5c56a39f..5905b222 100644 --- a/packages/overlay/src/integrations/sentry/data/sentryEventsContext.tsx +++ b/packages/overlay/src/integrations/sentry/data/sentryEventsContext.tsx @@ -34,12 +34,9 @@ export const SentryEventsContextProvider: React.FC<{ const [events, setEvents] = useReducer(eventReducer, sentryDataCache.getEvents()); useEffect(() => { - const unsubscribe = sentryDataCache.subscribe('event', (e: SentryEvent) => { + return sentryDataCache.subscribe('event', (e: SentryEvent) => { setEvents({ action: 'APPEND', e }); }); - return () => { - unsubscribe(); - }; }, []); const contextValue: SentryEventsContextProps = { diff --git a/packages/overlay/src/ui/Tooltip/Tooltip.tsx b/packages/overlay/src/ui/Tooltip/Tooltip.tsx index 67606266..4aad2583 100644 --- a/packages/overlay/src/ui/Tooltip/Tooltip.tsx +++ b/packages/overlay/src/ui/Tooltip/Tooltip.tsx @@ -1,4 +1,4 @@ -import { ReactNode, useState } from 'react'; +import { type ReactNode, useState } from 'react'; import classNames from '~/lib/classNames'; const getPositionClass = (position: string) => {