remove multi-text-select object
This commit is contained in:
@@ -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;
|
||||
|
||||
+13
-14
@@ -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'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+8
-17
@@ -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);
|
||||
|
||||
|
||||
+15
-28
@@ -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>
|
||||
|
||||
+5
-6
@@ -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,
|
||||
|
||||
+6
-42
@@ -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);
|
||||
|
||||
+1
-3
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user