|
@@ -17,6 +17,7 @@ import {
|
|
|
FilterType,
|
|
|
ParseResult,
|
|
|
parseSearch,
|
|
|
+ SearchConfig,
|
|
|
TermOperator,
|
|
|
Token,
|
|
|
TokenResult,
|
|
@@ -41,6 +42,7 @@ import {Organization, SavedSearchType, Tag, TagCollection, User} from 'sentry/ty
|
|
|
import {defined} from 'sentry/utils';
|
|
|
import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
|
|
|
import {callIfFunction} from 'sentry/utils/callIfFunction';
|
|
|
+import {CustomMeasurementCollection} from 'sentry/utils/customMeasurements/customMeasurements';
|
|
|
import {FieldValueType, getFieldDefinition} from 'sentry/utils/fields';
|
|
|
import getDynamicComponent from 'sentry/utils/getDynamicComponent';
|
|
|
import withApi from 'sentry/utils/withApi';
|
|
@@ -82,10 +84,41 @@ const ACTION_OVERFLOW_WIDTH = 400;
|
|
|
*/
|
|
|
const ACTION_OVERFLOW_STEPS = 75;
|
|
|
|
|
|
-const makeQueryState = (query: string) => ({
|
|
|
- query,
|
|
|
- parsedQuery: parseSearch(query),
|
|
|
-});
|
|
|
+const getSearchConfigFromCustomPerformanceMetrics = (
|
|
|
+ customPerformanceMetrics?: CustomMeasurementCollection
|
|
|
+): Partial<SearchConfig> => {
|
|
|
+ const searchConfigMap: Record<string, string[]> = {
|
|
|
+ sizeKeys: [],
|
|
|
+ durationKeys: [],
|
|
|
+ percentageKeys: [],
|
|
|
+ numericKeys: [],
|
|
|
+ };
|
|
|
+ if (customPerformanceMetrics) {
|
|
|
+ Object.keys(customPerformanceMetrics).forEach(metricName => {
|
|
|
+ const {fieldType} = customPerformanceMetrics[metricName];
|
|
|
+ switch (fieldType) {
|
|
|
+ case 'size':
|
|
|
+ searchConfigMap.sizeKeys.push(metricName);
|
|
|
+ break;
|
|
|
+ case 'duration':
|
|
|
+ searchConfigMap.durationKeys.push(metricName);
|
|
|
+ break;
|
|
|
+ case 'percentage':
|
|
|
+ searchConfigMap.percentageKeys.push(metricName);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ searchConfigMap.numericKeys.push(metricName);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ const searchConfig = {
|
|
|
+ sizeKeys: new Set(searchConfigMap.sizeKeys),
|
|
|
+ durationKeys: new Set(searchConfigMap.durationKeys),
|
|
|
+ percentageKeys: new Set(searchConfigMap.percentageKeys),
|
|
|
+ numericKeys: new Set(searchConfigMap.numericKeys),
|
|
|
+ };
|
|
|
+ return searchConfig;
|
|
|
+};
|
|
|
|
|
|
const generateOpAutocompleteGroup = (
|
|
|
validOps: readonly TermOperator[],
|
|
@@ -148,6 +181,10 @@ type Props = WithRouterProps & {
|
|
|
actionBarItems?: ActionBarItem[];
|
|
|
className?: string;
|
|
|
|
|
|
+ /**
|
|
|
+ * Custom Performance Metrics for query string unit parsing
|
|
|
+ */
|
|
|
+ customPerformanceMetrics?: CustomMeasurementCollection;
|
|
|
defaultQuery?: string;
|
|
|
/**
|
|
|
* Search items to display when there's no tag key. Is a tuple of search
|
|
@@ -344,12 +381,16 @@ class SmartSearchBar extends Component<Props, State> {
|
|
|
}
|
|
|
|
|
|
componentDidUpdate(prevProps: Props) {
|
|
|
- const {query} = this.props;
|
|
|
- const {query: lastQuery} = prevProps;
|
|
|
+ const {query, customPerformanceMetrics} = this.props;
|
|
|
+ const {query: lastQuery, customPerformanceMetrics: lastCustomPerformanceMetrics} =
|
|
|
+ prevProps;
|
|
|
|
|
|
- if (query !== lastQuery && (defined(query) || defined(lastQuery))) {
|
|
|
+ if (
|
|
|
+ (query !== lastQuery && (defined(query) || defined(lastQuery))) ||
|
|
|
+ customPerformanceMetrics !== lastCustomPerformanceMetrics
|
|
|
+ ) {
|
|
|
// eslint-disable-next-line react/no-did-update-set-state
|
|
|
- this.setState(makeQueryState(addSpace(query ?? undefined)));
|
|
|
+ this.setState(this.makeQueryState(addSpace(query ?? undefined)));
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -363,6 +404,14 @@ class SmartSearchBar extends Component<Props, State> {
|
|
|
return query !== null ? addSpace(query) : defaultQuery ?? '';
|
|
|
}
|
|
|
|
|
|
+ makeQueryState(query: string) {
|
|
|
+ const additionalConfig: Partial<SearchConfig> =
|
|
|
+ getSearchConfigFromCustomPerformanceMetrics(this.props.customPerformanceMetrics);
|
|
|
+ return {
|
|
|
+ query,
|
|
|
+ parsedQuery: parseSearch(query, additionalConfig),
|
|
|
+ };
|
|
|
+ }
|
|
|
/**
|
|
|
* Ref to the search element itself
|
|
|
*/
|
|
@@ -640,7 +689,7 @@ class SmartSearchBar extends Component<Props, State> {
|
|
|
};
|
|
|
|
|
|
clearSearch = () => {
|
|
|
- this.setState(makeQueryState(''), () => {
|
|
|
+ this.setState(this.makeQueryState(''), () => {
|
|
|
this.close();
|
|
|
callIfFunction(this.props.onSearch, this.state.query);
|
|
|
});
|
|
@@ -670,7 +719,7 @@ class SmartSearchBar extends Component<Props, State> {
|
|
|
onQueryChange = (evt: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
|
const query = evt.target.value.replace('\n', '');
|
|
|
|
|
|
- this.setState(makeQueryState(query), this.updateAutoCompleteItems);
|
|
|
+ this.setState(this.makeQueryState(query), this.updateAutoCompleteItems);
|
|
|
callIfFunction(this.props.onChange, evt.target.value, evt);
|
|
|
};
|
|
|
|
|
@@ -693,7 +742,7 @@ class SmartSearchBar extends Component<Props, State> {
|
|
|
const mergedText = `${textBefore}${text}${textAfter}`;
|
|
|
|
|
|
// Insert text manually
|
|
|
- this.setState(makeQueryState(mergedText), () => {
|
|
|
+ this.setState(this.makeQueryState(mergedText), () => {
|
|
|
this.updateAutoCompleteItems();
|
|
|
// Update cursor position after updating text
|
|
|
const newCursorPosition = cursorPosStart + text.length;
|
|
@@ -1542,7 +1591,7 @@ class SmartSearchBar extends Component<Props, State> {
|
|
|
};
|
|
|
|
|
|
updateQuery = (newQuery: string, cursorPosition?: number) =>
|
|
|
- this.setState(makeQueryState(newQuery), () => {
|
|
|
+ this.setState(this.makeQueryState(newQuery), () => {
|
|
|
// setting a new input value will lose focus; restore it
|
|
|
if (this.searchInput.current) {
|
|
|
this.searchInput.current.focus();
|
|
@@ -1659,7 +1708,7 @@ class SmartSearchBar extends Component<Props, State> {
|
|
|
search_source: 'recent_search',
|
|
|
});
|
|
|
|
|
|
- this.setState(makeQueryState(replaceText), () => {
|
|
|
+ this.setState(this.makeQueryState(replaceText), () => {
|
|
|
// Propagate onSearch and save to recent searches
|
|
|
this.doSearch();
|
|
|
});
|