Chore: Remove angular dependency from prometheus datasource (#20536)

* Chore: Remove angular dependency from prometheus datasource
This commit is contained in:
kay delaney 2019-11-21 15:36:56 +00:00 committed by GitHub
parent 37051cd844
commit fcad439c29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 539 additions and 494 deletions

View File

@ -1,12 +1,33 @@
const backendSrv = {
/**
* Creates a pretty bogus prom response. Definitelly needs more work but right now we do not test the contents of the
* messages anyway.
*/
function makePromResponse() {
return {
data: {
data: {
result: [
{
metric: {
__name__: 'test_metric',
},
values: [[1568369640, 1]],
},
],
resultType: 'matrix',
},
},
};
}
export const backendSrv = {
get: jest.fn(),
getDashboard: jest.fn(),
getDashboardByUid: jest.fn(),
getFolderByUid: jest.fn(),
post: jest.fn(),
resolveCancelerIfExists: jest.fn(),
datasourceRequest: jest.fn(() => Promise.resolve(makePromResponse())),
};
export function getBackendSrv() {
return backendSrv;
}
export const getBackendSrv = jest.fn().mockReturnValue(backendSrv);

View File

@ -2,46 +2,51 @@ import { PrometheusDatasource } from './datasource';
import { DataSourceInstanceSettings } from '@grafana/data';
import { PromContext, PromOptions } from './types';
import { dateTime, LoadingState } from '@grafana/data';
import { getBackendSrv, backendSrv } from 'app/core/services/__mocks__/backend_srv';
jest.mock('app/core/services/backend_srv');
jest.mock('app/features/dashboard/services/TimeSrv', () => ({
__esModule: true,
getTimeSrv: jest.fn().mockReturnValue({
timeRange(): any {
return {
from: dateTime(),
to: dateTime(),
};
},
}),
}));
jest.mock('app/features/templating/template_srv', () => {
return {
replace: jest.fn(() => null),
getAdhocFilters: jest.fn((): any[] => []),
};
});
beforeEach(() => {
getBackendSrv.mockClear();
for (const method in backendSrv) {
(backendSrv as any)[method].mockClear();
}
});
const defaultInstanceSettings: DataSourceInstanceSettings<PromOptions> = {
url: 'test_prom',
jsonData: {},
} as any;
const backendSrvMock: any = {
datasourceRequest: jest.fn(),
};
const templateSrvMock: any = {
replace(): null {
return null;
},
getAdhocFilters(): any[] {
return [];
},
};
const timeSrvMock: any = {
timeRange(): any {
return {
from: dateTime(),
to: dateTime(),
};
},
};
describe('datasource', () => {
describe('query', () => {
const ds = new PrometheusDatasource(
defaultInstanceSettings,
{} as any,
backendSrvMock,
templateSrvMock,
timeSrvMock
);
let ds: PrometheusDatasource;
beforeEach(() => {
ds = new PrometheusDatasource(defaultInstanceSettings);
});
it('returns empty array when no queries', done => {
expect.assertions(2);
ds.query(makeQuery([])).subscribe({
next(next) {
expect(next.data).toEqual([]);
@ -55,7 +60,7 @@ describe('datasource', () => {
it('performs time series queries', done => {
expect.assertions(2);
backendSrvMock.datasourceRequest.mockReturnValueOnce(Promise.resolve(makePromResponse()));
ds.query(makeQuery([{}])).subscribe({
next(next) {
expect(next.data.length).not.toBe(0);
@ -69,7 +74,7 @@ describe('datasource', () => {
it('with 2 queries and used from Explore, sends results as they arrive', done => {
expect.assertions(4);
backendSrvMock.datasourceRequest.mockReturnValue(Promise.resolve(makePromResponse()));
const responseStatus = [LoadingState.Loading, LoadingState.Done];
ds.query(makeQuery([{ context: PromContext.Explore }, { context: PromContext.Explore }])).subscribe({
next(next) {
@ -84,7 +89,6 @@ describe('datasource', () => {
it('with 2 queries and used from Panel, waits for all to finish until sending Done status', done => {
expect.assertions(2);
backendSrvMock.datasourceRequest.mockReturnValue(Promise.resolve(makePromResponse()));
ds.query(makeQuery([{ context: PromContext.Panel }, { context: PromContext.Panel }])).subscribe({
next(next) {
expect(next.data.length).not.toBe(0);
@ -117,25 +121,3 @@ function makeQuery(targets: any[]): any {
interval: '15s',
};
}
/**
* Creates a pretty bogus prom response. Definitelly needs more work but right now we do not test the contents of the
* messages anyway.
*/
function makePromResponse() {
return {
data: {
data: {
result: [
{
metric: {
__name__: 'test_metric',
},
values: [[1568369640, 1]],
},
],
resultType: 'matrix',
},
},
};
}

View File

@ -1,5 +1,6 @@
// Libraries
import _ from 'lodash';
import cloneDeep from 'lodash/cloneDeep';
import defaults from 'lodash/defaults';
import $ from 'jquery';
// Services & Utils
import kbn from 'app/core/utils/kbn';
@ -23,17 +24,28 @@ import { filter, map, tap } from 'rxjs/operators';
import PrometheusMetricFindQuery from './metric_find_query';
import { ResultTransformer } from './result_transformer';
import PrometheusLanguageProvider from './language_provider';
import { BackendSrv } from 'app/core/services/backend_srv';
import { getBackendSrv } from 'app/core/services/backend_srv';
import addLabelToQuery from './add_label_to_query';
import { getQueryHints } from './query_hints';
import { expandRecordingRules } from './language_utils';
// Types
import { PromContext, PromOptions, PromQuery, PromQueryRequest } from './types';
import { safeStringifyValue } from 'app/core/utils/explore';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import templateSrv from 'app/features/templating/template_srv';
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
import TableModel from 'app/core/table_model';
interface RequestOptions {
method?: string;
url?: string;
headers?: Record<string, string>;
transformRequest?: (data: any) => string;
data?: any;
withCredentials?: boolean;
silent?: boolean;
requestId?: string;
}
export interface PromDataQueryResponse {
data: {
status: string;
@ -46,6 +58,14 @@ export interface PromDataQueryResponse {
cancelled?: boolean;
}
export interface PromLabelQueryResponse {
data: {
status: string;
data: string[];
};
cancelled?: boolean;
}
export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions> {
type: string;
editorSrc: string;
@ -62,14 +82,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
resultTransformer: ResultTransformer;
customQueryParameters: any;
/** @ngInject */
constructor(
instanceSettings: DataSourceInstanceSettings<PromOptions>,
private $q: angular.IQService,
private backendSrv: BackendSrv,
private templateSrv: TemplateSrv,
private timeSrv: TimeSrv
) {
constructor(instanceSettings: DataSourceInstanceSettings<PromOptions>) {
super(instanceSettings);
this.type = 'prometheus';
@ -95,8 +108,8 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
return query.expr;
}
_addTracingHeaders(httpOptions: any, options: any) {
httpOptions.headers = options.headers || {};
_addTracingHeaders(httpOptions: PromQueryRequest, options: DataQueryRequest<PromQuery>) {
httpOptions.headers = {};
const proxyMode = !this.url.match(/^http/);
if (proxyMode) {
httpOptions.headers['X-Dashboard-Id'] = options.dashboardId;
@ -104,27 +117,25 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
}
}
_request(url: string, data?: any, options?: any) {
options = _.defaults(options || {}, {
_request(url: string, data: Record<string, string> = {}, options?: RequestOptions) {
options = defaults(options || {}, {
url: this.url + url,
method: this.httpMethod,
headers: {},
});
if (options.method === 'GET') {
if (!_.isEmpty(data)) {
if (data && Object.keys(data).length) {
options.url =
options.url +
'?' +
_.map(data, (v, k) => {
return encodeURIComponent(k) + '=' + encodeURIComponent(v);
}).join('&');
Object.entries(data)
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
.join('&');
}
} else {
options.headers['Content-Type'] = 'application/x-www-form-urlencoded';
options.transformRequest = (data: any) => {
return $.param(data);
};
options.transformRequest = (data: any) => $.param(data);
options.data = data;
}
@ -136,7 +147,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
options.headers.Authorization = this.basicAuth;
}
return this.backendSrv.datasourceRequest(options);
return getBackendSrv().datasourceRequest(options as Required<RequestOptions>);
}
// Use this for tab completion features, wont publish response to other components
@ -144,7 +155,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
return this._request(url, null, { method: 'GET', silent: true });
}
interpolateQueryExpr(value: any, variable: any, defaultFormatFn: any) {
interpolateQueryExpr(value: string | string[] = [], variable: any) {
// if no multi or include all do not regexEscape
if (!variable.multi && !variable.includeAll) {
return prometheusRegularEscape(value);
@ -154,12 +165,12 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
return prometheusSpecialRegexEscape(value);
}
const escapedValues = _.map(value, prometheusSpecialRegexEscape);
const escapedValues = value.map(val => prometheusSpecialRegexEscape(val));
return escapedValues.join('|');
}
targetContainsTemplate(target: PromQuery) {
return this.templateSrv.variableExists(target.expr);
return templateSrv.variableExists(target.expr);
}
processResult = (response: any, query: PromQueryRequest, target: PromQuery, responseListLength: number) => {
@ -199,7 +210,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
if (target.showingTable) {
// create instant target only if Table is showed in Explore
const instantTarget: any = _.cloneDeep(target);
const instantTarget: any = cloneDeep(target);
instantTarget.format = 'table';
instantTarget.instant = true;
instantTarget.valueWithRefId = true;
@ -227,14 +238,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
};
calledFromExplore = (options: DataQueryRequest<PromQuery>): boolean => {
let exploreTargets = 0;
for (let index = 0; index < options.targets.length; index++) {
const target = options.targets[index];
if (target.context === PromContext.Explore) {
exploreTargets++;
}
}
const exploreTargets = options.targets.filter(target => target.context === PromContext.Explore).length;
return exploreTargets === options.targets.length;
};
@ -244,7 +248,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
const { queries, activeTargets } = this.prepareTargets(options, start, end);
// No valid targets, return the empty result to save a round trip.
if (_.isEmpty(queries)) {
if (!queries || !queries.length) {
return of({
data: [],
state: LoadingState.Done,
@ -340,7 +344,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
let interval = kbn.interval_to_seconds(options.interval);
// Minimum interval ("Min step"), if specified for the query or datasource. or same as interval otherwise
const minInterval = kbn.interval_to_seconds(
this.templateSrv.replace(target.interval, options.scopedVars) || options.interval
templateSrv.replace(target.interval, options.scopedVars) || options.interval
);
const intervalFactor = target.intervalFactor || 1;
// Adjust the interval to take into account any specified minimum and interval factor plus Prometheus limits
@ -360,7 +364,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
let expr = target.expr;
// Apply adhoc filters
const adhocFilters = this.templateSrv.getAdhocFilters(this.name);
const adhocFilters = templateSrv.getAdhocFilters(this.name);
expr = adhocFilters.reduce((acc: string, filter: { key?: any; operator?: any; value?: any }) => {
const { key, operator } = filter;
let { value } = filter;
@ -371,11 +375,18 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
}, expr);
// Only replace vars in expression after having (possibly) updated interval vars
query.expr = this.templateSrv.replace(expr, scopedVars, this.interpolateQueryExpr);
query.expr = templateSrv.replace(expr, scopedVars, this.interpolateQueryExpr);
// Align query interval with step to allow query caching and to ensure
// that about-same-time query results look the same.
const adjusted = alignRange(start, end, query.step, this.timeSrv.timeRange().to.utcOffset() * 60);
const adjusted = alignRange(
start,
end,
query.step,
getTimeSrv()
.timeRange()
.to.utcOffset() * 60
);
query.start = adjusted.start;
query.end = adjusted.end;
this._addTracingHeaders(query, options);
@ -474,45 +485,36 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
return error;
};
performSuggestQuery(query: string, cache = false) {
const url = '/api/v1/label/__name__/values';
if (cache && this.metricsNameCache && this.metricsNameCache.expire > Date.now()) {
return this.$q.when(
_.filter(this.metricsNameCache.data, metricName => {
return metricName.indexOf(query) !== 1;
})
);
async performSuggestQuery(query: string, cache = false) {
if (cache && this.metricsNameCache?.expire > Date.now()) {
return this.metricsNameCache.data.filter((metricName: any) => metricName.indexOf(query) !== 1);
}
return this.metadataRequest(url).then((result: PromDataQueryResponse) => {
this.metricsNameCache = {
data: result.data.data,
expire: Date.now() + 60 * 1000,
};
return _.filter(result.data.data, metricName => {
return metricName.indexOf(query) !== 1;
});
});
const response: PromLabelQueryResponse = await this.metadataRequest('/api/v1/label/__name__/values');
this.metricsNameCache = {
data: response.data.data,
expire: Date.now() + 60 * 1000,
};
return response.data.data.filter(metricName => metricName.indexOf(query) !== 1);
}
metricFindQuery(query: string) {
if (!query) {
return this.$q.when([]);
return Promise.resolve([]);
}
const scopedVars = {
__interval: { text: this.interval, value: this.interval },
__interval_ms: { text: kbn.interval_to_ms(this.interval), value: kbn.interval_to_ms(this.interval) },
...this.getRangeScopedVars(this.timeSrv.timeRange()),
...this.getRangeScopedVars(getTimeSrv().timeRange()),
};
const interpolated = this.templateSrv.replace(query, scopedVars, this.interpolateQueryExpr);
const metricFindQuery = new PrometheusMetricFindQuery(this, interpolated, this.timeSrv);
const interpolated = templateSrv.replace(query, scopedVars, this.interpolateQueryExpr);
const metricFindQuery = new PrometheusMetricFindQuery(this, interpolated);
return metricFindQuery.process();
}
getRangeScopedVars(range: TimeRange) {
range = range || this.timeSrv.timeRange();
getRangeScopedVars(range: TimeRange = getTimeSrv().timeRange()) {
const msRange = range.to.diff(range.from);
const sRange = Math.round(msRange / 1000);
const regularRange = kbn.secondsToHms(msRange / 1000);
@ -523,18 +525,14 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
};
}
annotationQuery(options: any) {
async annotationQuery(options: any) {
const annotation = options.annotation;
const expr = annotation.expr || '';
let tagKeys = annotation.tagKeys || '';
const titleFormat = annotation.titleFormat || '';
const textFormat = annotation.textFormat || '';
const { expr = '', tagKeys = '', titleFormat = '', textFormat = '', step = '60s' } = annotation;
if (!expr) {
return this.$q.when([]);
return Promise.resolve([]);
}
const step = annotation.step || '60s';
const start = this.getPrometheusTime(options.range.from, false);
const end = this.getPrometheusTime(options.range.to, true);
const queryOptions = {
@ -554,89 +552,76 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
const query = this.createQuery(queryModel, queryOptions, start, end);
const self = this;
return this.performTimeSeriesQuery(query, query.start, query.end).then((results: PromDataQueryResponse) => {
const eventList: AnnotationEvent[] = [];
tagKeys = tagKeys.split(',');
const response: PromDataQueryResponse = await this.performTimeSeriesQuery(query, query.start, query.end);
const eventList: AnnotationEvent[] = [];
const splitKeys = tagKeys.split(',');
if (results.cancelled) {
return [];
}
_.each(results.data.data.result, series => {
const tags = _.chain(series.metric)
.filter((v, k) => {
return _.includes(tagKeys, k);
})
.value();
if (response.cancelled) {
return [];
}
const dupCheck: { [key: number]: boolean } = {};
for (const value of series.values) {
const valueIsTrue = value[1] === '1'; // e.g. ALERTS
if (valueIsTrue || annotation.useValueForTime) {
const event: AnnotationEvent = {
annotation: annotation,
title: self.resultTransformer.renderTemplate(titleFormat, series.metric),
tags: tags,
text: self.resultTransformer.renderTemplate(textFormat, series.metric),
};
response?.data?.data?.result?.forEach(series => {
const tags = Object.entries(series.metric)
.filter(([k]) => splitKeys.includes(k))
.map(([_k, v]: [string, string]) => v);
if (annotation.useValueForTime) {
const timestampValue = Math.floor(parseFloat(value[1]));
if (dupCheck[timestampValue]) {
continue;
}
dupCheck[timestampValue] = true;
event.time = timestampValue;
} else {
event.time = Math.floor(parseFloat(value[0])) * 1000;
const dupCheck: Record<number, boolean> = {};
for (const value of series.values) {
const valueIsTrue = value[1] === '1'; // e.g. ALERTS
if (valueIsTrue || annotation.useValueForTime) {
const event: AnnotationEvent = {
annotation,
title: self.resultTransformer.renderTemplate(titleFormat, series.metric),
tags,
text: self.resultTransformer.renderTemplate(textFormat, series.metric),
};
if (annotation.useValueForTime) {
const timestampValue = Math.floor(parseFloat(value[1]));
if (dupCheck[timestampValue]) {
continue;
}
eventList.push(event);
dupCheck[timestampValue] = true;
event.time = timestampValue;
} else {
event.time = Math.floor(parseFloat(value[0])) * 1000;
}
eventList.push(event);
}
});
return eventList;
}
});
return eventList;
}
getTagKeys(options: any = {}) {
const url = '/api/v1/labels';
return this.metadataRequest(url).then((result: any) => {
return _.map(result.data.data, value => {
return { text: value };
});
});
async getTagKeys() {
const result = await this.metadataRequest('/api/v1/labels');
return result?.data?.data?.map((value: any) => ({ text: value })) ?? [];
}
getTagValues(options: any = {}) {
const url = '/api/v1/label/' + options.key + '/values';
return this.metadataRequest(url).then((result: any) => {
return _.map(result.data.data, value => {
return { text: value };
});
});
async getTagValues(options: any = {}) {
const result = await this.metadataRequest(`/api/v1/label/${options.key}/values`);
return result?.data?.data?.map((value: any) => ({ text: value })) ?? [];
}
testDatasource() {
async testDatasource() {
const now = new Date().getTime();
const query = { expr: '1+1' } as PromQueryRequest;
return this.performInstantQuery(query, now / 1000).then((response: any) => {
if (response.data.status === 'success') {
return { status: 'success', message: 'Data source is working' };
} else {
return { status: 'error', message: response.error };
}
});
const response = await this.performInstantQuery(query, now / 1000);
return response.data.status === 'success'
? { status: 'success', message: 'Data source is working' }
: { status: 'error', message: response.error };
}
interpolateVariablesInQueries(queries: PromQuery[]): PromQuery[] {
let expandedQueries = queries;
if (queries && queries.length > 0) {
if (queries && queries.length) {
expandedQueries = queries.map(query => {
const expandedQuery = {
...query,
datasource: this.name,
expr: this.templateSrv.replace(query.expr, {}, this.interpolateQueryExpr),
expr: templateSrv.replace(query.expr, {}, this.interpolateQueryExpr),
};
return expandedQuery;
});
@ -645,26 +630,26 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
}
getQueryHints(query: PromQuery, result: any[]) {
return getQueryHints(query.expr || '', result, this);
return getQueryHints(query.expr ?? '', result, this);
}
loadRules() {
this.metadataRequest('/api/v1/rules')
.then((res: any) => res.data || res.json())
.then((body: any) => {
const groups = _.get(body, ['data', 'groups']);
if (groups) {
this.ruleMappings = extractRuleMappingFromGroups(groups);
}
})
.catch((e: any) => {
console.log('Rules API is experimental. Ignore next error.');
console.error(e);
});
async loadRules() {
try {
const res = await this.metadataRequest('/api/v1/rules');
const body = res.data || res.json();
const groups = body?.data?.groups;
if (groups) {
this.ruleMappings = extractRuleMappingFromGroups(groups);
}
} catch (e) {
console.log('Rules API is experimental. Ignore next error.');
console.error(e);
}
}
modifyQuery(query: PromQuery, action: any): PromQuery {
let expression = query.expr || '';
let expression = query.expr ?? '';
switch (action.type) {
case 'ADD_FILTER': {
expression = addLabelToQuery(expression, action.key, action.value);
@ -695,14 +680,15 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
}
getPrometheusTime(date: string | DateTime, roundUp: boolean) {
if (_.isString(date)) {
if (typeof date === 'string') {
date = dateMath.parse(date, roundUp);
}
return Math.ceil(date.valueOf() / 1000);
}
getTimeRange(): { start: number; end: number } {
const range = this.timeSrv.timeRange();
const range = getTimeSrv().timeRange();
return {
start: this.getPrometheusTime(range.from, false),
end: this.getPrometheusTime(range.to, true),
@ -753,15 +739,11 @@ export function extractRuleMappingFromGroups(groups: any[]) {
}
export function prometheusRegularEscape(value: any) {
if (typeof value === 'string') {
return value.replace(/'/g, "\\\\'");
}
return value;
return typeof value === 'string' ? value.replace(/'/g, "\\\\'") : value;
}
export function prometheusSpecialRegexEscape(value: any) {
if (typeof value === 'string') {
return prometheusRegularEscape(value.replace(/\\/g, '\\\\\\\\').replace(/[$^*{}\[\]+?.()|]/g, '\\\\$&'));
}
return value;
return typeof value === 'string'
? prometheusRegularEscape(value.replace(/\\/g, '\\\\\\\\').replace(/[$^*{}\[\]+?.()|]/g, '\\\\$&'))
: value;
}

View File

@ -1,16 +1,16 @@
import _ from 'lodash';
import { TimeRange } from '@grafana/data';
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { PrometheusDatasource, PromDataQueryResponse } from './datasource';
import { PromQueryRequest } from './types';
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
export default class PrometheusMetricFindQuery {
range: TimeRange;
constructor(private datasource: PrometheusDatasource, private query: string, timeSrv: TimeSrv) {
constructor(private datasource: PrometheusDatasource, private query: string) {
this.datasource = datasource;
this.query = query;
this.range = timeSrv.timeRange();
this.range = getTimeSrv().timeRange();
}
process() {
@ -18,7 +18,6 @@ export default class PrometheusMetricFindQuery {
const labelValuesRegex = /^label_values\((?:(.+),\s*)?([a-zA-Z_][a-zA-Z0-9_]*)\)\s*$/;
const metricNamesRegex = /^metrics\((.+)\)\s*$/;
const queryResultRegex = /^query_result\((.+)\)\s*$/;
const labelNamesQuery = this.query.match(labelNamesRegex);
if (labelNamesQuery) {
return this.labelNamesQuery();

View File

@ -1,6 +1,4 @@
import _ from 'lodash';
// @ts-ignore
import q from 'q';
import {
alignRange,
extractRuleMappingFromGroups,
@ -10,19 +8,46 @@ import {
} from '../datasource';
import { DataSourceInstanceSettings, DataQueryResponseData, DataQueryRequest, dateTime } from '@grafana/data';
import { PromOptions, PromQuery, PromContext } from '../types';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import templateSrv from 'app/features/templating/template_srv';
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { CustomVariable } from 'app/features/templating/custom_variable';
import * as Backend from 'app/core/services/backend_srv';
import { BackendSrv } from '@grafana/runtime/src/services/backendSrv';
jest.mock('../metric_find_query');
jest.mock('app/core/services/backend_srv');
jest.mock('app/features/templating/template_srv', () => {
return {
getAdhocFilters: jest.fn(() => [] as any[]),
replace: jest.fn((a: string) => a),
};
});
jest.mock('app/features/dashboard/services/TimeSrv', () => ({
__esModule: true,
getTimeSrv: jest.fn().mockReturnValue({
timeRange(): any {
return {
from: dateTime(1531468681),
to: dateTime(1531489712),
};
},
}),
}));
const DEFAULT_TEMPLATE_SRV_MOCK = {
getAdhocFilters: () => [] as any[],
replace: (a: string) => a,
};
const getBackendSrvMock = (Backend.getBackendSrv as any) as jest.Mock<BackendSrv>;
const getAdhocFiltersMock = (templateSrv.getAdhocFilters as any) as jest.Mock<any>;
const replaceMock = (templateSrv.replace as any) as jest.Mock<any>;
const getTimeSrvMock = (getTimeSrv as any) as jest.Mock<TimeSrv>;
beforeEach(() => {
getBackendSrvMock.mockClear();
getAdhocFiltersMock.mockClear();
replaceMock.mockClear();
getTimeSrvMock.mockClear();
});
describe('PrometheusDatasource', () => {
const ctx: any = {};
let ds: PrometheusDatasource;
const instanceSettings = ({
url: 'proxied',
directUrl: 'direct',
@ -31,57 +56,64 @@ describe('PrometheusDatasource', () => {
jsonData: {} as any,
} as unknown) as DataSourceInstanceSettings<PromOptions>;
ctx.backendSrvMock = {};
ctx.templateSrvMock = DEFAULT_TEMPLATE_SRV_MOCK;
ctx.timeSrvMock = {
timeRange: () => {
return {
from: dateTime(1531468681),
to: dateTime(1531489712),
};
},
};
beforeEach(() => {
ctx.ds = new PrometheusDatasource(instanceSettings, q, ctx.backendSrvMock, ctx.templateSrvMock, ctx.timeSrvMock);
ds = new PrometheusDatasource(instanceSettings);
});
describe('Datasource metadata requests', () => {
const originalBackendMock = getBackendSrvMock();
afterAll(() => {
getBackendSrvMock.mockImplementation(() => originalBackendMock);
});
it('should perform a GET request with the default config', () => {
ctx.backendSrvMock.datasourceRequest = jest.fn();
ctx.ds.metadataRequest('/foo');
expect(ctx.backendSrvMock.datasourceRequest.mock.calls.length).toBe(1);
expect(ctx.backendSrvMock.datasourceRequest.mock.calls[0][0].method).toBe('GET');
const datasourceRequestMock = jest.fn();
getBackendSrvMock.mockImplementation(
() =>
({
datasourceRequest: datasourceRequestMock,
} as any)
);
ds.metadataRequest('/foo');
expect(datasourceRequestMock.mock.calls.length).toBe(1);
expect(datasourceRequestMock.mock.calls[0][0].method).toBe('GET');
});
it('should still perform a GET request with the DS HTTP method set to POST', () => {
ctx.backendSrvMock.datasourceRequest = jest.fn();
const datasourceRequestMock = jest.fn();
getBackendSrvMock.mockImplementation(
() =>
({
datasourceRequest: datasourceRequestMock,
} as any)
);
const postSettings = _.cloneDeep(instanceSettings);
postSettings.jsonData.httpMethod = 'POST';
const ds = new PrometheusDatasource(postSettings, q, ctx.backendSrvMock, ctx.templateSrvMock, ctx.timeSrvMock);
ds.metadataRequest('/foo');
expect(ctx.backendSrvMock.datasourceRequest.mock.calls.length).toBe(1);
expect(ctx.backendSrvMock.datasourceRequest.mock.calls[0][0].method).toBe('GET');
const promDs = new PrometheusDatasource(postSettings);
promDs.metadataRequest('/foo');
expect(datasourceRequestMock.mock.calls.length).toBe(1);
expect(datasourceRequestMock.mock.calls[0][0].method).toBe('GET');
});
});
describe('When using adhoc filters', () => {
const DEFAULT_QUERY_EXPRESSION = 'metric{job="foo"} - metric';
const target = { expr: DEFAULT_QUERY_EXPRESSION };
const originalAdhocFiltersMock = getAdhocFiltersMock();
afterEach(() => {
ctx.templateSrvMock.getAdhocFilters = DEFAULT_TEMPLATE_SRV_MOCK.getAdhocFilters;
afterAll(() => {
getAdhocFiltersMock.mockReturnValue(originalAdhocFiltersMock);
});
it('should not modify expression with no filters', () => {
const result = ctx.ds.createQuery(target, { interval: '15s' });
const result = ds.createQuery(target as any, { interval: '15s' } as any, 0, 0);
expect(result).toMatchObject({ expr: DEFAULT_QUERY_EXPRESSION });
});
it('should add filters to expression', () => {
ctx.templateSrvMock.getAdhocFilters = () => [
getAdhocFiltersMock.mockReturnValue([
{
key: 'k1',
operator: '=',
@ -92,13 +124,13 @@ describe('PrometheusDatasource', () => {
operator: '!=',
value: 'v2',
},
];
const result = ctx.ds.createQuery(target, { interval: '15s' });
]);
const result = ds.createQuery(target as any, { interval: '15s' } as any, 0, 0);
expect(result).toMatchObject({ expr: 'metric{job="foo",k1="v1",k2!="v2"} - metric{k1="v1",k2!="v2"}' });
});
it('should add escaping if needed to regex filter expressions', () => {
ctx.templateSrvMock.getAdhocFilters = () => [
getAdhocFiltersMock.mockReturnValue([
{
key: 'k1',
operator: '=~',
@ -109,8 +141,8 @@ describe('PrometheusDatasource', () => {
operator: '=~',
value: `v'.*`,
},
];
const result = ctx.ds.createQuery(target, { interval: '15s' });
]);
const result = ds.createQuery(target as any, { interval: '15s' } as any, 0, 0);
expect(result).toMatchObject({
expr: `metric{job="foo",k1=~"v.*",k2=~"v\\\\'.*"} - metric{k1=~"v.*",k2=~"v\\\\'.*"}`,
});
@ -118,28 +150,36 @@ describe('PrometheusDatasource', () => {
});
describe('When performing performSuggestQuery', () => {
const originalBackendMock = getBackendSrvMock();
it('should cache response', async () => {
ctx.backendSrvMock.datasourceRequest.mockReturnValue(
Promise.resolve({
status: 'success',
data: { data: ['value1', 'value2', 'value3'] },
})
const datasourceRequestMock = Promise.resolve({
status: 'success',
data: { data: ['value1', 'value2', 'value3'] },
});
getBackendSrvMock.mockImplementation(
() =>
({
datasourceRequest: () => datasourceRequestMock,
} as any)
);
let results = await ctx.ds.performSuggestQuery('value', true);
let results = await ds.performSuggestQuery('value', true);
expect(results).toHaveLength(3);
ctx.backendSrvMock.datasourceRequest.mockReset();
results = await ctx.ds.performSuggestQuery('value', true);
getBackendSrvMock.mockImplementation(() => originalBackendMock);
results = await ds.performSuggestQuery('value', true);
expect(results).toHaveLength(3);
});
});
describe('When converting prometheus histogram to heatmap format', () => {
let query: any;
beforeEach(() => {
ctx.query = {
query = {
range: { from: dateTime(1443454528000), to: dateTime(1443454528000) },
targets: [{ expr: 'test{job="testjob"}', format: 'heatmap', legendFormat: '{{le}}' }],
interval: '1s',
@ -196,8 +236,8 @@ describe('PrometheusDatasource', () => {
},
];
ctx.ds.performTimeSeriesQuery = jest.fn().mockReturnValue([responseMock]);
ctx.ds.query(ctx.query).subscribe((result: any) => {
ds.performTimeSeriesQuery = jest.fn().mockReturnValue([responseMock]);
ds.query(query).subscribe((result: any) => {
const results = result.data;
return expect(results).toMatchObject(expected);
});
@ -238,8 +278,8 @@ describe('PrometheusDatasource', () => {
const expected = ['1', '2', '4', '+Inf'];
ctx.ds.performTimeSeriesQuery = jest.fn().mockReturnValue([responseMock]);
ctx.ds.query(ctx.query).subscribe((result: any) => {
ds.performTimeSeriesQuery = jest.fn().mockReturnValue([responseMock]);
ds.query(query).subscribe((result: any) => {
const seriesLabels = _.map(result.data, 'target');
return expect(seriesLabels).toEqual(expected);
});
@ -252,27 +292,32 @@ describe('PrometheusDatasource', () => {
expect(range.start).toEqual(0);
expect(range.end).toEqual(3);
});
it('does modify end-aligned intervals to reflect number of steps possible', () => {
const range = alignRange(1, 6, 3, 0);
expect(range.start).toEqual(0);
expect(range.end).toEqual(6);
});
it('does align intervals that are a multiple of steps', () => {
const range = alignRange(1, 4, 3, 0);
expect(range.start).toEqual(0);
expect(range.end).toEqual(3);
});
it('does align intervals that are not a multiple of steps', () => {
const range = alignRange(1, 5, 3, 0);
expect(range.start).toEqual(0);
expect(range.end).toEqual(3);
});
it('does align intervals with local midnight -UTC offset', () => {
//week range, location 4+ hours UTC offset, 24h step time
const range = alignRange(4 * 60 * 60, (7 * 24 + 4) * 60 * 60, 24 * 60 * 60, -4 * 60 * 60); //04:00 UTC, 7 day range
expect(range.start).toEqual(4 * 60 * 60);
expect(range.end).toEqual((7 * 24 + 4) * 60 * 60);
});
it('does align intervals with local midnight +UTC offset', () => {
//week range, location 4- hours UTC offset, 24h step time
const range = alignRange(20 * 60 * 60, (8 * 24 - 4) * 60 * 60, 24 * 60 * 60, 4 * 60 * 60); //20:00 UTC on day1, 7 days later is 20:00 on day8
@ -315,12 +360,15 @@ describe('PrometheusDatasource', () => {
it('should not escape non-string', () => {
expect(prometheusRegularEscape(12)).toEqual(12);
});
it('should not escape simple string', () => {
expect(prometheusRegularEscape('cryptodepression')).toEqual('cryptodepression');
});
it("should escape '", () => {
expect(prometheusRegularEscape("looking'glass")).toEqual("looking\\\\'glass");
});
it('should escape multiple characters', () => {
expect(prometheusRegularEscape("'looking'glass'")).toEqual("\\\\'looking\\\\'glass\\\\'");
});
@ -330,6 +378,7 @@ describe('PrometheusDatasource', () => {
it('should not escape simple string', () => {
expect(prometheusSpecialRegexEscape('cryptodepression')).toEqual('cryptodepression');
});
it('should escape $^*+?.()|\\', () => {
expect(prometheusSpecialRegexEscape("looking'glass")).toEqual("looking\\\\'glass");
expect(prometheusSpecialRegexEscape('looking{glass')).toEqual('looking\\\\{glass');
@ -347,54 +396,55 @@ describe('PrometheusDatasource', () => {
expect(prometheusSpecialRegexEscape('looking\\glass')).toEqual('looking\\\\\\\\glass');
expect(prometheusSpecialRegexEscape('looking|glass')).toEqual('looking\\\\|glass');
});
it('should escape multiple special characters', () => {
expect(prometheusSpecialRegexEscape('+looking$glass?')).toEqual('\\\\+looking\\\\$glass\\\\?');
});
});
describe('When interpolating variables', () => {
let customVariable: CustomVariable;
beforeEach(() => {
ctx.ds = new PrometheusDatasource(instanceSettings, q, ctx.backendSrvMock, ctx.templateSrvMock, ctx.timeSrvMock);
ctx.variable = new CustomVariable({}, {} as any);
customVariable = new CustomVariable({}, {} as any);
});
describe('and value is a string', () => {
it('should only escape single quotes', () => {
expect(ctx.ds.interpolateQueryExpr("abc'$^*{}[]+?.()|", ctx.variable)).toEqual("abc\\\\'$^*{}[]+?.()|");
expect(ds.interpolateQueryExpr("abc'$^*{}[]+?.()|", customVariable)).toEqual("abc\\\\'$^*{}[]+?.()|");
});
});
describe('and value is a number', () => {
it('should return a number', () => {
expect(ctx.ds.interpolateQueryExpr(1000, ctx.variable)).toEqual(1000);
expect(ds.interpolateQueryExpr(1000 as any, customVariable)).toEqual(1000);
});
});
describe('and variable allows multi-value', () => {
beforeEach(() => {
ctx.variable.multi = true;
customVariable.multi = true;
});
it('should regex escape values if the value is a string', () => {
expect(ctx.ds.interpolateQueryExpr('looking*glass', ctx.variable)).toEqual('looking\\\\*glass');
expect(ds.interpolateQueryExpr('looking*glass', customVariable)).toEqual('looking\\\\*glass');
});
it('should return pipe separated values if the value is an array of strings', () => {
expect(ctx.ds.interpolateQueryExpr(['a|bc', 'de|f'], ctx.variable)).toEqual('a\\\\|bc|de\\\\|f');
expect(ds.interpolateQueryExpr(['a|bc', 'de|f'], customVariable)).toEqual('a\\\\|bc|de\\\\|f');
});
});
describe('and variable allows all', () => {
beforeEach(() => {
ctx.variable.includeAll = true;
customVariable.includeAll = true;
});
it('should regex escape values if the array is a string', () => {
expect(ctx.ds.interpolateQueryExpr('looking*glass', ctx.variable)).toEqual('looking\\\\*glass');
expect(ds.interpolateQueryExpr('looking*glass', customVariable)).toEqual('looking\\\\*glass');
});
it('should return pipe separated values if the value is an array of strings', () => {
expect(ctx.ds.interpolateQueryExpr(['a|bc', 'de|f'], ctx.variable)).toEqual('a\\\\|bc|de\\\\|f');
expect(ds.interpolateQueryExpr(['a|bc', 'de|f'], customVariable)).toEqual('a\\\\|bc|de\\\\|f');
});
});
});
@ -402,33 +452,30 @@ describe('PrometheusDatasource', () => {
describe('metricFindQuery', () => {
beforeEach(() => {
const query = 'query_result(topk(5,rate(http_request_duration_microseconds_count[$__interval])))';
ctx.templateSrvMock.replace = jest.fn();
ctx.timeSrvMock.timeRange = () => {
return {
from: dateTime(1531468681),
to: dateTime(1531489712),
};
};
ctx.ds = new PrometheusDatasource(instanceSettings, q, ctx.backendSrvMock, ctx.templateSrvMock, ctx.timeSrvMock);
ctx.ds.metricFindQuery(query);
replaceMock.mockImplementation(jest.fn);
ds.metricFindQuery(query);
});
afterAll(() => {
replaceMock.mockImplementation((a: string) => a);
});
it('should call templateSrv.replace with scopedVars', () => {
expect(ctx.templateSrvMock.replace.mock.calls[0][1]).toBeDefined();
expect(replaceMock.mock.calls[0][1]).toBeDefined();
});
it('should have the correct range and range_ms', () => {
const range = ctx.templateSrvMock.replace.mock.calls[0][1].__range;
const rangeMs = ctx.templateSrvMock.replace.mock.calls[0][1].__range_ms;
const rangeS = ctx.templateSrvMock.replace.mock.calls[0][1].__range_s;
const range = replaceMock.mock.calls[0][1].__range;
const rangeMs = replaceMock.mock.calls[0][1].__range_ms;
const rangeS = replaceMock.mock.calls[0][1].__range_s;
expect(range).toEqual({ text: '21s', value: '21s' });
expect(rangeMs).toEqual({ text: 21031, value: 21031 });
expect(rangeS).toEqual({ text: 21, value: 21 });
});
it('should pass the default interval value', () => {
const interval = ctx.templateSrvMock.replace.mock.calls[0][1].__interval;
const intervalMs = ctx.templateSrvMock.replace.mock.calls[0][1].__interval_ms;
const interval = replaceMock.mock.calls[0][1].__interval;
const intervalMs = replaceMock.mock.calls[0][1].__interval_ms;
expect(interval).toEqual({ text: '15s', value: '15s' });
expect(intervalMs).toEqual({ text: 15000, value: 15000 });
});
@ -441,33 +488,20 @@ const HOUR = 60 * MINUTE;
const time = ({ hours = 0, seconds = 0, minutes = 0 }) => dateTime(hours * HOUR + minutes * MINUTE + seconds * SECOND);
const ctx = {} as any;
const instanceSettings = ({
url: 'proxied',
directUrl: 'direct',
user: 'test',
password: 'mupp',
jsonData: { httpMethod: 'GET' },
} as unknown) as DataSourceInstanceSettings<PromOptions>;
const backendSrv = {
datasourceRequest: jest.fn(),
} as any;
const templateSrv = ({
getAdhocFilters: (): any[] => [],
replace: jest.fn(str => str),
} as unknown) as TemplateSrv;
const timeSrv = ({
timeRange: () => {
return {
from: dateTime(1531468681),
to: dateTime(1531468681 + 2000),
};
},
} as unknown) as TimeSrv;
describe('PrometheusDatasource', () => {
const instanceSettings = ({
url: 'proxied',
directUrl: 'direct',
user: 'test',
password: 'mupp',
jsonData: { httpMethod: 'GET' },
} as unknown) as DataSourceInstanceSettings<PromOptions>;
let ds: PrometheusDatasource;
beforeEach(() => {
ds = new PrometheusDatasource(instanceSettings);
});
describe('When querying prometheus with one target using query editor target spec', () => {
describe('and query syntax is valid', () => {
let results: any;
@ -476,9 +510,11 @@ describe('PrometheusDatasource', () => {
targets: [{ expr: 'test{job="testjob"}', format: 'time_series' }],
interval: '60s',
};
// Interval alignment with step
const urlExpected =
'proxied/api/v1/query_range?query=' + encodeURIComponent('test{job="testjob"}') + '&start=60&end=180&step=60';
const urlExpected = `proxied/api/v1/query_range?query=${encodeURIComponent(
'test{job="testjob"}'
)}&start=60&end=180&step=60`;
beforeEach(async () => {
const response = {
@ -495,16 +531,14 @@ describe('PrometheusDatasource', () => {
},
},
};
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
ctx.ds.query(query).subscribe((data: any) => {
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve(response));
ds.query(query as any).subscribe((data: any) => {
results = data;
});
});
it('should generate the correct query', () => {
const res = backendSrv.datasourceRequest.mock.calls[0][0];
const res = (getBackendSrvMock().datasourceRequest as jest.Mock<any>).mock.calls[0][0];
expect(res.method).toBe('GET');
expect(res.url).toBe(urlExpected);
});
@ -533,9 +567,8 @@ describe('PrometheusDatasource', () => {
};
it('should generate an error', () => {
backendSrv.datasourceRequest = jest.fn(() => Promise.reject(response));
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
ctx.ds.query(query).subscribe((e: any) => {
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.reject(response));
ds.query(query as any).subscribe((e: any) => {
results = e.message;
expect(results).toBe(`"${errMessage}"`);
});
@ -579,10 +612,9 @@ describe('PrometheusDatasource', () => {
},
};
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve(response));
ctx.ds.query(query).subscribe((data: any) => {
ds.query(query as any).subscribe((data: any) => {
results = data;
});
});
@ -599,6 +631,7 @@ describe('PrometheusDatasource', () => {
expect(results.data[0].datapoints[1][1]).toBe((start + step * 1) * 1000);
expect(results.data[0].datapoints[1][0]).toBe(3846);
});
it('should fill null after last datapoint in response', () => {
const length = (end - start) / step + 1;
expect(results.data[0].datapoints[length - 2][1]).toBe((end - step * 1) * 1000);
@ -606,6 +639,7 @@ describe('PrometheusDatasource', () => {
expect(results.data[0].datapoints[length - 1][1]).toBe(end * 1000);
expect(results.data[0].datapoints[length - 1][0]).toBe(null);
});
it('should fill null at gap between series', () => {
expect(results.data[0].datapoints[2][1]).toBe((start + step * 2) * 1000);
expect(results.data[0].datapoints[2][0]).toBe(null);
@ -618,7 +652,7 @@ describe('PrometheusDatasource', () => {
describe('When querying prometheus with one target and instant = true', () => {
let results: any;
const urlExpected = 'proxied/api/v1/query?query=' + encodeURIComponent('test{job="testjob"}') + '&time=123';
const urlExpected = `proxied/api/v1/query?query=${encodeURIComponent('test{job="testjob"}')}&time=123`;
const query = {
range: { from: time({ seconds: 63 }), to: time({ seconds: 123 }) },
targets: [{ expr: 'test{job="testjob"}', format: 'time_series', instant: true }],
@ -641,17 +675,18 @@ describe('PrometheusDatasource', () => {
},
};
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
ctx.ds.query(query).subscribe((data: any) => {
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve(response));
ds.query(query as any).subscribe((data: any) => {
results = data;
});
});
it('should generate the correct query', () => {
const res = backendSrv.datasourceRequest.mock.calls[0][0];
const res = (getBackendSrvMock().datasourceRequest as jest.Mock<any>).mock.calls[0][0];
expect(res.method).toBe('GET');
expect(res.url).toBe(urlExpected);
});
it('should return series list', () => {
expect(results.data.length).toBe(1);
expect(results.data[0].target).toBe('test{job="testjob"}');
@ -660,7 +695,6 @@ describe('PrometheusDatasource', () => {
describe('When performing annotationQuery', () => {
let results: any;
const options: any = {
annotation: {
expr: 'ALERTS{alertstate="firing"}',
@ -697,10 +731,9 @@ describe('PrometheusDatasource', () => {
describe('when time series query is cancelled', () => {
it('should return empty results', async () => {
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve({ cancelled: true }));
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve({ cancelled: true }));
await ctx.ds.annotationQuery(options).then((data: any) => {
await ds.annotationQuery(options).then((data: any) => {
results = data;
});
@ -711,10 +744,9 @@ describe('PrometheusDatasource', () => {
describe('not use useValueForTime', () => {
beforeEach(async () => {
options.annotation.useValueForTime = false;
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve(response));
await ctx.ds.annotationQuery(options).then((data: any) => {
await ds.annotationQuery(options).then((data: any) => {
results = data;
});
});
@ -731,10 +763,9 @@ describe('PrometheusDatasource', () => {
describe('use useValueForTime', () => {
beforeEach(async () => {
options.annotation.useValueForTime = true;
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve(response));
await ctx.ds.annotationQuery(options).then((data: any) => {
await ds.annotationQuery(options).then((data: any) => {
results = data;
});
});
@ -746,8 +777,7 @@ describe('PrometheusDatasource', () => {
describe('step parameter', () => {
beforeEach(() => {
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve(response));
});
it('should use default step for short range if no interval is given', () => {
@ -758,8 +788,8 @@ describe('PrometheusDatasource', () => {
to: time({ seconds: 123 }),
},
};
ctx.ds.annotationQuery(query);
const req = backendSrv.datasourceRequest.mock.calls[0][0];
ds.annotationQuery(query);
const req = (getBackendSrvMock().datasourceRequest as jest.Mock<any>).mock.calls[0][0];
expect(req.url).toContain('step=60');
});
@ -776,8 +806,8 @@ describe('PrometheusDatasource', () => {
to: time({ seconds: 123 }),
},
};
ctx.ds.annotationQuery(query);
const req = backendSrv.datasourceRequest.mock.calls[0][0];
ds.annotationQuery(query);
const req = (getBackendSrvMock().datasourceRequest as jest.Mock<any>).mock.calls[0][0];
expect(req.url).toContain('step=10');
});
@ -794,8 +824,8 @@ describe('PrometheusDatasource', () => {
to: time({ seconds: 123 }),
},
};
ctx.ds.annotationQuery(query);
const req = backendSrv.datasourceRequest.mock.calls[0][0];
ds.annotationQuery(query);
const req = (getBackendSrvMock().datasourceRequest as jest.Mock<any>).mock.calls[0][0];
expect(req.url).toContain('step=10');
});
@ -807,8 +837,8 @@ describe('PrometheusDatasource', () => {
to: time({ hours: 24 * 30, seconds: 63 }),
},
};
ctx.ds.annotationQuery(query);
const req = backendSrv.datasourceRequest.mock.calls[0][0];
ds.annotationQuery(query);
const req = (getBackendSrvMock().datasourceRequest as jest.Mock<any>).mock.calls[0][0];
// Range in seconds: (to - from) / 1000
// Max_datapoints: 11000
// Step: range / max_datapoints
@ -842,9 +872,8 @@ describe('PrometheusDatasource', () => {
},
};
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
ctx.ds.query(query).subscribe((data: any) => {
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve(response));
ds.query(query as any).subscribe((data: any) => {
results = data;
});
});
@ -879,10 +908,9 @@ describe('PrometheusDatasource', () => {
};
const urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=10';
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
ctx.ds.query(query);
const res = backendSrv.datasourceRequest.mock.calls[0][0];
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve(response));
ds.query(query as any);
const res = (getBackendSrvMock().datasourceRequest as jest.Mock<any>).mock.calls[0][0];
expect(res.method).toBe('GET');
expect(res.url).toBe(urlExpected);
});
@ -895,10 +923,9 @@ describe('PrometheusDatasource', () => {
interval: '100ms',
};
const urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=1';
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
ctx.ds.query(query);
const res = backendSrv.datasourceRequest.mock.calls[0][0];
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve(response));
ds.query(query as any);
const res = (getBackendSrvMock().datasourceRequest as jest.Mock<any>).mock.calls[0][0];
expect(res.method).toBe('GET');
expect(res.url).toBe(urlExpected);
});
@ -916,13 +943,13 @@ describe('PrometheusDatasource', () => {
interval: '10s',
};
const urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=10';
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
ctx.ds.query(query);
const res = backendSrv.datasourceRequest.mock.calls[0][0];
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve(response));
ds.query(query as any);
const res = (getBackendSrvMock().datasourceRequest as jest.Mock<any>).mock.calls[0][0];
expect(res.method).toBe('GET');
expect(res.url).toBe(urlExpected);
});
it('should result in querying fewer than 11000 data points', async () => {
const query = {
// 6 hour range
@ -933,13 +960,13 @@ describe('PrometheusDatasource', () => {
const end = 7 * 60 * 60;
const start = 60 * 60;
const urlExpected = 'proxied/api/v1/query_range?query=test&start=' + start + '&end=' + end + '&step=2';
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
ctx.ds.query(query);
const res = backendSrv.datasourceRequest.mock.calls[0][0];
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve(response));
ds.query(query as any);
const res = (getBackendSrvMock().datasourceRequest as jest.Mock<any>).mock.calls[0][0];
expect(res.method).toBe('GET');
expect(res.url).toBe(urlExpected);
});
it('should not apply min interval when interval * intervalFactor greater', async () => {
const query = {
// 6 minute range
@ -955,13 +982,13 @@ describe('PrometheusDatasource', () => {
};
// times get rounded up to interval
const urlExpected = 'proxied/api/v1/query_range?query=test&start=50&end=400&step=50';
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
ctx.ds.query(query);
const res = backendSrv.datasourceRequest.mock.calls[0][0];
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve(response));
ds.query(query as any);
const res = (getBackendSrvMock().datasourceRequest as jest.Mock<any>).mock.calls[0][0];
expect(res.method).toBe('GET');
expect(res.url).toBe(urlExpected);
});
it('should apply min interval when interval * intervalFactor smaller', async () => {
const query = {
// 6 minute range
@ -976,13 +1003,13 @@ describe('PrometheusDatasource', () => {
interval: '5s',
};
const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=60&end=420&step=15';
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
ctx.ds.query(query);
const res = backendSrv.datasourceRequest.mock.calls[0][0];
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve(response));
ds.query(query as any);
const res = (getBackendSrvMock().datasourceRequest as jest.Mock<any>).mock.calls[0][0];
expect(res.method).toBe('GET');
expect(res.url).toBe(urlExpected);
});
it('should apply intervalFactor to auto interval when greater', async () => {
const query = {
// 6 minute range
@ -998,13 +1025,13 @@ describe('PrometheusDatasource', () => {
};
// times get aligned to interval
const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=0&end=400&step=100';
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
ctx.ds.query(query);
const res = backendSrv.datasourceRequest.mock.calls[0][0];
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve(response));
ds.query(query as any);
const res = (getBackendSrvMock().datasourceRequest as jest.Mock<any>).mock.calls[0][0];
expect(res.method).toBe('GET');
expect(res.url).toBe(urlExpected);
});
it('should not not be affected by the 11000 data points limit when large enough', async () => {
const query = {
// 1 week range
@ -1020,13 +1047,13 @@ describe('PrometheusDatasource', () => {
const end = 7 * 24 * 60 * 60;
const start = 0;
const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=' + start + '&end=' + end + '&step=100';
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
ctx.ds.query(query);
const res = backendSrv.datasourceRequest.mock.calls[0][0];
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve(response));
ds.query(query as any);
const res = (getBackendSrvMock().datasourceRequest as jest.Mock<any>).mock.calls[0][0];
expect(res.method).toBe('GET');
expect(res.url).toBe(urlExpected);
});
it('should be determined by the 11000 data points limit when too small', async () => {
const query = {
// 1 week range
@ -1042,10 +1069,9 @@ describe('PrometheusDatasource', () => {
const end = 7 * 24 * 60 * 60;
const start = 0;
const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=' + start + '&end=' + end + '&step=60';
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
ctx.ds.query(query);
const res = backendSrv.datasourceRequest.mock.calls[0][0];
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve(response));
ds.query(query as any);
const res = (getBackendSrvMock().datasourceRequest as jest.Mock<any>).mock.calls[0][0];
expect(res.method).toBe('GET');
expect(res.url).toBe(urlExpected);
});
@ -1085,10 +1111,9 @@ describe('PrometheusDatasource', () => {
'&start=60&end=420&step=10';
templateSrv.replace = jest.fn(str => str);
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
ctx.ds.query(query);
const res = backendSrv.datasourceRequest.mock.calls[0][0];
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve(response));
ds.query(query as any);
const res = (getBackendSrvMock().datasourceRequest as jest.Mock<any>).mock.calls[0][0];
expect(res.method).toBe('GET');
expect(res.url).toBe(urlExpected);
@ -1104,6 +1129,7 @@ describe('PrometheusDatasource', () => {
},
});
});
it('should be min interval when it is greater than auto interval', async () => {
const query = {
// 6 minute range
@ -1124,11 +1150,10 @@ describe('PrometheusDatasource', () => {
'proxied/api/v1/query_range?query=' +
encodeURIComponent('rate(test[$__interval])') +
'&start=60&end=420&step=10';
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve(response));
templateSrv.replace = jest.fn(str => str);
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
ctx.ds.query(query);
const res = backendSrv.datasourceRequest.mock.calls[0][0];
ds.query(query as any);
const res = (getBackendSrvMock().datasourceRequest as jest.Mock<any>).mock.calls[0][0];
expect(res.method).toBe('GET');
expect(res.url).toBe(urlExpected);
@ -1144,6 +1169,7 @@ describe('PrometheusDatasource', () => {
},
});
});
it('should account for intervalFactor', async () => {
const query = {
// 6 minute range
@ -1165,11 +1191,10 @@ describe('PrometheusDatasource', () => {
'proxied/api/v1/query_range?query=' +
encodeURIComponent('rate(test[$__interval])') +
'&start=0&end=400&step=100';
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve(response));
templateSrv.replace = jest.fn(str => str);
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
ctx.ds.query(query);
const res = backendSrv.datasourceRequest.mock.calls[0][0];
ds.query(query as any);
const res = (getBackendSrvMock().datasourceRequest as jest.Mock<any>).mock.calls[0][0];
expect(res.method).toBe('GET');
expect(res.url).toBe(urlExpected);
@ -1190,6 +1215,7 @@ describe('PrometheusDatasource', () => {
expect(query.scopedVars.__interval_ms.text).toBe(10 * 1000);
expect(query.scopedVars.__interval_ms.value).toBe(10 * 1000);
});
it('should be interval * intervalFactor when greater than min interval', async () => {
const query = {
// 6 minute range
@ -1213,10 +1239,9 @@ describe('PrometheusDatasource', () => {
'&start=50&end=400&step=50';
templateSrv.replace = jest.fn(str => str);
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
ctx.ds.query(query);
const res = backendSrv.datasourceRequest.mock.calls[0][0];
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve(response));
ds.query(query as any);
const res = (getBackendSrvMock().datasourceRequest as jest.Mock<any>).mock.calls[0][0];
expect(res.method).toBe('GET');
expect(res.url).toBe(urlExpected);
@ -1232,6 +1257,7 @@ describe('PrometheusDatasource', () => {
},
});
});
it('should be min interval when greater than interval * intervalFactor', async () => {
const query = {
// 6 minute range
@ -1254,10 +1280,9 @@ describe('PrometheusDatasource', () => {
encodeURIComponent('rate(test[$__interval])') +
'&start=60&end=420&step=15';
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
ctx.ds.query(query);
const res = backendSrv.datasourceRequest.mock.calls[0][0];
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve(response));
ds.query(query as any);
const res = (getBackendSrvMock().datasourceRequest as jest.Mock<any>).mock.calls[0][0];
expect(res.method).toBe('GET');
expect(res.url).toBe(urlExpected);
@ -1273,6 +1298,7 @@ describe('PrometheusDatasource', () => {
},
});
});
it('should be determined by the 11000 data points limit, accounting for intervalFactor', async () => {
const query = {
// 1 week range
@ -1299,11 +1325,10 @@ describe('PrometheusDatasource', () => {
'&end=' +
end +
'&step=60';
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve(response));
templateSrv.replace = jest.fn(str => str);
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
ctx.ds.query(query);
const res = backendSrv.datasourceRequest.mock.calls[0][0];
ds.query(query as any);
const res = (getBackendSrvMock().datasourceRequest as jest.Mock<any>).mock.calls[0][0];
expect(res.method).toBe('GET');
expect(res.url).toBe(urlExpected);
@ -1352,10 +1377,9 @@ describe('PrometheusDatasource', () => {
)}&start=0&end=3600&step=60`;
templateSrv.replace = jest.fn(str => str);
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
ctx.ds.query(query);
const res = backendSrv.datasourceRequest.mock.calls[0][0];
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve(response));
ds.query(query as any);
const res = (getBackendSrvMock().datasourceRequest as jest.Mock<any>).mock.calls[0][0];
expect(res.url).toBe(urlExpected);
// @ts-ignore
@ -1387,6 +1411,11 @@ describe('PrometheusDatasource for POST', () => {
jsonData: { httpMethod: 'POST' },
} as unknown) as DataSourceInstanceSettings<PromOptions>;
let ds: PrometheusDatasource;
beforeEach(() => {
ds = new PrometheusDatasource(instanceSettings);
});
describe('When querying prometheus with one target using query editor target spec', () => {
let results: any;
const urlExpected = 'proxied/api/v1/query_range';
@ -1417,15 +1446,14 @@ describe('PrometheusDatasource for POST', () => {
},
},
};
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
ctx.ds.query(query).subscribe((data: any) => {
getBackendSrvMock().datasourceRequest = jest.fn(() => Promise.resolve(response));
ds.query(query as any).subscribe((data: any) => {
results = data;
});
});
it('should generate the correct query', () => {
const res = backendSrv.datasourceRequest.mock.calls[0][0];
const res = (getBackendSrvMock().datasourceRequest as jest.Mock<any>).mock.calls[0][0];
expect(res.method).toBe('POST');
expect(res.url).toBe(urlExpected);
expect(res.data).toEqual(dataExpected);
@ -1439,22 +1467,19 @@ describe('PrometheusDatasource for POST', () => {
describe('When querying prometheus via check headers X-Dashboard-Id and X-Panel-Id', () => {
const options = { dashboardId: 1, panelId: 2 };
const httpOptions = {
headers: {} as { [key: string]: number | undefined },
};
it('with proxy access tracing headers should be added', () => {
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
ctx.ds._addTracingHeaders(httpOptions, options);
ds._addTracingHeaders(httpOptions as any, options as any);
expect(httpOptions.headers['X-Dashboard-Id']).toBe(1);
expect(httpOptions.headers['X-Panel-Id']).toBe(2);
});
it('with direct access tracing headers should not be added', () => {
instanceSettings.url = 'http://127.0.0.1:8000';
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
ctx.ds._addTracingHeaders(httpOptions, options);
const mockDs = new PrometheusDatasource({ ...instanceSettings, url: 'http://127.0.0.1:8000' });
mockDs._addTracingHeaders(httpOptions as any, options as any);
expect(httpOptions.headers['X-Dashboard-Id']).toBe(undefined);
expect(httpOptions.headers['X-Panel-Id']).toBe(undefined);
});
@ -1474,7 +1499,7 @@ const getPrepareTargetsContext = (target: PromQuery) => {
const panelId = '2';
const options = ({ targets: [target], interval: '1s', panelId } as any) as DataQueryRequest<PromQuery>;
const ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
const ds = new PrometheusDatasource(instanceSettings);
const { queries, activeTargets } = ds.prepareTargets(options, start, end);
return {
@ -1575,6 +1600,7 @@ describe('prepareTargets', () => {
});
});
});
describe('and both Graph and Table are hidden', () => {
it('then it should return empty arrays', () => {
const target: PromQuery = {

View File

@ -1,51 +1,86 @@
import { PrometheusDatasource } from '../datasource';
import PrometheusMetricFindQuery from '../metric_find_query';
//@ts-ignore
import q from 'q';
import { toUtc, DataSourceInstanceSettings } from '@grafana/data';
import { PromOptions } from '../types';
describe('PrometheusMetricFindQuery', () => {
const instanceSettings = ({
url: 'proxied',
directUrl: 'direct',
user: 'test',
password: 'mupp',
jsonData: { httpMethod: 'GET' },
} as unknown) as DataSourceInstanceSettings<PromOptions>;
const raw = {
from: toUtc('2018-04-25 10:00'),
to: toUtc('2018-04-25 11:00'),
import templateSrv from 'app/features/templating/template_srv';
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import * as Backend from 'app/core/services/backend_srv';
import { BackendSrv } from '@grafana/runtime/src/services/backendSrv';
jest.mock('app/features/templating/template_srv', () => {
return {
getAdhocFilters: jest.fn(() => [] as any[]),
replace: jest.fn((a: string) => a),
};
const ctx: any = {
backendSrvMock: {
datasourceRequest: jest.fn(() => Promise.resolve({})),
},
templateSrvMock: {
replace: (a: string) => a,
},
timeSrvMock: {
timeRange: () => ({
});
const getAdhocFiltersMock = (templateSrv.getAdhocFilters as any) as jest.Mock<any>;
const replaceMock = (templateSrv.replace as any) as jest.Mock<any>;
jest.mock('app/core/services/backend_srv');
const backendSrvMock = {
datasourceRequest: jest.fn(() => Promise.resolve({})),
};
const getBackendSrvMock = (Backend.getBackendSrv as any) as jest.Mock<BackendSrv>;
getBackendSrvMock.mockImplementation(() => backendSrvMock as any);
const instanceSettings = ({
url: 'proxied',
directUrl: 'direct',
user: 'test',
password: 'mupp',
jsonData: { httpMethod: 'GET' },
} as unknown) as DataSourceInstanceSettings<PromOptions>;
const raw = {
from: toUtc('2018-04-25 10:00'),
to: toUtc('2018-04-25 11:00'),
};
jest.mock('app/features/dashboard/services/TimeSrv', () => ({
__esModule: true,
getTimeSrv: jest.fn().mockReturnValue({
timeRange(): any {
return {
from: raw.from,
to: raw.to,
raw: raw,
}),
};
},
};
}),
}));
ctx.setupMetricFindQuery = (data: any) => {
ctx.backendSrvMock.datasourceRequest.mockReturnValue(Promise.resolve({ status: 'success', data: data.response }));
return new PrometheusMetricFindQuery(ctx.ds, data.query, ctx.timeSrvMock);
};
const getTimeSrvMock = (getTimeSrv as any) as jest.Mock<TimeSrv>;
beforeEach(() => {
getBackendSrvMock.mockClear();
getAdhocFiltersMock.mockClear();
replaceMock.mockClear();
getAdhocFiltersMock.mockClear();
getTimeSrvMock.mockClear();
});
describe('PrometheusMetricFindQuery', () => {
let datasourceRequestMock: any;
let ds: PrometheusDatasource;
beforeEach(() => {
ctx.backendSrvMock.datasourceRequest.mockReset();
ctx.ds = new PrometheusDatasource(instanceSettings, q, ctx.backendSrvMock, ctx.templateSrvMock, ctx.timeSrvMock);
ds = new PrometheusDatasource(instanceSettings);
});
const setupMetricFindQuery = (data: any) => {
datasourceRequestMock = jest.fn(() => Promise.resolve({ status: 'success', data: data.response }));
getBackendSrvMock.mockImplementation(
() =>
({
datasourceRequest: datasourceRequestMock,
} as any)
);
return new PrometheusMetricFindQuery(ds, data.query);
};
describe('When performing metricFindQuery', () => {
it('label_names() should generate label name search query', async () => {
const query = ctx.setupMetricFindQuery({
const query = setupMetricFindQuery({
query: 'label_names()',
response: {
data: ['name1', 'name2', 'name3'],
@ -54,8 +89,8 @@ describe('PrometheusMetricFindQuery', () => {
const results = await query.process();
expect(results).toHaveLength(3);
expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledTimes(1);
expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledWith({
expect(datasourceRequestMock).toHaveBeenCalledTimes(1);
expect(datasourceRequestMock).toHaveBeenCalledWith({
method: 'GET',
url: 'proxied/api/v1/labels',
silent: true,
@ -64,7 +99,7 @@ describe('PrometheusMetricFindQuery', () => {
});
it('label_values(resource) should generate label search query', async () => {
const query = ctx.setupMetricFindQuery({
const query = setupMetricFindQuery({
query: 'label_values(resource)',
response: {
data: ['value1', 'value2', 'value3'],
@ -73,8 +108,8 @@ describe('PrometheusMetricFindQuery', () => {
const results = await query.process();
expect(results).toHaveLength(3);
expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledTimes(1);
expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledWith({
expect(datasourceRequestMock).toHaveBeenCalledTimes(1);
expect(datasourceRequestMock).toHaveBeenCalledWith({
method: 'GET',
url: 'proxied/api/v1/label/resource/values',
silent: true,
@ -83,7 +118,7 @@ describe('PrometheusMetricFindQuery', () => {
});
it('label_values(metric, resource) should generate series query with correct time', async () => {
const query = ctx.setupMetricFindQuery({
const query = setupMetricFindQuery({
query: 'label_values(metric, resource)',
response: {
data: [
@ -96,8 +131,8 @@ describe('PrometheusMetricFindQuery', () => {
const results = await query.process();
expect(results).toHaveLength(3);
expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledTimes(1);
expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledWith({
expect(datasourceRequestMock).toHaveBeenCalledTimes(1);
expect(datasourceRequestMock).toHaveBeenCalledWith({
method: 'GET',
url: `proxied/api/v1/series?match[]=metric&start=${raw.from.unix()}&end=${raw.to.unix()}`,
silent: true,
@ -106,7 +141,7 @@ describe('PrometheusMetricFindQuery', () => {
});
it('label_values(metric{label1="foo", label2="bar", label3="baz"}, resource) should generate series query with correct time', async () => {
const query = ctx.setupMetricFindQuery({
const query = setupMetricFindQuery({
query: 'label_values(metric{label1="foo", label2="bar", label3="baz"}, resource)',
response: {
data: [
@ -119,8 +154,8 @@ describe('PrometheusMetricFindQuery', () => {
const results = await query.process();
expect(results).toHaveLength(3);
expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledTimes(1);
expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledWith({
expect(datasourceRequestMock).toHaveBeenCalledTimes(1);
expect(datasourceRequestMock).toHaveBeenCalledWith({
method: 'GET',
url: `proxied/api/v1/series?match[]=${encodeURIComponent(
'metric{label1="foo", label2="bar", label3="baz"}'
@ -131,7 +166,7 @@ describe('PrometheusMetricFindQuery', () => {
});
it('label_values(metric, resource) result should not contain empty string', async () => {
const query = ctx.setupMetricFindQuery({
const query = setupMetricFindQuery({
query: 'label_values(metric, resource)',
response: {
data: [
@ -146,8 +181,8 @@ describe('PrometheusMetricFindQuery', () => {
expect(results).toHaveLength(2);
expect(results[0].text).toBe('value1');
expect(results[1].text).toBe('value2');
expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledTimes(1);
expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledWith({
expect(datasourceRequestMock).toHaveBeenCalledTimes(1);
expect(datasourceRequestMock).toHaveBeenCalledWith({
method: 'GET',
url: `proxied/api/v1/series?match[]=metric&start=${raw.from.unix()}&end=${raw.to.unix()}`,
silent: true,
@ -156,7 +191,7 @@ describe('PrometheusMetricFindQuery', () => {
});
it('metrics(metric.*) should generate metric name query', async () => {
const query = ctx.setupMetricFindQuery({
const query = setupMetricFindQuery({
query: 'metrics(metric.*)',
response: {
data: ['metric1', 'metric2', 'metric3', 'nomatch'],
@ -165,8 +200,8 @@ describe('PrometheusMetricFindQuery', () => {
const results = await query.process();
expect(results).toHaveLength(3);
expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledTimes(1);
expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledWith({
expect(datasourceRequestMock).toHaveBeenCalledTimes(1);
expect(datasourceRequestMock).toHaveBeenCalledWith({
method: 'GET',
url: 'proxied/api/v1/label/__name__/values',
silent: true,
@ -175,7 +210,7 @@ describe('PrometheusMetricFindQuery', () => {
});
it('query_result(metric) should generate metric name query', async () => {
const query = ctx.setupMetricFindQuery({
const query = setupMetricFindQuery({
query: 'query_result(metric)',
response: {
data: {
@ -193,8 +228,8 @@ describe('PrometheusMetricFindQuery', () => {
expect(results).toHaveLength(1);
expect(results[0].text).toBe('metric{job="testjob"} 3846 1443454528000');
expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledTimes(1);
expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledWith({
expect(datasourceRequestMock).toHaveBeenCalledTimes(1);
expect(datasourceRequestMock).toHaveBeenCalledWith({
method: 'GET',
url: `proxied/api/v1/query?query=metric&time=${raw.to.unix()}`,
requestId: undefined,
@ -203,7 +238,7 @@ describe('PrometheusMetricFindQuery', () => {
});
it('up{job="job1"} should fallback using generate series query', async () => {
const query = ctx.setupMetricFindQuery({
const query = setupMetricFindQuery({
query: 'up{job="job1"}',
response: {
data: [
@ -219,8 +254,8 @@ describe('PrometheusMetricFindQuery', () => {
expect(results[0].text).toBe('up{instance="127.0.0.1:1234",job="job1"}');
expect(results[1].text).toBe('up{instance="127.0.0.1:5678",job="job1"}');
expect(results[2].text).toBe('up{instance="127.0.0.1:9102",job="job1"}');
expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledTimes(1);
expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledWith({
expect(datasourceRequestMock).toHaveBeenCalledTimes(1);
expect(datasourceRequestMock).toHaveBeenCalledWith({
method: 'GET',
url: `proxied/api/v1/series?match[]=${encodeURIComponent(
'up{job="job1"}'