[Azure monitor] Support variables for Azure Resource Group subscription picker (#38620)

This commit is contained in:
shuotli 2021-08-31 00:15:35 -07:00 committed by GitHub
parent f567bef0bf
commit f1529b83a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 126 additions and 101 deletions

View File

@ -20,7 +20,7 @@ import { Observable, from } from 'rxjs';
import { mergeMap } from 'rxjs/operators';
import { getAuthType, getAzureCloud, getAzurePortalUrl } from '../credentials';
import { isGUIDish } from '../components/ResourcePicker/utils';
import { routeNames } from '../utils/common';
import { interpolateVariable, routeNames } from '../utils/common';
interface AdhocQuery {
datasourceId: number;
@ -118,7 +118,7 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
workspace = this.firstWorkspace;
}
const query = templateSrv.replace(item.query, scopedVars, this.interpolateVariable);
const query = templateSrv.replace(item.query, scopedVars, interpolateVariable);
return {
refId: target.refId,
@ -272,7 +272,7 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
private buildQuery(query: string, options: any, workspace: string): AdhocQuery[] {
const querystringBuilder = new LogAnalyticsQuerystringBuilder(
getTemplateSrv().replace(query, {}, this.interpolateVariable),
getTemplateSrv().replace(query, {}, interpolateVariable),
options,
'TimeGenerated'
);
@ -293,29 +293,6 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
return queries;
}
interpolateVariable(value: string, variable: { multi: any; includeAll: any }) {
if (typeof value === 'string') {
if (variable.multi || variable.includeAll) {
return "'" + value + "'";
} else {
return value;
}
}
if (typeof value === 'number') {
return value;
}
const quotedValues = map(value, (val) => {
if (typeof value === 'number') {
return value;
}
return "'" + val + "'";
});
return quotedValues.join(',');
}
async getDefaultOrFirstSubscription(): Promise<string | undefined> {
if (this.defaultSubscriptionId) {
return this.defaultSubscriptionId;

View File

@ -2,9 +2,6 @@ import { TemplateSrv } from 'app/features/templating/template_srv';
import { backendSrv } from 'app/core/services/backend_srv';
import AzureResourceGraphDatasource from './azure_resource_graph_datasource';
import { CustomVariableModel, initialVariableModelState, VariableHide } from 'app/features/variables/types';
import { initialCustomVariableModelState } from 'app/features/variables/custom/reducer';
const templateSrv = new TemplateSrv();
const single: CustomVariableModel = {
...initialVariableModelState,
@ -38,7 +35,30 @@ const multi: CustomVariableModel = {
type: 'custom',
};
templateSrv.init([single, multi]);
const subs: CustomVariableModel = {
...initialVariableModelState,
id: 'subs',
name: 'subs',
index: 3,
current: { value: ['sub-foo', 'sub-baz'], text: 'sub-foo + sub-baz', selected: true },
options: [
{ selected: true, value: 'sub-foo', text: 'sub-foo' },
{ selected: false, value: 'sub-bar', text: 'sub-bar' },
{ selected: true, value: 'sub-baz', text: 'sub-baz' },
],
multi: true,
includeAll: false,
query: '',
hide: VariableHide.dontHide,
type: 'custom',
};
const templateSrv = new TemplateSrv({
getVariables: () => [subs, single, multi],
getVariableWithName: jest.fn(),
getFilteredVariables: jest.fn(),
});
templateSrv.init([subs, single, multi]);
jest.mock('app/core/services/backend_srv');
jest.mock('@grafana/runtime', () => ({
@ -77,7 +97,7 @@ describe('AzureResourceGraphDatasource', () => {
azureResourceGraph: { query: 'Resources | var1-foo', resultFormat: 'table' },
queryType: 'Azure Resource Graph',
refId: undefined,
subscriptions: undefined,
subscriptions: [],
});
});
@ -95,53 +115,27 @@ describe('AzureResourceGraphDatasource', () => {
},
queryType: 'Azure Resource Graph',
refId: undefined,
subscriptions: undefined,
subscriptions: [],
});
});
});
describe('When interpolating variables', () => {
beforeEach(() => {
ctx.variable = { ...initialCustomVariableModelState };
});
describe('and value is a string', () => {
it('should return an unquoted value', () => {
expect(ctx.ds.interpolateVariable('abc', ctx.variable)).toEqual('abc');
});
});
describe('and value is a number', () => {
it('should return an unquoted value', () => {
expect(ctx.ds.interpolateVariable(1000, ctx.variable)).toEqual(1000);
});
});
describe('and value is an array of strings', () => {
it('should return comma separated quoted values', () => {
expect(ctx.ds.interpolateVariable(['a', 'b', 'c'], ctx.variable)).toEqual("'a','b','c'");
});
});
describe('and variable allows multi-value and value is a string', () => {
it('should return a quoted value', () => {
ctx.variable.multi = true;
expect(ctx.ds.interpolateVariable('abc', ctx.variable)).toEqual("'abc'");
});
});
describe('and variable contains single quote', () => {
it('should return a quoted value', () => {
ctx.variable.multi = true;
expect(ctx.ds.interpolateVariable("a'bc", ctx.variable)).toEqual("'a'bc'");
});
});
describe('and variable allows all and value is a string', () => {
it('should return a quoted value', () => {
ctx.variable.includeAll = true;
expect(ctx.ds.interpolateVariable('abc', ctx.variable)).toEqual("'abc'");
});
it('should apply subscription variable', () => {
const target = {
subscriptions: ['$subs'],
azureResourceGraph: {
query: 'resources | where $__contains(name, $var3)',
resultFormat: '',
},
};
expect(ctx.ds.applyTemplateVariables(target)).toStrictEqual({
azureResourceGraph: {
query: `resources | where $__contains(name, 'var3-foo','var3-baz')`,
resultFormat: 'table',
},
queryType: 'Azure Resource Graph',
refId: undefined,
subscriptions: ['sub-foo', 'sub-baz'],
});
});
});

View File

@ -3,6 +3,7 @@ import _ from 'lodash';
import { AzureMonitorQuery, AzureDataSourceJsonData, AzureQueryType } from '../types';
import { ScopedVars } from '@grafana/data';
import { getTemplateSrv, DataSourceWithBackend } from '@grafana/runtime';
import { interpolateVariable } from '../utils/common';
export default class AzureResourceGraphDatasource extends DataSourceWithBackend<
AzureMonitorQuery,
@ -19,39 +20,26 @@ export default class AzureResourceGraphDatasource extends DataSourceWithBackend<
}
const templateSrv = getTemplateSrv();
const query = templateSrv.replace(item.query, scopedVars, this.interpolateVariable);
const variableNames = templateSrv.getVariables().map((v) => `$${v.name}`);
const subscriptionVar = _.find(target.subscriptions, (sub) => _.includes(variableNames, sub));
const interpolatedSubscriptions = templateSrv
.replace(subscriptionVar, scopedVars, (v: any) => v)
.split(',')
.filter((v) => v.length > 0);
const subscriptions = [
...interpolatedSubscriptions,
..._.filter(target.subscriptions, (sub) => !_.includes(variableNames, sub)),
];
const query = templateSrv.replace(item.query, scopedVars, interpolateVariable);
return {
refId: target.refId,
queryType: AzureQueryType.AzureResourceGraph,
subscriptions: target.subscriptions,
subscriptions,
azureResourceGraph: {
resultFormat: 'table',
query,
},
};
}
interpolateVariable(value: string, variable: { multi: any; includeAll: any }) {
if (typeof value === 'string') {
if (variable.multi || variable.includeAll) {
return "'" + value + "'";
} else {
return value;
}
}
if (typeof value === 'number') {
return value;
}
const quotedValues = _.map(value, (val) => {
if (typeof value === 'number') {
return value;
}
return "'" + val + "'";
});
return quotedValues.join(',');
}
}

View File

@ -69,7 +69,7 @@ const SubscriptionField: React.FC<SubscriptionFieldProps> = ({
<MultiSelect
menuShouldPortal
isClearable
value={findOptions(subscriptions, query.subscriptions)}
value={findOptions([...subscriptions, ...variableOptionGroup.options], query.subscriptions)}
inputId="azure-monitor-subscriptions-field"
onChange={onSubscriptionsChange}
options={options}

View File

@ -1,4 +1,5 @@
import { hasOption } from './common';
import { initialCustomVariableModelState } from 'app/features/variables/custom/reducer';
import { hasOption, interpolateVariable } from './common';
describe('AzureMonitor: hasOption', () => {
it('can find an option in flat array', () => {
@ -40,3 +41,44 @@ describe('AzureMonitor: hasOption', () => {
expect(hasOption(options, 'c-b')).toBeTruthy();
});
});
describe('When interpolating variables', () => {
describe('and value is a string', () => {
it('should return an unquoted value', () => {
expect(interpolateVariable('abc', initialCustomVariableModelState)).toEqual('abc');
});
});
describe('and value is a number', () => {
it('should return an unquoted value', () => {
expect(interpolateVariable(1000, initialCustomVariableModelState)).toEqual(1000);
});
});
describe('and value is an array of strings', () => {
it('should return comma separated quoted values', () => {
expect(interpolateVariable(['a', 'b', 'c'], initialCustomVariableModelState)).toEqual("'a','b','c'");
});
});
describe('and variable allows multi-value and value is a string', () => {
it('should return a quoted value', () => {
const variable = { ...initialCustomVariableModelState, multi: true };
expect(interpolateVariable('abc', variable)).toEqual("'abc'");
});
});
describe('and variable contains single quote', () => {
it('should return a quoted value', () => {
const variable = { ...initialCustomVariableModelState, multi: true };
expect(interpolateVariable("a'bc", variable)).toEqual("'a'bc'");
});
});
describe('and variable allows all and value is a string', () => {
it('should return a quoted value', () => {
const variable = { ...initialCustomVariableModelState, includeAll: true };
expect(interpolateVariable('abc', variable)).toEqual("'abc'");
});
});
});

View File

@ -1,3 +1,4 @@
import { map } from 'lodash';
import { rangeUtil } from '@grafana/data';
import TimegrainConverter from '../time_grain_converter';
import { AzureMonitorOption } from '../types';
@ -36,3 +37,26 @@ export const routeNames = {
appInsights: 'appinsights',
resourceGraph: 'resourcegraph',
};
export function interpolateVariable(value: any, variable: { multi: any; includeAll: any }) {
if (typeof value === 'string') {
if (variable.multi || variable.includeAll) {
return "'" + value + "'";
} else {
return value;
}
}
if (typeof value === 'number') {
return value;
}
const quotedValues = map(value, (val) => {
if (typeof value === 'number') {
return value;
}
return "'" + val + "'";
});
return quotedValues.join(',');
}