From 3c8217da43f4c75e7e916d5b7e35ed1fc62a558b Mon Sep 17 00:00:00 2001 From: Tony Xiao Date: Wed, 26 Nov 2025 16:54:36 -0500 Subject: [PATCH 1/3] chore(search-query-builder): Add metric for invalids and warnings --- .../components/searchQueryBuilder/context.tsx | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/static/app/components/searchQueryBuilder/context.tsx b/static/app/components/searchQueryBuilder/context.tsx index 3e8ab5c6ffe458..0812bfe65c57ad 100644 --- a/static/app/components/searchQueryBuilder/context.tsx +++ b/static/app/components/searchQueryBuilder/context.tsx @@ -2,11 +2,13 @@ import { createContext, useCallback, useContext, + useEffect, useMemo, useRef, useState, type Dispatch, } from 'react'; +import * as Sentry from '@sentry/react'; import {useOrganizationSeerSetup} from 'sentry/components/events/autofix/useOrganizationSeerSetup'; import type {SearchQueryBuilderProps} from 'sentry/components/searchQueryBuilder'; @@ -26,10 +28,12 @@ import type { import {parseQueryBuilderValue} from 'sentry/components/searchQueryBuilder/utils'; import type {ParseResult} from 'sentry/components/searchSyntax/parser'; import type {SavedSearchType, Tag, TagCollection} from 'sentry/types/group'; +import {defined} from 'sentry/utils'; import type {FieldDefinition, FieldKind} from 'sentry/utils/fields'; import {getFieldDefinition} from 'sentry/utils/fields'; import {useDimensions} from 'sentry/utils/useDimensions'; import useOrganization from 'sentry/utils/useOrganization'; +import usePrevious from 'sentry/utils/usePrevious'; interface SearchQueryBuilderContextData { actionBarRef: React.RefObject; @@ -189,6 +193,34 @@ export function SearchQueryBuilderProvider({ ); const parsedQuery = useMemo(() => parseQuery(state.query), [parseQuery, state.query]); + const previousQuery = usePrevious(state.query); + const firstRender = useRef(true); + useEffect(() => { + if (!parsedQuery?.length) { + return; + } + + // on the first render, we want to check the currently parsed query, + // then on subsequent renders, we want to ensure the parsedQuery hasnt changed + if (!firstRender.current && state.query === previousQuery) { + return; + } + firstRender.current = false; + + const warnings = parsedQuery + .filter(token => 'warning' in token && defined(token.warning)) + .reduce((acc, isWarning) => acc + (isWarning ? 1 : 0), 0); + const invalids = parsedQuery + .filter(token => 'invalid' in token && defined(token.invalid)) + .reduce((acc, isInvalids) => acc + (isInvalids ? 1 : 0), 0); + Sentry.metrics.distribution('search-query-builder.token.warnings', warnings, { + attributes: {searchSource}, + }); + Sentry.metrics.distribution('search-query-builder.token.invalids', invalids, { + attributes: {searchSource}, + }); + }, [parsedQuery, state.query, previousQuery, searchSource]); + const handleSearch = useHandleSearch({ parsedQuery, recentSearches, From 9af21311864e1498f092d6b0a3a461594c8b67e1 Mon Sep 17 00:00:00 2001 From: Tony Xiao Date: Wed, 26 Nov 2025 16:58:06 -0500 Subject: [PATCH 2/3] dont report 0s --- .../components/searchQueryBuilder/context.tsx | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/static/app/components/searchQueryBuilder/context.tsx b/static/app/components/searchQueryBuilder/context.tsx index 0812bfe65c57ad..1cd1cfd647522a 100644 --- a/static/app/components/searchQueryBuilder/context.tsx +++ b/static/app/components/searchQueryBuilder/context.tsx @@ -196,10 +196,6 @@ export function SearchQueryBuilderProvider({ const previousQuery = usePrevious(state.query); const firstRender = useRef(true); useEffect(() => { - if (!parsedQuery?.length) { - return; - } - // on the first render, we want to check the currently parsed query, // then on subsequent renders, we want to ensure the parsedQuery hasnt changed if (!firstRender.current && state.query === previousQuery) { @@ -208,17 +204,22 @@ export function SearchQueryBuilderProvider({ firstRender.current = false; const warnings = parsedQuery - .filter(token => 'warning' in token && defined(token.warning)) - .reduce((acc, isWarning) => acc + (isWarning ? 1 : 0), 0); + ?.filter(token => 'warning' in token && defined(token.warning)) + ?.reduce((acc, isWarning) => acc + (isWarning ? 1 : 0), 0); + if (warnings) { + Sentry.metrics.distribution('search-query-builder.token.warnings', warnings, { + attributes: {searchSource}, + }); + } + const invalids = parsedQuery - .filter(token => 'invalid' in token && defined(token.invalid)) - .reduce((acc, isInvalids) => acc + (isInvalids ? 1 : 0), 0); - Sentry.metrics.distribution('search-query-builder.token.warnings', warnings, { - attributes: {searchSource}, - }); - Sentry.metrics.distribution('search-query-builder.token.invalids', invalids, { - attributes: {searchSource}, - }); + ?.filter(token => 'invalid' in token && defined(token.invalid)) + ?.reduce((acc, isInvalids) => acc + (isInvalids ? 1 : 0), 0); + if (invalids) { + Sentry.metrics.distribution('search-query-builder.token.invalids', invalids, { + attributes: {searchSource}, + }); + } }, [parsedQuery, state.query, previousQuery, searchSource]); const handleSearch = useHandleSearch({ From f6619377088fc526f5059e8ff2773e76eff82c88 Mon Sep 17 00:00:00 2001 From: Tony Xiao Date: Wed, 26 Nov 2025 17:05:25 -0500 Subject: [PATCH 3/3] .length is enough --- static/app/components/searchQueryBuilder/context.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/static/app/components/searchQueryBuilder/context.tsx b/static/app/components/searchQueryBuilder/context.tsx index 1cd1cfd647522a..04525eaca86289 100644 --- a/static/app/components/searchQueryBuilder/context.tsx +++ b/static/app/components/searchQueryBuilder/context.tsx @@ -203,18 +203,18 @@ export function SearchQueryBuilderProvider({ } firstRender.current = false; - const warnings = parsedQuery - ?.filter(token => 'warning' in token && defined(token.warning)) - ?.reduce((acc, isWarning) => acc + (isWarning ? 1 : 0), 0); + const warnings = parsedQuery?.filter( + token => 'warning' in token && defined(token.warning) + )?.length; if (warnings) { Sentry.metrics.distribution('search-query-builder.token.warnings', warnings, { attributes: {searchSource}, }); } - const invalids = parsedQuery - ?.filter(token => 'invalid' in token && defined(token.invalid)) - ?.reduce((acc, isInvalids) => acc + (isInvalids ? 1 : 0), 0); + const invalids = parsedQuery?.filter( + token => 'invalid' in token && defined(token.invalid) + )?.length; if (invalids) { Sentry.metrics.distribution('search-query-builder.token.invalids', invalids, { attributes: {searchSource},