AzureMonitor: remove requirement for default subscription (#34787)

* Do not require default subscription for Azure Monitor

* Fix explore URLs when default subscription not set

* Test datasource fixes

* Added comment

* Fix first or default subscription/workspace

* SubscriptionField doesn't depend on Log Analytics

* Tests fixed

* Select default subscription only when user clicked
This commit is contained in:
Sergey Kostrukov 2021-05-28 02:19:31 -07:00 committed by GitHub
parent 48dc78fd48
commit 179eb0898e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 196 additions and 157 deletions

View File

@ -1,7 +1,7 @@
import AzureMonitorDatasource from '../datasource';
import FakeSchemaData from './__mocks__/schema';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { AzureLogsVariable } from '../types';
import { AzureLogsVariable, DatasourceValidationResult } from '../types';
import { toUtc } from '@grafana/data';
import { backendSrv } from 'app/core/services/backend_srv';
@ -112,17 +112,15 @@ describe('AzureLogAnalyticsDatasource', () => {
};
beforeEach(() => {
ctx.instanceSettings.jsonData.logAnalyticsSubscriptionId = 'xxx';
ctx.instanceSettings.jsonData.logAnalyticsTenantId = 'xxx';
ctx.instanceSettings.jsonData.logAnalyticsClientId = 'xxx';
ctx.instanceSettings.jsonData.azureAuthType = 'msi';
datasourceRequestMock.mockImplementation(() => Promise.reject(error));
});
it('should return error status and a detailed error message', () => {
return ctx.ds.testDatasource().then((results: any) => {
expect(results.status).toEqual('error');
expect(results.message).toEqual(
'1. Azure Log Analytics: Bad Request: InvalidApiVersionParameter. An error message. '
return ctx.ds.azureLogAnalyticsDatasource.testDatasource().then((result: DatasourceValidationResult) => {
expect(result.status).toEqual('error');
expect(result.message).toEqual(
'Azure Log Analytics requires access to Azure Monitor but had the following error: Bad Request: InvalidApiVersionParameter. An error message.'
);
});
});

View File

@ -37,7 +37,7 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
baseUrl: string;
applicationId: string;
subscriptionId: string;
defaultSubscriptionId?: string;
azureMonitorUrl: string;
defaultOrFirstWorkspace: string;
@ -55,12 +55,24 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
this.azureMonitorUrl = `/${managementRoute}/subscriptions`;
this.url = instanceSettings.url || '';
this.subscriptionId = this.instanceSettings.jsonData.logAnalyticsSubscriptionId || '';
this.defaultSubscriptionId = this.instanceSettings.jsonData.logAnalyticsSubscriptionId || '';
this.defaultOrFirstWorkspace = this.instanceSettings.jsonData.logAnalyticsDefaultWorkspace || '';
}
isConfigured(): boolean {
return !!this.subscriptionId && this.subscriptionId.length > 0;
// If validation didn't return any error then the data source is properly configured
return !this.validateDatasource();
}
async getSubscriptions(): Promise<Array<{ text: string; value: string }>> {
if (!this.isConfigured()) {
return [];
}
const url = `${this.azureMonitorUrl}?api-version=2019-03-01`;
return await this.doRequest(url).then((result: any) => {
return ResponseParser.parseSubscriptions(result);
});
}
async getWorkspaces(subscription: string): Promise<AzureLogsVariable[]> {
@ -73,8 +85,8 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
);
}
getWorkspaceList(subscription: string): Promise<any> {
const subscriptionId = getTemplateSrv().replace(subscription || this.subscriptionId);
private getWorkspaceList(subscription: string): Promise<any> {
const subscriptionId = getTemplateSrv().replace(subscription || this.defaultSubscriptionId);
const workspaceListUrl =
this.azureMonitorUrl +
@ -109,7 +121,7 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
workspace = this.defaultOrFirstWorkspace;
}
const subscriptionId = templateSrv.replace(target.subscription || this.subscriptionId, scopedVars);
const subscriptionId = templateSrv.replace(target.subscription || this.defaultSubscriptionId, scopedVars);
const query = templateSrv.replace(item.query, scopedVars, this.interpolateVariable);
return {
@ -182,10 +194,10 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
}
async getWorkspaceDetails(workspaceId: string) {
if (!this.subscriptionId) {
if (!this.defaultSubscriptionId) {
return {};
}
const response = await this.getWorkspaceList(this.subscriptionId);
const response = await this.getWorkspaceList(this.defaultSubscriptionId);
const details = response.data.value.find((o: any) => {
return o.properties.customerId === workspaceId;
@ -216,8 +228,8 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
metricFindQueryInternal(query: string): Promise<MetricFindValue[]> {
// workspaces() - Get workspaces in the default subscription
const workspacesQuery = query.match(/^workspaces\(\)/i);
if (workspacesQuery) {
return this.getWorkspaces(this.subscriptionId);
if (workspacesQuery && this.defaultSubscriptionId) {
return this.getWorkspaces(this.defaultSubscriptionId);
}
// workspaces("abc-def-etc") - Get workspaces a specified subscription
@ -228,6 +240,10 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
// Execute the query as KQL to the default or first workspace
return this.getDefaultOrFirstWorkspace().then((resourceURI) => {
if (!resourceURI) {
return [];
}
const queries = this.buildQuery(query, null, resourceURI);
const promises = this.doQueries(queries);
@ -299,16 +315,32 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
return quotedValues.join(',');
}
getDefaultOrFirstWorkspace() {
async getDefaultOrFirstSubscription(): Promise<string | undefined> {
if (this.defaultSubscriptionId) {
return this.defaultSubscriptionId;
}
const subscriptions = await this.getSubscriptions();
return subscriptions[0]?.value;
}
async getDefaultOrFirstWorkspace(): Promise<string | undefined> {
if (this.defaultOrFirstWorkspace) {
return Promise.resolve(this.defaultOrFirstWorkspace);
return this.defaultOrFirstWorkspace;
}
return this.getWorkspaces(this.subscriptionId).then((workspaces) => {
this.defaultOrFirstWorkspace = workspaces[0].value;
const subscriptionId = await this.getDefaultOrFirstSubscription();
if (!subscriptionId) {
return undefined;
}
return this.defaultOrFirstWorkspace;
});
const workspaces = await this.getWorkspaces(subscriptionId);
const workspace = workspaces[0]?.value;
if (workspace) {
this.defaultOrFirstWorkspace = workspace;
}
return workspace;
}
annotationQuery(options: any) {
@ -371,21 +403,36 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
}
// TODO: update to be completely resource-centric
testDatasource(): Promise<DatasourceValidationResult> {
async testDatasource(): Promise<DatasourceValidationResult> {
const validationError = this.validateDatasource();
if (validationError) {
return Promise.resolve(validationError);
return validationError;
}
return this.getDefaultOrFirstWorkspace()
.then((resourceOrWorkspace) => {
const url = isGUIDish(resourceOrWorkspace)
? `${this.baseUrl}/v1/workspaces/${resourceOrWorkspace}/metadata`
: `${this.baseUrl}/v1${resourceOrWorkspace}/metadata`;
let resourceOrWorkspace: string;
try {
const result = await this.getDefaultOrFirstWorkspace();
if (!result) {
return {
status: 'error',
message: 'Workspace not found.',
};
}
resourceOrWorkspace = result;
} catch (e) {
let message = 'Azure Log Analytics requires access to Azure Monitor but had the following error: ';
return {
status: 'error',
message: this.getErrorMessage(message, e),
};
}
return this.doRequest(url);
})
.then<DatasourceValidationResult>((response: any) => {
try {
const url = isGUIDish(resourceOrWorkspace)
? `${this.baseUrl}/v1/workspaces/${resourceOrWorkspace}/metadata`
: `${this.baseUrl}/v1${resourceOrWorkspace}/metadata`;
return await this.doRequest(url).then<DatasourceValidationResult>((response: any) => {
if (response.status === 200) {
return {
status: 'success',
@ -398,20 +445,14 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
status: 'error',
message: 'Returned http status code ' + response.status,
};
})
.catch((error: any) => {
let message = 'Azure Log Analytics: ';
if (error.config && error.config.url && error.config.url.indexOf('workspacesloganalytics') > -1) {
message = 'Azure Log Analytics requires access to Azure Monitor but had the following error: ';
}
message = this.getErrorMessage(message, error);
return {
status: 'error',
message: message,
};
});
} catch (e) {
let message = 'Azure Log Analytics: ';
return {
status: 'error',
message: this.getErrorMessage(message, e),
};
}
}
private getErrorMessage(message: string, error: any) {
@ -447,13 +488,6 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
}
}
if (!this.isValidConfigField(this.subscriptionId)) {
return {
status: 'error',
message: 'The Subscription Id field is required.',
};
}
return undefined;
}

View File

@ -1,4 +1,4 @@
import { concat, find, flattenDeep, forEach, map } from 'lodash';
import { concat, find, flattenDeep, forEach, get, map } from 'lodash';
import { AnnotationEvent, dateTime, TimeSeries } from '@grafana/data';
import { AzureLogsTableData, AzureLogsVariable } from '../types';
import { AzureLogAnalyticsMetadata } from '../types/logAnalyticsMetadata';
@ -147,6 +147,27 @@ export default class ResponseParser {
static dateTimeToEpoch(dateTimeValue: any) {
return dateTime(dateTimeValue).valueOf();
}
static parseSubscriptions(result: any): Array<{ text: string; value: string }> {
const list: Array<{ text: string; value: string }> = [];
if (!result) {
return list;
}
const valueFieldName = 'subscriptionId';
const textFieldName = 'displayName';
for (let i = 0; i < result.data.value.length; i++) {
if (!find(list, ['value', get(result.data.value[i], valueFieldName)])) {
list.push({
text: `${get(result.data.value[i], textFieldName)}`,
value: get(result.data.value[i], valueFieldName),
});
}
}
return list;
}
}
// matches (name):(type) = (defaultValue)

View File

@ -3,7 +3,7 @@ import AzureMonitorDatasource from '../datasource';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { DataSourceInstanceSettings } from '@grafana/data';
import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
import { AzureDataSourceJsonData } from '../types';
import { AzureDataSourceJsonData, DatasourceValidationResult } from '../types';
const templateSrv = new TemplateSrv();
@ -47,17 +47,14 @@ describe('AzureMonitorDatasource', () => {
};
beforeEach(() => {
ctx.instanceSettings.jsonData.tenantId = 'xxx';
ctx.instanceSettings.jsonData.clientId = 'xxx';
ctx.instanceSettings.jsonData.azureAuthType = 'msi';
datasourceRequestMock.mockImplementation(() => Promise.reject(error));
});
it('should return error status and a detailed error message', () => {
return ctx.ds.testDatasource().then((results: any) => {
expect(results.status).toEqual('error');
expect(results.message).toEqual(
'1. Azure Monitor: Bad Request: InvalidApiVersionParameter. An error message. '
);
return ctx.ds.azureMonitorDatasource.testDatasource().then((result: DatasourceValidationResult) => {
expect(result.status).toEqual('error');
expect(result.message).toEqual('Azure Monitor: Bad Request: InvalidApiVersionParameter. An error message.');
});
});
});
@ -78,8 +75,8 @@ describe('AzureMonitorDatasource', () => {
});
it('should return success status', () => {
return ctx.ds.testDatasource().then((results: any) => {
expect(results.status).toEqual('success');
return ctx.ds.azureMonitorDatasource.testDatasource().then((result: DatasourceValidationResult) => {
expect(result.status).toEqual('success');
});
});
});
@ -99,6 +96,7 @@ describe('AzureMonitorDatasource', () => {
};
beforeEach(() => {
ctx.instanceSettings.jsonData.azureAuthType = 'msi';
datasourceRequestMock.mockImplementation(() => Promise.resolve(response));
});
@ -515,10 +513,11 @@ describe('AzureMonitorDatasource', () => {
};
beforeEach(() => {
ctx.instanceSettings.jsonData.azureAuthType = 'msi';
datasourceRequestMock.mockImplementation(() => Promise.resolve(response));
});
it('should return list of Resource Groups', () => {
it('should return list of subscriptions', () => {
return ctx.ds.getSubscriptions().then((results: Array<{ text: string; value: string }>) => {
expect(results.length).toEqual(1);
expect(results[0].text).toEqual('Primary Subscription');

View File

@ -44,7 +44,7 @@ const aggregationTypeMap: Record<string, number> = {
export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureMonitorQuery, AzureDataSourceJsonData> {
apiVersion = '2018-01-01';
apiPreviewVersion = '2017-12-01-preview';
subscriptionId: string;
defaultSubscriptionId?: string;
baseUrl: string;
resourceGroup: string;
resourceName: string;
@ -56,7 +56,7 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
super(instanceSettings);
this.timeSrv = getTimeSrv();
this.subscriptionId = instanceSettings.jsonData.subscriptionId!;
this.defaultSubscriptionId = instanceSettings.jsonData.subscriptionId;
const cloud = getAzureCloud(instanceSettings);
const route = getManagementApiRoute(cloud);
@ -67,7 +67,8 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
}
isConfigured(): boolean {
return !!this.subscriptionId && this.subscriptionId.length > 0;
// If validation didn't return any error then the data source is properly configured
return !this.validateDatasource();
}
filterQuery(item: AzureMonitorQuery): boolean {
@ -105,9 +106,13 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
): Promise<DataQueryResponse> {
if (res.data) {
for (const df of res.data) {
const metricQuery = metricQueries[df.refId]?.azureMonitor;
if (metricQuery) {
const url = this.buildAzurePortalUrl(metricQuery, this.subscriptionId, this.timeSrv.timeRange());
const metricQuery = metricQueries[df.refId];
if (metricQuery && metricQuery.azureMonitor) {
const url = this.buildAzurePortalUrl(
metricQuery.azureMonitor,
metricQuery.subscription,
this.timeSrv.timeRange()
);
for (const field of df.fields) {
field.config.links = [
@ -174,7 +179,7 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
const templateSrv = getTemplateSrv();
const subscriptionId = templateSrv.replace(target.subscription || this.subscriptionId, scopedVars);
const subscriptionId = templateSrv.replace(target.subscription || this.defaultSubscriptionId, scopedVars);
const resourceGroup = templateSrv.replace(item.resourceGroup, scopedVars);
const resourceName = templateSrv.replace(item.resourceName, scopedVars);
const metricNamespace = templateSrv.replace(item.metricNamespace, scopedVars);
@ -229,8 +234,8 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
}
const resourceGroupsQuery = query.match(/^ResourceGroups\(\)/i);
if (resourceGroupsQuery) {
return this.getResourceGroups(this.subscriptionId);
if (resourceGroupsQuery && this.defaultSubscriptionId) {
return this.getResourceGroups(this.defaultSubscriptionId);
}
const resourceGroupsQueryWithSub = query.match(/^ResourceGroups\(([^\)]+?)(,\s?([^,]+?))?\)/i);
@ -239,9 +244,9 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
}
const metricDefinitionsQuery = query.match(/^Namespaces\(([^\)]+?)(,\s?([^,]+?))?\)/i);
if (metricDefinitionsQuery) {
if (metricDefinitionsQuery && this.defaultSubscriptionId) {
if (!metricDefinitionsQuery[3]) {
return this.getMetricDefinitions(this.subscriptionId, this.toVariable(metricDefinitionsQuery[1]));
return this.getMetricDefinitions(this.defaultSubscriptionId, this.toVariable(metricDefinitionsQuery[1]));
}
}
@ -254,10 +259,10 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
}
const resourceNamesQuery = query.match(/^ResourceNames\(([^,]+?),\s?([^,]+?)\)/i);
if (resourceNamesQuery) {
if (resourceNamesQuery && this.defaultSubscriptionId) {
const resourceGroup = this.toVariable(resourceNamesQuery[1]);
const metricDefinition = this.toVariable(resourceNamesQuery[2]);
return this.getResourceNames(this.subscriptionId, resourceGroup, metricDefinition);
return this.getResourceNames(this.defaultSubscriptionId, resourceGroup, metricDefinition);
}
const resourceNamesQueryWithSub = query.match(/^ResourceNames\(([^,]+?),\s?([^,]+?),\s?(.+?)\)/i);
@ -269,11 +274,11 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
}
const metricNamespaceQuery = query.match(/^MetricNamespace\(([^,]+?),\s?([^,]+?),\s?([^,]+?)\)/i);
if (metricNamespaceQuery) {
if (metricNamespaceQuery && this.defaultSubscriptionId) {
const resourceGroup = this.toVariable(metricNamespaceQuery[1]);
const metricDefinition = this.toVariable(metricNamespaceQuery[2]);
const resourceName = this.toVariable(metricNamespaceQuery[3]);
return this.getMetricNamespaces(this.subscriptionId, resourceGroup, metricDefinition, resourceName);
return this.getMetricNamespaces(this.defaultSubscriptionId, resourceGroup, metricDefinition, resourceName);
}
const metricNamespaceQueryWithSub = query.match(
@ -288,13 +293,19 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
}
const metricNamesQuery = query.match(/^MetricNames\(([^,]+?),\s?([^,]+?),\s?([^,]+?),\s?([^,]+?)\)/i);
if (metricNamesQuery) {
if (metricNamesQuery && this.defaultSubscriptionId) {
if (metricNamesQuery[3].indexOf(',') === -1) {
const resourceGroup = this.toVariable(metricNamesQuery[1]);
const metricDefinition = this.toVariable(metricNamesQuery[2]);
const resourceName = this.toVariable(metricNamesQuery[3]);
const metricNamespace = this.toVariable(metricNamesQuery[4]);
return this.getMetricNames(this.subscriptionId, resourceGroup, metricDefinition, resourceName, metricNamespace);
return this.getMetricNames(
this.defaultSubscriptionId,
resourceGroup,
metricDefinition,
resourceName,
metricNamespace
);
}
}
@ -318,9 +329,13 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
return getTemplateSrv().replace((metric || '').trim());
}
getSubscriptions() {
async getSubscriptions(): Promise<Array<{ text: string; value: string }>> {
if (!this.isConfigured()) {
return [];
}
const url = `${this.baseUrl}?api-version=2019-03-01`;
return this.doRequest(url).then((result: any) => {
return await this.doRequest(url).then((result: any) => {
return ResponseParser.parseSubscriptions(result);
});
}
@ -459,15 +474,16 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
});
}
testDatasource(): Promise<DatasourceValidationResult> {
async testDatasource(): Promise<DatasourceValidationResult> {
const validationError = this.validateDatasource();
if (validationError) {
return Promise.resolve(validationError);
}
const url = `${this.baseUrl}?api-version=2019-03-01`;
return this.doRequest(url)
.then<DatasourceValidationResult>((response: any) => {
try {
const url = `${this.baseUrl}?api-version=2019-03-01`;
return await this.doRequest(url).then<DatasourceValidationResult>((response: any) => {
if (response.status === 200) {
return {
status: 'success',
@ -480,25 +496,25 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
status: 'error',
message: 'Returned http status code ' + response.status,
};
})
.catch((error: any) => {
let message = 'Azure Monitor: ';
message += error.statusText ? error.statusText + ': ' : '';
if (error.data && error.data.error && error.data.error.code) {
message += error.data.error.code + '. ' + error.data.error.message;
} else if (error.data && error.data.error) {
message += error.data.error;
} else if (error.data) {
message += error.data;
} else {
message += 'Cannot connect to Azure Monitor REST API.';
}
return {
status: 'error',
message: message,
};
});
} catch (e) {
let message = 'Azure Monitor: ';
message += e.statusText ? e.statusText + ': ' : '';
if (e.data && e.data.error && e.data.error.code) {
message += e.data.error.code + '. ' + e.data.error.message;
} else if (e.data && e.data.error) {
message += e.data.error;
} else if (e.data) {
message += e.data;
} else {
message += 'Cannot connect to Azure Monitor REST API.';
}
return {
status: 'error',
message: message,
};
}
}
private validateDatasource(): DatasourceValidationResult | undefined {
@ -520,13 +536,6 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
}
}
if (!this.isValidConfigField(this.subscriptionId)) {
return {
status: 'error',
message: 'The Subscription Id field is required.',
};
}
return undefined;
}

View File

@ -8,7 +8,7 @@ import QueryField from './QueryField';
interface LogsQueryEditorProps {
query: AzureMonitorQuery;
datasource: Datasource;
subscriptionId: string;
subscriptionId?: string;
onChange: (newQuery: AzureMonitorQuery) => void;
variableOptionGroup: { label: string; options: AzureMonitorOption[] };
setError: (source: string, error: AzureMonitorErrorish | undefined) => void;

View File

@ -29,7 +29,7 @@ export const AzureCredentialsForm: FunctionComponent<Props> = (props: Props) =>
const hasRequiredFields = isCredentialsComplete(credentials);
const [subscriptions, setSubscriptions] = useState<Array<SelectableValue<string>>>([]);
const [loadSubscriptions, onLoadSubscriptions] = useReducer((val) => val + 1, 0);
const [loadSubscriptionsClicked, onLoadSubscriptions] = useReducer((val) => val + 1, 0);
useEffect(() => {
if (!getSubscriptions || !hasRequiredFields) {
updateSubscriptions([]);
@ -38,7 +38,7 @@ export const AzureCredentialsForm: FunctionComponent<Props> = (props: Props) =>
let canceled = false;
getSubscriptions().then((result) => {
if (!canceled) {
updateSubscriptions(result);
updateSubscriptions(result, loadSubscriptionsClicked);
}
});
return () => {
@ -46,18 +46,18 @@ export const AzureCredentialsForm: FunctionComponent<Props> = (props: Props) =>
};
// This effect is intended to be called only once initially and on Load Subscriptions click
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [loadSubscriptions]);
}, [loadSubscriptionsClicked]);
const updateSubscriptions = (received: Array<SelectableValue<string>>) => {
const updateSubscriptions = (received: Array<SelectableValue<string>>, autoSelect = false) => {
setSubscriptions(received);
if (getSubscriptions) {
if (!credentials.defaultSubscriptionId && received.length > 0) {
// Setting the default subscription if subscriptions received but no default subscription selected
if (autoSelect && !credentials.defaultSubscriptionId && received.length > 0) {
// Selecting the default subscription if subscriptions received but no default subscription selected
onSubscriptionChange(received[0]);
} else if (credentials.defaultSubscriptionId) {
const found = received.find((opt) => opt.value === credentials.defaultSubscriptionId);
if (!found) {
// Unsetting the default found if it isn't found among the received subscriptions
// Unselecting the default subscription if it isn't found among the received subscriptions
onSubscriptionChange(undefined);
}
}

View File

@ -10,7 +10,7 @@ import useMigrations from './useMigrations';
interface LogsQueryEditorProps {
query: AzureMonitorQuery;
datasource: Datasource;
subscriptionId: string;
subscriptionId?: string;
onChange: (newQuery: AzureMonitorQuery) => void;
variableOptionGroup: { label: string; options: AzureMonitorOption[] };
setError: (source: string, error: AzureMonitorErrorish | undefined) => void;

View File

@ -19,6 +19,7 @@ const WorkspaceField: React.FC<AzureQueryEditorFieldProps> = ({
useEffect(() => {
if (!subscriptionId) {
workspaces.length > 0 && setWorkspaces([]);
return;
}
datasource

View File

@ -19,7 +19,7 @@ import { InlineFieldRow } from '@grafana/ui';
interface MetricsQueryEditorProps {
query: AzureMonitorQuery;
datasource: Datasource;
subscriptionId: string;
subscriptionId?: string;
onChange: (newQuery: AzureMonitorQuery) => void;
variableOptionGroup: { label: string; options: AzureMonitorOption[] };
setError: (source: string, error: AzureMonitorErrorish | undefined) => void;

View File

@ -20,7 +20,7 @@ interface BaseQueryEditorProps {
const QueryEditor: React.FC<BaseQueryEditorProps> = ({ query, datasource, onChange }) => {
const [errorMessage, setError] = useLastError();
const subscriptionId = query.subscription || datasource.azureMonitorDatasource.subscriptionId;
const subscriptionId = query.subscription || datasource.azureMonitorDatasource.defaultSubscriptionId;
const variableOptionGroup = {
label: 'Template Variables',
options: datasource.getVariables().map((v) => ({ label: v, value: v })),
@ -52,7 +52,7 @@ const QueryEditor: React.FC<BaseQueryEditorProps> = ({ query, datasource, onChan
};
interface EditorForQueryTypeProps extends BaseQueryEditorProps {
subscriptionId: string;
subscriptionId?: string;
setError: (source: string, error: AzureMonitorErrorish | undefined) => void;
}

View File

@ -23,10 +23,6 @@ const SubscriptionField: React.FC<SubscriptionFieldProps> = ({
const [subscriptions, setSubscriptions] = useState<AzureMonitorOption[]>([]);
useEffect(() => {
if (!datasource.azureMonitorDatasource.isConfigured()) {
return;
}
datasource.azureMonitorDatasource
.getSubscriptions()
.then((results) => {
@ -34,28 +30,22 @@ const SubscriptionField: React.FC<SubscriptionFieldProps> = ({
setSubscriptions(newSubscriptions);
setError(ERROR_SOURCE, undefined);
// 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.subscriptionId;
}
let newSubscription = query.subscription || datasource.azureMonitorDatasource.defaultSubscriptionId;
if (!newSubscription && newSubscriptions.length > 0) {
newSubscription = newSubscriptions[0].value;
}
newSubscription !== query.subscription &&
if (newSubscription && newSubscription !== query.subscription) {
onQueryChange({
...query,
subscription: newSubscription,
});
}
})
.catch((err) => setError(ERROR_SOURCE, err));
}, [
datasource.azureLogAnalyticsDatasource?.subscriptionId,
datasource.azureMonitorDatasource?.defaultSubscriptionId,
datasource.azureMonitorDatasource,
onQueryChange,
query,

View File

@ -14,7 +14,7 @@ export interface MetricMetadata {
export function useMetricsMetadata(
datasource: Datasource,
query: AzureMonitorQuery,
subscriptionId: string,
subscriptionId: string | undefined,
onQueryChange: (newQuery: AzureMonitorQuery) => void
) {
const [metricMetadata, setMetricMetadata] = useState<MetricMetadata>({

View File

@ -150,26 +150,13 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
async testDatasource(): Promise<DatasourceValidationResult> {
const promises: Array<Promise<DatasourceValidationResult>> = [];
if (this.azureMonitorDatasource.isConfigured()) {
promises.push(this.azureMonitorDatasource.testDatasource());
}
promises.push(this.azureMonitorDatasource.testDatasource());
promises.push(this.azureLogAnalyticsDatasource.testDatasource());
if (this.appInsightsDatasource.isConfigured()) {
promises.push(this.appInsightsDatasource.testDatasource());
}
if (this.azureLogAnalyticsDatasource.isConfigured()) {
promises.push(this.azureLogAnalyticsDatasource.testDatasource());
}
if (promises.length === 0) {
return {
status: 'error',
message: `Nothing configured. At least one of the API's must be configured.`,
title: 'Error',
};
}
return await Promise.all(promises).then((results) => {
let status: 'success' | 'error' = 'success';
let message = '';

View File

@ -222,7 +222,7 @@ export interface AzureMonitorOption<T = string> {
export interface AzureQueryEditorFieldProps {
query: AzureMonitorQuery;
datasource: Datasource;
subscriptionId: string;
subscriptionId?: string;
variableOptionGroup: { label: string; options: AzureMonitorOption[] };
onQueryChange: (newQuery: AzureMonitorQuery) => void;