mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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
This commit is contained in:
parent
acf9938f61
commit
1e2f3ca599
@ -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,
|
||||
|
@ -1,7 +1,7 @@
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
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;
|
||||
|
@ -1,5 +1,6 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user