mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
87ae3e0644
commit
d7c16e06d2
@ -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).
|
||||
|
@ -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 ")
|
||||
}
|
||||
|
@ -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×pan=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×pan=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×pan=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×pan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z&top=30",
|
||||
},
|
||||
}
|
||||
|
||||
commonAzureModelProps := map[string]interface{}{
|
||||
|
@ -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);
|
||||
}
|
@ -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');
|
||||
});
|
||||
});
|
@ -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"
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -185,7 +185,7 @@ export function appendDimensionFilter(
|
||||
query: AzureMonitorQuery,
|
||||
dimension = '',
|
||||
operator = 'eq',
|
||||
filter = ''
|
||||
filter = '*'
|
||||
): AzureMonitorQuery {
|
||||
const existingFilters = query.azureMonitor?.dimensionFilters ?? [];
|
||||
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user