Skip to content

Commit f771e98

Browse files
ref(seer): Deduplicate widget query hints and shorten dashboard context
Move per-widget queryHint to a dashboard-level legend keyed by displayType so hints are not repeated on every widget. Shorten the dashboard contextHint to reduce token overhead. Update hint text to reference tool names and say "in each widget" instead of "below". Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0cb5c5a commit f771e98

File tree

4 files changed

+67
-9
lines changed

4 files changed

+67
-9
lines changed

static/app/views/dashboards/dashboard.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {useLLMContext} from 'sentry/views/seerExplorer/contexts/llmContext';
3434
import {registerLLMContext} from 'sentry/views/seerExplorer/contexts/registerLLMContext';
3535

3636
import {WidgetSyncContextProvider} from './contexts/widgetSyncContext';
37+
import {getQueryHintLegend} from './widgetCard/widgetLLMContext';
3738
import {ADD_WIDGET_BUTTON_DRAG_ID, AddWidget} from './addWidget';
3839
import {
3940
assignDefaultLayout,
@@ -129,9 +130,10 @@ function DashboardInner({
129130
// Push dashboard metadata into the LLM context tree for Seer Explorer.
130131
useLLMContext({
131132
contextHint:
132-
'This is a Sentry dashboard. The dateRange, environments, and projects below are global page filters that scope every widget query. Each child widget node contains its own query config that can be used with tools like telemetry_live_search and telemetry_index_list_nodes to fetch data for that widget and dig deeper. Based on the user question, data might be needed from multiple widgets.',
133+
'Sentry dashboard. dateRange, environments, and projects are global filters applied to every widget. Each widget has its own query config. Use telemetry_live_search or telemetry_index_list_nodes to fetch data. Based on the user question, data might be needed from multiple widgets.',
133134
title: dashboard.title,
134135
widgetCount: dashboard.widgets.length,
136+
queryHints: getQueryHintLegend(dashboard.widgets),
135137
filters: dashboard.filters,
136138
isEditingDashboard,
137139
dateRange: selection.datetime,

static/app/views/dashboards/widgetCard/index.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ import {
6464
useTransactionsDeprecationWarning,
6565
} from './widgetCardContextMenu';
6666
import {WidgetFrame} from './widgetFrame';
67-
import {getWidgetQueryLLMHint, readableConditions} from './widgetLLMContext';
67+
import {readableConditions} from './widgetLLMContext';
6868

6969
export type OnDataFetchedParams = {
7070
tableResults?: TableDataWithTitle[];
@@ -162,7 +162,6 @@ function WidgetCard(props: Props) {
162162
title: props.widget.title,
163163
displayType: resolvedDisplayType,
164164
widgetType: props.widget.widgetType,
165-
queryHint: getWidgetQueryLLMHint(resolvedDisplayType),
166165
queries: props.widget.queries.map(q => ({
167166
name: q.name,
168167
conditions: readableConditions(q.conditions),

static/app/views/dashboards/widgetCard/widgetLLMContext.spec.tsx

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
import {WidgetFixture} from 'sentry-fixture/widget';
2+
13
import {DisplayType} from 'sentry/views/dashboards/types';
24

3-
import {getWidgetQueryLLMHint, readableConditions} from './widgetLLMContext';
5+
import {
6+
getQueryHintLegend,
7+
getWidgetQueryLLMHint,
8+
readableConditions,
9+
} from './widgetLLMContext';
410

511
describe('getWidgetQueryLLMHint', () => {
612
it.each([
@@ -18,7 +24,7 @@ describe('getWidgetQueryLLMHint', () => {
1824
it('returns single number hint for BIG_NUMBER', () => {
1925
expect(getWidgetQueryLLMHint(DisplayType.BIG_NUMBER)).toContain('single number');
2026
expect(getWidgetQueryLLMHint(DisplayType.BIG_NUMBER)).toContain(
21-
'current value is included below'
27+
'current value is included in each widget'
2228
);
2329
});
2430

@@ -27,6 +33,43 @@ describe('getWidgetQueryLLMHint', () => {
2733
});
2834
});
2935

36+
describe('getQueryHintLegend', () => {
37+
it('returns hints keyed by display type', () => {
38+
const widgets = [
39+
WidgetFixture({displayType: DisplayType.BAR}),
40+
WidgetFixture({displayType: DisplayType.BIG_NUMBER}),
41+
];
42+
const legend = getQueryHintLegend(widgets);
43+
expect(Object.keys(legend)).toEqual(
44+
expect.arrayContaining([DisplayType.BAR, DisplayType.BIG_NUMBER])
45+
);
46+
expect(Object.keys(legend)).toHaveLength(2);
47+
});
48+
49+
it('only includes display types present in the widget list', () => {
50+
const widgets = [WidgetFixture({displayType: DisplayType.TABLE})];
51+
const legend = getQueryHintLegend(widgets);
52+
expect(Object.keys(legend)).toEqual([DisplayType.TABLE]);
53+
});
54+
55+
it('deduplicates multiple widgets of the same type', () => {
56+
const widgets = [
57+
WidgetFixture({displayType: DisplayType.BAR}),
58+
WidgetFixture({displayType: DisplayType.BAR}),
59+
WidgetFixture({displayType: DisplayType.BAR}),
60+
];
61+
const legend = getQueryHintLegend(widgets);
62+
expect(Object.keys(legend)).toEqual([DisplayType.BAR]);
63+
});
64+
65+
it('resolves TOP_N to AREA', () => {
66+
const widgets = [WidgetFixture({displayType: DisplayType.TOP_N})];
67+
const legend = getQueryHintLegend(widgets);
68+
expect(Object.keys(legend)).toEqual([DisplayType.AREA]);
69+
expect(legend[DisplayType.AREA]).toContain('timeseries chart');
70+
});
71+
});
72+
3073
describe('readableConditions', () => {
3174
it('replaces Contains operator with readable label', () => {
3275
expect(readableConditions('span.name:\uf00dContains\uf00dfoo')).toBe(

static/app/views/dashboards/widgetCard/widgetLLMContext.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {OP_LABELS} from 'sentry/components/searchQueryBuilder/tokens/filter/utils';
2+
import type {Widget} from 'sentry/views/dashboards/types';
23
import {DisplayType} from 'sentry/views/dashboards/types';
34

45
/**
@@ -23,12 +24,25 @@ export function getWidgetQueryLLMHint(displayType: DisplayType): string {
2324
case DisplayType.LINE:
2425
case DisplayType.AREA:
2526
case DisplayType.BAR:
26-
return 'This widget shows a timeseries chart. The aggregates are the y-axis metrics, columns are the group-by breakdowns, and conditions filter the data. Understand the intent from the query config below.';
27+
return 'This widget shows a timeseries chart. The aggregates are the y-axis metrics, columns are the group-by breakdowns, and conditions filter the data. Understand the intent from the query config in each widget. Use telemetry_live_search or telemetry_index_list_nodes to fetch data.';
2728
case DisplayType.TABLE:
28-
return 'This widget shows a table. The aggregates and columns define the visible fields, orderby is the sort, and conditions filter the data. Understand the intent from the query config below.';
29+
return 'This widget shows a table. The aggregates and columns define the visible fields, orderby is the sort, and conditions filter the data. Understand the intent from the query config in each widget. Use telemetry_live_search or telemetry_index_list_nodes to fetch data.';
2930
case DisplayType.BIG_NUMBER:
30-
return 'This widget shows a single number. The aggregate is the metric, conditions filter the data, and the current value is included below. Understand the intent from the query config below.';
31+
return 'This widget shows a single number. The aggregate is the metric, conditions filter the data, and the current value is included in each widget. Use telemetry_live_search or telemetry_index_list_nodes to fetch data.';
3132
default:
32-
return 'This widget shows data. The aggregates, columns, and conditions define what is displayed. Understand the intent from the query config below.';
33+
return 'This widget shows data. The aggregates, columns, and conditions define what is displayed. Understand the intent from the query config in each widget. Use telemetry_live_search or telemetry_index_list_nodes to fetch data.';
3334
}
3435
}
36+
37+
/**
38+
* Build a legend mapping each display type present in the dashboard to its
39+
* query hint. This lives on the dashboard node so hints aren't repeated on
40+
* every widget.
41+
*/
42+
export function getQueryHintLegend(widgets: Widget[]): Record<string, string> {
43+
const resolved = widgets.map(w =>
44+
w.displayType === DisplayType.TOP_N ? DisplayType.AREA : w.displayType
45+
);
46+
const uniqueTypes = new Set(resolved);
47+
return Object.fromEntries([...uniqueTypes].map(dt => [dt, getWidgetQueryLLMHint(dt)]));
48+
}

0 commit comments

Comments
 (0)