AzureMonitor: add NewDimension component using experimental UI (#48946)

* AzureMonitor: add NewDimension component using experimental UI

This new component is exercised by the same unit test file as the current Dimension component.

Also cleans up a few unneeded `await` keywords in the Dimensions test file.

* AzureMonitor: make tweaks based on PR comments.

- I was importing the wrong Field component
- We can use a typeguard to avoid the strange `if`.
This commit is contained in:
Adam Simpson 2022-06-03 02:35:53 +00:00 committed by GitHub
parent 52ed651958
commit 53cb94a2ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 557 additions and 330 deletions

View File

@ -10,6 +10,7 @@ import createMockPanelData from '../../__mocks__/panelData';
import createMockQuery from '../../__mocks__/query'; import createMockQuery from '../../__mocks__/query';
import DimensionFields from './DimensionFields'; import DimensionFields from './DimensionFields';
import NewDimensionFields from './NewDimensionFields';
import { appendDimensionFilter, setDimensionFilterValue } from './setQueryValue'; import { appendDimensionFilter, setDimensionFilterValue } from './setQueryValue';
const variableOptionGroup = { const variableOptionGroup = {
@ -18,342 +19,365 @@ const variableOptionGroup = {
}; };
const user = userEvent.setup(); const user = userEvent.setup();
describe('Azure Monitor QueryEditor', () => { const tests = [
const mockDatasource = createMockDatasource(); {
component: DimensionFields,
label: 'Dimension Fields',
addDimension: async () => {
const addDimension = await screen.findByText('Add new dimension');
await user.click(addDimension);
},
},
{
component: NewDimensionFields,
label: 'Dimension Fields experimental UI',
addDimension: async () => {
const addDimension = await screen.findByLabelText('Add');
await user.click(addDimension);
},
},
];
it('should render a dimension filter', async () => { for (const t of tests) {
let mockQuery = createMockQuery(); describe(`Azure Monitor QueryEditor: ${t.label}`, () => {
const mockPanelData = createMockPanelData(); const mockDatasource = createMockDatasource();
const onQueryChange = jest.fn();
const dimensionOptions = [ it('should render a dimension filter', async () => {
{ label: 'Test Dimension 1', value: 'TestDimension1' }, let mockQuery = createMockQuery();
{ label: 'Test Dimension 2', value: 'TestDimension2' }, const mockPanelData = createMockPanelData();
]; const onQueryChange = jest.fn();
const { rerender } = render( const dimensionOptions = [
<DimensionFields { label: 'Test Dimension 1', value: 'TestDimension1' },
data={mockPanelData} { label: 'Test Dimension 2', value: 'TestDimension2' },
subscriptionId="123" ];
query={mockQuery} const { rerender } = render(
onQueryChange={onQueryChange} <t.component
datasource={mockDatasource} data={mockPanelData}
variableOptionGroup={variableOptionGroup} subscriptionId="123"
setError={() => {}} query={mockQuery}
dimensionOptions={dimensionOptions} onQueryChange={onQueryChange}
/> datasource={mockDatasource}
); variableOptionGroup={variableOptionGroup}
const addDimension = await screen.findByText('Add new dimension'); setError={() => {}}
await user.click(addDimension); dimensionOptions={dimensionOptions}
mockQuery = appendDimensionFilter(mockQuery); />
expect(onQueryChange).toHaveBeenCalledWith({ );
...mockQuery,
azureMonitor: { await t.addDimension();
...mockQuery.azureMonitor,
dimensionFilters: [{ dimension: '', operator: 'eq', filters: [] }], mockQuery = appendDimensionFilter(mockQuery);
}, expect(onQueryChange).toHaveBeenCalledWith({
...mockQuery,
azureMonitor: {
...mockQuery.azureMonitor,
dimensionFilters: [{ dimension: '', operator: 'eq', filters: [] }],
},
});
rerender(
<t.component
data={mockPanelData}
subscriptionId="123"
query={mockQuery}
onQueryChange={onQueryChange}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
setError={() => {}}
dimensionOptions={dimensionOptions}
/>
);
const dimensionSelect = await screen.findByText('Field');
await selectOptionInTest(dimensionSelect, 'Test Dimension 1');
expect(onQueryChange).toHaveBeenCalledWith({
...mockQuery,
azureMonitor: {
...mockQuery.azureMonitor,
dimensionFilters: [{ dimension: 'TestDimension1', operator: 'eq', filters: [] }],
},
});
expect(screen.queryByText('Test Dimension 1')).toBeInTheDocument();
expect(screen.queryByText('==')).toBeInTheDocument();
}); });
rerender(
<DimensionFields it('correctly filters out dimensions when selected', async () => {
data={mockPanelData} let mockQuery = createMockQuery();
subscriptionId="123" const mockPanelData = createMockPanelData();
query={mockQuery} mockQuery.azureMonitor = {
onQueryChange={onQueryChange}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
setError={() => {}}
dimensionOptions={dimensionOptions}
/>
);
const dimensionSelect = await screen.findByText('Field');
await selectOptionInTest(dimensionSelect, 'Test Dimension 1');
expect(onQueryChange).toHaveBeenCalledWith({
...mockQuery,
azureMonitor: {
...mockQuery.azureMonitor, ...mockQuery.azureMonitor,
dimensionFilters: [{ dimension: 'TestDimension1', operator: 'eq', filters: [] }], dimensionFilters: [{ dimension: 'TestDimension1', operator: 'eq', filters: [] }],
}, };
const onQueryChange = jest.fn();
const dimensionOptions = [
{ label: 'Test Dimension 1', value: 'TestDimension1' },
{ label: 'Test Dimension 2', value: 'TestDimension2' },
];
const { rerender } = render(
<t.component
data={mockPanelData}
subscriptionId="123"
query={mockQuery}
onQueryChange={onQueryChange}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
setError={() => {}}
dimensionOptions={dimensionOptions}
/>
);
await t.addDimension();
mockQuery = appendDimensionFilter(mockQuery);
rerender(
<t.component
data={mockPanelData}
subscriptionId="123"
query={mockQuery}
onQueryChange={onQueryChange}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
setError={() => {}}
dimensionOptions={dimensionOptions}
/>
);
const dimensionSelect = await screen.findByText('Field');
await user.click(dimensionSelect);
const options = await screen.findAllByLabelText('Select option');
expect(options).toHaveLength(1);
expect(options[0]).toHaveTextContent('Test Dimension 2');
}); });
expect(screen.queryByText('Test Dimension 1')).toBeInTheDocument();
expect(screen.queryByText('==')).toBeInTheDocument();
});
it('correctly filters out dimensions when selected', async () => { it('correctly displays dimension labels', async () => {
let mockQuery = createMockQuery(); let mockQuery = createMockQuery();
const mockPanelData = createMockPanelData(); const mockPanelData = createMockPanelData();
mockQuery.azureMonitor = { mockQuery.azureMonitor = {
...mockQuery.azureMonitor,
dimensionFilters: [{ dimension: 'TestDimension1', operator: 'eq', filters: [] }],
};
const onQueryChange = jest.fn();
const dimensionOptions = [
{ label: 'Test Dimension 1', value: 'TestDimension1' },
{ label: 'Test Dimension 2', value: 'TestDimension2' },
];
const { rerender } = render(
<DimensionFields
data={mockPanelData}
subscriptionId="123"
query={mockQuery}
onQueryChange={onQueryChange}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
setError={() => {}}
dimensionOptions={dimensionOptions}
/>
);
const addDimension = await screen.findByText('Add new dimension');
await user.click(addDimension);
mockQuery = appendDimensionFilter(mockQuery);
rerender(
<DimensionFields
data={mockPanelData}
subscriptionId="123"
query={mockQuery}
onQueryChange={onQueryChange}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
setError={() => {}}
dimensionOptions={dimensionOptions}
/>
);
const dimensionSelect = await screen.findByText('Field');
await user.click(dimensionSelect);
const options = await screen.findAllByLabelText('Select option');
expect(options).toHaveLength(1);
expect(options[0]).toHaveTextContent('Test Dimension 2');
});
it('correctly displays dimension labels', async () => {
let mockQuery = createMockQuery();
const mockPanelData = createMockPanelData();
mockQuery.azureMonitor = {
...mockQuery.azureMonitor,
dimensionFilters: [{ dimension: 'TestDimension1', operator: 'eq', filters: [] }],
};
mockPanelData.series = [
{
...mockPanelData.series[0],
fields: [
{
...mockPanelData.series[0].fields[0],
name: 'Test Dimension 1',
labels: { testdimension1: 'testlabel' },
},
],
},
];
const onQueryChange = jest.fn();
const dimensionOptions = [{ label: 'Test Dimension 1', value: 'TestDimension1' }];
render(
<DimensionFields
data={mockPanelData}
subscriptionId="123"
query={mockQuery}
onQueryChange={onQueryChange}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
setError={() => {}}
dimensionOptions={dimensionOptions}
/>
);
const labelSelect = await screen.findByText('Select value(s)');
await user.click(labelSelect);
const options = await screen.findAllByLabelText('Select option');
expect(options).toHaveLength(1);
expect(options[0]).toHaveTextContent('testlabel');
});
it('correctly updates dimension labels', async () => {
let mockQuery = createMockQuery();
const mockPanelData = createMockPanelData();
mockQuery.azureMonitor = {
...mockQuery.azureMonitor,
dimensionFilters: [{ dimension: 'TestDimension1', operator: 'eq', filters: ['testlabel'] }],
};
mockPanelData.series = [
{
...mockPanelData.series[0],
fields: [
{
...mockPanelData.series[0].fields[0],
name: 'Test Dimension 1',
labels: { testdimension1: 'testlabel' },
},
],
},
];
const onQueryChange = jest.fn();
const dimensionOptions = [{ label: 'Test Dimension 1', value: 'TestDimension1' }];
const { rerender } = render(
<DimensionFields
data={mockPanelData}
subscriptionId="123"
query={mockQuery}
onQueryChange={onQueryChange}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
setError={() => {}}
dimensionOptions={dimensionOptions}
/>
);
await screen.findByText('testlabel');
const labelClear = await screen.findByLabelText('Remove testlabel');
await user.click(labelClear);
mockQuery = setDimensionFilterValue(mockQuery, 0, 'filters', []);
expect(onQueryChange).toHaveBeenCalledWith({
...mockQuery,
azureMonitor: {
...mockQuery.azureMonitor, ...mockQuery.azureMonitor,
dimensionFilters: [{ dimension: 'TestDimension1', operator: 'eq', filters: [] }], dimensionFilters: [{ dimension: 'TestDimension1', operator: 'eq', filters: [] }],
}, };
});
mockPanelData.series = [
...mockPanelData.series,
{
...mockPanelData.series[0],
fields: [
{
...mockPanelData.series[0].fields[0],
name: 'Test Dimension 1',
labels: { testdimension1: 'testlabel2' },
},
],
},
];
rerender(
<DimensionFields
data={mockPanelData}
subscriptionId="123"
query={mockQuery}
onQueryChange={onQueryChange}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
setError={() => {}}
dimensionOptions={dimensionOptions}
/>
);
const labelSelect = await screen.getByLabelText('dimension-labels-select');
await openMenu(labelSelect);
const options = await screen.findAllByLabelText('Select option');
expect(options).toHaveLength(2);
expect(options[0]).toHaveTextContent('testlabel');
expect(options[1]).toHaveTextContent('testlabel2');
});
it('correctly selects multiple dimension labels', async () => { mockPanelData.series = [
let mockQuery = createMockQuery(); {
const mockPanelData = createMockPanelData(); ...mockPanelData.series[0],
mockPanelData.series = [ fields: [
{ {
...mockPanelData.series[0], ...mockPanelData.series[0].fields[0],
fields: [ name: 'Test Dimension 1',
{ labels: { testdimension1: 'testlabel' },
...mockPanelData.series[0].fields[0], },
name: 'Test Dimension 1', ],
labels: { testdimension1: 'testlabel' }, },
}, ];
], const onQueryChange = jest.fn();
}, const dimensionOptions = [{ label: 'Test Dimension 1', value: 'TestDimension1' }];
{ render(
...mockPanelData.series[0], <t.component
fields: [ data={mockPanelData}
{ subscriptionId="123"
...mockPanelData.series[0].fields[0], query={mockQuery}
name: 'Test Dimension 1', onQueryChange={onQueryChange}
labels: { testdimension1: 'testlabel2' }, datasource={mockDatasource}
}, variableOptionGroup={variableOptionGroup}
], setError={() => {}}
}, dimensionOptions={dimensionOptions}
]; />
const onQueryChange = jest.fn(); );
const dimensionOptions = [{ label: 'Test Dimension 1', value: 'TestDimension1' }]; const labelSelect = await screen.findByText('Select value(s)');
mockQuery = appendDimensionFilter(mockQuery, 'TestDimension1'); await user.click(labelSelect);
const { rerender } = render( const options = await screen.findAllByLabelText('Select option');
<DimensionFields expect(options).toHaveLength(1);
data={mockPanelData} expect(options[0]).toHaveTextContent('testlabel');
subscriptionId="123" });
query={mockQuery}
onQueryChange={onQueryChange} it('correctly updates dimension labels', async () => {
datasource={mockDatasource} let mockQuery = createMockQuery();
variableOptionGroup={variableOptionGroup} const mockPanelData = createMockPanelData();
setError={() => {}} mockQuery.azureMonitor = {
dimensionOptions={dimensionOptions}
/>
);
const labelSelect = await screen.getByLabelText('dimension-labels-select');
await user.click(labelSelect);
await openMenu(labelSelect);
await screen.getByText('testlabel');
await screen.getByText('testlabel2');
await selectOptionInTest(labelSelect, 'testlabel');
mockQuery = setDimensionFilterValue(mockQuery, 0, 'filters', ['testlabel']);
expect(onQueryChange).toHaveBeenCalledWith({
...mockQuery,
azureMonitor: {
...mockQuery.azureMonitor, ...mockQuery.azureMonitor,
dimensionFilters: [{ dimension: 'TestDimension1', operator: 'eq', filters: ['testlabel'] }], dimensionFilters: [{ dimension: 'TestDimension1', operator: 'eq', filters: ['testlabel'] }],
}, };
mockPanelData.series = [
{
...mockPanelData.series[0],
fields: [
{
...mockPanelData.series[0].fields[0],
name: 'Test Dimension 1',
labels: { testdimension1: 'testlabel' },
},
],
},
];
const onQueryChange = jest.fn();
const dimensionOptions = [{ label: 'Test Dimension 1', value: 'TestDimension1' }];
const { rerender } = render(
<t.component
data={mockPanelData}
subscriptionId="123"
query={mockQuery}
onQueryChange={onQueryChange}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
setError={() => {}}
dimensionOptions={dimensionOptions}
/>
);
await screen.findByText('testlabel');
const labelClear = await screen.findByLabelText('Remove testlabel');
await user.click(labelClear);
mockQuery = setDimensionFilterValue(mockQuery, 0, 'filters', []);
expect(onQueryChange).toHaveBeenCalledWith({
...mockQuery,
azureMonitor: {
...mockQuery.azureMonitor,
dimensionFilters: [{ dimension: 'TestDimension1', operator: 'eq', filters: [] }],
},
});
mockPanelData.series = [
...mockPanelData.series,
{
...mockPanelData.series[0],
fields: [
{
...mockPanelData.series[0].fields[0],
name: 'Test Dimension 1',
labels: { testdimension1: 'testlabel2' },
},
],
},
];
rerender(
<t.component
data={mockPanelData}
subscriptionId="123"
query={mockQuery}
onQueryChange={onQueryChange}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
setError={() => {}}
dimensionOptions={dimensionOptions}
/>
);
const labelSelect = screen.getByLabelText('dimension-labels-select');
await openMenu(labelSelect);
const options = await screen.findAllByLabelText('Select option');
expect(options).toHaveLength(2);
expect(options[0]).toHaveTextContent('testlabel');
expect(options[1]).toHaveTextContent('testlabel2');
}); });
mockPanelData.series = [
{ it('correctly selects multiple dimension labels', async () => {
...mockPanelData.series[0], let mockQuery = createMockQuery();
fields: [ const mockPanelData = createMockPanelData();
{ mockPanelData.series = [
...mockPanelData.series[0].fields[0], {
name: 'Test Dimension 1', ...mockPanelData.series[0],
labels: { testdimension1: 'testlabel' }, fields: [
}, {
], ...mockPanelData.series[0].fields[0],
}, name: 'Test Dimension 1',
]; labels: { testdimension1: 'testlabel' },
rerender( },
<DimensionFields ],
data={mockPanelData} },
subscriptionId="123" {
query={mockQuery} ...mockPanelData.series[0],
onQueryChange={onQueryChange} fields: [
datasource={mockDatasource} {
variableOptionGroup={variableOptionGroup} ...mockPanelData.series[0].fields[0],
setError={() => {}} name: 'Test Dimension 1',
dimensionOptions={dimensionOptions} labels: { testdimension1: 'testlabel2' },
/> },
); ],
const labelSelect2 = await screen.getByLabelText('dimension-labels-select'); },
await openMenu(labelSelect2); ];
const refreshedOptions = await screen.findAllByLabelText('Select options menu'); const onQueryChange = jest.fn();
expect(refreshedOptions).toHaveLength(1); const dimensionOptions = [{ label: 'Test Dimension 1', value: 'TestDimension1' }];
expect(refreshedOptions[0]).toHaveTextContent('testlabel2'); mockQuery = appendDimensionFilter(mockQuery, 'TestDimension1');
await selectOptionInTest(labelSelect2, 'testlabel2'); const { rerender } = render(
mockQuery = setDimensionFilterValue(mockQuery, 0, 'filters', ['testlabel', 'testlabel2']); <t.component
expect(onQueryChange).toHaveBeenCalledWith({ data={mockPanelData}
...mockQuery, subscriptionId="123"
azureMonitor: { query={mockQuery}
...mockQuery.azureMonitor, onQueryChange={onQueryChange}
dimensionFilters: [{ dimension: 'TestDimension1', operator: 'eq', filters: ['testlabel', 'testlabel2'] }], datasource={mockDatasource}
}, variableOptionGroup={variableOptionGroup}
setError={() => {}}
dimensionOptions={dimensionOptions}
/>
);
const labelSelect = screen.getByLabelText('dimension-labels-select');
await user.click(labelSelect);
await openMenu(labelSelect);
screen.getByText('testlabel');
screen.getByText('testlabel2');
await selectOptionInTest(labelSelect, 'testlabel');
mockQuery = setDimensionFilterValue(mockQuery, 0, 'filters', ['testlabel']);
expect(onQueryChange).toHaveBeenCalledWith({
...mockQuery,
azureMonitor: {
...mockQuery.azureMonitor,
dimensionFilters: [{ dimension: 'TestDimension1', operator: 'eq', filters: ['testlabel'] }],
},
});
mockPanelData.series = [
{
...mockPanelData.series[0],
fields: [
{
...mockPanelData.series[0].fields[0],
name: 'Test Dimension 1',
labels: { testdimension1: 'testlabel' },
},
],
},
];
rerender(
<t.component
data={mockPanelData}
subscriptionId="123"
query={mockQuery}
onQueryChange={onQueryChange}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
setError={() => {}}
dimensionOptions={dimensionOptions}
/>
);
const labelSelect2 = screen.getByLabelText('dimension-labels-select');
await openMenu(labelSelect2);
const refreshedOptions = await screen.findAllByLabelText('Select options menu');
expect(refreshedOptions).toHaveLength(1);
expect(refreshedOptions[0]).toHaveTextContent('testlabel2');
await selectOptionInTest(labelSelect2, 'testlabel2');
mockQuery = setDimensionFilterValue(mockQuery, 0, 'filters', ['testlabel', 'testlabel2']);
expect(onQueryChange).toHaveBeenCalledWith({
...mockQuery,
azureMonitor: {
...mockQuery.azureMonitor,
dimensionFilters: [{ dimension: 'TestDimension1', operator: 'eq', filters: ['testlabel', 'testlabel2'] }],
},
});
mockPanelData.series = [
{
...mockPanelData.series[0],
fields: [
{
...mockPanelData.series[0].fields[0],
name: 'Test Dimension 1',
labels: { testdimension1: 'testlabel' },
},
],
},
{
...mockPanelData.series[0],
fields: [
{
...mockPanelData.series[0].fields[0],
name: 'Test Dimension 1',
labels: { testdimension1: 'testlabel2' },
},
],
},
];
}); });
mockPanelData.series = [
{
...mockPanelData.series[0],
fields: [
{
...mockPanelData.series[0].fields[0],
name: 'Test Dimension 1',
labels: { testdimension1: 'testlabel' },
},
],
},
{
...mockPanelData.series[0],
fields: [
{
...mockPanelData.series[0].fields[0],
name: 'Test Dimension 1',
labels: { testdimension1: 'testlabel2' },
},
],
},
];
}); });
}); }

View File

@ -0,0 +1,206 @@
import React, { useEffect, useMemo, useState } from 'react';
import { SelectableValue, DataFrame, PanelData, Labels } from '@grafana/data';
import { AccessoryButton, EditorList } from '@grafana/experimental';
import { Select, HorizontalGroup, MultiSelect } from '@grafana/ui';
import { AzureMetricDimension, AzureMonitorOption, AzureMonitorQuery, AzureQueryEditorFieldProps } from '../../types';
import { Field } from '../Field';
import { setDimensionFilters } from './setQueryValue';
interface DimensionFieldsProps extends AzureQueryEditorFieldProps {
dimensionOptions: AzureMonitorOption[];
}
interface DimensionLabels {
[key: string]: Set<string>;
}
const useDimensionLabels = (data: PanelData | undefined, query: AzureMonitorQuery) => {
const [dimensionLabels, setDimensionLabels] = useState<DimensionLabels>({});
useEffect(() => {
let labelsObj: DimensionLabels = {};
if (data?.series?.length) {
// Identify which series' in the dataframe are relevant to the current query
const series: DataFrame[] = data.series.flat().filter((series) => series.refId === query.refId);
const fields = series.flatMap((series) => series.fields);
// Retrieve labels for series fields
const labels = fields
.map((fields) => fields.labels)
.flat()
.filter((item): item is Labels => item !== null && item !== undefined);
for (const label of labels) {
// Labels only exist for series that have a dimension selected
for (const [dimension, value] of Object.entries(label)) {
if (labelsObj[dimension]) {
labelsObj[dimension].add(value);
} else {
labelsObj[dimension] = new Set([value]);
}
}
}
}
setDimensionLabels((prevLabels) => {
const newLabels: DimensionLabels = {};
const currentLabels = Object.keys(labelsObj);
if (currentLabels.length === 0) {
return prevLabels;
}
for (const label of currentLabels) {
if (prevLabels[label] && labelsObj[label].size < prevLabels[label].size) {
newLabels[label] = prevLabels[label];
} else {
newLabels[label] = labelsObj[label];
}
}
return newLabels;
});
}, [data?.series, query.refId]);
return dimensionLabels;
};
const NewDimensionFields: React.FC<DimensionFieldsProps> = ({ data, query, dimensionOptions, onQueryChange }) => {
const dimensionFilters = useMemo(
() => query.azureMonitor?.dimensionFilters ?? [],
[query.azureMonitor?.dimensionFilters]
);
const dimensionLabels = useDimensionLabels(data, query);
const dimensionOperators: Array<SelectableValue<string>> = [
{ label: '==', value: 'eq' },
{ label: '!=', value: 'ne' },
{ label: 'starts with', value: 'sw' },
];
const validDimensionOptions = useMemo(() => {
// We filter out any dimensions that have already been used in a filter as the API doesn't support having multiple filters with the same dimension name.
// The Azure portal also doesn't support this feature so it makes sense for consistency.
let t = dimensionOptions;
if (dimensionFilters.length) {
t = dimensionOptions.filter(
(val) => !dimensionFilters.some((dimensionFilter) => dimensionFilter.dimension === val.value)
);
}
return t;
}, [dimensionFilters, dimensionOptions]);
const onFieldChange = <Key extends keyof AzureMetricDimension>(
fieldName: Key,
item: Partial<AzureMetricDimension>,
value: AzureMetricDimension[Key],
onChange: (item: Partial<AzureMetricDimension>) => void
) => {
item[fieldName] = value;
onChange(item);
};
const getValidDimensionOptions = (selectedDimension: string) => {
return validDimensionOptions.concat(dimensionOptions.filter((item) => item.value === selectedDimension));
};
const getValidFilterOptions = (selectedFilter: string | undefined, dimension: string) => {
const dimensionFilters = Array.from(dimensionLabels[dimension.toLowerCase()] ?? []);
if (dimensionFilters.find((filter) => filter === selectedFilter)) {
return dimensionFilters.map((filter) => ({ value: filter, label: filter }));
}
return [...dimensionFilters, ...(selectedFilter && selectedFilter !== '*' ? [selectedFilter] : [])].map((item) => ({
value: item,
label: item,
}));
};
const getValidMultiSelectOptions = (selectedFilters: string[] | undefined, dimension: string) => {
const labelOptions = getValidFilterOptions(undefined, dimension);
if (selectedFilters) {
for (const filter of selectedFilters) {
if (!labelOptions.find((label) => label.value === filter)) {
labelOptions.push({ value: filter, label: filter });
}
}
}
return labelOptions;
};
const getValidOperators = (selectedOperator: string) => {
if (dimensionOperators.find((operator: SelectableValue) => operator.value === selectedOperator)) {
return dimensionOperators;
}
return [...dimensionOperators, ...(selectedOperator ? [{ label: selectedOperator, value: selectedOperator }] : [])];
};
const changedFunc = (changed: Array<Partial<AzureMetricDimension>>) => {
const properData: AzureMetricDimension[] = changed.map((x) => {
return {
dimension: x.dimension ?? '',
operator: x.operator ?? 'eq',
filters: x.filters ?? [],
};
});
onQueryChange(setDimensionFilters(query, properData));
};
const renderFilters = (
item: Partial<AzureMetricDimension>,
onChange: (item: Partial<AzureMetricDimension>) => void,
onDelete: () => void
) => {
return (
<HorizontalGroup spacing="none">
<Select
menuShouldPortal
placeholder="Field"
value={item.dimension}
options={getValidDimensionOptions(item.dimension || '')}
onChange={(e) => onFieldChange('dimension', item, e.value ?? '', onChange)}
/>
<Select
menuShouldPortal
placeholder="Operation"
value={item.operator}
options={getValidOperators(item.operator || 'eq')}
onChange={(e) => onFieldChange('operator', item, e.value ?? '', onChange)}
allowCustomValue
/>
{item.operator === 'eq' || item.operator === 'ne' ? (
<MultiSelect
menuShouldPortal
placeholder="Select value(s)"
value={item.filters}
options={getValidMultiSelectOptions(item.filters, item.dimension ?? '')}
onChange={(e) =>
onFieldChange(
'filters',
item,
e.map((x) => x.value ?? ''),
onChange
)
}
aria-label={'dimension-labels-select'}
allowCustomValue
/>
) : (
// The API does not currently allow for multiple "starts with" clauses to be used.
<Select
menuShouldPortal
placeholder="Select value"
value={item.filters ? item.filters[0] : ''}
allowCustomValue
options={getValidFilterOptions(item.filters ? item.filters[0] : '', item.dimension ?? '')}
onChange={(e) => onFieldChange('filters', item, [e?.value ?? ''], onChange)}
isClearable
/>
)}
<AccessoryButton aria-label="Remove" icon="times" variant="secondary" onClick={onDelete} type="button" />
</HorizontalGroup>
);
};
return (
<Field label="Dimensions">
<EditorList items={dimensionFilters} onChange={changedFunc} renderItem={renderFilters} />
</Field>
);
};
export default NewDimensionFields;

View File

@ -13,6 +13,7 @@ import DimensionFields from '../MetricsQueryEditor/DimensionFields';
import LegendFormatField from '../MetricsQueryEditor/LegendFormatField'; import LegendFormatField from '../MetricsQueryEditor/LegendFormatField';
import MetricNameField from '../MetricsQueryEditor/MetricNameField'; import MetricNameField from '../MetricsQueryEditor/MetricNameField';
import MetricNamespaceField from '../MetricsQueryEditor/MetricNamespaceField'; import MetricNamespaceField from '../MetricsQueryEditor/MetricNamespaceField';
import NewDimensionFields from '../MetricsQueryEditor/NewDimensionFields';
import TimeGrainField from '../MetricsQueryEditor/TimeGrainField'; import TimeGrainField from '../MetricsQueryEditor/TimeGrainField';
import TopField from '../MetricsQueryEditor/TopField'; import TopField from '../MetricsQueryEditor/TopField';
import { setResource } from '../MetricsQueryEditor/setQueryValue'; import { setResource } from '../MetricsQueryEditor/setQueryValue';
@ -105,7 +106,7 @@ const MetricsQueryEditor: React.FC<MetricsQueryEditorProps> = ({
</EditorRow> </EditorRow>
<EditorRow> <EditorRow>
<EditorFieldGroup> <EditorFieldGroup>
<DimensionFields <NewDimensionFields
data={data} data={data}
query={query} query={query}
datasource={datasource} datasource={datasource}
@ -125,10 +126,6 @@ const MetricsQueryEditor: React.FC<MetricsQueryEditorProps> = ({
onQueryChange={onChange} onQueryChange={onChange}
setError={setError} setError={setError}
/> />
</EditorFieldGroup>
</EditorRow>
<EditorRow>
<EditorFieldGroup>
<LegendFormatField <LegendFormatField
query={query} query={query}
datasource={datasource} datasource={datasource}