diff --git a/public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.ts b/public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.ts index bed9f6cc52c..c1e8c036e69 100644 --- a/public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.ts +++ b/public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.ts @@ -12,7 +12,7 @@ export default class AzureResourceGraphDatasource extends DataSourceWithBackend< AzureDataSourceJsonData > { filterQuery(item: AzureMonitorQuery): boolean { - return !!item.azureResourceGraph?.query; + return !!item.azureResourceGraph?.query && !!item.subscriptions && item.subscriptions.length > 0; } applyTemplateVariables(target: AzureMonitorQuery, scopedVars: ScopedVars): AzureMonitorQuery { diff --git a/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/ArgQueryEditor.test.tsx b/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/ArgQueryEditor.test.tsx index 2ec87df7808..3f5f31a1966 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/ArgQueryEditor.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/ArgQueryEditor.test.tsx @@ -1,4 +1,5 @@ -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import React from 'react'; import createMockDatasource from '../../__mocks__/datasource'; @@ -118,4 +119,67 @@ describe('ArgQueryEditor', () => { ).toHaveTextContent('$test'); expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ subscriptions: ['$test'] })); }); + + it('should display an error if no subscription is selected', async () => { + const onChange = jest.fn(); + const datasource = createMockDatasource({ + getSubscriptions: jest.fn().mockResolvedValue([]), + }); + const query = createMockQuery({ + subscriptions: [], + }); + render( + + ); + + expect(await waitFor(() => screen.getByText('At least one subscription must be chosen.'))).toBeInTheDocument(); + }); + + it('should display an error if subscriptions are cleared', async () => { + const onChange = jest.fn(); + const datasource = createMockDatasource({ + getSubscriptions: jest.fn().mockResolvedValue([{ text: 'foo', value: 'test-subscription-value' }]), + }); + const query = createMockQuery({ + subscription: undefined, + subscriptions: ['test-subscription-value'], + }); + const { rerender } = render( + + ); + + expect(datasource.getSubscriptions).toHaveBeenCalled(); + expect(await waitFor(() => onChange)).toHaveBeenCalledWith( + expect.objectContaining({ subscriptions: ['test-subscription-value'] }) + ); + expect(await waitFor(() => screen.findByText('foo'))).toBeInTheDocument(); + + const clear = screen.getByLabelText('select-clear-value'); + await userEvent.click(clear); + + expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ subscriptions: [] })); + + rerender( + + ); + expect(await waitFor(() => screen.getByText('At least one subscription must be chosen.'))).toBeInTheDocument(); + }); }); diff --git a/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/ArgQueryEditor.tsx b/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/ArgQueryEditor.tsx index 488f1cf1211..78b685d491b 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/ArgQueryEditor.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/ArgQueryEditor.tsx @@ -6,9 +6,9 @@ import { EditorFieldGroup, EditorRow, EditorRows } from '@grafana/experimental'; import Datasource from '../../datasource'; import { selectors } from '../../e2e/selectors'; import { AzureMonitorErrorish, AzureMonitorOption, AzureMonitorQuery } from '../../types'; -import SubscriptionField from '../SubscriptionField'; import QueryField from './QueryField'; +import SubscriptionField from './SubscriptionField'; interface ArgQueryEditorProps { query: AzureMonitorQuery; @@ -81,7 +81,6 @@ const ArgQueryEditor = ({ void; + subscriptions: AzureMonitorOption[]; +} + +const SubscriptionField = ({ query, subscriptions, variableOptionGroup, onQueryChange }: SubscriptionFieldProps) => { + const [error, setError] = useState(false); + const [values, setValues] = useState>>([]); + const options = useMemo(() => [...subscriptions, variableOptionGroup], [subscriptions, variableOptionGroup]); + + useEffect(() => { + if (query.subscriptions && query.subscriptions.length > 0) { + setValues(findOptions([...subscriptions, ...variableOptionGroup.options], query.subscriptions)); + setError(false); + } else { + setError(true); + } + }, [query.subscriptions, subscriptions, variableOptionGroup.options]); + + const onChange = (change: Array>) => { + if (!change || change.length === 0) { + setValues([]); + onQueryChange({ + ...query, + subscriptions: [], + }); + setError(true); + } else { + const newSubs = change.map((c) => c.value ?? ''); + onQueryChange({ + ...query, + subscriptions: newSubs, + }); + setValues(findOptions([...subscriptions, ...variableOptionGroup.options], newSubs)); + setError(false); + } + }; + + return ( + + <> + + {error ? At least one subscription must be chosen. : null} + + + ); +}; + +export default SubscriptionField; diff --git a/public/app/plugins/datasource/azuremonitor/components/SubscriptionField.tsx b/public/app/plugins/datasource/azuremonitor/components/SubscriptionField.tsx deleted file mode 100644 index 396bed8b410..00000000000 --- a/public/app/plugins/datasource/azuremonitor/components/SubscriptionField.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; - -import { SelectableValue } from '@grafana/data'; -import { Select, MultiSelect } from '@grafana/ui'; - -import { selectors } from '../e2e/selectors'; -import { AzureMonitorQuery, AzureQueryEditorFieldProps, AzureMonitorOption, AzureQueryType } from '../types'; -import { findOptions } from '../utils/common'; - -import { Field } from './Field'; - -interface SubscriptionFieldProps extends AzureQueryEditorFieldProps { - onQueryChange: (newQuery: AzureMonitorQuery) => void; - subscriptions: AzureMonitorOption[]; - multiSelect?: boolean; -} - -const SubscriptionField = ({ - query, - subscriptions, - variableOptionGroup, - onQueryChange, - multiSelect = false, -}: SubscriptionFieldProps) => { - const handleChange = useCallback( - (change: SelectableValue) => { - if (!change.value) { - return; - } - - let newQuery: AzureMonitorQuery = { - ...query, - subscription: change.value, - }; - - if (query.queryType === AzureQueryType.AzureMonitor) { - newQuery.azureMonitor = { - ...newQuery.azureMonitor, - resources: undefined, - metricNamespace: undefined, - metricName: undefined, - aggregation: undefined, - timeGrain: '', - dimensionFilters: [], - }; - } - - onQueryChange(newQuery); - }, - [query, onQueryChange] - ); - - const onSubscriptionsChange = useCallback( - (change: Array>) => { - if (!change) { - return; - } - - onQueryChange({ - ...query, - subscriptions: change.map((c) => c.value ?? ''), - }); - }, - [query, onQueryChange] - ); - - const options = useMemo(() => [...subscriptions, variableOptionGroup], [subscriptions, variableOptionGroup]); - - return multiSelect ? ( - - - - ) : ( - -