mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
AzureMonitor: display errors from requests for the dropdowns (#31921)
* AzureMonitor: display errors from requests for the dropdowns * switch to array of errors, using just the last one * unify error object * move files into utils * TESTS * fix tests
This commit is contained in:
parent
2179a2658e
commit
9a7c10cffe
@ -8,7 +8,7 @@ export default function createMockDatasource() {
|
||||
// We make this a partial so we get _some_ kind of type safety when making this, rather than
|
||||
// having it be any or casted immediately to Datasource
|
||||
const _mockDatasource: DeepPartial<Datasource> = {
|
||||
getVariables: jest.fn().mockReturnValueOnce([]),
|
||||
getVariables: jest.fn().mockReturnValue([]),
|
||||
|
||||
azureMonitorDatasource: {
|
||||
isConfigured() {
|
||||
|
@ -0,0 +1,22 @@
|
||||
export function invalidNamespaceError() {
|
||||
return {
|
||||
status: 404,
|
||||
statusText: 'Not Found',
|
||||
data: {
|
||||
error: {
|
||||
code: 'InvalidResourceNamespace',
|
||||
message: "The resource namespace 'grafanadev' is invalid.",
|
||||
},
|
||||
},
|
||||
config: {
|
||||
url:
|
||||
'api/datasources/proxy/31/azuremonitor/subscriptions/44693801-6ee6-49de-9b2d-9106972f9572/resourceGroups/grafanadev/providers/grafanadev/select/providers/microsoft.insights/metricdefinitions?api-version=2018-01-01&metricnamespace=select',
|
||||
method: 'GET',
|
||||
retry: 0,
|
||||
headers: {
|
||||
'X-Grafana-Org-Id': 1,
|
||||
},
|
||||
hideFromInspector: false,
|
||||
},
|
||||
};
|
||||
}
|
@ -3,7 +3,7 @@ import { Select } from '@grafana/ui';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
|
||||
import { Field } from '../Field';
|
||||
import { findOption } from '../common';
|
||||
import { findOption } from '../../utils/common';
|
||||
import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types';
|
||||
|
||||
interface AggregationFieldProps extends AzureQueryEditorFieldProps {
|
||||
|
@ -2,7 +2,7 @@ import React, { useCallback } from 'react';
|
||||
import { Button, Select, Input, HorizontalGroup, VerticalGroup, InlineLabel } from '@grafana/ui';
|
||||
|
||||
import { Field } from '../Field';
|
||||
import { findOption } from '../common';
|
||||
import { findOption } from '../../utils/common';
|
||||
import { AzureMetricDimension, AzureMonitorOption, AzureQueryEditorFieldProps } from '../../types';
|
||||
|
||||
interface DimensionFieldsProps extends AzureQueryEditorFieldProps {
|
||||
|
@ -3,15 +3,17 @@ import { Select } from '@grafana/ui';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
|
||||
import { Field } from '../Field';
|
||||
import { findOption, toOption } from '../common';
|
||||
import { findOption, toOption } from '../../utils/common';
|
||||
import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types';
|
||||
|
||||
const ERROR_SOURCE = 'metrics-metricname';
|
||||
const MetricName: React.FC<AzureQueryEditorFieldProps> = ({
|
||||
query,
|
||||
datasource,
|
||||
subscriptionId,
|
||||
variableOptionGroup,
|
||||
onQueryChange,
|
||||
setError,
|
||||
}) => {
|
||||
const [metricNames, setMetricNames] = useState<AzureMonitorOption[]>([]);
|
||||
|
||||
@ -28,10 +30,7 @@ const MetricName: React.FC<AzureQueryEditorFieldProps> = ({
|
||||
.then((results) => {
|
||||
setMetricNames(results.map(toOption));
|
||||
})
|
||||
.catch((err) => {
|
||||
// TODO: handle error
|
||||
console.error(err);
|
||||
});
|
||||
.catch((err) => setError(ERROR_SOURCE, err));
|
||||
}, [
|
||||
subscriptionId,
|
||||
query.azureMonitor.resourceGroup,
|
||||
|
@ -3,15 +3,17 @@ import { Select } from '@grafana/ui';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
|
||||
import { Field } from '../Field';
|
||||
import { findOption, toOption } from '../common';
|
||||
import { findOption, toOption } from '../../utils/common';
|
||||
import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types';
|
||||
|
||||
const ERROR_SOURCE = 'metrics-metricnamespace';
|
||||
const MetricNamespaceField: React.FC<AzureQueryEditorFieldProps> = ({
|
||||
query,
|
||||
datasource,
|
||||
subscriptionId,
|
||||
variableOptionGroup,
|
||||
onQueryChange,
|
||||
setError,
|
||||
}) => {
|
||||
const [metricNamespaces, setMetricNamespaces] = useState<AzureMonitorOption[]>([]);
|
||||
|
||||
@ -26,21 +28,18 @@ const MetricNamespaceField: React.FC<AzureQueryEditorFieldProps> = ({
|
||||
datasource
|
||||
.getMetricNamespaces(subscriptionId, resourceGroup, metricDefinition, resourceName)
|
||||
.then((results) => {
|
||||
// if (results.length === 1) {
|
||||
// onQueryChange({
|
||||
// ...query,
|
||||
// azureMonitor: {
|
||||
// ...query.azureMonitor,
|
||||
// metricNamespace: results[0].value,
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
if (results.length === 1) {
|
||||
onQueryChange({
|
||||
...query,
|
||||
azureMonitor: {
|
||||
...query.azureMonitor,
|
||||
metricNamespace: results[0].value,
|
||||
},
|
||||
});
|
||||
}
|
||||
setMetricNamespaces(results.map(toOption));
|
||||
})
|
||||
.catch((err) => {
|
||||
// TODO: handle error
|
||||
console.error(err);
|
||||
});
|
||||
.catch((err) => setError(ERROR_SOURCE, err));
|
||||
}, [
|
||||
subscriptionId,
|
||||
query.azureMonitor.resourceGroup,
|
||||
|
@ -22,6 +22,7 @@ describe('Azure Monitor QueryEditor', () => {
|
||||
datasource={mockDatasource}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onChange={() => {}}
|
||||
setError={() => {}}
|
||||
/>
|
||||
);
|
||||
await waitFor(() => expect(screen.getByTestId('azure-monitor-metrics-query-editor')).toBeInTheDocument());
|
||||
@ -50,6 +51,7 @@ describe('Azure Monitor QueryEditor', () => {
|
||||
datasource={mockDatasource}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onChange={onChange}
|
||||
setError={() => {}}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -94,6 +96,7 @@ describe('Azure Monitor QueryEditor', () => {
|
||||
datasource={mockDatasource}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onChange={onChange}
|
||||
setError={() => {}}
|
||||
/>
|
||||
);
|
||||
await waitFor(() => expect(screen.getByTestId('azure-monitor-metrics-query-editor')).toBeInTheDocument());
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import Datasource from '../../datasource';
|
||||
import { AzureMonitorQuery, AzureMonitorOption } from '../../types';
|
||||
import { AzureMonitorQuery, AzureMonitorOption, AzureMonitorErrorish } from '../../types';
|
||||
import { useMetricsMetadata } from '../metrics';
|
||||
import SubscriptionField from '../SubscriptionField';
|
||||
import MetricNamespaceField from './MetricNamespaceField';
|
||||
@ -22,6 +22,7 @@ interface MetricsQueryEditorProps {
|
||||
subscriptionId: string;
|
||||
onChange: (newQuery: AzureMonitorQuery) => void;
|
||||
variableOptionGroup: { label: string; options: AzureMonitorOption[] };
|
||||
setError: (source: string, error: AzureMonitorErrorish | undefined) => void;
|
||||
}
|
||||
|
||||
const MetricsQueryEditor: React.FC<MetricsQueryEditorProps> = ({
|
||||
@ -30,6 +31,7 @@ const MetricsQueryEditor: React.FC<MetricsQueryEditorProps> = ({
|
||||
subscriptionId,
|
||||
variableOptionGroup,
|
||||
onChange,
|
||||
setError,
|
||||
}) => {
|
||||
const metricsMetadata = useMetricsMetadata(datasource, query, subscriptionId, onChange);
|
||||
|
||||
@ -42,6 +44,7 @@ const MetricsQueryEditor: React.FC<MetricsQueryEditorProps> = ({
|
||||
subscriptionId={subscriptionId}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onQueryChange={onChange}
|
||||
setError={setError}
|
||||
/>
|
||||
|
||||
<ResourceGroupsField
|
||||
@ -50,6 +53,7 @@ const MetricsQueryEditor: React.FC<MetricsQueryEditorProps> = ({
|
||||
subscriptionId={subscriptionId}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onQueryChange={onChange}
|
||||
setError={setError}
|
||||
/>
|
||||
</InlineFieldRow>
|
||||
|
||||
@ -60,6 +64,7 @@ const MetricsQueryEditor: React.FC<MetricsQueryEditorProps> = ({
|
||||
subscriptionId={subscriptionId}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onQueryChange={onChange}
|
||||
setError={setError}
|
||||
/>
|
||||
<ResourceNameField
|
||||
query={query}
|
||||
@ -67,6 +72,7 @@ const MetricsQueryEditor: React.FC<MetricsQueryEditorProps> = ({
|
||||
subscriptionId={subscriptionId}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onQueryChange={onChange}
|
||||
setError={setError}
|
||||
/>
|
||||
</InlineFieldRow>
|
||||
|
||||
@ -77,6 +83,7 @@ const MetricsQueryEditor: React.FC<MetricsQueryEditorProps> = ({
|
||||
subscriptionId={subscriptionId}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onQueryChange={onChange}
|
||||
setError={setError}
|
||||
/>
|
||||
<MetricNameField
|
||||
query={query}
|
||||
@ -84,6 +91,7 @@ const MetricsQueryEditor: React.FC<MetricsQueryEditorProps> = ({
|
||||
subscriptionId={subscriptionId}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onQueryChange={onChange}
|
||||
setError={setError}
|
||||
/>
|
||||
</InlineFieldRow>
|
||||
<InlineFieldRow>
|
||||
@ -93,6 +101,7 @@ const MetricsQueryEditor: React.FC<MetricsQueryEditorProps> = ({
|
||||
subscriptionId={subscriptionId}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onQueryChange={onChange}
|
||||
setError={setError}
|
||||
aggregationOptions={metricsMetadata?.aggOptions ?? []}
|
||||
/>
|
||||
<TimeGrainField
|
||||
@ -101,6 +110,7 @@ const MetricsQueryEditor: React.FC<MetricsQueryEditorProps> = ({
|
||||
subscriptionId={subscriptionId}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onQueryChange={onChange}
|
||||
setError={setError}
|
||||
timeGrainOptions={metricsMetadata?.timeGrains ?? []}
|
||||
/>
|
||||
</InlineFieldRow>
|
||||
@ -110,6 +120,7 @@ const MetricsQueryEditor: React.FC<MetricsQueryEditorProps> = ({
|
||||
subscriptionId={subscriptionId}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onQueryChange={onChange}
|
||||
setError={setError}
|
||||
dimensionOptions={metricsMetadata?.dimensions ?? []}
|
||||
/>
|
||||
<TopField
|
||||
@ -118,6 +129,7 @@ const MetricsQueryEditor: React.FC<MetricsQueryEditorProps> = ({
|
||||
subscriptionId={subscriptionId}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onQueryChange={onChange}
|
||||
setError={setError}
|
||||
/>
|
||||
<LegendFormatField
|
||||
query={query}
|
||||
@ -125,6 +137,7 @@ const MetricsQueryEditor: React.FC<MetricsQueryEditorProps> = ({
|
||||
subscriptionId={subscriptionId}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onQueryChange={onChange}
|
||||
setError={setError}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -3,15 +3,17 @@ import { Select } from '@grafana/ui';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
|
||||
import { Field } from '../Field';
|
||||
import { findOption, toOption } from '../common';
|
||||
import { findOption, toOption } from '../../utils/common';
|
||||
import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types';
|
||||
|
||||
const ERROR_SOURCE = 'metrics-namespace';
|
||||
const NamespaceField: React.FC<AzureQueryEditorFieldProps> = ({
|
||||
query,
|
||||
datasource,
|
||||
subscriptionId,
|
||||
variableOptionGroup,
|
||||
onQueryChange,
|
||||
setError,
|
||||
}) => {
|
||||
const [namespaces, setNamespaces] = useState<AzureMonitorOption[]>([]);
|
||||
|
||||
@ -26,10 +28,7 @@ const NamespaceField: React.FC<AzureQueryEditorFieldProps> = ({
|
||||
datasource
|
||||
.getMetricDefinitions(subscriptionId, resourceGroup)
|
||||
.then((results) => setNamespaces(results.map(toOption)))
|
||||
.catch((err) => {
|
||||
// TODO: handle error
|
||||
console.error(err);
|
||||
});
|
||||
.catch((err) => setError(ERROR_SOURCE, err));
|
||||
}, [subscriptionId, query.azureMonitor.resourceGroup]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
|
@ -3,15 +3,17 @@ import { Select } from '@grafana/ui';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
|
||||
import { Field } from '../Field';
|
||||
import { findOption, toOption } from '../common';
|
||||
import { findOption, toOption } from '../../utils/common';
|
||||
import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types';
|
||||
|
||||
const ERROR_SOURCE = 'metrics-resourcegroups';
|
||||
const ResourceGroupsField: React.FC<AzureQueryEditorFieldProps> = ({
|
||||
query,
|
||||
datasource,
|
||||
subscriptionId,
|
||||
variableOptionGroup,
|
||||
onQueryChange,
|
||||
setError,
|
||||
}) => {
|
||||
const [resourceGroups, setResourceGroups] = useState<AzureMonitorOption[]>([]);
|
||||
|
||||
@ -23,11 +25,11 @@ const ResourceGroupsField: React.FC<AzureQueryEditorFieldProps> = ({
|
||||
|
||||
datasource
|
||||
.getResourceGroups(subscriptionId)
|
||||
.then((results) => setResourceGroups(results.map(toOption)))
|
||||
.catch((err) => {
|
||||
// TODO: handle error
|
||||
console.error(err);
|
||||
});
|
||||
.then((results) => {
|
||||
setResourceGroups(results.map(toOption));
|
||||
setError(ERROR_SOURCE, undefined);
|
||||
})
|
||||
.catch((err) => setError(ERROR_SOURCE, err));
|
||||
}, [subscriptionId]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
|
@ -3,15 +3,17 @@ import { Select } from '@grafana/ui';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
|
||||
import { Field } from '../Field';
|
||||
import { findOption, toOption } from '../common';
|
||||
import { findOption, toOption } from '../../utils/common';
|
||||
import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types';
|
||||
|
||||
const ERROR_SOURCE = 'metrics-resource';
|
||||
const ResourceNameField: React.FC<AzureQueryEditorFieldProps> = ({
|
||||
query,
|
||||
datasource,
|
||||
subscriptionId,
|
||||
variableOptionGroup,
|
||||
onQueryChange,
|
||||
setError,
|
||||
}) => {
|
||||
const [resourceNames, setResourceNames] = useState<AzureMonitorOption[]>([]);
|
||||
|
||||
@ -26,10 +28,7 @@ const ResourceNameField: React.FC<AzureQueryEditorFieldProps> = ({
|
||||
datasource
|
||||
.getResourceNames(subscriptionId, resourceGroup, metricDefinition)
|
||||
.then((results) => setResourceNames(results.map(toOption)))
|
||||
.catch((err) => {
|
||||
// TODO: handle error
|
||||
console.error(err);
|
||||
});
|
||||
.catch((err) => setError(ERROR_SOURCE, err));
|
||||
}, [subscriptionId, query.azureMonitor.resourceGroup, query.azureMonitor.metricDefinition]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
|
@ -3,7 +3,7 @@ import { Select } from '@grafana/ui';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
|
||||
import { Field } from '../Field';
|
||||
import { findOption } from '../common';
|
||||
import { findOption } from '../../utils/common';
|
||||
import TimegrainConverter from '../../time_grain_converter';
|
||||
import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types';
|
||||
|
||||
|
@ -7,6 +7,7 @@ import QueryEditor from './QueryEditor';
|
||||
import createMockQuery from '../../__mocks__/query';
|
||||
import createMockDatasource from '../../__mocks__/datasource';
|
||||
import { AzureQueryType } from '../../types';
|
||||
import { invalidNamespaceError } from '../../__mocks__/errors';
|
||||
|
||||
const variableOptionGroup = {
|
||||
label: 'Template variables',
|
||||
@ -67,4 +68,20 @@ describe('Azure Monitor QueryEditor', () => {
|
||||
queryType: AzureQueryType.LogAnalytics,
|
||||
});
|
||||
});
|
||||
|
||||
it('displays error messages from frontend Azure calls', async () => {
|
||||
const mockDatasource = createMockDatasource();
|
||||
mockDatasource.azureMonitorDatasource.getSubscriptions = jest.fn().mockRejectedValue(invalidNamespaceError());
|
||||
render(
|
||||
<QueryEditor
|
||||
query={createMockQuery()}
|
||||
datasource={mockDatasource}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
);
|
||||
await waitFor(() => expect(screen.getByTestId('azure-monitor-query-editor')).toBeInTheDocument());
|
||||
|
||||
expect(screen.getByText("The resource namespace 'grafanadev' is invalid.")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { Alert, VerticalGroup } from '@grafana/ui';
|
||||
import React from 'react';
|
||||
import Datasource from '../../datasource';
|
||||
import { AzureMonitorQuery, AzureQueryType, AzureMonitorOption } from '../../types';
|
||||
import { AzureMonitorQuery, AzureQueryType, AzureMonitorOption, AzureMonitorErrorish } from '../../types';
|
||||
import MetricsQueryEditor from '../MetricsQueryEditor';
|
||||
import QueryTypeField from './QueryTypeField';
|
||||
import useLastError from '../../utils/useLastError';
|
||||
|
||||
interface BaseQueryEditorProps {
|
||||
query: AzureMonitorQuery;
|
||||
@ -12,6 +14,7 @@ interface BaseQueryEditorProps {
|
||||
}
|
||||
|
||||
const QueryEditor: React.FC<BaseQueryEditorProps> = ({ query, datasource, onChange }) => {
|
||||
const [errorMessage, setError] = useLastError();
|
||||
const subscriptionId = query.subscription || datasource.azureMonitorDatasource.subscriptionId;
|
||||
const variableOptionGroup = {
|
||||
label: 'Template Variables',
|
||||
@ -21,19 +24,30 @@ const QueryEditor: React.FC<BaseQueryEditorProps> = ({ query, datasource, onChan
|
||||
return (
|
||||
<div data-testid="azure-monitor-query-editor">
|
||||
<QueryTypeField query={query} onQueryChange={onChange} />
|
||||
<EditorForQueryType
|
||||
subscriptionId={subscriptionId}
|
||||
query={query}
|
||||
datasource={datasource}
|
||||
onChange={onChange}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
/>
|
||||
|
||||
<VerticalGroup>
|
||||
<EditorForQueryType
|
||||
subscriptionId={subscriptionId}
|
||||
query={query}
|
||||
datasource={datasource}
|
||||
onChange={onChange}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
setError={setError}
|
||||
/>
|
||||
|
||||
{errorMessage && (
|
||||
<Alert severity="error" title="An error occurred while requesting metadata from Azure Monitor">
|
||||
{errorMessage}
|
||||
</Alert>
|
||||
)}
|
||||
</VerticalGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface EditorForQueryTypeProps extends BaseQueryEditorProps {
|
||||
subscriptionId: string;
|
||||
setError: (source: string, error: AzureMonitorErrorish | undefined) => void;
|
||||
}
|
||||
|
||||
const EditorForQueryType: React.FC<EditorForQueryTypeProps> = ({
|
||||
@ -42,6 +56,7 @@ const EditorForQueryType: React.FC<EditorForQueryTypeProps> = ({
|
||||
datasource,
|
||||
variableOptionGroup,
|
||||
onChange,
|
||||
setError,
|
||||
}) => {
|
||||
switch (query.queryType) {
|
||||
case AzureQueryType.AzureMonitor:
|
||||
@ -52,6 +67,7 @@ const EditorForQueryType: React.FC<EditorForQueryTypeProps> = ({
|
||||
datasource={datasource}
|
||||
onChange={onChange}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
setError={setError}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { Select } from '@grafana/ui';
|
||||
import { Field } from '../Field';
|
||||
import { AzureMonitorQuery, AzureQueryType } from '../../types';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { findOption } from '../common';
|
||||
import { findOption } from '../../utils/common';
|
||||
|
||||
const QUERY_TYPES = [
|
||||
{ value: AzureQueryType.AzureMonitor, label: 'Metrics' },
|
||||
|
@ -3,18 +3,20 @@ import { SelectableValue } from '@grafana/data';
|
||||
import { Select } from '@grafana/ui';
|
||||
|
||||
import { AzureMonitorQuery, AzureQueryType, AzureQueryEditorFieldProps, AzureMonitorOption } from '../types';
|
||||
import { findOption } from './common';
|
||||
import { findOption } from '../utils/common';
|
||||
import { Field } from './Field';
|
||||
|
||||
interface SubscriptionFieldProps extends AzureQueryEditorFieldProps {
|
||||
onQueryChange: (newQuery: AzureMonitorQuery) => void;
|
||||
}
|
||||
|
||||
const ERROR_SOURCE = 'metrics-subscription';
|
||||
const SubscriptionField: React.FC<SubscriptionFieldProps> = ({
|
||||
datasource,
|
||||
query,
|
||||
variableOptionGroup,
|
||||
onQueryChange,
|
||||
setError,
|
||||
}) => {
|
||||
const [subscriptions, setSubscriptions] = useState<AzureMonitorOption[]>([]);
|
||||
|
||||
@ -23,31 +25,35 @@ const SubscriptionField: React.FC<SubscriptionFieldProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
datasource.azureMonitorDatasource.getSubscriptions().then((results) => {
|
||||
const newSubscriptions = results.map((v) => ({ label: v.text, value: v.value, description: v.value }));
|
||||
setSubscriptions(newSubscriptions);
|
||||
datasource.azureMonitorDatasource
|
||||
.getSubscriptions()
|
||||
.then((results) => {
|
||||
const newSubscriptions = results.map((v) => ({ label: v.text, value: v.value, description: v.value }));
|
||||
setSubscriptions(newSubscriptions);
|
||||
setError(ERROR_SOURCE, undefined);
|
||||
|
||||
// Set a default subscription ID, if we can
|
||||
let newSubscription = query.subscription;
|
||||
// Set a default subscription ID, if we can
|
||||
let newSubscription = query.subscription;
|
||||
|
||||
if (!newSubscription && query.queryType === AzureQueryType.AzureMonitor) {
|
||||
newSubscription = datasource.azureMonitorDatasource.subscriptionId;
|
||||
} else if (!query.subscription && query.queryType === AzureQueryType.LogAnalytics) {
|
||||
newSubscription =
|
||||
datasource.azureLogAnalyticsDatasource.logAnalyticsSubscriptionId ||
|
||||
datasource.azureLogAnalyticsDatasource.subscriptionId;
|
||||
}
|
||||
if (!newSubscription && query.queryType === AzureQueryType.AzureMonitor) {
|
||||
newSubscription = datasource.azureMonitorDatasource.subscriptionId;
|
||||
} else if (!query.subscription && query.queryType === AzureQueryType.LogAnalytics) {
|
||||
newSubscription =
|
||||
datasource.azureLogAnalyticsDatasource.logAnalyticsSubscriptionId ||
|
||||
datasource.azureLogAnalyticsDatasource.subscriptionId;
|
||||
}
|
||||
|
||||
if (!newSubscription && newSubscriptions.length > 0) {
|
||||
newSubscription = newSubscriptions[0].value;
|
||||
}
|
||||
if (!newSubscription && newSubscriptions.length > 0) {
|
||||
newSubscription = newSubscriptions[0].value;
|
||||
}
|
||||
|
||||
newSubscription !== query.subscription &&
|
||||
onQueryChange({
|
||||
...query,
|
||||
subscription: newSubscription,
|
||||
});
|
||||
});
|
||||
newSubscription !== query.subscription &&
|
||||
onQueryChange({
|
||||
...query,
|
||||
subscription: newSubscription,
|
||||
});
|
||||
})
|
||||
.catch((err) => setError(ERROR_SOURCE, err));
|
||||
}, []);
|
||||
|
||||
const handleChange = useCallback(
|
||||
@ -62,6 +68,8 @@ const SubscriptionField: React.FC<SubscriptionFieldProps> = ({
|
||||
};
|
||||
|
||||
if (query.queryType === AzureQueryType.AzureMonitor) {
|
||||
// TODO: set the fields to undefined so we don't
|
||||
// get "resource group select could not be found" errors
|
||||
newQuery.azureMonitor = {
|
||||
...newQuery.azureMonitor,
|
||||
resourceGroup: undefined,
|
||||
|
@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
|
||||
|
||||
import Datasource from '../datasource';
|
||||
import { AzureMonitorQuery } from '../types';
|
||||
import { convertTimeGrainsToMs } from './common';
|
||||
import { convertTimeGrainsToMs } from '../utils/common';
|
||||
|
||||
export interface MetricMetadata {
|
||||
aggOptions: Array<{ label: string; value: string }>;
|
||||
|
@ -7,7 +7,7 @@ import { TemplateSrv } from '@grafana/runtime';
|
||||
import { auto } from 'angular';
|
||||
import { DataFrame, PanelEvents } from '@grafana/data';
|
||||
import { AzureQueryType, AzureMetricQuery, AzureMonitorQuery } from './types';
|
||||
import { convertTimeGrainsToMs } from './components/common';
|
||||
import { convertTimeGrainsToMs } from './utils/common';
|
||||
import Datasource from './datasource';
|
||||
|
||||
export interface ResultFormat {
|
||||
|
@ -90,6 +90,10 @@ export interface InsightsAnalyticsQuery {
|
||||
resultFormat: string;
|
||||
}
|
||||
|
||||
// Represents an errors that come back from frontend requests.
|
||||
// Not totally sure how accurate this type is.
|
||||
export type AzureMonitorErrorish = Error;
|
||||
|
||||
// Azure Monitor API Types
|
||||
|
||||
export interface AzureMonitorMetricsMetadataResponse {
|
||||
@ -191,4 +195,5 @@ export interface AzureQueryEditorFieldProps {
|
||||
variableOptionGroup: { label: string; options: AzureMonitorOption[] };
|
||||
|
||||
onQueryChange: (newQuery: AzureMonitorQuery) => void;
|
||||
setError: (source: string, error: AzureMonitorErrorish | undefined) => void;
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
import { invalidNamespaceError } from '../__mocks__/errors';
|
||||
import messageFromError from './messageFromError';
|
||||
|
||||
describe('AzureMonitor: messageFromError', () => {
|
||||
it('returns message from Error exception', () => {
|
||||
const err = new Error('wowee an error');
|
||||
expect(messageFromError(err)).toBe('wowee an error');
|
||||
});
|
||||
|
||||
it('returns message from Azure API error', () => {
|
||||
const err = invalidNamespaceError();
|
||||
expect(messageFromError(err)).toBe("The resource namespace 'grafanadev' is invalid.");
|
||||
});
|
||||
});
|
@ -0,0 +1,33 @@
|
||||
export default function messageFromError(error: any): string | undefined {
|
||||
if (!error || typeof error !== 'object') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (typeof error.message === 'string') {
|
||||
return error.message;
|
||||
}
|
||||
|
||||
if (typeof error.data?.error?.message === 'string') {
|
||||
return error.data.error.message;
|
||||
}
|
||||
|
||||
// Copied from the old Angular code - this might be checking for errors in places
|
||||
// that the new code just doesnt use.
|
||||
// As new error objects are discovered they should be added to the above code, rather
|
||||
// than below
|
||||
const maybeAMessage =
|
||||
error.error?.data?.error?.innererror?.innererror?.message ||
|
||||
error.error?.data?.error?.innererror?.message ||
|
||||
error.error?.data?.error?.message ||
|
||||
error.error?.data?.message ||
|
||||
error.data?.message ||
|
||||
error;
|
||||
|
||||
if (typeof maybeAMessage === 'string') {
|
||||
return maybeAMessage;
|
||||
} else if (maybeAMessage && maybeAMessage.toString) {
|
||||
return maybeAMessage.toString();
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
import useLastError from './useLastError';
|
||||
|
||||
describe('AzureMonitor: useLastError', () => {
|
||||
it('returns the set error', () => {
|
||||
const { result } = renderHook(() => useLastError());
|
||||
|
||||
act(() => {
|
||||
result.current[1]('component-a', new Error('an error'));
|
||||
});
|
||||
|
||||
expect(result.current[0]).toBe('an error');
|
||||
});
|
||||
|
||||
it('returns the most recent error', () => {
|
||||
const { result } = renderHook(() => useLastError());
|
||||
|
||||
act(() => {
|
||||
result.current[1]('component-a', new Error('component a error'));
|
||||
result.current[1]('component-b', new Error('component b error'));
|
||||
result.current[1]('component-a', new Error('second component a error'));
|
||||
});
|
||||
|
||||
expect(result.current[0]).toBe('second component a error');
|
||||
});
|
||||
});
|
@ -0,0 +1,38 @@
|
||||
import { useState, useCallback, useMemo } from 'react';
|
||||
import { AzureMonitorErrorish } from '../types';
|
||||
import messageFromError from './messageFromError';
|
||||
|
||||
type SourcedError = [string, AzureMonitorErrorish];
|
||||
|
||||
export default function useLastError() {
|
||||
const [errors, setErrors] = useState<SourcedError[]>([]);
|
||||
|
||||
// Handles errors from any child components that request data to display their options
|
||||
const addError = useCallback((errorSource: string, error: AzureMonitorErrorish | undefined) => {
|
||||
setErrors((errors) => {
|
||||
const errorsCopy = [...errors];
|
||||
const index = errors.findIndex(([vSource]) => vSource === errorSource);
|
||||
|
||||
// If there's already an error, remove it. If we're setting a new error
|
||||
// below, we'll move it to the front
|
||||
if (index > -1) {
|
||||
errorsCopy.splice(index, 1);
|
||||
}
|
||||
|
||||
// And then add the new error to the top of the array. If error is defined, it was already
|
||||
// removed above.
|
||||
if (error) {
|
||||
errorsCopy.unshift([errorSource, error]);
|
||||
}
|
||||
|
||||
return errorsCopy;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const errorMessage = useMemo(() => {
|
||||
const recentError = errors[0];
|
||||
return recentError && messageFromError(recentError[1]);
|
||||
}, [errors]);
|
||||
|
||||
return [errorMessage, addError] as const;
|
||||
}
|
Loading…
Reference in New Issue
Block a user