|
@@ -1,5 +1,6 @@
|
|
import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
|
import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
|
import styled from '@emotion/styled';
|
|
import styled from '@emotion/styled';
|
|
|
|
+import {isMac} from '@react-aria/utils';
|
|
import {Item, Section} from '@react-stately/collections';
|
|
import {Item, Section} from '@react-stately/collections';
|
|
import type {KeyboardEvent} from '@react-types/shared';
|
|
import type {KeyboardEvent} from '@react-types/shared';
|
|
|
|
|
|
@@ -19,6 +20,7 @@ import {
|
|
replaceCommaSeparatedValue,
|
|
replaceCommaSeparatedValue,
|
|
unescapeTagValue,
|
|
unescapeTagValue,
|
|
} from 'sentry/components/searchQueryBuilder/tokens/filter/utils';
|
|
} from 'sentry/components/searchQueryBuilder/tokens/filter/utils';
|
|
|
|
+import {ValueListBox} from 'sentry/components/searchQueryBuilder/tokens/filter/valueListBox';
|
|
import {getDefaultAbsoluteDateValue} from 'sentry/components/searchQueryBuilder/tokens/filter/valueSuggestions/date';
|
|
import {getDefaultAbsoluteDateValue} from 'sentry/components/searchQueryBuilder/tokens/filter/valueSuggestions/date';
|
|
import type {
|
|
import type {
|
|
SuggestionItem,
|
|
SuggestionItem,
|
|
@@ -55,6 +57,7 @@ import {type FieldDefinition, FieldValueType} from 'sentry/utils/fields';
|
|
import {isCtrlKeyPressed} from 'sentry/utils/isCtrlKeyPressed';
|
|
import {isCtrlKeyPressed} from 'sentry/utils/isCtrlKeyPressed';
|
|
import {type QueryKey, useQuery} from 'sentry/utils/queryClient';
|
|
import {type QueryKey, useQuery} from 'sentry/utils/queryClient';
|
|
import {useDebouncedValue} from 'sentry/utils/useDebouncedValue';
|
|
import {useDebouncedValue} from 'sentry/utils/useDebouncedValue';
|
|
|
|
+import useKeyPress from 'sentry/utils/useKeyPress';
|
|
import useOrganization from 'sentry/utils/useOrganization';
|
|
import useOrganization from 'sentry/utils/useOrganization';
|
|
|
|
|
|
type SearchQueryValueBuilderProps = {
|
|
type SearchQueryValueBuilderProps = {
|
|
@@ -258,7 +261,9 @@ function useFilterSuggestions({
|
|
token,
|
|
token,
|
|
filterValue,
|
|
filterValue,
|
|
selectedValues,
|
|
selectedValues,
|
|
|
|
+ ctrlKeyPressed,
|
|
}: {
|
|
}: {
|
|
|
|
+ ctrlKeyPressed: boolean;
|
|
filterValue: string;
|
|
filterValue: string;
|
|
selectedValues: string[];
|
|
selectedValues: string[];
|
|
token: TokenResult<Token.FILTER>;
|
|
token: TokenResult<Token.FILTER>;
|
|
@@ -320,12 +325,13 @@ function useFilterSuggestions({
|
|
token={token}
|
|
token={token}
|
|
disabled={disabled}
|
|
disabled={disabled}
|
|
value={suggestion.value}
|
|
value={suggestion.value}
|
|
|
|
+ ctrlKeyPressed={ctrlKeyPressed}
|
|
/>
|
|
/>
|
|
);
|
|
);
|
|
},
|
|
},
|
|
};
|
|
};
|
|
},
|
|
},
|
|
- [canSelectMultipleValues, token]
|
|
|
|
|
|
+ [canSelectMultipleValues, token, ctrlKeyPressed]
|
|
);
|
|
);
|
|
|
|
|
|
const suggestionGroups: SuggestionSection[] = useMemo(() => {
|
|
const suggestionGroups: SuggestionSection[] = useMemo(() => {
|
|
@@ -379,7 +385,9 @@ function ItemCheckbox({
|
|
selected,
|
|
selected,
|
|
disabled,
|
|
disabled,
|
|
value,
|
|
value,
|
|
|
|
+ ctrlKeyPressed,
|
|
}: {
|
|
}: {
|
|
|
|
+ ctrlKeyPressed: boolean;
|
|
disabled: boolean;
|
|
disabled: boolean;
|
|
isFocused: boolean;
|
|
isFocused: boolean;
|
|
selected: boolean;
|
|
selected: boolean;
|
|
@@ -394,7 +402,7 @@ function ItemCheckbox({
|
|
onMouseUp={e => e.stopPropagation()}
|
|
onMouseUp={e => e.stopPropagation()}
|
|
onClick={e => e.stopPropagation()}
|
|
onClick={e => e.stopPropagation()}
|
|
>
|
|
>
|
|
- <CheckWrap visible={isFocused || selected} role="presentation">
|
|
|
|
|
|
+ <CheckWrap visible={isFocused || selected || ctrlKeyPressed} role="presentation">
|
|
<Checkbox
|
|
<Checkbox
|
|
size="sm"
|
|
size="sm"
|
|
checked={selected}
|
|
checked={selected}
|
|
@@ -436,8 +444,14 @@ export function SearchQueryBuilderValueCombobox({
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
const organization = useOrganization();
|
|
const organization = useOrganization();
|
|
- const {getFieldDefinition, filterKeys, dispatch, searchSource, recentSearches} =
|
|
|
|
- useSearchQueryBuilder();
|
|
|
|
|
|
+ const {
|
|
|
|
+ getFieldDefinition,
|
|
|
|
+ filterKeys,
|
|
|
|
+ dispatch,
|
|
|
|
+ searchSource,
|
|
|
|
+ recentSearches,
|
|
|
|
+ wrapperRef: topLevelWrapperRef,
|
|
|
|
+ } = useSearchQueryBuilder();
|
|
const keyName = getKeyName(token.key);
|
|
const keyName = getKeyName(token.key);
|
|
const fieldDefinition = getFieldDefinition(keyName);
|
|
const fieldDefinition = getFieldDefinition(keyName);
|
|
const canSelectMultipleValues = tokenSupportsMultipleValues(
|
|
const canSelectMultipleValues = tokenSupportsMultipleValues(
|
|
@@ -470,6 +484,11 @@ export function SearchQueryBuilderValueCombobox({
|
|
[canSelectMultipleValues, inputValue]
|
|
[canSelectMultipleValues, inputValue]
|
|
);
|
|
);
|
|
|
|
|
|
|
|
+ const ctrlKeyPressed = useKeyPress(
|
|
|
|
+ isMac() ? 'Meta' : 'Control',
|
|
|
|
+ topLevelWrapperRef.current
|
|
|
|
+ );
|
|
|
|
+
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
if (canSelectMultipleValues) {
|
|
if (canSelectMultipleValues) {
|
|
setInputValue(getMultiSelectInputValue(token));
|
|
setInputValue(getMultiSelectInputValue(token));
|
|
@@ -489,6 +508,7 @@ export function SearchQueryBuilderValueCombobox({
|
|
token,
|
|
token,
|
|
filterValue,
|
|
filterValue,
|
|
selectedValues,
|
|
selectedValues,
|
|
|
|
+ ctrlKeyPressed,
|
|
});
|
|
});
|
|
|
|
|
|
const analyticsData = useMemo(
|
|
const analyticsData = useMemo(
|
|
@@ -533,7 +553,7 @@ export function SearchQueryBuilderValueCombobox({
|
|
value: newValue,
|
|
value: newValue,
|
|
});
|
|
});
|
|
|
|
|
|
- if (newValue && newValue !== '""') {
|
|
|
|
|
|
+ if (newValue && newValue !== '""' && !ctrlKeyPressed) {
|
|
onCommit();
|
|
onCommit();
|
|
}
|
|
}
|
|
|
|
|
|
@@ -548,7 +568,10 @@ export function SearchQueryBuilderValueCombobox({
|
|
replaceCommaSeparatedValue(inputValue, selectionIndex, value)
|
|
replaceCommaSeparatedValue(inputValue, selectionIndex, value)
|
|
),
|
|
),
|
|
});
|
|
});
|
|
- onCommit();
|
|
|
|
|
|
+
|
|
|
|
+ if (!ctrlKeyPressed) {
|
|
|
|
+ onCommit();
|
|
|
|
+ }
|
|
} else {
|
|
} else {
|
|
dispatch({
|
|
dispatch({
|
|
type: 'UPDATE_TOKEN_VALUE',
|
|
type: 'UPDATE_TOKEN_VALUE',
|
|
@@ -570,6 +593,7 @@ export function SearchQueryBuilderValueCombobox({
|
|
selectedValues,
|
|
selectedValues,
|
|
selectionIndex,
|
|
selectionIndex,
|
|
token,
|
|
token,
|
|
|
|
+ ctrlKeyPressed,
|
|
]
|
|
]
|
|
);
|
|
);
|
|
|
|
|
|
@@ -690,7 +714,16 @@ export function SearchQueryBuilderValueCombobox({
|
|
|
|
|
|
const customMenu: CustomComboboxMenu<SelectOptionWithKey<string>> | undefined =
|
|
const customMenu: CustomComboboxMenu<SelectOptionWithKey<string>> | undefined =
|
|
useMemo(() => {
|
|
useMemo(() => {
|
|
- if (!showDatePicker) return undefined;
|
|
|
|
|
|
+ if (!showDatePicker)
|
|
|
|
+ return function (props) {
|
|
|
|
+ return (
|
|
|
|
+ <ValueListBox
|
|
|
|
+ {...props}
|
|
|
|
+ isMultiSelect={canSelectMultipleValues}
|
|
|
|
+ items={items}
|
|
|
|
+ />
|
|
|
|
+ );
|
|
|
|
+ };
|
|
|
|
|
|
return function (props) {
|
|
return function (props) {
|
|
return (
|
|
return (
|
|
@@ -722,7 +755,16 @@ export function SearchQueryBuilderValueCombobox({
|
|
/>
|
|
/>
|
|
);
|
|
);
|
|
};
|
|
};
|
|
- }, [analyticsData, dispatch, inputValue, onCommit, showDatePicker, token]);
|
|
|
|
|
|
+ }, [
|
|
|
|
+ showDatePicker,
|
|
|
|
+ canSelectMultipleValues,
|
|
|
|
+ items,
|
|
|
|
+ inputValue,
|
|
|
|
+ token,
|
|
|
|
+ analyticsData,
|
|
|
|
+ dispatch,
|
|
|
|
+ onCommit,
|
|
|
|
+ ]);
|
|
|
|
|
|
return (
|
|
return (
|
|
<ValueEditing ref={ref} data-test-id="filter-value-editing">
|
|
<ValueEditing ref={ref} data-test-id="filter-value-editing">
|