Просмотр исходного кода

feat(query-builder): Update the savedSearchType prop to be more descriptive and inclusive of other types (#74879)

To be more descriptive, renames `savedSearchType` to `recentSearches`
because that is actually what it configures.

Additionally, when recording analytics, it will use the correct label
for each type (the current search bar only records 'issues' and
'events').
Malachi Willey 7 месяцев назад
Родитель
Сommit
3f7e924156

+ 1 - 1
static/app/components/searchQueryBuilder/context.tsx

@@ -24,7 +24,7 @@ interface ContextData {
   size: 'small' | 'normal';
   wrapperRef: React.RefObject<HTMLDivElement>;
   placeholder?: string;
-  savedSearchType?: SavedSearchType;
+  recentSearches?: SavedSearchType;
 }
 
 export function useSearchQueryBuilder() {

+ 15 - 11
static/app/components/searchQueryBuilder/hooks/useHandleSearch.tsx

@@ -3,8 +3,12 @@ import * as Sentry from '@sentry/react';
 
 import {saveRecentSearch} from 'sentry/actionCreators/savedSearches';
 import type {Client} from 'sentry/api';
-import {tokenIsInvalid} from 'sentry/components/searchQueryBuilder/utils';
+import {
+  recentSearchTypeToLabel,
+  tokenIsInvalid,
+} from 'sentry/components/searchQueryBuilder/utils';
 import {type ParseResult, Token} from 'sentry/components/searchSyntax/parser';
+import type {SavedSearchType} from 'sentry/types';
 import type {Organization} from 'sentry/types/organization';
 import {trackAnalytics} from 'sentry/utils/analytics';
 import useApi from 'sentry/utils/useApi';
@@ -12,13 +16,13 @@ import useOrganization from 'sentry/utils/useOrganization';
 
 type UseHandleSearchProps = {
   parsedQuery: ParseResult | null;
-  savedSearchType: any;
+  recentSearches: SavedSearchType | undefined;
   searchSource: string;
   onSearch?: (query: string) => void;
 };
 
 async function saveAsRecentSearch({
-  savedSearchType,
+  recentSearches,
   query,
   api,
   organization,
@@ -26,16 +30,16 @@ async function saveAsRecentSearch({
   api: Client;
   organization: Organization;
   query: string;
-  savedSearchType: any;
+  recentSearches: SavedSearchType | undefined;
 }) {
-  // Only save recent search query if we have a savedSearchType (also 0 is a valid value)
+  // Only save recent search query if there is a type provided.
   // Do not save empty string queries (i.e. if they clear search)
-  if (typeof savedSearchType === 'undefined' || !query) {
+  if (typeof recentSearches === 'undefined' || !query) {
     return;
   }
 
   try {
-    await saveRecentSearch(api, organization.slug, savedSearchType, query);
+    await saveRecentSearch(api, organization.slug, recentSearches, query);
   } catch (err) {
     // Silently capture errors if it fails to save
     Sentry.captureException(err);
@@ -84,7 +88,7 @@ function trackIndividualSearchFilters({
 
 export function useHandleSearch({
   parsedQuery,
-  savedSearchType,
+  recentSearches,
   searchSource,
   onSearch,
 }: UseHandleSearchProps) {
@@ -95,7 +99,7 @@ export function useHandleSearch({
     (query: string) => {
       onSearch?.(query);
 
-      const searchType = savedSearchType === 0 ? 'issues' : 'events';
+      const searchType = recentSearchTypeToLabel(recentSearches);
 
       if (parsedQuery?.some(token => tokenIsInvalid(token))) {
         trackAnalytics('search.search_with_invalid', {
@@ -124,8 +128,8 @@ export function useHandleSearch({
         organization,
       });
 
-      saveAsRecentSearch({api, organization, query, savedSearchType});
+      saveAsRecentSearch({api, organization, query, recentSearches});
     },
-    [api, onSearch, organization, parsedQuery, savedSearchType, searchSource]
+    [api, onSearch, organization, parsedQuery, recentSearches, searchSource]
   );
 }

+ 7 - 0
static/app/components/searchQueryBuilder/index.stories.tsx

@@ -469,6 +469,9 @@ export default storyBook(SearchQueryBuilder, story => {
               <code>highlightUnsupportedTags</code> {'->'}{' '}
               <code>disallowUnsupportedFilters</code>
             </li>
+            <li>
+              <code>savedSearchType</code> {'->'} <code>recentSearches</code>
+            </li>
           </ul>
         </p>
         <p>
@@ -487,6 +490,10 @@ export default storyBook(SearchQueryBuilder, story => {
               some of the analytics events. If your use case requires this, you can record
               these events manually with the <code>onSearch</code> callback.
             </li>
+            <li>
+              <code>hasRecentSearches</code> is no longer required. Saved searches will be
+              saved and displayed when <code>recentSearches</code> is provided.
+            </li>
           </ul>
         </p>
       </Fragment>

+ 10 - 7
static/app/components/searchQueryBuilder/index.tsx

@@ -85,7 +85,10 @@ export interface SearchQueryBuilderProps {
   onSearch?: (query: string) => void;
   placeholder?: string;
   queryInterface?: QueryInterfaceType;
-  savedSearchType?: SavedSearchType;
+  /**
+   * If provided, saves and displays recent searches of the given type.
+   */
+  recentSearches?: SavedSearchType;
 }
 
 function ActionButtons() {
@@ -129,9 +132,9 @@ export function SearchQueryBuilder({
   onSearch,
   onBlur,
   placeholder,
-  searchSource,
-  savedSearchType,
   queryInterface = QueryInterfaceType.TOKENIZED,
+  recentSearches,
+  searchSource,
 }: SearchQueryBuilderProps) {
   const wrapperRef = useRef<HTMLDivElement>(null);
   const {state, dispatch} = useQueryBuilderState({
@@ -172,7 +175,7 @@ export function SearchQueryBuilder({
 
   const handleSearch = useHandleSearch({
     parsedQuery,
-    savedSearchType,
+    recentSearches,
     searchSource,
     onSearch,
   });
@@ -193,7 +196,7 @@ export function SearchQueryBuilder({
       wrapperRef,
       handleSearch,
       placeholder,
-      savedSearchType,
+      recentSearches,
       searchSource,
       size,
     };
@@ -207,9 +210,9 @@ export function SearchQueryBuilder({
     fieldDefinitionGetter,
     dispatch,
     onSearch,
-    placeholder,
     handleSearch,
-    savedSearchType,
+    placeholder,
+    recentSearches,
     searchSource,
     size,
   ]);

+ 6 - 3
static/app/components/searchQueryBuilder/tokens/filter/filterKeyOperator.tsx

@@ -9,7 +9,10 @@ import InteractionStateLayer from 'sentry/components/interactionStateLayer';
 import {useSearchQueryBuilder} from 'sentry/components/searchQueryBuilder/context';
 import {useFilterButtonProps} from 'sentry/components/searchQueryBuilder/tokens/filter/useFilterButtonProps';
 import {getValidOpsForFilter} from 'sentry/components/searchQueryBuilder/tokens/filter/utils';
-import {isDateToken} from 'sentry/components/searchQueryBuilder/utils';
+import {
+  isDateToken,
+  recentSearchTypeToLabel,
+} from 'sentry/components/searchQueryBuilder/utils';
 import {
   FilterType,
   type ParseResultToken,
@@ -193,7 +196,7 @@ export function FilterKeyOperator({
   onOpenChange,
 }: FilterOperatorProps) {
   const organization = useOrganization();
-  const {dispatch, searchSource, query, savedSearchType, disabled} =
+  const {dispatch, searchSource, query, recentSearches, disabled} =
     useSearchQueryBuilder();
   const filterButtonProps = useFilterButtonProps({state, item});
 
@@ -220,7 +223,7 @@ export function FilterKeyOperator({
         trackAnalytics('search.operator_autocompleted', {
           organization,
           query,
-          search_type: savedSearchType === 0 ? 'issues' : 'events',
+          search_type: recentSearchTypeToLabel(recentSearches),
           search_source: searchSource,
           new_experience: true,
           search_operator: option.value,

+ 7 - 4
static/app/components/searchQueryBuilder/tokens/filter/valueCombobox.tsx

@@ -18,7 +18,10 @@ import {
   unescapeTagValue,
 } from 'sentry/components/searchQueryBuilder/tokens/filter/utils';
 import {getDefaultFilterValue} from 'sentry/components/searchQueryBuilder/tokens/utils';
-import {isDateToken} from 'sentry/components/searchQueryBuilder/utils';
+import {
+  isDateToken,
+  recentSearchTypeToLabel,
+} from 'sentry/components/searchQueryBuilder/utils';
 import {
   FilterType,
   TermOperator,
@@ -722,7 +725,7 @@ export function SearchQueryBuilderValueCombobox({
   const ref = useRef<HTMLDivElement>(null);
   const inputRef = useRef<HTMLInputElement>(null);
   const organization = useOrganization();
-  const {getFieldDefinition, filterKeys, dispatch, searchSource, savedSearchType} =
+  const {getFieldDefinition, filterKeys, dispatch, searchSource, recentSearches} =
     useSearchQueryBuilder();
   const fieldDefinition = getFieldDefinition(token.key.text);
   const canSelectMultipleValues = tokenSupportsMultipleValues(
@@ -779,7 +782,7 @@ export function SearchQueryBuilderValueCombobox({
   const analyticsData = useMemo(
     () => ({
       organization,
-      search_type: savedSearchType === 0 ? 'issues' : 'events',
+      search_type: recentSearchTypeToLabel(recentSearches),
       search_source: searchSource,
       filter_key: token.key.text,
       filter_operator: token.operator,
@@ -789,7 +792,7 @@ export function SearchQueryBuilderValueCombobox({
     [
       fieldDefinition?.valueType,
       organization,
-      savedSearchType,
+      recentSearches,
       searchSource,
       token.key.text,
       token.operator,

+ 4 - 3
static/app/components/searchQueryBuilder/tokens/freeText.tsx

@@ -21,6 +21,7 @@ import type {
   FilterKeySection,
   FocusOverride,
 } from 'sentry/components/searchQueryBuilder/types';
+import {recentSearchTypeToLabel} from 'sentry/components/searchQueryBuilder/utils';
 import {
   InvalidReason,
   type ParseResultToken,
@@ -354,7 +355,7 @@ function SearchQueryBuilderInputInternal({
     handleSearch,
     placeholder,
     searchSource,
-    savedSearchType,
+    recentSearches,
   } = useSearchQueryBuilder();
 
   const items = useSortItems({filterValue});
@@ -467,7 +468,7 @@ function SearchQueryBuilderInputInternal({
           const selectedKey = filterKeys[value];
           trackAnalytics('search.key_autocompleted', {
             organization,
-            search_type: savedSearchType === 0 ? 'issues' : 'events',
+            search_type: recentSearchTypeToLabel(recentSearches),
             search_source: searchSource,
             item_name: value,
             item_kind: selectedKey?.kind ?? FieldKind.FIELD,
@@ -541,7 +542,7 @@ function SearchQueryBuilderInputInternal({
             resetInputValue();
             trackAnalytics('search.key_manually_typed', {
               organization,
-              search_type: savedSearchType === 0 ? 'issues' : 'events',
+              search_type: recentSearchTypeToLabel(recentSearches),
               search_source: searchSource,
               item_name: filterKey,
               item_kind: key?.kind ?? FieldKind.FIELD,

+ 20 - 1
static/app/components/searchQueryBuilder/utils.tsx

@@ -9,7 +9,7 @@ import {
   Token,
   type TokenResult,
 } from 'sentry/components/searchSyntax/parser';
-import type {TagCollection} from 'sentry/types/group';
+import {SavedSearchType, type TagCollection} from 'sentry/types/group';
 import {FieldValueType} from 'sentry/utils/fields';
 
 export const INTERFACE_TYPE_LOCALSTORAGE_KEY = 'search-query-builder-interface';
@@ -170,3 +170,22 @@ export function isDateToken(token: TokenResult<Token.FILTER>) {
     token.filter
   );
 }
+
+export function recentSearchTypeToLabel(type: SavedSearchType | undefined) {
+  switch (type) {
+    case SavedSearchType.ISSUE:
+      return 'issues';
+    case SavedSearchType.EVENT:
+      return 'events';
+    case SavedSearchType.METRIC:
+      return 'metrics';
+    case SavedSearchType.REPLAY:
+      return 'replays';
+    case SavedSearchType.SESSION:
+      return 'sessions';
+    case SavedSearchType.SPAN:
+      return 'spans';
+    default:
+      return 'none';
+  }
+}

+ 1 - 1
static/app/views/issueList/searchBar.tsx

@@ -230,7 +230,7 @@ function IssueListSearchBar({organization, tags, onClose, ...props}: Props) {
         onBlur={props.onBlur}
         onChange={onChange}
         searchSource={props.searchSource ?? 'issues'}
-        savedSearchType={SavedSearchType.ISSUE}
+        recentSearches={SavedSearchType.ISSUE}
         disallowLogicalOperators
         placeholder={props.placeholder}
       />