Skip to content
Merged
9 changes: 8 additions & 1 deletion static/app/views/dashboards/dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,20 @@ function DashboardInner({
const organization = useOrganization();
const api = useApi();

const {selection} = usePageFilters();

// Push dashboard metadata into the LLM context tree for Seer Explorer.
useLLMContext({
contextHint:
'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 the telemetry_live_search tool to fetch or drill into its data.',
title: dashboard.title,
widgetCount: dashboard.widgets.length,
filters: dashboard.filters,
isEditingDashboard,
dateRange: selection.datetime,
environments: selection.environments,
projects: selection.projects,
});
const {selection} = usePageFilters();
const {queue} = useWidgetQueryQueue();
const layouts = useMemo<LayoutState>(() => {
const desktopLayout = getDashboardLayout(dashboard.widgets);
Expand Down
30 changes: 29 additions & 1 deletion static/app/views/dashboards/widgetCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ import {WidgetCardChartContainer} from 'sentry/views/dashboards/widgetCard/widge
import type {WidgetLegendSelectionState} from 'sentry/views/dashboards/widgetLegendSelectionState';
import type {TabularColumn} from 'sentry/views/dashboards/widgets/common/types';
import {Widget} from 'sentry/views/dashboards/widgets/widget/widget';
import {useLLMContext} from 'sentry/views/seerExplorer/contexts/llmContext';
import {registerLLMContext} from 'sentry/views/seerExplorer/contexts/registerLLMContext';

import {useDashboardsMEPContext} from './dashboardsMEPContext';
import {VisualizationWidget} from './visualizationWidget';
Expand All @@ -62,6 +64,7 @@ import {
useTransactionsDeprecationWarning,
} from './widgetCardContextMenu';
import {WidgetFrame} from './widgetFrame';
import {getWidgetQueryLLMHint} from './widgetLLMContext';

export type OnDataFetchedParams = {
tableResults?: TableDataWithTitle[];
Expand Down Expand Up @@ -147,6 +150,28 @@ function WidgetCard(props: Props) {
const {dashboardId: currentDashboardId} = useParams<{dashboardId: string}>();
const timeoutRef = useRef<NodeJS.Timeout | null>(null);

// Resolve TOP_N → AREA before capturing context (the render body mutates
// this later, but useLLMContext needs the resolved value on first render).
const resolvedDisplayType =
props.widget.displayType === DisplayType.TOP_N
? DisplayType.AREA
: props.widget.displayType;

// Push widget metadata into the LLM context tree for Seer Explorer.
useLLMContext({
title: props.widget.title,
displayType: resolvedDisplayType,
widgetType: props.widget.widgetType,
queryHint: getWidgetQueryLLMHint(resolvedDisplayType),
queries: props.widget.queries.map(q => ({
name: q.name,
conditions: q.conditions,
aggregates: q.aggregates,
columns: q.columns,
orderby: q.orderby,
})),
});
Comment thread
cursor[bot] marked this conversation as resolved.

const onDataFetched = (newData: Data) => {
if (props.onDataFetched) {
props.onDataFetched({
Expand Down Expand Up @@ -435,7 +460,10 @@ function WidgetCard(props: Props) {
);
}

export default withApi(withOrganization(withPageFilters(withSentryRouter(WidgetCard))));
export default registerLLMContext(
'widget',
withApi(withOrganization(withPageFilters(withSentryRouter(WidgetCard))))
);

function useOnDemandWarning(props: {widget: TWidget}): string | null {
const organization = useOrganization();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {DisplayType} from 'sentry/views/dashboards/types';

import {getWidgetQueryLLMHint} from './widgetLLMContext';

describe('getWidgetQueryLLMHint', () => {
it.each([
[DisplayType.LINE, 'timeseries'],
[DisplayType.AREA, 'timeseries'],
[DisplayType.BAR, 'timeseries'],
])('returns timeseries hint for %s', (displayType, expected) => {
expect(getWidgetQueryLLMHint(displayType)).toContain(expected);
});

it('returns table hint for TABLE', () => {
expect(getWidgetQueryLLMHint(DisplayType.TABLE)).toContain('table query');
});

it('returns single aggregate hint for BIG_NUMBER', () => {
expect(getWidgetQueryLLMHint(DisplayType.BIG_NUMBER)).toContain('single aggregate');
expect(getWidgetQueryLLMHint(DisplayType.BIG_NUMBER)).toContain(
'value is included below'
);
});

it('returns table hint as default for unknown types', () => {
expect(getWidgetQueryLLMHint(DisplayType.WHEEL)).toContain('table query');
});
});
20 changes: 20 additions & 0 deletions static/app/views/dashboards/widgetCard/widgetLLMContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {DisplayType} from 'sentry/views/dashboards/types';

/**
* Returns a hint for the Seer Explorer agent describing how to re-query this
* widget's data using a tool call, if the user wants to dig deeper.
*/
export function getWidgetQueryLLMHint(displayType: DisplayType): string {
switch (displayType) {
case DisplayType.LINE:
case DisplayType.AREA:
case DisplayType.BAR:
return 'To dig deeper into this widget, run a timeseries query using y_axes (aggregates) + group_by (columns) + query (conditions)';
case DisplayType.TABLE:
return 'To dig deeper into this widget, run a table query using fields (aggregates + columns) + query (conditions) + sort (orderby)';
case DisplayType.BIG_NUMBER:
return 'To dig deeper into this widget, run a single aggregate query using fields (aggregates) + query (conditions); current value is included below';
default:
return 'To dig deeper into this widget, run a table query using fields (aggregates + columns) + query (conditions)';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ import type {
TabularValueUnit,
Thresholds,
} from 'sentry/views/dashboards/widgets/common/types';
import {useLLMContext} from 'sentry/views/seerExplorer/contexts/llmContext';
import {registerLLMContext} from 'sentry/views/seerExplorer/contexts/registerLLMContext';

import {DEEMPHASIS_VARIANT, LOADING_PLACEHOLDER} from './settings';
import {ThresholdsIndicator} from './thresholdsIndicator';

interface BigNumberWidgetVisualizationProps {
type BigNumberWidgetVisualizationProps = {
field: string;
value: number | string;
maximumValue?: number;
Expand All @@ -31,9 +33,9 @@ interface BigNumberWidgetVisualizationProps {
thresholds?: Thresholds;
type?: TabularValueType;
unit?: TabularValueUnit;
}
};

export function BigNumberWidgetVisualization(props: BigNumberWidgetVisualizationProps) {
function BigNumberWidgetVisualizationInner(props: BigNumberWidgetVisualizationProps) {
const {
field,
value,
Expand All @@ -44,6 +46,10 @@ export function BigNumberWidgetVisualization(props: BigNumberWidgetVisualization
unit,
} = props;

// Push parsed display values into the LLM context tree for Seer Explorer.
// These are already computed by the parent — no raw data involved.
useLLMContext({field, value, type, unit, thresholds: props.thresholds});

const theme = useTheme();

if ((typeof value === 'number' && !Number.isFinite(value)) || Number.isNaN(value)) {
Expand Down Expand Up @@ -206,6 +212,11 @@ const LoadingPlaceholder = styled('span')`
font-size: ${p => p.theme.font.size.lg};
`;

BigNumberWidgetVisualization.LoadingPlaceholder = function () {
return <LoadingPlaceholder>{LOADING_PLACEHOLDER}</LoadingPlaceholder>;
};
export const BigNumberWidgetVisualization = Object.assign(
registerLLMContext('chart', BigNumberWidgetVisualizationInner),
{
LoadingPlaceholder() {
return <LoadingPlaceholder>{LOADING_PLACEHOLDER}</LoadingPlaceholder>;
},
}
);
Loading