import {Fragment, useState} from 'react';
import MultipleCheckbox from 'sentry/components/forms/controls/multipleCheckbox';
import {SearchQueryBuilder} from 'sentry/components/searchQueryBuilder';
import {FormattedQuery} from 'sentry/components/searchQueryBuilder/formattedQuery';
import type {
FieldDefinitionGetter,
FilterKeySection,
} from 'sentry/components/searchQueryBuilder/types';
import {InvalidReason} from 'sentry/components/searchSyntax/parser';
import {ItemType} from 'sentry/components/smartSearchBar/types';
import JSXNode from 'sentry/components/stories/jsxNode';
import JSXProperty from 'sentry/components/stories/jsxProperty';
import storyBook from 'sentry/stories/storyBook';
import type {TagCollection} from 'sentry/types/group';
import {
FieldKey,
FieldKind,
FieldValueType,
getFieldDefinition,
MobileVital,
WebVital,
} from 'sentry/utils/fields';
const FILTER_KEYS: TagCollection = {
[FieldKey.ASSIGNED]: {
key: FieldKey.ASSIGNED,
name: 'Assigned To',
kind: FieldKind.FIELD,
predefined: true,
values: [
{
title: 'Suggested',
type: 'header',
icon: null,
children: [{value: 'me'}, {value: 'unassigned'}],
},
{
title: 'All',
type: 'header',
icon: null,
children: [{value: 'person1@sentry.io'}, {value: 'person2@sentry.io'}],
},
],
},
[FieldKey.BROWSER_NAME]: {
key: FieldKey.BROWSER_NAME,
name: 'Browser Name',
kind: FieldKind.FIELD,
predefined: true,
values: ['Chrome', 'Firefox', 'Safari', 'Edge', 'Internet Explorer', 'Opera 1,2'],
},
[FieldKey.IS]: {
key: FieldKey.IS,
name: 'is',
predefined: true,
values: ['resolved', 'unresolved', 'ignored'],
},
[FieldKey.LAST_SEEN]: {
key: FieldKey.LAST_SEEN,
name: 'lastSeen',
kind: FieldKind.FIELD,
},
[FieldKey.TIMES_SEEN]: {
key: FieldKey.TIMES_SEEN,
name: 'timesSeen',
kind: FieldKind.FIELD,
},
[WebVital.LCP]: {
key: WebVital.LCP,
name: 'lcp',
kind: FieldKind.FIELD,
},
[MobileVital.FRAMES_SLOW_RATE]: {
key: MobileVital.FRAMES_SLOW_RATE,
name: 'framesSlowRate',
kind: FieldKind.FIELD,
},
custom_tag_name: {
key: 'custom_tag_name',
name: 'Custom_Tag_Name',
},
};
const FILTER_KEY_SECTIONS: FilterKeySection[] = [
{
value: 'cat_1',
label: 'Category 1',
children: [FieldKey.ASSIGNED, FieldKey.IS],
},
{
value: 'cat_2',
label: 'Category 2',
children: [WebVital.LCP, MobileVital.FRAMES_SLOW_RATE],
},
{
value: 'cat_3',
label: 'Category 3',
children: [FieldKey.TIMES_SEEN],
},
{
value: 'cat_4',
label: 'Category 4',
children: [FieldKey.LAST_SEEN, FieldKey.TIMES_SEEN],
},
{
value: 'cat_5',
label: 'Category 5',
children: ['custom_tag_name'],
},
];
const getTagValues = (): Promise
The search query, unless configured otherwise, may contain filters, logical
operators, and free text. These filters can have defined data types, but default
to a multi-selectable string filter.
Required props:
initialQuery
: The initial query to display in the search input.
filterKeys
: A collection of filter keys which are used to populate the dropdowns. All
valid filter keys should be defined here.
getTagValues
: A function which returns an array of filter value suggestions. Any filter
key which does not have predefined: true
will use this function
to get value suggestions.
searchSource
: Used to differentiate between different search bars for analytics.
Typically snake_case (e.g. issue_details
,{' '}
performance_landing
).
To guide the user in building a search query, filter value suggestions can be provided in a few different ways:
filterKeys
. These
suggestions can also be formatted:
values
.
values
. Each object should
have a title
and children
array.
ItemType.TAG_VALUE
with a{' '}
documentation
property.
predefined: true
, it will use the getTagValues
{' '}
function to fetch suggestions. The filter key and query are provided, and it
is up to the consumer to return the suggestions.
A special menu can be displayed when no text is entered in the search input, allowing for better organization and discovery of filter keys.
This menu is defined by filterKeySections
, which accepts a list of
sections. Each section contains a name and a list of filter keys. Note that the
order of both the sections and the items within each section are respected.
If you wish to modify the size of the filter key menu, use
filterKeyMenuWidth
to define the width in pixels.
Field definitions very important for the search query builder to work correctly. They provide information such as what data types are allow for a given filter, as well as the description and keywords.
By default, field definitions are sourced from{' '}
EVENT_FIELD_DEFINITIONS
in sentry/utils/fields.ts
. If
these definitions are not correct for the use case, they can be overridden by
passing fieldDefinitionGetter
.
Filter keys can be defined as aggregate filters, which allow for more complex operations. They may accept any number of parameters, which are defined in the field definition.
To define an aggregate filter, set the kind
to{' '}
FieldKind.FUNCTION
, and the valueType
to the return
type of the function. Then define the parameters
, which is an array
of acceptable column types or a predicate function.
name
: The name of the parameter.
kind
: Parameters may be defined as either a column parameter or a value
parameter.
'value'
: If this parameter is a value it also requires a{' '}
dataType
and, optionally, a list of options
{' '}
that will be displayed as suggestions.
'column'
: Column parameters suggest other existing filter
keys. This also requires columnTypes
to be defined, which
may be a list of data types that the column may be or a predicate
function.
required
: Whether or not the parameter is required.
defaultValue
: The default value that the parameter will be set to when the filter is
first added.
Some aggreate filters may have a return type that is dependent on the
parameters. For example, p95(column)
may return a few different
types depending on the column type. In this case, the field definition should
implement parameterDependentValueType
. This function accepts an
array of parameters and returns the value type.
onChange
is called whenever the search query changes. This can be
used to update the UI as the user updates the query.
onSearch
is called when the user presses enter. This can be used to
submit the search query.
onChange
value
: {onChangeValue}
onSearch
value
: {onSearchValue}
There are some config options which allow you to customize which types of syntax are considered valid. This should be used when the search backend does not support certain operators like boolean logic or wildcards. Use the checkboxes below to enable/disable the following options:
{config}
The query above has a few invalid tokens. The invalid tokens are highlighted in
red and display a tooltip with a message when focused. The invalid token
messages can be customized using the invalidMessages
prop. In this
case, the unsupported tag message is modified with{' '}
You can display an indicator when the search query has been modified but not
fully submitted using the showUnsubmittedIndicator
prop. This can
be useful to remind the user that they have unsaved changes for use cases which
require manual submission.
Current query: {query}
If you just need to render a formatted query outside of the search bar,{' '}
Some props have been renamed:
supportedTags
{'->'} filterKeys
onGetTagValues
{'->'} getTagValues
highlightUnsupportedTags
{'->'}{' '}
disallowUnsupportedFilters
savedSearchType
{'->'} recentSearches
Some props have been removed:
excludedTags
is no longer supported. If a filter key should not
be shown, do not include it in filterKeys
.
(boolean|date|duration)Keys
no longer need to be specified. The
filter value types are inferred from the field definitions.
projectIds
was used to add is_multi_project
to
some of the analytics events. If your use case requires this, you can record
these events manually with the onSearch
callback.
hasRecentSearches
is no longer required. Saved searches will be
saved and displayed when recentSearches
is provided.