Table panel: Fix filter not working for special characters and allow filter's case sensitivity control (#40458) (#40556)
* Fix filter not working for special characters and allow case sansitive filter control
* Memoize regex
(cherry picked from commit 6201b5b1d9)
Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
This commit is contained in:
co-authored by
Dominik Prokop
parent
3b5deb42c4
commit
014e5ba0c2
@@ -10,18 +10,26 @@ interface Props {
|
|||||||
values: SelectableValue[];
|
values: SelectableValue[];
|
||||||
options: SelectableValue[];
|
options: SelectableValue[];
|
||||||
onChange: (options: SelectableValue[]) => void;
|
onChange: (options: SelectableValue[]) => void;
|
||||||
|
caseSensitive?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ITEM_HEIGHT = 28;
|
const ITEM_HEIGHT = 28;
|
||||||
const MIN_HEIGHT = ITEM_HEIGHT * 5;
|
const MIN_HEIGHT = ITEM_HEIGHT * 5;
|
||||||
|
|
||||||
export const FilterList: FC<Props> = ({ options, values, onChange }) => {
|
export const FilterList: FC<Props> = ({ options, values, caseSensitive, onChange }) => {
|
||||||
const theme = useTheme2();
|
const theme = useTheme2();
|
||||||
const styles = getStyles(theme);
|
const styles = getStyles(theme);
|
||||||
const [searchFilter, setSearchFilter] = useState('');
|
const [searchFilter, setSearchFilter] = useState('');
|
||||||
|
const regex = useMemo(() => new RegExp(searchFilter, caseSensitive ? undefined : 'i'), [searchFilter, caseSensitive]);
|
||||||
const items = useMemo(
|
const items = useMemo(
|
||||||
() => options.filter((option) => option.label?.toLowerCase().includes(searchFilter.toLowerCase())),
|
() =>
|
||||||
[options, searchFilter]
|
options.filter((option) => {
|
||||||
|
if (option.label === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return regex.test(option.label);
|
||||||
|
}),
|
||||||
|
[options, regex]
|
||||||
);
|
);
|
||||||
const gutter = theme.spacing.gridSize;
|
const gutter = theme.spacing.gridSize;
|
||||||
const height = useMemo(() => Math.min(items.length * ITEM_HEIGHT, MIN_HEIGHT) + gutter, [gutter, items.length]);
|
const height = useMemo(() => Math.min(items.length * ITEM_HEIGHT, MIN_HEIGHT) + gutter, [gutter, items.length]);
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { Field, GrafanaTheme, SelectableValue } from '@grafana/data';
|
|||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
|
|
||||||
import { TableStyles } from './styles';
|
import { TableStyles } from './styles';
|
||||||
import { stylesFactory, useStyles } from '../../themes';
|
import { stylesFactory, useStyles, useTheme2 } from '../../themes';
|
||||||
import { Button, ClickOutsideWrapper, HorizontalGroup, Label, VerticalGroup } from '..';
|
import { Button, ClickOutsideWrapper, HorizontalGroup, IconButton, Label, VerticalGroup } from '..';
|
||||||
import { FilterList } from './FilterList';
|
import { FilterList } from './FilterList';
|
||||||
import { calculateUniqueFieldValues, getFilteredOptions, valuesToOptions } from './utils';
|
import { calculateUniqueFieldValues, getFilteredOptions, valuesToOptions } from './utils';
|
||||||
|
|
||||||
@@ -16,10 +16,12 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const FilterPopup: FC<Props> = ({ column: { preFilteredRows, filterValue, setFilter }, onClose, field }) => {
|
export const FilterPopup: FC<Props> = ({ column: { preFilteredRows, filterValue, setFilter }, onClose, field }) => {
|
||||||
|
const theme = useTheme2();
|
||||||
const uniqueValues = useMemo(() => calculateUniqueFieldValues(preFilteredRows, field), [preFilteredRows, field]);
|
const uniqueValues = useMemo(() => calculateUniqueFieldValues(preFilteredRows, field), [preFilteredRows, field]);
|
||||||
const options = useMemo(() => valuesToOptions(uniqueValues), [uniqueValues]);
|
const options = useMemo(() => valuesToOptions(uniqueValues), [uniqueValues]);
|
||||||
const filteredOptions = useMemo(() => getFilteredOptions(options, filterValue), [options, filterValue]);
|
const filteredOptions = useMemo(() => getFilteredOptions(options, filterValue), [options, filterValue]);
|
||||||
const [values, setValues] = useState<SelectableValue[]>(filteredOptions);
|
const [values, setValues] = useState<SelectableValue[]>(filteredOptions);
|
||||||
|
const [matchCase, setMatchCase] = useState(false);
|
||||||
|
|
||||||
const onCancel = useCallback((event?: React.MouseEvent) => onClose(), [onClose]);
|
const onCancel = useCallback((event?: React.MouseEvent) => onClose(), [onClose]);
|
||||||
|
|
||||||
@@ -49,9 +51,19 @@ export const FilterPopup: FC<Props> = ({ column: { preFilteredRows, filterValue,
|
|||||||
<div className={cx(styles.filterContainer)} onClick={stopPropagation}>
|
<div className={cx(styles.filterContainer)} onClick={stopPropagation}>
|
||||||
<VerticalGroup spacing="lg">
|
<VerticalGroup spacing="lg">
|
||||||
<VerticalGroup spacing="xs">
|
<VerticalGroup spacing="xs">
|
||||||
<Label>Filter by values:</Label>
|
<HorizontalGroup justify="space-between" align="center">
|
||||||
|
<Label className={styles.label}>Filter by values:</Label>
|
||||||
|
<IconButton
|
||||||
|
name="text-fields"
|
||||||
|
tooltip="Match case"
|
||||||
|
style={{ color: matchCase ? theme.colors.text.link : theme.colors.text.disabled }}
|
||||||
|
onClick={() => {
|
||||||
|
setMatchCase((s) => !s);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</HorizontalGroup>
|
||||||
<div className={cx(styles.listDivider)} />
|
<div className={cx(styles.listDivider)} />
|
||||||
<FilterList onChange={setValues} values={values} options={options} />
|
<FilterList onChange={setValues} values={values} options={options} caseSensitive={matchCase} />
|
||||||
</VerticalGroup>
|
</VerticalGroup>
|
||||||
<HorizontalGroup spacing="lg">
|
<HorizontalGroup spacing="lg">
|
||||||
<HorizontalGroup>
|
<HorizontalGroup>
|
||||||
@@ -96,6 +108,9 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => ({
|
|||||||
border-top: ${theme.border.width.sm} solid ${theme.colors.border2};
|
border-top: ${theme.border.width.sm} solid ${theme.colors.border2};
|
||||||
padding: ${theme.spacing.xs} ${theme.spacing.md};
|
padding: ${theme.spacing.xs} ${theme.spacing.md};
|
||||||
`,
|
`,
|
||||||
|
label: css`
|
||||||
|
margin-bottom: 0;
|
||||||
|
`,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const stopPropagation = (event: React.MouseEvent) => {
|
const stopPropagation = (event: React.MouseEvent) => {
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ export const getAvailableIcons = () =>
|
|||||||
'sync',
|
'sync',
|
||||||
'table',
|
'table',
|
||||||
'tag-alt',
|
'tag-alt',
|
||||||
|
'text-fields',
|
||||||
'times',
|
'times',
|
||||||
'toggle-on',
|
'toggle-on',
|
||||||
'trash-alt',
|
'trash-alt',
|
||||||
|
|||||||
Reference in New Issue
Block a user