remove multi-text-select object

This commit is contained in:
Isabella Siu
2022-04-22 17:27:43 -04:00
parent 5b151894cf
commit 0603f8eb21
10 changed files with 53 additions and 119 deletions
@@ -7,10 +7,7 @@ interface DropdownIndicatorProps {
hidden?: boolean;
}
export const DropdownIndicator: React.FC<DropdownIndicatorProps> = ({ isOpen, hidden = false }) => {
if (hidden) {
return <></>;
}
export const DropdownIndicator: React.FC<DropdownIndicatorProps> = ({ isOpen }) => {
const icon = isOpen ? 'angle-up' : 'angle-down';
return <Icon name={icon} />;
};
@@ -102,7 +102,6 @@ export function SelectBase<T>({
formatCreateLabel,
getOptionLabel,
getOptionValue,
hideIndicator,
inputValue,
invalid,
isClearable = false,
@@ -333,7 +332,7 @@ export function SelectBase<T>({
);
},
DropdownIndicator(props: any) {
return <DropdownIndicator isOpen={props.selectProps.menuIsOpen} hidden={hideIndicator} />;
return <DropdownIndicator isOpen={props.selectProps.menuIsOpen} />;
},
SingleValue(props: any) {
return <SingleValue {...props} disabled={disabled} />;
@@ -29,7 +29,6 @@ export interface SelectCommonProps<T> {
formatCreateLabel?: (input: string) => string;
getOptionLabel?: (item: SelectableValue<T>) => React.ReactNode;
getOptionValue?: (item: SelectableValue<T>) => string;
hideIndicator?: boolean;
inputValue?: string;
invalid?: boolean;
isClearable?: boolean;
@@ -16,11 +16,10 @@ describe('MultiFilters', () => {
expect(filterItems.length).toBe(2);
expect(within(filterItems[0]).getByDisplayValue('InstanceId')).toBeInTheDocument();
expect(within(filterItems[0]).getByText('a')).toBeInTheDocument();
expect(within(filterItems[0]).getByText('b')).toBeInTheDocument();
expect(within(filterItems[0]).getByDisplayValue('a, b')).toBeInTheDocument();
expect(within(filterItems[1]).getByDisplayValue('InstanceGroup')).toBeInTheDocument();
expect(within(filterItems[1]).getByText('Group1')).toBeInTheDocument();
expect(within(filterItems[1]).getByDisplayValue('Group1')).toBeInTheDocument();
});
});
@@ -40,16 +39,16 @@ describe('MultiFilters', () => {
it('it should add the new item but not call onChange', async () => {
const filters = {};
const onChange = jest.fn();
const { container } = render(<MultiFilter filters={filters} onChange={onChange} />);
render(<MultiFilter filters={filters} onChange={onChange} />);
userEvent.click(screen.getByLabelText('Add'));
const filterItemElement = screen.getByTestId('cloudwatch-multifilter-item');
expect(filterItemElement).toBeInTheDocument();
const keyElement = container.querySelector('#cloudwatch-multifilter-item-key');
const keyElement = screen.getByTestId('cloudwatch-multifilter-item-key');
expect(keyElement).toBeInTheDocument();
userEvent.type(keyElement!, 'my-key');
fireEvent.keyDown(keyElement!, { keyCode: 13 });
fireEvent.blur(keyElement!);
expect(within(filterItemElement).getByDisplayValue('my-key')).toBeInTheDocument();
expect(onChange).not.toHaveBeenCalled();
@@ -60,27 +59,27 @@ describe('MultiFilters', () => {
it('it should add the new item and trigger onChange', async () => {
const filters = {};
const onChange = jest.fn();
const { container } = render(<MultiFilter filters={filters} onChange={onChange} />);
render(<MultiFilter filters={filters} onChange={onChange} />);
const label = await screen.findByLabelText('Add');
userEvent.click(label);
const filterItemElement = screen.getByTestId('cloudwatch-multifilter-item');
expect(filterItemElement).toBeInTheDocument();
const keyElement = container.querySelector('#cloudwatch-multifilter-item-key');
const keyElement = screen.getByTestId('cloudwatch-multifilter-item-key');
expect(keyElement).toBeInTheDocument();
userEvent.type(keyElement!, 'my-key');
fireEvent.keyDown(keyElement!, { keyCode: 13 });
fireEvent.blur(keyElement!);
expect(within(filterItemElement).getByDisplayValue('my-key')).toBeInTheDocument();
expect(onChange).not.toHaveBeenCalled();
const valueElement = container.querySelector('#cloudwatch-multifilter-item-value');
const valueElement = screen.getByTestId('cloudwatch-multifilter-item-value');
expect(valueElement).toBeInTheDocument();
userEvent.type(valueElement!, 'my-value');
fireEvent.keyDown(valueElement!, { keyCode: 13 });
expect(within(filterItemElement).getByText('my-value')).toBeInTheDocument();
userEvent.type(valueElement!, 'my-value1,my-value2');
fireEvent.blur(valueElement!);
expect(within(filterItemElement).getByDisplayValue('my-value1,my-value2')).toBeInTheDocument();
expect(onChange).toHaveBeenCalledWith({
'my-key': ['my-value'],
'my-key': ['my-value1', 'my-value2'],
});
});
});
@@ -15,31 +15,22 @@ export interface MultiFilterCondition {
value?: string[];
}
const multiFiltersToFilterConditions = (filters: MultiFilters | undefined) =>
Object.entries(filters ?? {}).reduce<MultiFilterCondition[]>((acc, [key, value]) => {
if (value && typeof value === 'object') {
const filter = {
key,
value,
operator: '=',
};
return [...acc, filter];
}
return acc;
}, []);
const multiFiltersToFilterConditions = (filters: MultiFilters) =>
Object.keys(filters).map((key) => ({ key, value: filters[key], operator: '=' }));
const filterConditionsToMultiFilters = (filters: MultiFilterCondition[]) => {
return filters.reduce<MultiFilters>((acc, { key, value }) => {
const res: MultiFilters = {};
filters.forEach(({ key, value }) => {
if (key && value) {
return { ...acc, [key]: value };
res[key] = value;
}
return acc;
}, {});
});
return res;
};
export const MultiFilter: React.FC<Props> = ({ filters, onChange }) => {
const [items, setItems] = useState<MultiFilterCondition[]>([]);
useEffect(() => setItems(multiFiltersToFilterConditions(filters)), [filters]);
useEffect(() => setItems(filters ? multiFiltersToFilterConditions(filters) : []), [filters]);
const onFiltersChange = (newItems: Array<Partial<MultiFilterCondition>>) => {
setItems(newItems);
@@ -1,7 +1,7 @@
import { css, cx } from '@emotion/css';
import { GrafanaTheme2, toOption } from '@grafana/data';
import { GrafanaTheme2 } from '@grafana/data';
import { AccessoryButton, InputGroup } from '@grafana/experimental';
import { Input, MultiSelect, stylesFactory, useTheme2 } from '@grafana/ui';
import { Input, stylesFactory, useTheme2 } from '@grafana/ui';
import React, { FunctionComponent, useState } from 'react';
import { MultiFilterCondition } from './MultiFilter';
@@ -13,27 +13,16 @@ export interface Props {
export const MultiFilterItem: FunctionComponent<Props> = ({ filter, onChange, onDelete }) => {
const [localKey, setLocalKey] = useState(filter.key || '');
const [inputValue, setInputValue] = useState('');
const [localValue, setLocalValue] = useState(filter.value?.join(', ') || '');
const theme = useTheme2();
const styles = getOperatorStyles(theme);
const handleInputChange = (inputValue: string) => {
setInputValue(inputValue);
};
const handleKeyDown = (keyEvent: React.KeyboardEvent) => {
if (['Tab', 'Enter'].includes(keyEvent.key)) {
onChange({ ...filter, value: filter.value ? [...filter.value, inputValue] : [inputValue] });
setInputValue('');
keyEvent.preventDefault();
}
};
return (
<div data-testid="cloudwatch-multifilter-item">
<InputGroup>
<Input
data-testid="cloudwatch-multifilter-item-key"
aria-label="Filter key"
id="cloudwatch-multifilter-item-key"
value={localKey}
placeholder="key"
onChange={(e) => setLocalKey(e.currentTarget.value)}
@@ -46,22 +35,20 @@ export const MultiFilterItem: FunctionComponent<Props> = ({ filter, onChange, on
<span className={cx(styles.root)}>=</span>
<MultiSelect
<Input
data-testid="cloudwatch-multifilter-item-value"
aria-label="Filter value"
value={filter.value ? filter.value.map(toOption) : []}
inputId="cloudwatch-multifilter-item-value"
inputValue={inputValue}
allowCustomValue
onChange={(items) => {
onChange({ ...filter, value: items.map((item) => item.value ?? '') });
value={localValue}
placeholder="value1, value2,..."
onChange={(e) => setLocalValue(e.currentTarget.value)}
onBlur={() => {
const newValues = localValue.replace(' ', '').split(',');
if (localValue && newValues !== filter.value) {
onChange({ ...filter, value: newValues });
}
}}
onInputChange={handleInputChange}
onKeyDown={handleKeyDown}
isOpen={false}
hideIndicator
options={filter.value ? filter.value.map(toOption) : []}
placeholder="value"
/>
<AccessoryButton aria-label="remove" icon="times" variant="secondary" onClick={onDelete} type="button" />
</InputGroup>
</div>
@@ -179,19 +179,18 @@ describe('VariableEditor', () => {
render(<VariableQueryEditor {...props} />);
await waitFor(() => {
expect(screen.getByText('Tags.blah')).toBeInTheDocument();
expect(screen.getByDisplayValue('Tags.blah')).toBeInTheDocument();
});
const filterItem = screen.getByTestId('cloudwatch-multifilter-item');
expect(filterItem).toBeInTheDocument();
expect(within(filterItem).getByText('foo')).toBeInTheDocument();
expect(within(filterItem).getByText('bar')).toBeInTheDocument();
expect(within(filterItem).getByDisplayValue('foo, bar')).toBeInTheDocument();
// set filter value
const valueElement = filterItem.querySelector('#cloudwatch-multifilter-item-value');
const valueElement = screen.getByTestId('cloudwatch-multifilter-item-value');
expect(valueElement).toBeInTheDocument();
userEvent.type(valueElement!, 'baz');
fireEvent.keyDown(valueElement!, { keyCode: 13 });
userEvent.type(valueElement!, ',baz');
fireEvent.blur(valueElement!);
expect(onChange).toHaveBeenCalledWith({
...defaultQuery,
@@ -1,7 +1,6 @@
import { union } from 'lodash';
import React from 'react';
import { QueryEditorProps, SelectableValue, toOption } from '@grafana/data';
import { QueryEditorProps, SelectableValue } from '@grafana/data';
import { InlineField } from '@grafana/ui';
import { Dimensions } from '..';
@@ -162,21 +161,17 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
)}
{parsedQuery.queryType === VariableQueryType.EC2InstanceAttributes && (
<>
<VariableQueryField
<VariableTextField
value={parsedQuery.attributeName}
options={
parsedQuery.attributeName ? union(ec2Attributes, [toOption(parsedQuery.attributeName)]) : ec2Attributes
}
onChange={(value: string) => onQueryChange({ ...parsedQuery, attributeName: value })}
placeholder="attribute name"
onBlur={(value: string) => onQueryChange({ ...parsedQuery, attributeName: value })}
label="Attribute Name"
inputId={`variable-query-attribute-name-${query.refId}`}
allowCustomValue
tooltip='Create a "Tags.<name>" value to select a tag'
tooltip='Attribute or tag to query on. Tags should be formatted "Tags.<name>".'
/>
<InlineField
label="Filters"
labelWidth={20}
tooltip='Pre-defined ec2:DescribeInstances filters and tags to filter the returned values on. Tags should be formatted "tag:<name>" '
tooltip='Pre-defined ec2:DescribeInstances filters/tags and the values to filter on. Tags should be formatted "tag:<name>" '
>
<MultiFilter
filters={parsedQuery.ec2Filters}
@@ -208,34 +203,3 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
</>
);
};
const ec2Attributes = [
'AmiLaunchIndex',
'Architecture',
'ClientToken',
'EbsOptimized',
'EnaSupport',
'Hypervisor',
'IamInstanceProfile',
'ImageId',
'InstanceId',
'InstanceLifecycle',
'InstanceType',
'KernelId',
'KeyName',
'LaunchTime',
'Platform',
'PrivateDnsName',
'PrivateIpAddress',
'PublicDnsName',
'PublicIpAddress',
'RamdiskId',
'RootDeviceName',
'RootDeviceType',
'SourceDestCheck',
'SpotInstanceRequestId',
'SriovNetSupport',
'SubnetId',
'VirtualizationType',
'VpcId',
].map(toOption);
@@ -15,7 +15,6 @@ interface VariableQueryFieldProps<T> {
inputId?: string;
allowCustomValue?: boolean;
isLoading?: boolean;
tooltip?: string;
}
export const VariableQueryField = <T extends string | VariableQueryType>({
@@ -26,10 +25,9 @@ export const VariableQueryField = <T extends string | VariableQueryType>({
allowCustomValue = false,
isLoading = false,
inputId = label,
tooltip,
}: VariableQueryFieldProps<T>) => {
return (
<InlineField label={label} labelWidth={LABEL_WIDTH} htmlFor={inputId} tooltip={tooltip}>
<InlineField label={label} labelWidth={LABEL_WIDTH} htmlFor={inputId}>
<Select
menuShouldPortal
aria-label={label}
@@ -88,6 +88,7 @@ export function migrateVariableQuery(rawQuery: string | VariableQuery): Variable
resourceType: '',
tags: {},
};
if (rawQuery === '') {
return newQuery;
}
@@ -147,7 +148,7 @@ export function migrateVariableQuery(rawQuery: string | VariableQuery): Variable
newQuery.queryType = VariableQueryType.EC2InstanceAttributes;
newQuery.region = ec2InstanceAttributeQuery[1];
newQuery.attributeName = ec2InstanceAttributeQuery[2];
if (!!ec2InstanceAttributeQuery[3]) {
if (ec2InstanceAttributeQuery[3]) {
try {
newQuery.ec2Filters = JSON.parse(ec2InstanceAttributeQuery[3]);
} catch {
@@ -162,7 +163,7 @@ export function migrateVariableQuery(rawQuery: string | VariableQuery): Variable
newQuery.queryType = VariableQueryType.ResourceArns;
newQuery.region = resourceARNsQuery[1];
newQuery.resourceType = resourceARNsQuery[2];
if (!!resourceARNsQuery[3]) {
if (resourceARNsQuery[3]) {
try {
newQuery.tags = JSON.parse(resourceARNsQuery[3]);
} catch {