feat(explore): Validate trace item search keys asynchronously#111189
Merged
nsdeschenes merged 33 commits intoMar 30, 2026
Conversation
Contributor
Author
|
@sentry review |
Contributor
Author
|
@cursor review |
malwilley
reviewed
Mar 20, 2026
ef85047 to
76bade1
Compare
2bf3a2f to
932e9c6
Compare
Contributor
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Ref guard prevents re-validation on page filter changes
- Removed the initialQueryValidatedRef guard that prevented the useEffect from re-running when page filters changed, allowing validation to occur on datetime and project changes.
- ✅ Fixed: Concurrent validations can resolve out of order
- Added a request counter that increments on each validation call and only applies results if the request counter matches, preventing stale results from overwriting newer ones.
Or push these changes by commenting:
@cursor push e2a2ed7b7d
Preview (e2a2ed7b7d)
diff --git a/static/app/views/explore/components/traceItemSearchQueryBuilder.tsx b/static/app/views/explore/components/traceItemSearchQueryBuilder.tsx
--- a/static/app/views/explore/components/traceItemSearchQueryBuilder.tsx
+++ b/static/app/views/explore/components/traceItemSearchQueryBuilder.tsx
@@ -1,4 +1,4 @@
-import {useCallback, useEffect, useMemo, useRef} from 'react';
+import {useCallback, useEffect, useMemo} from 'react';
import type {SpanSearchQueryBuilderProps} from 'sentry/components/performance/spanSearchQueryBuilder';
import {
@@ -110,11 +110,9 @@
const {invalidFilterKeys, validateQuery} = useAttributeValidation(itemType, projects);
- const initialQueryValidatedRef = useRef(false);
useEffect(() => {
- if (initialQuery && !initialQueryValidatedRef.current) {
+ if (initialQuery) {
validateQuery(initialQuery);
- initialQueryValidatedRef.current = true;
}
}, [initialQuery, validateQuery]);
diff --git a/static/app/views/explore/hooks/useAttributeValidation.tsx b/static/app/views/explore/hooks/useAttributeValidation.tsx
--- a/static/app/views/explore/hooks/useAttributeValidation.tsx
+++ b/static/app/views/explore/hooks/useAttributeValidation.tsx
@@ -1,4 +1,4 @@
-import {useCallback, useState} from 'react';
+import {useCallback, useRef, useState} from 'react';
import {normalizeDateTimeParams} from 'sentry/components/pageFilters/parse';
import {usePageFilters} from 'sentry/components/pageFilters/usePageFilters';
@@ -85,6 +85,7 @@
const organization = useOrganization();
const {selection} = usePageFilters();
const effectiveProjects = projects ?? selection.projects;
+ const requestCounterRef = useRef(0);
const validateQuery = useCallback(
async (query: string) => {
@@ -93,6 +94,7 @@
setInvalidFilterKeys([]);
return;
}
+ const currentRequest = ++requestCounterRef.current;
try {
const [data] = await queryClient.fetchQuery(
validateAttributesQueryOptions({
@@ -103,11 +105,13 @@
projects: effectiveProjects,
})
);
- setInvalidFilterKeys(
- Object.entries(data.attributes)
- .filter(([, result]) => !result.valid)
- .map(([key]) => key)
- );
+ if (currentRequest === requestCounterRef.current) {
+ setInvalidFilterKeys(
+ Object.entries(data.attributes)
+ .filter(([, result]) => !result.valid)
+ .map(([key]) => key)
+ );
+ }
} catch {
// leave previous state on error
}This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.
Move the early return before the keySet creation to also handle the null case upfront. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Check keySet.size after token extraction instead of parsedQuery.length, since a non-empty query can still contain zero filter tokens. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Parse and validate filter keys from initialQuery on mount so that keys loaded from URL params are validated without requiring user interaction. Uses a ref guard to ensure validation only fires once. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract the filter key extraction logic into a standalone exported utility function. This prepares for migrating the validation hook from useMutation to useApiQuery by making key extraction reusable.
…iQuery Replace the imperative useMutation pattern with a declarative useApiQuery approach. The hook now accepts filter keys as input and returns invalid keys, letting React Query handle deduplication, caching, and stale response management via keepPreviousData. The consumer tracks the current query in state and derives filter keys with extractFilterKeys, removing the need for useRef/useEffect-based initial validation.
Cover extractFilterKeys (null input, empty input, sorting, deduplication, stable references) and the useAsyncAttributeValidation hook (empty keys, invalid key detection, request parameters, custom projects override, query skipping when no keys present). Co-Authored-By: Claude Opus 4.6 <noreply@example.com>
…ation Co-Authored-By: Claude Opus 4.6 <noreply@example.com>
…onent Remove the useAttributeValidation hook and move the pure validation logic into a new utils/attributeValidation.ts module. Inline the async query call directly into traceItemSearchQueryBuilder using queryClient.fetchQuery, which avoids an intermediate hook layer and makes the data flow more explicit. Refs EXP-641 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…irectory Move attribute validation logic out of the component into a dedicated useAttributeValidation hook in the hooks directory. The hook encapsulates invalidFilterKeys state and the validateQuery callback, keeping the component lean. Also clean up test casts by using parseSearch and parseQueryKey instead of unsafe type assertions. Refs EXP-641 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ests Remove the export from validateAttributesQueryOptions since it is an implementation detail of the hook. Replace the validateAttributesQueryOptions test suite with hook-level tests that cover the same behaviour through the public API, including asserting the API is not called when the query has no filter keys. Refs EXP-641 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Use a ref to guard the initial validation effect so it only fires once, and remove the eslint-disable comment by including the proper dependencies. Refs EXP-641 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When validateQuery is called rapidly, concurrent fetchQuery calls with different cache keys can resolve out of order, causing stale results to overwrite newer ones. Cancel any in-flight validation queries before starting a new fetch to prevent race conditions. Co-Authored-By: Claude Opus 4.6 <noreply@example.com>
The initialQueryValidatedRef guard permanently prevented the validation effect from re-running after mount. When page filters (datetime or projects) change, validateQuery gets a new reference but the ref was already true, so validation was skipped. Remove the ref guard so the effect re-runs whenever validateQuery identity changes. Co-Authored-By: Claude Opus 4.6 <noreply@example.com>
Move page filter selection out of useAttributeValidation and accept it as a parameter. This gives the hook a stable identity and lets the call site control when re-validation happens. Use staleTime: Infinity on the query options so TanStack Query deduplicates calls with the same filter keys and selection without extra refs. Co-Authored-By: Claude Opus 4.6 <noreply@example.com>
The cancelQueries call used only the API URL as a partial query key, which cancelled all in-flight validation requests across every search bar on the page. This caused a race condition where one component's validation would silently cancel another's, leaving stale invalid key warnings. Narrow the cancellation key to include itemType so each hook instance only cancels its own previous requests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move useAttributeValidation from a callback-based approach (manual fetchQuery + useState) to a declarative useQuery hook. The hook now accepts query and selection as parameters, letting React Query handle caching, deduplication, and cancellation automatically. This simplifies the consumer in traceItemSearchQueryBuilder by removing the useEffect and wrapped onChange callback. Co-Authored-By: Claude Opus 4.6 <noreply@example.com>
extractFilterKeys only iterated top-level tokens, so filter keys nested inside parenthesized groups (Token.LOGIC_GROUP) were never sent to the validation API. Add a recursive walk to inspect inner tokens. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…efactor The tests were still using the old imperative pattern of calling onChange to trigger validation. After the refactor to declarative useQuery, validation is driven by the initialQuery prop, so update tests to use initialQuery and rerender instead. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Only validate search query builder filter keys when the organization has the 'search-query-attribute-validation' feature flag enabled. When disabled, skip parsing and API calls entirely. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ture Combine hasValidation with filterKeys.length > 0 in the useQuery enabled option so the validation API is not called for empty queries. Add the search-query-attribute-validation feature flag to the query builder test fixture so validation tests actually exercise the code path. Co-Authored-By: Claude Opus 4.6 <noreply@example.com>
Add nullish coalescing fallback when accessing the attributes field from the validation API response. The prior optional chaining on data[0]?.attributes could pass undefined to Object.entries() if the response body lacked an attributes field, causing a TypeError. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
d76cb1b to
0ca1e62
Compare
malwilley
approved these changes
Mar 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

The goal of this PR is to validate the filter keys on any trace item based search query builder. To do this we've introduced a new API endpoint that accepts in a list of attributes and checks to see if they exist and are valid.
We trigger this with a useQuery that runs on mount, and whenever the user modifies the query. We also have guards in place to ignore running the validation if the user is just modifying the filter values as those don't need to be validated, reducing the amount of calls we make.
Validation is currently gated behind this flag: #111752