Skip to content

feat(assisted-query): Add AI search bar and built-in fields for metrics#111795

Closed
isaacwang-sentry wants to merge 2 commits intomasterfrom
isaac/feat/metrics-built-in-fields
Closed

feat(assisted-query): Add AI search bar and built-in fields for metrics#111795
isaacwang-sentry wants to merge 2 commits intomasterfrom
isaac/feat/metrics-built-in-fields

Conversation

@isaacwang-sentry
Copy link
Copy Markdown
Member

Summary

  • Add tracemetrics built-in fields (metric.name, metric.type, metric.unit, value) to _get_built_in_fields so the Seer assisted query agent can discover them
  • Integrate the Seer AI search combobox into the metrics tab filter bar, enabling natural language to metrics query translation
  • Register metrics AI query analytics events (ai_query_applied, ai_query_submitted, ai_query_rejected, ai_query_interface)

Details

The Seer backend already has a fully implemented MetricsStrategy for the assisted query agent. This PR wires up the frontend by:

  1. Creating MetricsTabSeerComboBox — follows the established LogsTabSeerComboBox / SpansTabSeerComboBox pattern with metrics-specific adaptations (per-query state updates via useSetQueryParams, global time range via navigate(), default mode 'metrics')
  2. Modifying the metrics Filter component to add enableAISearch to SearchQueryBuilderProvider and conditionally render the seer combobox when displayAskSeer is true
  3. Gated behind the existing gen-ai-search-agent-translate feature flag

Test plan

  • Enable gen-ai-features and gen-ai-search-agent-translate feature flags for org
  • Navigate to Explore > Metrics tab
  • Verify sparkle icon (AI search toggle) appears in the filter bar
  • Submit a natural language query (e.g., "count of http.request.duration by environment last 7 days")
  • Verify query, group-bys, mode, and time range update for the specific metric row
  • Verify other metric query rows are unaffected
  • Test "None of these" feedback flow
  • Verify analytics events fire correctly

🤖 Generated with Claude Code

isaacwang-sentry and others added 2 commits March 25, 2026 16:01
…in_fields

Add metrics-specific built-in field definitions (metric.name,
metric.type, metric.unit, value, etc.) so the get_attribute_names
RPC returns correct built-in fields for tracemetrics instead of
falling through to span fields.

This eliminates the need for the _reclassify_metrics_built_in_fields
workaround in Seer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Integrate the Seer assisted query agent into the metrics tab filter bar,
enabling natural language to metrics query translation. The Seer backend
already supports the Metrics strategy; this wires up the frontend.

- Create MetricsTabSeerComboBox component following the logs/spans pattern
- Modify filter.tsx to conditionally render AI search when feature flag is on
- Register metrics AI query analytics events

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@isaacwang-sentry isaacwang-sentry requested review from a team as code owners March 30, 2026 16:56
@github-actions github-actions bot added Scope: Frontend Automatically applied to PRs that change frontend components Scope: Backend Automatically applied to PRs that change backend components labels Mar 30, 2026
@github-actions
Copy link
Copy Markdown
Contributor

🚨 Warning: This pull request contains Frontend and Backend changes!

It's discouraged to make changes to Sentry's Frontend and Backend in a single pull request. The Frontend and Backend are not atomically deployed. If the changes are interdependent of each other, they must be separated into two pull requests and be made forward or backwards compatible, such that the Backend or Frontend can be safely deployed independently.

Have questions? Please ask in the #discuss-dev-infra channel.

}

// Update per-query state atomically (query, aggregateFields, mode)
setQueryParams({query: queryToUse, aggregateFields, mode});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: AI-suggested visualizations from the Seer response are extracted but never applied to the metrics query, causing the AI's visualization recommendations to be ignored.
Severity: HIGH

Suggested Fix

Incorporate the visualizations array from the Seer response into the parameters passed to setQueryParams. This can be achieved by transforming the visualizations into a format suitable for the URL query, similar to the implementation in spansTabSeerComboBox.tsx.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: static/app/views/explore/metrics/metricsTabSeerComboBox.tsx#L228

Potential issue: In the Metrics tab's AI-assisted search, the code fetches query
suggestions from the Seer API, which can include recommended visualizations. While the
`visualizations` array is correctly extracted from the API response and its count is
tracked for analytics, it is never actually applied to the query. The `setQueryParams`
function call omits these new visualizations, only using the query string and existing
aggregations. This results in the AI's suggested visualizations (e.g., `p95(value)`,
chart type) being silently ignored, providing an incomplete query to the user and
creating an inconsistency with the Spans and Logs implementations where this
functionality works correctly.

Did we get this right? 👍 / 👎 to inform future reviews.

@isaacwang-sentry
Copy link
Copy Markdown
Member Author

Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Sequential navigations cause per-query state changes to be lost
    • Replaced sequential navigations with a single navigation that combines metric query state (query, aggregateFields, mode) and datetime params, preventing the second navigation from overwriting the first with stale location.query.

Create PR

Or push these changes by commenting:

@cursor push c6d9c5f768
Preview (c6d9c5f768)
diff --git a/static/app/views/explore/metrics/metricsTabSeerComboBox.tsx b/static/app/views/explore/metrics/metricsTabSeerComboBox.tsx
--- a/static/app/views/explore/metrics/metricsTabSeerComboBox.tsx
+++ b/static/app/views/explore/metrics/metricsTabSeerComboBox.tsx
@@ -9,18 +9,22 @@
 import {stringifyToken} from 'sentry/components/searchSyntax/utils';
 import {ConfigStore} from 'sentry/stores/configStore';
 import type {DateString} from 'sentry/types/core';
+import {defined} from 'sentry/utils';
 import {trackAnalytics} from 'sentry/utils/analytics';
 import {getFieldDefinition} from 'sentry/utils/fields';
 import {fetchMutation, mutationOptions} from 'sentry/utils/queryClient';
+import {decodeList} from 'sentry/utils/queryString';
 import {useLocation} from 'sentry/utils/useLocation';
 import {useNavigate} from 'sentry/utils/useNavigate';
 import {useOrganization} from 'sentry/utils/useOrganization';
 import {useProjects} from 'sentry/utils/useProjects';
+import {
+  decodeMetricsQueryParams,
+  encodeMetricQueryParams,
+  type BaseMetricQuery,
+} from 'sentry/views/explore/metrics/metricQuery';
 import type {WritableAggregateField} from 'sentry/views/explore/queryParams/aggregateField';
-import {
-  useQueryParams,
-  useSetQueryParams,
-} from 'sentry/views/explore/queryParams/context';
+import {useQueryParams} from 'sentry/views/explore/queryParams/context';
 import {isGroupBy} from 'sentry/views/explore/queryParams/groupBy';
 import {Mode} from 'sentry/views/explore/queryParams/mode';
 import {isVisualize} from 'sentry/views/explore/queryParams/visualize';
@@ -66,7 +70,6 @@
   const pageFilters = usePageFilters();
   const organization = useOrganization();
   const queryParams = useQueryParams();
-  const setQueryParams = useSetQueryParams();
   const {
     currentInputValueRef,
     query,
@@ -224,10 +227,7 @@
         aggregateFields.push({groupBy});
       }
 
-      // Update per-query state atomically (query, aggregateFields, mode)
-      setQueryParams({query: queryToUse, aggregateFields, mode});
-
-      // Update global time range via navigation
+      // Build datetime selection
       const selection = {
         ...pageFilters.selection,
         datetime: {
@@ -255,12 +255,42 @@
         visualize_count: visualizations?.length ?? 0,
       });
 
-      // Navigate to update global time range params
+      // Get current metric queries from URL and update the first one
+      const rawQueryParams = decodeList(location.query.metric);
+      const metricQueries = rawQueryParams
+        .map(value => decodeMetricsQueryParams(value))
+        .filter(defined);
+
+      // Update the first metric query with new params
+      const updatedMetricQueries = metricQueries.map(
+        (metricQuery: BaseMetricQuery, index: number) => {
+          if (index !== 0) {
+            return metricQuery;
+          }
+          return {
+            metric: metricQuery.metric,
+            queryParams: queryParams.replace({
+              query: queryToUse,
+              aggregateFields,
+              mode,
+            }),
+          };
+        }
+      );
+
+      // Encode updated metric queries
+      const newMetricParams = updatedMetricQueries
+        .map((metricQuery: BaseMetricQuery) => encodeMetricQueryParams(metricQuery))
+        .filter(defined)
+        .filter(Boolean);
+
+      // Single navigation with all params (metric query state + datetime)
       navigate(
         {
           ...location,
           query: {
             ...location.query,
+            metric: newMetricParams,
             start: selection.datetime.start,
             end: selection.datetime.end,
             statsPeriod: selection.datetime.period,
@@ -276,8 +306,7 @@
       navigate,
       organization,
       pageFilters.selection,
-      queryParams.aggregateFields,
-      setQueryParams,
+      queryParams,
     ]
   );

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

},
},
{replace: true, preventScrollReset: true}
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sequential navigations cause per-query state changes to be lost

High Severity

The applySeerSearchQuery callback calls setQueryParams({query, aggregateFields, mode}) on line 228, which internally triggers a navigate() via setQueryParamsForIndex in multiMetricsQueryParams.tsx to update the metric URL param. Immediately after, a second navigate() on line 259 updates datetime params but spreads the stale location.query — which still contains the old metric value. The second navigation overwrites the first, so the per-query state changes (query, aggregateFields, mode) are silently discarded. This makes the entire Seer AI query feature non-functional for metrics. Compare with LogsTabSeerComboBox, which correctly combines all URL param changes into a single navigate() call.

Additional Locations (1)
Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant