mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
52ed651958
commit
53cb94a2ad
@ -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' },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
@ -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;
|
@ -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}
|
||||||
|
Loading…
Reference in New Issue
Block a user