From 8aabafc235404db9ef918d641fae38fcb7c0ab3a Mon Sep 17 00:00:00 2001 From: George Gritsouk <989898+gggritso@users.noreply.github.com> Date: Tue, 7 Apr 2026 13:09:42 -0400 Subject: [PATCH 1/5] fix(insights): Add has:span.group filter to widget queries Widgets that query for span.group can receive null values from the backend for OpenTelemetry-ingested spans, causing the page to crash. Add has:span.group to the search queries in overviewTimeConsumingQueriesWidget and overviewSlowNextjsSSRWidget, matching the pattern already used by overviewSlowAssetsWidget and overviewSlowQueriesChartWidget. Fixes DAIN-1467 --- .../common/components/widgets/overviewSlowNextjsSSRWidget.tsx | 2 +- .../components/widgets/overviewTimeConsumingQueriesWidget.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/static/app/views/insights/common/components/widgets/overviewSlowNextjsSSRWidget.tsx b/static/app/views/insights/common/components/widgets/overviewSlowNextjsSSRWidget.tsx index b8b8fd914dc7ab..1ca34553fad687 100644 --- a/static/app/views/insights/common/components/widgets/overviewSlowNextjsSSRWidget.tsx +++ b/static/app/views/insights/common/components/widgets/overviewSlowNextjsSSRWidget.tsx @@ -39,7 +39,7 @@ export default function OverviewSlowNextjsSSRWidget(props: LoadableChartWidgetPr const pageFilterChartParams = usePageFilterChartParams(); const {query} = useTransactionNameQuery(); - const fullQuery = `span.op:function.nextjs ${query}`; + const fullQuery = `has:span.group span.op:function.nextjs ${query}`; const spansRequest = useSpans( { diff --git a/static/app/views/insights/common/components/widgets/overviewTimeConsumingQueriesWidget.tsx b/static/app/views/insights/common/components/widgets/overviewTimeConsumingQueriesWidget.tsx index 543843021d4f37..d77edbc0181c12 100644 --- a/static/app/views/insights/common/components/widgets/overviewTimeConsumingQueriesWidget.tsx +++ b/static/app/views/insights/common/components/widgets/overviewTimeConsumingQueriesWidget.tsx @@ -52,7 +52,7 @@ export default function OverviewTimeConsumingQueriesWidget( const supportedSystems = Object.values(SupportedDatabaseSystem); const search = new MutableSearch( - `${SpanFields.DB_SYSTEM}:[${supportedSystems.join(',')}] ${query}`.trim() + `has:${SpanFields.SPAN_GROUP} ${SpanFields.DB_SYSTEM}:[${supportedSystems.join(',')}] ${query}`.trim() ); const referrer = Referrer.OVERVIEW_TIME_CONSUMING_QUERIES_WIDGET; const groupBy = SpanFields.NORMALIZED_DESCRIPTION; From e37778b94d7efd9e694a2fb1f9dbf9009fac3997 Mon Sep 17 00:00:00 2001 From: George Gritsouk <989898+gggritso@users.noreply.github.com> Date: Tue, 7 Apr 2026 13:13:33 -0400 Subject: [PATCH 2/5] ref(insights): Make SpanFields.SPAN_GROUP nullable Move SPAN_GROUP from NonNullableStringFields to NullableStringFields, reflecting the reality that OpenTelemetry-ingested spans can have null span.group values. This makes SpanResponse['span.group'] resolve to string | null, letting TypeScript catch unsafe usages. Fix all surfaced type errors by updating prop types to accept null and adding null coalescing where values are passed to MutableSearch. --- .../eventStatisticalDetector/eventRegressionTable.tsx | 2 +- .../widgets/detailsWidget/detailsWidgetVisualization.tsx | 2 +- .../insights/common/components/fullSpanDescription.tsx | 9 ++++++--- .../views/insights/common/components/spanDescription.tsx | 2 +- .../insights/common/components/spanGroupDetailsLink.tsx | 2 +- .../common/components/tableCells/spanDescriptionCell.tsx | 2 +- static/app/views/insights/types.tsx | 3 +-- 7 files changed, 12 insertions(+), 10 deletions(-) diff --git a/static/app/components/events/eventStatisticalDetector/eventRegressionTable.tsx b/static/app/components/events/eventStatisticalDetector/eventRegressionTable.tsx index 861699ac6d562f..e3ce89c510394d 100644 --- a/static/app/components/events/eventStatisticalDetector/eventRegressionTable.tsx +++ b/static/app/components/events/eventStatisticalDetector/eventRegressionTable.tsx @@ -18,7 +18,7 @@ import {formatPercentage} from 'sentry/utils/number/formatPercentage'; import {unreachable} from 'sentry/utils/unreachable'; export interface EventRegressionTableRow { - group: string; + group: string | null; operation: string; percentageChange: number; description?: string; diff --git a/static/app/views/dashboards/widgets/detailsWidget/detailsWidgetVisualization.tsx b/static/app/views/dashboards/widgets/detailsWidget/detailsWidgetVisualization.tsx index d2ba2364fd4e1d..0e0690a037c5e1 100644 --- a/static/app/views/dashboards/widgets/detailsWidget/detailsWidgetVisualization.tsx +++ b/static/app/views/dashboards/widgets/detailsWidget/detailsWidgetVisualization.tsx @@ -61,7 +61,7 @@ export function DetailsWidgetVisualization(props: DetailsWidgetVisualizationProp (spanDescription.split('?')[0] ?? '').split('.').pop()?.toLowerCase() ?? '' ); - if (isImage) { + if (isImage && spanGroup) { const projectId = span[SpanFields.PROJECT_ID] ? Number(span[SpanFields.PROJECT_ID]) : undefined; diff --git a/static/app/views/insights/common/components/fullSpanDescription.tsx b/static/app/views/insights/common/components/fullSpanDescription.tsx index 84c25a2de4c32d..2e4069d7297e39 100644 --- a/static/app/views/insights/common/components/fullSpanDescription.tsx +++ b/static/app/views/insights/common/components/fullSpanDescription.tsx @@ -21,7 +21,7 @@ const formatter = new SQLishFormatter(); interface Props { moduleName: ModuleName; filters?: Record; - group?: string; + group?: string | null; shortDescription?: string; } @@ -33,7 +33,10 @@ export function FullSpanDescription({ }: Props) { const {data: indexedSpans, isFetching: areIndexedSpansLoading} = useSpans( { - search: MutableSearch.fromQueryObject({'span.group': group, ...filters}), + search: MutableSearch.fromQueryObject({ + 'span.group': group ?? undefined, + ...filters, + }), limit: 1, fields: [ SpanFields.PROJECT_ID, @@ -104,7 +107,7 @@ export function FullSpanDescription({ type TruncatedQueryClipBoxProps = { children: ReactNode; - group: string | undefined; + group: string | null | undefined; }; function QueryClippedBox({group, children}: TruncatedQueryClipBoxProps) { diff --git a/static/app/views/insights/common/components/spanDescription.tsx b/static/app/views/insights/common/components/spanDescription.tsx index 2212b9bac0a823..7f262feb786616 100644 --- a/static/app/views/insights/common/components/spanDescription.tsx +++ b/static/app/views/insights/common/components/spanDescription.tsx @@ -49,7 +49,7 @@ export function DatabaseSpanDescription({ const {data: indexedSpans, isFetching: areIndexedSpansLoading} = useSpans( { - search: MutableSearch.fromQueryObject({'span.group': groupId}), + search: MutableSearch.fromQueryObject({'span.group': groupId ?? undefined}), limit: 1, fields: [ SpanFields.PROJECT_ID, diff --git a/static/app/views/insights/common/components/spanGroupDetailsLink.tsx b/static/app/views/insights/common/components/spanGroupDetailsLink.tsx index d0cc9f26c49941..13b9f8901c649c 100644 --- a/static/app/views/insights/common/components/spanGroupDetailsLink.tsx +++ b/static/app/views/insights/common/components/spanGroupDetailsLink.tsx @@ -17,7 +17,7 @@ interface Props { moduleName: ModuleName.DB | ModuleName.RESOURCE; projectId: number; extraLinkQueryParams?: Record; - group?: string; + group?: string | null; spanOp?: string; } diff --git a/static/app/views/insights/common/components/tableCells/spanDescriptionCell.tsx b/static/app/views/insights/common/components/tableCells/spanDescriptionCell.tsx index c7dec474f85a96..1a7eeeba366277 100644 --- a/static/app/views/insights/common/components/tableCells/spanDescriptionCell.tsx +++ b/static/app/views/insights/common/components/tableCells/spanDescriptionCell.tsx @@ -19,7 +19,7 @@ interface Props { moduleName: ModuleName.DB | ModuleName.RESOURCE; projectId: number; extraLinkQueryParams?: Record; - group?: string; + group?: string | null; spanAction?: string; spanOp?: string; system?: string; diff --git a/static/app/views/insights/types.tsx b/static/app/views/insights/types.tsx index 82b374c14fe1dc..40a0285d19a2c6 100644 --- a/static/app/views/insights/types.tsx +++ b/static/app/views/insights/types.tsx @@ -314,7 +314,6 @@ export type NonNullableStringFields = | SpanFields.FILE_EXTENSION | SpanFields.SPAN_OP | SpanFields.SPAN_DESCRIPTION - | SpanFields.SPAN_GROUP | SpanFields.SPAN_CATEGORY | SpanFields.SPAN_SYSTEM | SpanFields.TIMESTAMP @@ -331,7 +330,7 @@ export type NonNullableStringFields = | SpanFields.USER_DISPLAY | SpanFields.SENTRY_ORIGIN; -type NullableStringFields = SpanFields.NORMALIZED_DESCRIPTION; +type NullableStringFields = SpanFields.NORMALIZED_DESCRIPTION | SpanFields.SPAN_GROUP; export type SpanStringFields = NullableStringFields | NonNullableStringFields; From d011829b82aff14f4e2e1d3a1de21ea04735147e Mon Sep 17 00:00:00 2001 From: George Gritsouk <989898+gggritso@users.noreply.github.com> Date: Tue, 7 Apr 2026 18:05:53 -0400 Subject: [PATCH 3/5] fix(insights): Guard detail page links when span.group is null MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Don't show "View full query" link in QueryClippedBox when group is null — navigating to /spans/span/null is not useful. --- .../common/components/fullSpanDescription.tsx | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/static/app/views/insights/common/components/fullSpanDescription.tsx b/static/app/views/insights/common/components/fullSpanDescription.tsx index 2e4069d7297e39..4f80948c3424e3 100644 --- a/static/app/views/insights/common/components/fullSpanDescription.tsx +++ b/static/app/views/insights/common/components/fullSpanDescription.tsx @@ -117,16 +117,20 @@ function QueryClippedBox({group, children}: TruncatedQueryClipBoxProps) { return ( , - onClick: () => - navigate({ - pathname: `${databaseURL}/spans/span/${group}`, - query: {...location.query, isExpanded: true}, - }), - }} + buttonProps={ + group + ? { + icon: , + onClick: () => + navigate({ + pathname: `${databaseURL}/spans/span/${group}`, + query: {...location.query, isExpanded: true}, + }), + } + : undefined + } > {children} From d57dc322392eab69b3db7338ea326444a3e6785d Mon Sep 17 00:00:00 2001 From: George Gritsouk <989898+gggritso@users.noreply.github.com> Date: Wed, 8 Apr 2026 11:58:23 -0400 Subject: [PATCH 4/5] fix(insights): Use stable React key when span group is null row.group can be null, so using it alone as a React key would cause duplicate keys. Use group + operation + index as a composite key. --- .../events/eventStatisticalDetector/eventRegressionTable.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/app/components/events/eventStatisticalDetector/eventRegressionTable.tsx b/static/app/components/events/eventStatisticalDetector/eventRegressionTable.tsx index e3ce89c510394d..f9f57301ff4871 100644 --- a/static/app/components/events/eventStatisticalDetector/eventRegressionTable.tsx +++ b/static/app/components/events/eventStatisticalDetector/eventRegressionTable.tsx @@ -99,8 +99,8 @@ export function EventRegressionTable({ ) : data.length === 0 ? ( {t('No results found for your query')} ) : ( - data.map(row => ( - + data.map((row, index) => ( + {columns.map(column => ( Date: Fri, 10 Apr 2026 11:45:40 -0400 Subject: [PATCH 5/5] ref(insights): Use SpanFields enum values in SSR widget query Use SpanFields.SPAN_GROUP and SpanFields.SPAN_OP instead of raw strings in overviewSlowNextjsSSRWidget for consistency and to make field usages easier to find via search. --- .../common/components/widgets/overviewSlowNextjsSSRWidget.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/app/views/insights/common/components/widgets/overviewSlowNextjsSSRWidget.tsx b/static/app/views/insights/common/components/widgets/overviewSlowNextjsSSRWidget.tsx index 1ca34553fad687..745b78883d4177 100644 --- a/static/app/views/insights/common/components/widgets/overviewSlowNextjsSSRWidget.tsx +++ b/static/app/views/insights/common/components/widgets/overviewSlowNextjsSSRWidget.tsx @@ -39,7 +39,7 @@ export default function OverviewSlowNextjsSSRWidget(props: LoadableChartWidgetPr const pageFilterChartParams = usePageFilterChartParams(); const {query} = useTransactionNameQuery(); - const fullQuery = `has:span.group span.op:function.nextjs ${query}`; + const fullQuery = `has:${SpanFields.SPAN_GROUP} ${SpanFields.SPAN_OP}:function.nextjs ${query}`; const spansRequest = useSpans( {