AzureMonitor: begin moving metrics query editor to use @grafana/experimental (#48878)

* feat: migrate Azure Metrics Query Editor Field to experimental UI

* feat: ensure tests pass with experimental feature flag enabled

* feat: avoid duplicate unit tests for experimental MetricsQueryEditor
This commit is contained in:
Adam Simpson
2022-05-11 22:54:43 -04:00
committed by GitHub
parent 906484b809
commit a1217ef8da
4 changed files with 383 additions and 244 deletions

View File

@ -1,10 +1,19 @@
import React from 'react';
import { EditorField } from '@grafana/experimental';
import { config } from '@grafana/runtime';
import { InlineField } from '@grafana/ui';
import { Props as InlineFieldProps } from '@grafana/ui/src/components/Forms/InlineField';
interface Props extends InlineFieldProps {
label: string;
}
const DEFAULT_LABEL_WIDTH = 18;
export const Field = (props: InlineFieldProps) => {
export const Field = (props: Props) => {
if (config.featureToggles.azureMonitorExperimentalUI) {
return <EditorField width={DEFAULT_LABEL_WIDTH} {...props} />;
}
return <InlineField labelWidth={DEFAULT_LABEL_WIDTH} {...props} />;
};

View File

@ -2,6 +2,7 @@ import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { config } from '@grafana/runtime';
import { selectOptionInTest } from '@grafana/ui';
import createMockDatasource from '../../__mocks__/datasource';
@ -22,6 +23,15 @@ const variableOptionGroup = {
options: [],
};
const tests = [
{
id: 'azure-monitor-metrics-query-editor-with-resource-picker',
},
{
id: 'azure-monitor-metrics-query-editor-with-experimental-ui',
},
];
export function createMockResourcePickerData() {
const mockDatasource = new ResourcePickerData(createMockInstanceSetttings());
@ -36,209 +46,215 @@ export function createMockResourcePickerData() {
return mockDatasource;
}
describe('MetricsQueryEditor', () => {
const originalScrollIntoView = window.HTMLElement.prototype.scrollIntoView;
beforeEach(() => {
window.HTMLElement.prototype.scrollIntoView = function () {};
});
afterEach(() => {
window.HTMLElement.prototype.scrollIntoView = originalScrollIntoView;
});
const mockPanelData = createMockPanelData();
for (const t of tests) {
describe(`MetricsQueryEditor: ${t.id}`, () => {
const originalScrollIntoView = window.HTMLElement.prototype.scrollIntoView;
const mockPanelData = createMockPanelData();
it('should render', async () => {
const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() });
beforeEach(() => {
window.HTMLElement.prototype.scrollIntoView = function () {};
config.featureToggles.azureMonitorExperimentalUI =
t.id === 'azure-monitor-metrics-query-editor-with-experimental-ui';
});
afterEach(() => {
window.HTMLElement.prototype.scrollIntoView = originalScrollIntoView;
config.featureToggles.azureMonitorExperimentalUI = false;
});
render(
<MetricsQueryEditor
data={mockPanelData}
query={createMockQuery()}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
onChange={() => {}}
setError={() => {}}
/>
);
it('should render', async () => {
const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() });
expect(await screen.findByTestId('azure-monitor-metrics-query-editor-with-resource-picker')).toBeInTheDocument();
});
render(
<MetricsQueryEditor
data={mockPanelData}
query={createMockQuery()}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
onChange={() => {}}
setError={() => {}}
/>
);
it('should change resource when a resource is selected in the ResourcePicker', async () => {
const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() });
const query = createMockQuery();
delete query?.azureMonitor?.resourceUri;
const onChange = jest.fn();
expect(await screen.findByTestId(t.id)).toBeInTheDocument();
});
render(
<MetricsQueryEditor
data={mockPanelData}
query={query}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
onChange={onChange}
setError={() => {}}
/>
);
it('should change resource when a resource is selected in the ResourcePicker', async () => {
const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() });
const query = createMockQuery();
delete query?.azureMonitor?.resourceUri;
const onChange = jest.fn();
const resourcePickerButton = await screen.findByRole('button', { name: 'Select a resource' });
expect(resourcePickerButton).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Expand Primary Subscription' })).not.toBeInTheDocument();
resourcePickerButton.click();
render(
<MetricsQueryEditor
data={mockPanelData}
query={query}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
onChange={onChange}
setError={() => {}}
/>
);
const subscriptionButton = await screen.findByRole('button', { name: 'Expand Primary Subscription' });
expect(subscriptionButton).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Expand A Great Resource Group' })).not.toBeInTheDocument();
subscriptionButton.click();
const resourcePickerButton = await screen.findByRole('button', { name: 'Select a resource' });
expect(resourcePickerButton).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Expand Primary Subscription' })).not.toBeInTheDocument();
resourcePickerButton.click();
const resourceGroupButton = await screen.findByRole('button', { name: 'Expand A Great Resource Group' });
expect(resourceGroupButton).toBeInTheDocument();
expect(screen.queryByLabelText('web-server')).not.toBeInTheDocument();
resourceGroupButton.click();
const subscriptionButton = await screen.findByRole('button', { name: 'Expand Primary Subscription' });
expect(subscriptionButton).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Expand A Great Resource Group' })).not.toBeInTheDocument();
subscriptionButton.click();
const checkbox = await screen.findByLabelText('web-server');
expect(checkbox).toBeInTheDocument();
expect(checkbox).not.toBeChecked();
await userEvent.click(checkbox);
expect(checkbox).toBeChecked();
await userEvent.click(await screen.findByRole('button', { name: 'Apply' }));
const resourceGroupButton = await screen.findByRole('button', { name: 'Expand A Great Resource Group' });
expect(resourceGroupButton).toBeInTheDocument();
expect(screen.queryByLabelText('web-server')).not.toBeInTheDocument();
resourceGroupButton.click();
expect(onChange).toBeCalledTimes(1);
expect(onChange).toBeCalledWith(
expect.objectContaining({
azureMonitor: expect.objectContaining({
resourceUri:
'/subscriptions/def-456/resourceGroups/dev-3/providers/Microsoft.Compute/virtualMachines/web-server',
}),
})
);
});
const checkbox = await screen.findByLabelText('web-server');
expect(checkbox).toBeInTheDocument();
expect(checkbox).not.toBeChecked();
await userEvent.click(checkbox);
expect(checkbox).toBeChecked();
await userEvent.click(await screen.findByRole('button', { name: 'Apply' }));
it('should reset metric namespace, metric name, and aggregation fields after selecting a new resource when a valid query has already been set', async () => {
const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() });
const query = createMockQuery();
const onChange = jest.fn();
expect(onChange).toBeCalledTimes(1);
expect(onChange).toBeCalledWith(
expect.objectContaining({
azureMonitor: expect.objectContaining({
resourceUri:
'/subscriptions/def-456/resourceGroups/dev-3/providers/Microsoft.Compute/virtualMachines/web-server',
}),
})
);
});
render(
<MetricsQueryEditor
data={mockPanelData}
query={query}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
onChange={onChange}
setError={() => {}}
/>
);
it('should reset metric namespace, metric name, and aggregation fields after selecting a new resource when a valid query has already been set', async () => {
const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() });
const query = createMockQuery();
const onChange = jest.fn();
const resourcePickerButton = await screen.findByRole('button', { name: /grafanastaging/ });
render(
<MetricsQueryEditor
data={mockPanelData}
query={query}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
onChange={onChange}
setError={() => {}}
/>
);
expect(screen.getByText('Microsoft.Compute/virtualMachines')).toBeInTheDocument();
expect(screen.getByText('Metric A')).toBeInTheDocument();
expect(screen.getByText('Average')).toBeInTheDocument();
const resourcePickerButton = await screen.findByRole('button', { name: /grafanastaging/ });
expect(resourcePickerButton).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Expand Primary Subscription' })).not.toBeInTheDocument();
resourcePickerButton.click();
expect(screen.getByText('Microsoft.Compute/virtualMachines')).toBeInTheDocument();
expect(screen.getByText('Metric A')).toBeInTheDocument();
expect(screen.getByText('Average')).toBeInTheDocument();
const subscriptionButton = await screen.findByRole('button', { name: 'Expand Dev Subscription' });
expect(subscriptionButton).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Expand Development 3' })).not.toBeInTheDocument();
subscriptionButton.click();
expect(resourcePickerButton).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Expand Primary Subscription' })).not.toBeInTheDocument();
resourcePickerButton.click();
const resourceGroupButton = await screen.findByRole('button', { name: 'Expand Development 3' });
expect(resourceGroupButton).toBeInTheDocument();
expect(screen.queryByLabelText('db-server')).not.toBeInTheDocument();
resourceGroupButton.click();
const subscriptionButton = await screen.findByRole('button', { name: 'Expand Dev Subscription' });
expect(subscriptionButton).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Expand Development 3' })).not.toBeInTheDocument();
subscriptionButton.click();
const checkbox = await screen.findByLabelText('db-server');
expect(checkbox).toBeInTheDocument();
expect(checkbox).not.toBeChecked();
await userEvent.click(checkbox);
expect(checkbox).toBeChecked();
await userEvent.click(await screen.findByRole('button', { name: 'Apply' }));
const resourceGroupButton = await screen.findByRole('button', { name: 'Expand Development 3' });
expect(resourceGroupButton).toBeInTheDocument();
expect(screen.queryByLabelText('db-server')).not.toBeInTheDocument();
resourceGroupButton.click();
expect(onChange).toBeCalledTimes(1);
expect(onChange).toBeCalledWith(
expect.objectContaining({
azureMonitor: expect.objectContaining({
resourceUri:
'/subscriptions/def-456/resourceGroups/dev-3/providers/Microsoft.Compute/virtualMachines/db-server',
metricNamespace: undefined,
metricName: undefined,
const checkbox = await screen.findByLabelText('db-server');
expect(checkbox).toBeInTheDocument();
expect(checkbox).not.toBeChecked();
await userEvent.click(checkbox);
expect(checkbox).toBeChecked();
await userEvent.click(await screen.findByRole('button', { name: 'Apply' }));
expect(onChange).toBeCalledTimes(1);
expect(onChange).toBeCalledWith(
expect.objectContaining({
azureMonitor: expect.objectContaining({
resourceUri:
'/subscriptions/def-456/resourceGroups/dev-3/providers/Microsoft.Compute/virtualMachines/db-server',
metricNamespace: undefined,
metricName: undefined,
aggregation: undefined,
timeGrain: '',
dimensionFilters: [],
}),
})
);
});
it('should change the metric name when selected', async () => {
const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() });
const onChange = jest.fn();
const mockQuery = createMockQuery();
mockDatasource.azureMonitorDatasource.getMetricNames = jest.fn().mockResolvedValue([
{
value: 'metric-a',
text: 'Metric A',
},
{
value: 'metric-b',
text: 'Metric B',
},
]);
render(
<MetricsQueryEditor
data={mockPanelData}
query={createMockQuery()}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
onChange={onChange}
setError={() => {}}
/>
);
const metrics = await screen.findByLabelText('Metric');
expect(metrics).toBeInTheDocument();
await selectOptionInTest(metrics, 'Metric B');
expect(onChange).toHaveBeenLastCalledWith({
...mockQuery,
azureMonitor: {
...mockQuery.azureMonitor,
metricName: 'metric-b',
aggregation: undefined,
timeGrain: '',
dimensionFilters: [],
}),
})
);
});
},
});
});
it('should change the metric name when selected', async () => {
const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() });
const onChange = jest.fn();
const mockQuery = createMockQuery();
mockDatasource.azureMonitorDatasource.getMetricNames = jest.fn().mockResolvedValue([
{
value: 'metric-a',
text: 'Metric A',
},
{
value: 'metric-b',
text: 'Metric B',
},
]);
it('should change the aggregation type when selected', async () => {
const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() });
const onChange = jest.fn();
const mockQuery = createMockQuery();
render(
<MetricsQueryEditor
data={mockPanelData}
query={createMockQuery()}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
onChange={onChange}
setError={() => {}}
/>
);
render(
<MetricsQueryEditor
data={mockPanelData}
query={createMockQuery()}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
onChange={onChange}
setError={() => {}}
/>
);
const metrics = await screen.findByLabelText('Metric');
expect(metrics).toBeInTheDocument();
await selectOptionInTest(metrics, 'Metric B');
const aggregation = await screen.findByLabelText('Aggregation');
expect(aggregation).toBeInTheDocument();
await selectOptionInTest(aggregation, 'Maximum');
expect(onChange).toHaveBeenLastCalledWith({
...mockQuery,
azureMonitor: {
...mockQuery.azureMonitor,
metricName: 'metric-b',
aggregation: undefined,
timeGrain: '',
},
expect(onChange).toHaveBeenLastCalledWith({
...mockQuery,
azureMonitor: {
...mockQuery.azureMonitor,
aggregation: 'Maximum',
},
});
});
});
it('should change the aggregation type when selected', async () => {
const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() });
const onChange = jest.fn();
const mockQuery = createMockQuery();
render(
<MetricsQueryEditor
data={mockPanelData}
query={createMockQuery()}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
onChange={onChange}
setError={() => {}}
/>
);
const aggregation = await screen.findByLabelText('Aggregation');
expect(aggregation).toBeInTheDocument();
await selectOptionInTest(aggregation, 'Maximum');
expect(onChange).toHaveBeenLastCalledWith({
...mockQuery,
azureMonitor: {
...mockQuery.azureMonitor,
aggregation: 'Maximum',
},
});
});
});
}

View File

@ -1,6 +1,8 @@
import React from 'react';
import { PanelData } from '@grafana/data/src/types';
import { EditorRows, EditorRow, EditorFieldGroup } from '@grafana/experimental';
import { config } from '@grafana/runtime';
import { InlineFieldRow } from '@grafana/ui';
import type Datasource from '../../datasource';
@ -38,83 +40,183 @@ const MetricsQueryEditor: React.FC<MetricsQueryEditorProps> = ({
const metricsMetadata = useMetricMetadata(query, datasource, onChange);
const metricNamespaces = useMetricNamespaces(query, datasource, onChange, setError);
const metricNames = useMetricNames(query, datasource, onChange, setError);
return (
<div data-testid="azure-monitor-metrics-query-editor-with-resource-picker">
<InlineFieldRow>
<ResourceField
query={query}
datasource={datasource}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
selectableEntryTypes={[ResourceRowType.Resource]}
setResource={setResource}
resourceUri={query.azureMonitor?.resourceUri}
/>
</InlineFieldRow>
if (config.featureToggles.azureMonitorExperimentalUI) {
return (
<span data-testid="azure-monitor-metrics-query-editor-with-experimental-ui">
<EditorRows>
<EditorRow>
<EditorFieldGroup>
<ResourceField
query={query}
datasource={datasource}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
selectableEntryTypes={[ResourceRowType.Resource]}
setResource={setResource}
resourceUri={query.azureMonitor?.resourceUri}
/>
</EditorFieldGroup>
</EditorRow>
<InlineFieldRow>
<MetricNamespaceField
metricNamespaces={metricNamespaces}
<EditorRow>
<EditorFieldGroup>
<MetricNamespaceField
metricNamespaces={metricNamespaces}
query={query}
datasource={datasource}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
/>
<MetricNameField
metricNames={metricNames}
query={query}
datasource={datasource}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
/>
</EditorFieldGroup>
</EditorRow>
<EditorRow>
<EditorFieldGroup>
<AggregationField
query={query}
datasource={datasource}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
aggregationOptions={metricsMetadata?.aggOptions ?? []}
isLoading={metricsMetadata.isLoading}
/>
<TimeGrainField
query={query}
datasource={datasource}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
timeGrainOptions={metricsMetadata?.timeGrains ?? []}
/>
</EditorFieldGroup>
</EditorRow>
<EditorRow>
<EditorFieldGroup>
<DimensionFields
data={data}
query={query}
datasource={datasource}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
dimensionOptions={metricsMetadata?.dimensions ?? []}
/>
</EditorFieldGroup>
</EditorRow>
<EditorRow>
<EditorFieldGroup>
<TopField
query={query}
datasource={datasource}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
/>
</EditorFieldGroup>
</EditorRow>
<EditorRow>
<EditorFieldGroup>
<LegendFormatField
query={query}
datasource={datasource}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
/>
</EditorFieldGroup>
</EditorRow>
</EditorRows>
</span>
);
} else {
return (
<div data-testid="azure-monitor-metrics-query-editor-with-resource-picker">
<InlineFieldRow>
<ResourceField
query={query}
datasource={datasource}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
selectableEntryTypes={[ResourceRowType.Resource]}
setResource={setResource}
resourceUri={query.azureMonitor?.resourceUri}
/>
</InlineFieldRow>
<InlineFieldRow>
<MetricNamespaceField
metricNamespaces={metricNamespaces}
query={query}
datasource={datasource}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
/>
<MetricNameField
metricNames={metricNames}
query={query}
datasource={datasource}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
/>
</InlineFieldRow>
<InlineFieldRow>
<AggregationField
query={query}
datasource={datasource}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
aggregationOptions={metricsMetadata?.aggOptions ?? []}
isLoading={metricsMetadata.isLoading}
/>
<TimeGrainField
query={query}
datasource={datasource}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
timeGrainOptions={metricsMetadata?.timeGrains ?? []}
/>
</InlineFieldRow>
<DimensionFields
data={data}
query={query}
datasource={datasource}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
dimensionOptions={metricsMetadata?.dimensions ?? []}
/>
<TopField
query={query}
datasource={datasource}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
/>
<MetricNameField
metricNames={metricNames}
<LegendFormatField
query={query}
datasource={datasource}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
/>
</InlineFieldRow>
<InlineFieldRow>
<AggregationField
query={query}
datasource={datasource}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
aggregationOptions={metricsMetadata?.aggOptions ?? []}
isLoading={metricsMetadata.isLoading}
/>
<TimeGrainField
query={query}
datasource={datasource}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
timeGrainOptions={metricsMetadata?.timeGrains ?? []}
/>
</InlineFieldRow>
<DimensionFields
data={data}
query={query}
datasource={datasource}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
dimensionOptions={metricsMetadata?.dimensions ?? []}
/>
<TopField
query={query}
datasource={datasource}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
/>
<LegendFormatField
query={query}
datasource={datasource}
variableOptionGroup={variableOptionGroup}
onQueryChange={onChange}
setError={setError}
/>
</div>
);
</div>
);
}
};
export default MetricsQueryEditor;

View File

@ -108,4 +108,16 @@ describe('Azure Monitor QueryEditor', () => {
config.featureToggles.azureMonitorExperimentalUI = originalConfigValue;
});
it('should not render the experimental QueryHeader when feature toggle is disabled', async () => {
const mockDatasource = createMockDatasource();
const mockQuery = {
...createMockQuery(),
queryType: AzureQueryType.AzureMonitor,
};
render(<QueryEditor query={mockQuery} datasource={mockDatasource} onChange={() => {}} onRunQuery={() => {}} />);
await waitFor(() => expect(screen.queryByTestId('azure-monitor-experimental-header')).not.toBeInTheDocument());
});
});