AzureMonitor: Add support for not equals and startsWith operators when creating Azure Metrics dimension filters. (#48077)

* Allow dimension operator selection

- Add dimension operators and function to update the operator in the query
- Add logic to ensure the same dimension cannot be selected multiple times (Azure restriction)
- Add selection component

* Update backend logic to default operation and filter to eq '*'

- This must be done as the ne and sw operators do not work with the wildcard filter

* Add tests on dimension operators

* Correct placement of 'and' when building query

* Add comment and simplify filtering logic

* Allow multiSelect for eq and ne operators

- Pass PanelData to DimensionFields component
- Add logic to retrieve labels from PanelData
- Add MultiSelect component for relevant operators
- Update frontend types to allow filter to be an array of strings
- Update backend types to allow filter to be an array of strings
- Update filter string building

* Improve setting of labels

* Update go tests

* Update frontend tests

- Add panelData mock (to be expanded later)
- Update null check in DimensionFields

* Allow custom value and set default

* Add frontend test and fix lint issues

* Improved handling of options for sw operator

* Remove changes related to multiselect

* Add check on refId to ensure dimension labels are correct for query

* Extract custom hook for setting dimension labels

* Add documentation around Azure Monitor metrics dimensions

* Update MetricQueryEditor tests

- Add missing data prop

* Correctly set field values

* Add additional expect for onQueryChange

* Correctly set operators

- Simplify onFilterInputChange

* Ensure no duplicate filters appear

* Ensure that filters are displayed correctly for saved queries

* Update dimension filter test

* Include additional test around changing dimension labels

* Pass panel data through new metrics query editor
This commit is contained in:
Andreas Christou 2022-04-29 17:01:13 +01:00 committed by GitHub
parent 87ae3e0644
commit d7c16e06d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 488 additions and 14 deletions

View File

@ -82,6 +82,14 @@ The legend label for Metrics can be changed using aliases. In the Legend Format
| `{{ dimensionname }}` | _(Legacy for backwards compatibility)_ Replaced with the name of the first dimension |
| `{{ dimensionvalue }}` | _(Legacy for backwards compatibility)_ Replaced with the value of the first dimension |
#### Dimensions
Some metrics have additional metadata associated - dimensions. Dimensions are represented as key-value pairs assigned to each value of a metric. Grafana allows for the display and filtering of metrics based on dimension values.
Multiple operators are supported (as detailed [here](https://docs.microsoft.com/en-us/rest/api/monitor/metrics/list)) - the `equals`, `not equals`, and `starts with` operators.
Further documentation on multi-dimensional metrics is available [here](https://docs.microsoft.com/en-us/azure/azure-monitor/essentials/data-platform-metrics#multi-dimensional-metrics), and documentation on filtering [here](https://docs.microsoft.com/en-us/azure/azure-monitor/essentials/metrics-charts#filters).
#### Supported Azure Monitor metrics
Not all metrics returned by the Azure Monitor Metrics API have values. To make it easier for you when building a query, the Grafana data source has a list of supported metrics and ignores metrics which will never have values. This list is updated regularly as new services and metrics are added to the Azure cloud. For more information about the list of metrics, refer to [current supported namespaces](https://github.com/grafana/grafana/blob/main/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/supported_namespaces.ts).

View File

@ -129,7 +129,11 @@ func (e *AzureMonitorDatasource) buildQueries(queries []backend.DataQuery, dsInf
dimSB.WriteString(fmt.Sprintf("%s eq '%s'", dimension, dimensionFilter))
} else {
for i, filter := range azJSONModel.DimensionFilters {
dimSB.WriteString(filter.String())
if filter.Operator != "eq" && filter.Filter == "*" {
dimSB.WriteString(fmt.Sprintf("%s eq '*'", filter.Dimension))
} else {
dimSB.WriteString(filter.String())
}
if i != len(azJSONModel.DimensionFilters)-1 {
dimSB.WriteString(" and ")
}

View File

@ -130,6 +130,39 @@ func TestAzureMonitorBuildQueries(t *testing.T) {
expectedInterval: "PT1M",
azureMonitorQueryTarget: "%24filter=blob+eq+%27%2A%27&aggregation=Average&api-version=2018-01-01&interval=PT1M&metricnames=Percentage+CPU&metricnamespace=Microsoft.Compute-virtualMachines&timespan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z",
},
{
name: "has dimensionFilter*s* property with not equals operator",
azureMonitorVariedProperties: map[string]interface{}{
"timeGrain": "PT1M",
"dimensionFilters": []types.AzureMonitorDimensionFilter{{Dimension: "blob", Operator: "ne", Filter: "test"}},
"top": "30",
},
queryInterval: duration,
expectedInterval: "PT1M",
azureMonitorQueryTarget: "%24filter=blob+ne+%27test%27&aggregation=Average&api-version=2018-01-01&interval=PT1M&metricnames=Percentage+CPU&metricnamespace=Microsoft.Compute-virtualMachines&timespan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z&top=30",
},
{
name: "has dimensionFilter*s* property with startsWith operator",
azureMonitorVariedProperties: map[string]interface{}{
"timeGrain": "PT1M",
"dimensionFilters": []types.AzureMonitorDimensionFilter{{Dimension: "blob", Operator: "sw", Filter: "test"}},
"top": "30",
},
queryInterval: duration,
expectedInterval: "PT1M",
azureMonitorQueryTarget: "%24filter=blob+sw+%27test%27&aggregation=Average&api-version=2018-01-01&interval=PT1M&metricnames=Percentage+CPU&metricnamespace=Microsoft.Compute-virtualMachines&timespan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z&top=30",
},
{
name: "correctly sets dimension operator to eq (irrespective of operator) when filter value is '*'",
azureMonitorVariedProperties: map[string]interface{}{
"timeGrain": "PT1M",
"dimensionFilters": []types.AzureMonitorDimensionFilter{{Dimension: "blob", Operator: "sw", Filter: "*"}, {Dimension: "tier", Operator: "ne", Filter: "*"}},
"top": "30",
},
queryInterval: duration,
expectedInterval: "PT1M",
azureMonitorQueryTarget: "%24filter=blob+eq+%27%2A%27+and+tier+eq+%27%2A%27&aggregation=Average&api-version=2018-01-01&interval=PT1M&metricnames=Percentage+CPU&metricnamespace=Microsoft.Compute-virtualMachines&timespan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z&top=30",
},
}
commonAzureModelProps := map[string]interface{}{

View File

@ -0,0 +1,50 @@
import { FieldType, LoadingState, PanelData } from '@grafana/data';
type DeepPartial<T> = {
[P in keyof T]?: DeepPartial<T[P]>;
};
export default function createMockPanelData(overrides?: DeepPartial<PanelData>) {
const _mockPanelData: DeepPartial<PanelData> = {
state: 'Loading' as LoadingState,
series: [
{
refId: 'A',
fields: [
{
name: 'Time',
type: 'time' as FieldType,
config: { links: Array(1) },
values: [],
state: null,
},
],
length: 360,
},
],
annotations: [],
request: {
app: 'dashboard',
requestId: 'request',
timezone: 'browser',
panelId: 1,
dashboardId: 0,
timeInfo: '',
interval: '20s',
intervalMs: 20000,
targets: [],
maxDataPoints: 100,
rangeRaw: {
from: 'now-6h',
to: 'now',
},
},
structureRev: 15,
...overrides,
};
const mockPanelData = _mockPanelData as PanelData;
return jest.mocked(mockPanelData, true);
}

View File

@ -0,0 +1,236 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { selectOptionInTest } from '@grafana/ui';
import createMockDatasource from '../../__mocks__/datasource';
import createMockPanelData from '../../__mocks__/panelData';
import createMockQuery from '../../__mocks__/query';
import DimensionFields from './DimensionFields';
import { appendDimensionFilter, setDimensionFilterValue } from './setQueryValue';
const variableOptionGroup = {
label: 'Template variables',
options: [],
};
const user = userEvent.setup();
describe('Azure Monitor QueryEditor', () => {
const mockPanelData = createMockPanelData();
const mockDatasource = createMockDatasource();
it('should render a dimension filter', async () => {
let mockQuery = createMockQuery();
const onQueryChange = jest.fn();
const dimensionOptions = [
{ label: 'Test Dimension 1', value: 'TestDimension1' },
{ label: 'Test Dimension 2', value: 'TestDimension2' },
];
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);
expect(onQueryChange).toHaveBeenCalledWith({
...mockQuery,
azureMonitor: { ...mockQuery.azureMonitor, dimensionFilters: [{ dimension: '', operator: 'eq', filter: '*' }] },
});
render(
<DimensionFields
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', filter: '*' }],
},
});
expect(screen.queryByText('Test Dimension 1')).toBeInTheDocument();
expect(screen.queryByText('==')).toBeInTheDocument();
});
it('correctly filters out dimensions when selected', async () => {
let mockQuery = createMockQuery();
mockQuery.azureMonitor = {
...mockQuery.azureMonitor,
dimensionFilters: [{ dimension: 'TestDimension1', operator: 'eq', filter: '*' }],
};
const onQueryChange = jest.fn();
const dimensionOptions = [
{ label: 'Test Dimension 1', value: 'TestDimension1' },
{ label: 'Test Dimension 2', value: 'TestDimension2' },
];
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);
render(
<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();
mockQuery.azureMonitor = {
...mockQuery.azureMonitor,
dimensionFilters: [{ dimension: 'TestDimension1', operator: 'eq', filter: '*' }],
};
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');
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();
mockQuery.azureMonitor = {
...mockQuery.azureMonitor,
dimensionFilters: [{ dimension: 'TestDimension1', operator: 'eq', filter: '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' }];
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('select-clear-value');
await user.click(labelClear);
mockQuery = setDimensionFilterValue(mockQuery, 0, 'filter', '');
expect(onQueryChange).toHaveBeenCalledWith({
...mockQuery,
azureMonitor: {
...mockQuery.azureMonitor,
dimensionFilters: [{ dimension: 'TestDimension1', operator: 'eq', filter: '' }],
},
});
mockPanelData.series = [
...mockPanelData.series,
{
...mockPanelData.series[0],
fields: [
{
...mockPanelData.series[0].fields[0],
name: 'Test Dimension 1',
labels: { testdimension1: 'testlabel2' },
},
],
},
];
render(
<DimensionFields
data={mockPanelData}
subscriptionId="123"
query={mockQuery}
onQueryChange={onQueryChange}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
setError={() => {}}
dimensionOptions={dimensionOptions}
/>
);
const labelSelect = await screen.findByText('Select value');
await user.click(labelSelect);
const options = await screen.findAllByLabelText('Select option');
expect(options).toHaveLength(2);
expect(options[0]).toHaveTextContent('testlabel');
expect(options[1]).toHaveTextContent('testlabel2');
});
});

View File

@ -1,8 +1,9 @@
import React, { useMemo } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { Button, Select, Input, HorizontalGroup, VerticalGroup, InlineLabel } from '@grafana/ui';
import { SelectableValue, DataFrame, PanelData } from '@grafana/data';
import { Button, Select, HorizontalGroup, VerticalGroup } from '@grafana/ui';
import { AzureMetricDimension, AzureMonitorOption, AzureQueryEditorFieldProps } from '../../types';
import { AzureMetricDimension, AzureMonitorOption, AzureMonitorQuery, AzureQueryEditorFieldProps } from '../../types';
import { Field } from '../Field';
import { appendDimensionFilter, removeDimensionFilter, setDimensionFilterValue } from './setQueryValue';
@ -11,12 +12,77 @@ interface DimensionFieldsProps extends AzureQueryEditorFieldProps {
dimensionOptions: AzureMonitorOption[];
}
const DimensionFields: React.FC<DimensionFieldsProps> = ({ query, dimensionOptions, onQueryChange }) => {
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!);
for (const label of labels) {
// Labels only exist for series that have a dimension selected
if (label) {
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 = {};
for (const label of Object.keys(labelsObj)) {
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 DimensionFields: 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 addFilter = () => {
onQueryChange(appendDimensionFilter(query));
};
@ -33,10 +99,30 @@ const DimensionFields: React.FC<DimensionFieldsProps> = ({ query, dimensionOptio
onQueryChange(setDimensionFilterValue(query, filterIndex, fieldName, value));
};
const onFilterInputChange = (index: number, ev: React.FormEvent) => {
if (ev.target instanceof HTMLInputElement) {
onFieldChange(index, 'filter', ev.target.value);
const onFilterInputChange = (index: number, v: SelectableValue<string> | null) => {
onFieldChange(index, 'filter', v?.value ?? '');
};
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 getValidOperators = (selectedOperator: string) => {
if (dimensionOperators.find((operator: SelectableValue) => operator.value === selectedOperator)) {
return dimensionOperators;
}
return [...dimensionOperators, ...(selectedOperator ? [{ label: selectedOperator, value: selectedOperator }] : [])];
};
return (
@ -48,12 +134,28 @@ const DimensionFields: React.FC<DimensionFieldsProps> = ({ query, dimensionOptio
menuShouldPortal
placeholder="Field"
value={filter.dimension}
options={dimensionOptions}
options={getValidDimensionOptions(filter.dimension)}
onChange={(v) => onFieldChange(index, 'dimension', v.value ?? '')}
width={38}
/>
<InlineLabel aria-label="equals">==</InlineLabel>
<Input placeholder="" value={filter.filter} onChange={(ev) => onFilterInputChange(index, ev)} />
<Select
menuShouldPortal
placeholder="Operation"
value={filter.operator}
options={getValidOperators(filter.operator)}
onChange={(v) => onFieldChange(index, 'operator', v.value ?? '')}
allowCustomValue
/>
<Select
menuShouldPortal
placeholder="Select value"
value={filter.filter ? filter.filter : ''}
allowCustomValue
options={getValidFilterOptions(filter.filter, filter.dimension)}
onChange={(v) => onFilterInputChange(index, v)}
isClearable
/>
<Button
variant="secondary"
size="md"

View File

@ -4,6 +4,7 @@ import React from 'react';
import { selectOptionInTest } from '@grafana/ui';
import createMockDatasource from '../../__mocks__/datasource';
import createMockPanelData from '../../__mocks__/panelData';
import createMockQuery from '../../__mocks__/query';
import MetricsQueryEditor from './MetricsQueryEditor';
@ -14,10 +15,12 @@ const variableOptionGroup = {
};
describe('Azure Monitor QueryEditor', () => {
const mockPanelData = createMockPanelData();
it('should render', async () => {
const mockDatasource = createMockDatasource();
render(
<MetricsQueryEditor
data={mockPanelData}
subscriptionId="123"
query={createMockQuery()}
datasource={mockDatasource}
@ -47,6 +50,7 @@ describe('Azure Monitor QueryEditor', () => {
render(
<MetricsQueryEditor
data={mockPanelData}
subscriptionId="123"
query={mockQuery}
datasource={mockDatasource}
@ -86,6 +90,7 @@ describe('Azure Monitor QueryEditor', () => {
]);
render(
<MetricsQueryEditor
data={mockPanelData}
subscriptionId="123"
query={createMockQuery()}
datasource={mockDatasource}
@ -126,6 +131,7 @@ describe('Azure Monitor QueryEditor', () => {
]);
render(
<MetricsQueryEditor
data={mockPanelData}
subscriptionId="123"
query={createMockQuery()}
datasource={mockDatasource}
@ -165,6 +171,7 @@ describe('Azure Monitor QueryEditor', () => {
]);
render(
<MetricsQueryEditor
data={mockPanelData}
subscriptionId="123"
query={createMockQuery()}
datasource={mockDatasource}
@ -209,6 +216,7 @@ describe('Azure Monitor QueryEditor', () => {
]);
render(
<MetricsQueryEditor
data={mockPanelData}
subscriptionId="123"
query={createMockQuery()}
datasource={mockDatasource}
@ -239,6 +247,7 @@ describe('Azure Monitor QueryEditor', () => {
const mockQuery = createMockQuery();
render(
<MetricsQueryEditor
data={mockPanelData}
subscriptionId="123"
query={createMockQuery()}
datasource={mockDatasource}

View File

@ -1,5 +1,6 @@
import React from 'react';
import { PanelData } from '@grafana/data/src/types';
import { InlineFieldRow } from '@grafana/ui';
import Datasource from '../../datasource';
@ -27,6 +28,7 @@ import {
} from './dataHooks';
interface MetricsQueryEditorProps {
data: PanelData | undefined;
query: AzureMonitorQuery;
datasource: Datasource;
subscriptionId?: string;
@ -36,6 +38,7 @@ interface MetricsQueryEditorProps {
}
const MetricsQueryEditor: React.FC<MetricsQueryEditorProps> = ({
data,
query,
datasource,
subscriptionId,
@ -138,6 +141,7 @@ const MetricsQueryEditor: React.FC<MetricsQueryEditorProps> = ({
/>
</InlineFieldRow>
<DimensionFields
data={data}
query={query}
datasource={datasource}
subscriptionId={subscriptionId}

View File

@ -185,7 +185,7 @@ export function appendDimensionFilter(
query: AzureMonitorQuery,
dimension = '',
operator = 'eq',
filter = ''
filter = '*'
): AzureMonitorQuery {
const existingFilters = query.azureMonitor?.dimensionFilters ?? [];

View File

@ -6,6 +6,7 @@ import { selectOptionInTest } from '@grafana/ui';
import createMockDatasource from '../../__mocks__/datasource';
import { createMockInstanceSetttings } from '../../__mocks__/instanceSettings';
import createMockPanelData from '../../__mocks__/panelData';
import createMockQuery from '../../__mocks__/query';
import {
createMockResourceGroupsBySubscription,
@ -43,12 +44,14 @@ describe('MetricsQueryEditor', () => {
afterEach(() => {
window.HTMLElement.prototype.scrollIntoView = originalScrollIntoView;
});
const mockPanelData = createMockPanelData();
it('should render', async () => {
const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() });
render(
<MetricsQueryEditor
data={mockPanelData}
query={createMockQuery()}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
@ -68,6 +71,7 @@ describe('MetricsQueryEditor', () => {
render(
<MetricsQueryEditor
data={mockPanelData}
query={query}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
@ -116,6 +120,7 @@ describe('MetricsQueryEditor', () => {
render(
<MetricsQueryEditor
data={mockPanelData}
query={query}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
@ -184,6 +189,7 @@ describe('MetricsQueryEditor', () => {
render(
<MetricsQueryEditor
data={mockPanelData}
query={createMockQuery()}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
@ -214,6 +220,7 @@ describe('MetricsQueryEditor', () => {
render(
<MetricsQueryEditor
data={mockPanelData}
query={createMockQuery()}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}

View File

@ -1,5 +1,6 @@
import React from 'react';
import { PanelData } from '@grafana/data/src/types';
import { InlineFieldRow } from '@grafana/ui';
import type Datasource from '../../datasource';
@ -18,6 +19,7 @@ import { ResourceRowType } from '../ResourcePicker/types';
import { useMetricNames, useMetricNamespaces, useMetricMetadata } from './dataHooks';
interface MetricsQueryEditorProps {
data: PanelData | undefined;
query: AzureMonitorQuery;
datasource: Datasource;
onChange: (newQuery: AzureMonitorQuery) => void;
@ -26,6 +28,7 @@ interface MetricsQueryEditorProps {
}
const MetricsQueryEditor: React.FC<MetricsQueryEditorProps> = ({
data,
query,
datasource,
variableOptionGroup,
@ -88,6 +91,7 @@ const MetricsQueryEditor: React.FC<MetricsQueryEditorProps> = ({
/>
</InlineFieldRow>
<DimensionFields
data={data}
query={query}
datasource={datasource}
variableOptionGroup={variableOptionGroup}

View File

@ -34,6 +34,7 @@ const QueryEditor: React.FC<AzureMonitorQueryEditorProps> = ({
datasource,
onChange,
onRunQuery: baseOnRunQuery,
data,
}) => {
const [errorMessage, setError] = useLastError();
const onRunQuery = useMemo(() => debounce(baseOnRunQuery, 500), [baseOnRunQuery]);
@ -59,6 +60,7 @@ const QueryEditor: React.FC<AzureMonitorQueryEditorProps> = ({
<QueryTypeField query={query} onQueryChange={onQueryChange} />
<EditorForQueryType
data={data}
subscriptionId={subscriptionId}
query={query}
datasource={datasource}
@ -86,6 +88,7 @@ interface EditorForQueryTypeProps extends Omit<AzureMonitorQueryEditorProps, 'on
}
const EditorForQueryType: React.FC<EditorForQueryTypeProps> = ({
data,
subscriptionId,
query,
datasource,
@ -98,6 +101,7 @@ const EditorForQueryType: React.FC<EditorForQueryTypeProps> = ({
if (config.featureToggles.azureMonitorResourcePickerForMetrics) {
return (
<NewMetricsQueryEditor
data={data}
query={query}
datasource={datasource}
onChange={onChange}
@ -108,6 +112,7 @@ const EditorForQueryType: React.FC<EditorForQueryTypeProps> = ({
}
return (
<MetricsQueryEditor
data={data}
subscriptionId={subscriptionId}
query={query}
datasource={datasource}

View File

@ -1,4 +1,10 @@
import { DataSourceInstanceSettings, DataSourceJsonData, DataSourceSettings, TableData } from '@grafana/data';
import {
DataSourceInstanceSettings,
DataSourceJsonData,
DataSourceSettings,
PanelData,
TableData,
} from '@grafana/data';
import Datasource from '../datasource';
@ -162,6 +168,7 @@ export interface AzureMonitorOption<T = string> {
}
export interface AzureQueryEditorFieldProps {
data?: PanelData;
query: AzureMonitorQuery;
datasource: Datasource;
subscriptionId?: string;

View File

@ -84,7 +84,12 @@ function migrateMetricsDimensionFilters(query: AzureMonitorQuery): AzureMonitorQ
const oldDimension = workingQuery.azureMonitor?.dimension;
if (oldDimension && oldDimension !== 'None') {
workingQuery = appendDimensionFilter(workingQuery, oldDimension, 'eq', workingQuery.azureMonitor?.dimensionFilter);
workingQuery = appendDimensionFilter(
workingQuery,
oldDimension,
'eq',
workingQuery.azureMonitor?.dimensionFilter || ''
);
}
return workingQuery;