Commit 1e2f3ca5 by Giordano Ricci Committed by GitHub

Explore: Expand template variables when redirecting from dashboard panel (#27354)

* Explore: Add interpolateVariablesInQueries to opentsdb  datasource

* Explore: Add interpolateVariablesInQueries to cloudwatch datasource

* Explore: Add interpolateVariablesInQueries to CloudMonitoring datasource

* Explore: Add interpolateVariablesInQueries to Azure datasource

* Explore: Add dimensions to interpolateMetricsQueryVariables in cloudwatch datasource
parent acf9938f
......@@ -137,7 +137,8 @@ export default class CloudMonitoringDatasource extends DataSourceApi<CloudMonito
const queries = options.targets
.map(this.migrateQuery)
.filter(this.shouldRunQuery)
.map(q => this.prepareTimeSeriesQuery(q, options));
.map(q => this.prepareTimeSeriesQuery(q, options.scopedVars))
.map(q => ({ ...q, intervalMs: options.intervalMs, type: 'timeSeriesQuery' }));
if (queries.length > 0) {
const { data } = await this.api.post({
......@@ -309,13 +310,13 @@ export default class CloudMonitoringDatasource extends DataSourceApi<CloudMonito
return query;
}
interpolateProps(object: { [key: string]: any } = {}, scopedVars: ScopedVars = {}): { [key: string]: any } {
interpolateProps<T extends Record<string, any>>(object: T, scopedVars: ScopedVars = {}): T {
return Object.entries(object).reduce((acc, [key, value]) => {
return {
...acc,
[key]: value && _.isString(value) ? this.templateSrv.replace(value, scopedVars) : value,
};
}, {});
}, {} as T);
}
shouldRunQuery(query: CloudMonitoringQuery): boolean {
......@@ -335,14 +336,12 @@ export default class CloudMonitoringDatasource extends DataSourceApi<CloudMonito
prepareTimeSeriesQuery(
{ metricQuery, refId, queryType, sloQuery }: CloudMonitoringQuery,
{ scopedVars, intervalMs }: DataQueryRequest<CloudMonitoringQuery>
) {
scopedVars: ScopedVars
): CloudMonitoringQuery {
return {
datasourceId: this.id,
refId,
queryType,
intervalMs: intervalMs,
type: 'timeSeriesQuery',
metricQuery: {
...this.interpolateProps(metricQuery, scopedVars),
projectName: this.templateSrv.replace(
......@@ -352,10 +351,14 @@ export default class CloudMonitoringDatasource extends DataSourceApi<CloudMonito
groupBys: this.interpolateGroupBys(metricQuery.groupBys || [], scopedVars),
view: metricQuery.view || 'FULL',
},
sloQuery: this.interpolateProps(sloQuery, scopedVars),
sloQuery: sloQuery && this.interpolateProps(sloQuery, scopedVars),
};
}
interpolateVariablesInQueries(queries: CloudMonitoringQuery[], scopedVars: ScopedVars): CloudMonitoringQuery[] {
return queries.map(query => this.prepareTimeSeriesQuery(query, scopedVars));
}
interpolateFilters(filters: string[], scopedVars: ScopedVars) {
const completeFilter = _.chunk(filters, 4)
.map(([key, operator, value, condition]) => ({
......
......@@ -39,6 +39,7 @@ import {
MetricQuery,
MetricRequest,
TSDBResponse,
isCloudWatchLogsQuery,
} from './types';
import { from, Observable, of, merge, zip } from 'rxjs';
import { catchError, finalize, map, mergeMap, tap, concatMap, scan, share, repeat, takeWhile } from 'rxjs/operators';
......@@ -927,12 +928,12 @@ export class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery, CloudWa
}
replace(
target: string,
scopedVars: ScopedVars | undefined,
target?: string,
scopedVars?: ScopedVars,
displayErrorIfIsMultiTemplateVariable?: boolean,
fieldName?: string
) {
if (displayErrorIfIsMultiTemplateVariable) {
if (displayErrorIfIsMultiTemplateVariable && !!target) {
const variable = this.templateSrv
.getVariables()
.find(({ name }) => name === this.templateSrv.getVariableName(target));
......@@ -973,6 +974,39 @@ export class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery, CloudWa
metricsQueries,
};
};
interpolateVariablesInQueries(queries: CloudWatchQuery[], scopedVars: ScopedVars): CloudWatchQuery[] {
if (!queries.length) {
return queries;
}
return queries.map(query => ({
...query,
region: this.getActualRegion(this.replace(query.region, scopedVars)),
expression: this.replace(query.expression, scopedVars),
...(!isCloudWatchLogsQuery(query) && this.interpolateMetricsQueryVariables(query, scopedVars)),
}));
}
interpolateMetricsQueryVariables(
query: CloudWatchMetricsQuery,
scopedVars: ScopedVars
): Pick<CloudWatchMetricsQuery, 'alias' | 'metricName' | 'namespace' | 'period' | 'dimensions'> {
return {
alias: this.replace(query.alias, scopedVars),
metricName: this.replace(query.metricName, scopedVars),
namespace: this.replace(query.namespace, scopedVars),
period: this.replace(query.period, scopedVars),
dimensions: Object.entries(query.dimensions).reduce((prev, [key, value]) => {
if (Array.isArray(value)) {
return { ...prev, [key]: value };
}
return { ...prev, [this.replace(key, scopedVars)]: this.replace(value, scopedVars) };
}, {}),
};
}
}
function withTeardown<T = any>(observable: Observable<T>, onUnsubscribe: () => void): Observable<T> {
......
......@@ -10,13 +10,20 @@ import {
DataQueryErrorType,
} from '@grafana/data';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { CloudWatchLogsQueryStatus, CloudWatchMetricsQuery, CloudWatchQuery, LogAction } from '../types';
import {
CloudWatchLogsQueryStatus,
CloudWatchMetricsQuery,
CloudWatchQuery,
LogAction,
CloudWatchLogsQuery,
} from '../types';
import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { convertToStoreState } from '../../../../../test/helpers/convertToStoreState';
import { getTemplateSrvDependencies } from 'test/helpers/getTemplateSrvDependencies';
import { of, interval } from 'rxjs';
import { CustomVariableModel, VariableHide } from '../../../../features/variables/types';
import { TimeSrvStub } from '../../../../../test/specs/helpers';
import * as rxjsUtils from '../utils/rxjs/increasingInterval';
......@@ -677,6 +684,69 @@ describe('CloudWatchDatasource', () => {
});
});
describe('When interpolating variables', () => {
beforeEach(() => {
jest.clearAllMocks();
ctx.mockedTemplateSrv = {
replace: jest.fn(),
};
ctx.ds = new CloudWatchDatasource(
instanceSettings,
ctx.mockedTemplateSrv,
(new TimeSrvStub() as unknown) as TimeSrv
);
});
it('should return an empty array if no queries are provided', () => {
expect(ctx.ds.interpolateVariablesInQueries([], {})).toHaveLength(0);
});
it('should replace correct variables in CloudWatchLogsQuery', () => {
const variableName = 'someVar';
const logQuery: CloudWatchLogsQuery = {
id: 'someId',
refId: 'someRefId',
queryMode: 'Logs',
expression: `$${variableName}`,
region: `$${variableName}`,
};
ctx.ds.interpolateVariablesInQueries([logQuery], {});
// We interpolate `expression` and `region` in CloudWatchLogsQuery
expect(ctx.mockedTemplateSrv.replace).toHaveBeenCalledWith(`$${variableName}`, {});
expect(ctx.mockedTemplateSrv.replace).toHaveBeenCalledTimes(2);
});
it('should replace correct variables in CloudWatchMetricsQuery', () => {
const variableName = 'someVar';
const logQuery: CloudWatchMetricsQuery = {
id: 'someId',
refId: 'someRefId',
queryMode: 'Metrics',
expression: `$${variableName}`,
region: `$${variableName}`,
period: `$${variableName}`,
alias: `$${variableName}`,
metricName: `$${variableName}`,
namespace: `$${variableName}`,
dimensions: {
[`$${variableName}`]: `$${variableName}`,
},
matchExact: false,
statistics: [],
};
ctx.ds.interpolateVariablesInQueries([logQuery], {});
// We interpolate `expression`, `region`, `period`, `alias`, `metricName`, `nameSpace` and `dimensions` in CloudWatchMetricsQuery
expect(ctx.mockedTemplateSrv.replace).toHaveBeenCalledWith(`$${variableName}`, {});
expect(ctx.mockedTemplateSrv.replace).toHaveBeenCalledTimes(8);
});
});
describe('When performing CloudWatch query for extended statistics', () => {
const query = {
range: defaultTimeRange,
......
import { DataQuery, SelectableValue, DataSourceJsonData } from '@grafana/data';
export interface CloudWatchMetricsQuery extends DataQuery {
queryMode: 'Metrics';
queryMode?: 'Metrics';
id: string;
region: string;
......@@ -44,6 +44,9 @@ export interface CloudWatchLogsQuery extends DataQuery {
export type CloudWatchQuery = CloudWatchMetricsQuery | CloudWatchLogsQuery;
export const isCloudWatchLogsQuery = (cloudwatchQuery: CloudWatchQuery): cloudwatchQuery is CloudWatchLogsQuery =>
(cloudwatchQuery as CloudWatchLogsQuery).queryMode === 'Logs';
export interface AnnotationQuery extends CloudWatchMetricsQuery {
prefixMatching: boolean;
actionPrefix: string;
......
......@@ -9,6 +9,7 @@ import {
DataSourceInstanceSettings,
DataQueryResponseData,
LoadingState,
ScopedVars,
} from '@grafana/data';
import { Observable, of, from } from 'rxjs';
import { DataSourceWithBackend } from '@grafana/runtime';
......@@ -255,4 +256,10 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
getSubscriptions() {
return this.azureMonitorDatasource.getSubscriptions();
}
interpolateVariablesInQueries(queries: AzureMonitorQuery[], scopedVars: ScopedVars): AzureMonitorQuery[] {
return queries.map(
query => this.pseudoDatasource[query.queryType].applyTemplateVariables(query, scopedVars) as AzureMonitorQuery
);
}
}
import angular from 'angular';
import _ from 'lodash';
import { dateMath, DataQueryRequest, DataSourceApi } from '@grafana/data';
import { dateMath, DataQueryRequest, DataSourceApi, ScopedVars } from '@grafana/data';
import { getBackendSrv } from '@grafana/runtime';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { OpenTsdbOptions, OpenTsdbQuery } from './types';
......@@ -493,6 +493,17 @@ export default class OpenTsDatasource extends DataSourceApi<OpenTsdbQuery, OpenT
});
}
interpolateVariablesInQueries(queries: OpenTsdbQuery[], scopedVars: ScopedVars): OpenTsdbQuery[] {
if (!queries.length) {
return queries;
}
return queries.map(query => ({
...query,
metric: this.templateSrv.replace(query.metric, scopedVars),
}));
}
convertToTSDBTime(date: any, roundUp: any, timezone: any) {
if (date === 'now') {
return null;
......
import OpenTsDatasource from '../datasource';
import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
import { OpenTsdbQuery } from '../types';
jest.mock('@grafana/runtime', () => ({
...((jest.requireActual('@grafana/runtime') as unknown) as object),
......@@ -108,4 +109,33 @@ describe('opentsdb', () => {
expect(requestOptions.params.q).toBe('bar');
});
});
describe('When interpolating variables', () => {
beforeEach(() => {
jest.clearAllMocks();
ctx.mockedTemplateSrv = {
replace: jest.fn(),
};
ctx.ds = new OpenTsDatasource(instanceSettings, ctx.mockedTemplateSrv);
});
it('should return an empty array if no queries are provided', () => {
expect(ctx.ds.interpolateVariablesInQueries([], {})).toHaveLength(0);
});
it('should replace correct variables', () => {
const variableName = 'someVar';
const logQuery: OpenTsdbQuery = {
refId: 'someRefId',
metric: `$${variableName}`,
};
ctx.ds.interpolateVariablesInQueries([logQuery], {});
expect(ctx.mockedTemplateSrv.replace).toHaveBeenCalledWith(`$${variableName}`, {});
expect(ctx.mockedTemplateSrv.replace).toHaveBeenCalledTimes(1);
});
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment