Skip to content

Commit 9d627bd

Browse files
nsdeschenescodex
andcommitted
fix(dashboards): Avoid narrowing trace metric suggestions
Keep trace metric attribute suggestions unscoped when a dashboard widget selects multiple distinct metrics. This matches the rendered search bar behavior and avoids hiding valid attributes for non-primary metrics. Add a regression test that covers both distinct multi-metric selections and multiple aggregates on the same metric. Co-Authored-By: GPT-5.4 <noreply@openai.com> Made-with: Cursor
1 parent 3f10a2a commit 9d627bd

File tree

2 files changed

+132
-48
lines changed

2 files changed

+132
-48
lines changed

static/app/views/dashboards/datasetConfig/traceMetrics.spec.tsx

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,20 @@ import {
1212
} from 'sentry/views/dashboards/datasetConfig/traceMetrics';
1313
import {DisplayType, WidgetType, type WidgetQuery} from 'sentry/views/dashboards/types';
1414
import {WidgetBuilderProvider} from 'sentry/views/dashboards/widgetBuilder/contexts/widgetBuilderContext';
15+
import {createTraceMetricFilter} from 'sentry/views/explore/metrics/utils';
16+
17+
const mockUseTraceItemSearchQueryBuilderProps = jest.fn(() => ({
18+
filterKeys: {},
19+
filterKeySections: [],
20+
getTagValues: jest.fn(),
21+
}));
1522

1623
jest.mock('sentry/views/explore/components/traceItemSearchQueryBuilder', () => {
1724
return {
1825
...jest.requireActual('sentry/views/explore/components/traceItemSearchQueryBuilder'),
26+
useTraceItemSearchQueryBuilderProps: (
27+
...args: Parameters<typeof mockUseTraceItemSearchQueryBuilderProps>
28+
) => mockUseTraceItemSearchQueryBuilderProps(...args),
1929
TraceItemSearchQueryBuilder: jest.fn(props => (
2030
<div
2131
data-test-id="trace-item-search-query-builder"
@@ -794,4 +804,93 @@ describe('TraceMetricsConfig', () => {
794804
expect(searchBar).toHaveAttribute('data-namespace', 'same_metric');
795805
});
796806
});
807+
808+
describe('useTraceMetricsSearchBarDataProvider', () => {
809+
const defaultWidgetQuery: WidgetQuery = {
810+
name: '',
811+
fields: [],
812+
columns: [],
813+
fieldAliases: [],
814+
aggregates: [],
815+
conditions: '',
816+
orderby: '',
817+
};
818+
819+
function SearchBarDataProviderProbe({widgetQuery}: {widgetQuery?: WidgetQuery}) {
820+
TraceMetricsConfig.useSearchBarDataProvider?.({
821+
pageFilters: PageFiltersFixture({projects: [1]}),
822+
widgetQuery,
823+
});
824+
return null;
825+
}
826+
827+
beforeEach(() => {
828+
mockUseTraceItemSearchQueryBuilderProps.mockClear();
829+
});
830+
831+
it('does not scope attribute suggestions to the first metric when multiple metrics are selected', () => {
832+
render(
833+
<WidgetBuilderProvider>
834+
<SearchBarDataProviderProbe widgetQuery={defaultWidgetQuery} />
835+
</WidgetBuilderProvider>,
836+
{
837+
organization: OrganizationFixture({
838+
features: ['tracemetrics-multi-metric-selection-in-dashboards'],
839+
}),
840+
initialRouterConfig: {
841+
location: {
842+
pathname: DASHBOARD_WIDGET_BUILDER_PATHNAME,
843+
query: {
844+
yAxis: ['avg(value,metric_a,counter,-)', 'avg(value,metric_b,gauge,-)'],
845+
dataset: WidgetType.TRACEMETRICS,
846+
displayType: DisplayType.LINE,
847+
},
848+
},
849+
},
850+
}
851+
);
852+
853+
expect(mockUseTraceItemSearchQueryBuilderProps).toHaveBeenCalledWith(
854+
expect.objectContaining({
855+
attributeQuery: undefined,
856+
})
857+
);
858+
});
859+
860+
it('keeps attribute suggestions scoped when all selected aggregates target the same metric', () => {
861+
render(
862+
<WidgetBuilderProvider>
863+
<SearchBarDataProviderProbe widgetQuery={defaultWidgetQuery} />
864+
</WidgetBuilderProvider>,
865+
{
866+
organization: OrganizationFixture({
867+
features: ['tracemetrics-multi-metric-selection-in-dashboards'],
868+
}),
869+
initialRouterConfig: {
870+
location: {
871+
pathname: DASHBOARD_WIDGET_BUILDER_PATHNAME,
872+
query: {
873+
yAxis: [
874+
'avg(value,same_metric,counter,-)',
875+
'p50(value,same_metric,counter,-)',
876+
],
877+
dataset: WidgetType.TRACEMETRICS,
878+
displayType: DisplayType.LINE,
879+
},
880+
},
881+
},
882+
}
883+
);
884+
885+
expect(mockUseTraceItemSearchQueryBuilderProps).toHaveBeenCalledWith(
886+
expect.objectContaining({
887+
attributeQuery: createTraceMetricFilter({
888+
name: 'same_metric',
889+
type: 'counter',
890+
unit: '-',
891+
}),
892+
})
893+
);
894+
});
895+
});
797896
});

static/app/views/dashboards/datasetConfig/traceMetrics.tsx

Lines changed: 33 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import {DisplayType, type WidgetQuery} from 'sentry/views/dashboards/types';
3232
import {useWidgetBuilderContext} from 'sentry/views/dashboards/widgetBuilder/contexts/widgetBuilderContext';
3333
import {useTraceMetricMultiMetricSelection} from 'sentry/views/dashboards/widgetBuilder/hooks/useTraceMetricMultiMetricSelection';
3434
import {
35-
extractTraceMetricFromAggregates,
3635
extractTraceMetricFromColumn,
3736
getTraceMetricAggregateSource,
3837
} from 'sentry/views/dashboards/widgetBuilder/utils/buildTraceMetricAggregate';
@@ -85,30 +84,13 @@ function TraceMetricsSearchBar({
8584
const {
8685
selection: {projects},
8786
} = usePageFilters();
88-
const {state: widgetBuilderState} = useWidgetBuilderContext();
89-
const hasMultiMetricSelection = useTraceMetricMultiMetricSelection();
90-
91-
const aggregateSource = getTraceMetricAggregateSource(
92-
widgetBuilderState.displayType,
93-
widgetBuilderState.yAxis,
94-
widgetBuilderState.fields
95-
);
96-
const traceMetrics =
97-
aggregateSource?.map(extractTraceMetricFromColumn).filter(defined) ?? [];
98-
99-
const hasMultipleMetrics = hasMultipleMetricsSelected(
100-
traceMetrics,
101-
hasMultiMetricSelection
102-
);
87+
const {attributeQuery, hasMultipleMetrics, traceMetrics} = useTraceMetricsSearchScope();
10388

10489
// In the case of multiple metrics, wipe the query so it fetches all attributes
10590
const {attributes: stringAttributes, secondaryAliases: stringSecondaryAliases} =
10691
useTraceMetricItemAttributes(
10792
{
108-
query:
109-
!hasMultipleMetrics && traceMetrics[0]
110-
? createTraceMetricFilter(traceMetrics[0])
111-
: undefined,
93+
query: attributeQuery,
11294
enabled: defined(traceMetrics[0]), // Only enable if there is at least one metric
11395
},
11496
'string',
@@ -117,10 +99,7 @@ function TraceMetricsSearchBar({
11799
const {attributes: numberAttributes, secondaryAliases: numberSecondaryAliases} =
118100
useTraceMetricItemAttributes(
119101
{
120-
query:
121-
!hasMultipleMetrics && traceMetrics[0]
122-
? createTraceMetricFilter(traceMetrics[0])
123-
: undefined,
102+
query: attributeQuery,
124103
enabled: defined(traceMetrics[0]),
125104
},
126105
'number',
@@ -129,10 +108,7 @@ function TraceMetricsSearchBar({
129108
const {attributes: booleanAttributes, secondaryAliases: booleanSecondaryAliases} =
130109
useTraceMetricItemAttributes(
131110
{
132-
query:
133-
!hasMultipleMetrics && traceMetrics[0]
134-
? createTraceMetricFilter(traceMetrics[0])
135-
: undefined,
111+
query: attributeQuery,
136112
enabled: defined(traceMetrics[0]),
137113
},
138114
'boolean',
@@ -159,11 +135,7 @@ function TraceMetricsSearchBar({
159135
namespace={hasMultipleMetrics ? undefined : traceMetrics?.[0]?.name}
160136
disableRecentSearches={hasMultipleMetrics}
161137
hiddenAttributeKeys={HiddenTraceMetricSearchFields}
162-
attributeQuery={
163-
!hasMultipleMetrics && traceMetrics[0]
164-
? createTraceMetricFilter(traceMetrics[0])
165-
: undefined
166-
}
138+
attributeQuery={attributeQuery}
167139
/>
168140
);
169141
}
@@ -172,33 +144,23 @@ function useTraceMetricsSearchBarDataProvider(
172144
props: SearchBarDataProviderProps
173145
): SearchBarData {
174146
const {pageFilters, widgetQuery} = props;
175-
const {state: widgetBuilderState} = useWidgetBuilderContext();
176-
177-
const aggregateSource = getTraceMetricAggregateSource(
178-
widgetBuilderState.displayType,
179-
widgetBuilderState.yAxis,
180-
widgetBuilderState.fields
181-
);
182-
const traceMetric = extractTraceMetricFromAggregates(aggregateSource) ?? {
183-
name: '',
184-
type: '',
185-
};
147+
const {attributeQuery, traceMetrics} = useTraceMetricsSearchScope();
186148

187149
const {attributes: stringAttributes, secondaryAliases: stringSecondaryAliases} =
188150
useTraceMetricItemAttributes(
189-
{query: createTraceMetricFilter(traceMetric)},
151+
{query: attributeQuery, enabled: defined(traceMetrics[0])},
190152
'string',
191153
HiddenTraceMetricSearchFields
192154
);
193155
const {attributes: numberAttributes, secondaryAliases: numberSecondaryAliases} =
194156
useTraceMetricItemAttributes(
195-
{query: createTraceMetricFilter(traceMetric)},
157+
{query: attributeQuery, enabled: defined(traceMetrics[0])},
196158
'number',
197159
HiddenTraceMetricSearchFields
198160
);
199161
const {attributes: booleanAttributes, secondaryAliases: booleanSecondaryAliases} =
200162
useTraceMetricItemAttributes(
201-
{query: createTraceMetricFilter(traceMetric)},
163+
{query: attributeQuery, enabled: defined(traceMetrics[0])},
202164
'boolean',
203165
HiddenTraceMetricSearchFields
204166
);
@@ -216,7 +178,7 @@ function useTraceMetricsSearchBarDataProvider(
216178
initialQuery: widgetQuery?.conditions ?? '',
217179
projects: pageFilters.projects,
218180
hiddenAttributeKeys: HiddenTraceMetricSearchFields,
219-
attributeQuery: createTraceMetricFilter(traceMetric),
181+
attributeQuery,
220182
});
221183

222184
return {
@@ -226,6 +188,29 @@ function useTraceMetricsSearchBarDataProvider(
226188
};
227189
}
228190

191+
function useTraceMetricsSearchScope() {
192+
const {state: widgetBuilderState} = useWidgetBuilderContext();
193+
const hasMultiMetricSelection = useTraceMetricMultiMetricSelection();
194+
195+
const aggregateSource = getTraceMetricAggregateSource(
196+
widgetBuilderState.displayType,
197+
widgetBuilderState.yAxis,
198+
widgetBuilderState.fields
199+
);
200+
const traceMetrics =
201+
aggregateSource?.map(extractTraceMetricFromColumn).filter(defined) ?? [];
202+
const hasMultipleMetrics = hasMultipleMetricsSelected(
203+
traceMetrics,
204+
hasMultiMetricSelection
205+
);
206+
const attributeQuery =
207+
!hasMultipleMetrics && traceMetrics[0]
208+
? createTraceMetricFilter(traceMetrics[0])
209+
: undefined;
210+
211+
return {attributeQuery, hasMultipleMetrics, traceMetrics};
212+
}
213+
229214
export function formatTraceMetricsFunction(
230215
valueToParse: string | string[],
231216
defaultValue?: string | ReactNode

0 commit comments

Comments
 (0)