mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Chore: Remove angular dependency from prometheus datasource (#20536)
* Chore: Remove angular dependency from prometheus datasource
This commit is contained in:
parent
37051cd844
commit
fcad439c29
@ -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);
|
||||
|
@ -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',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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 = {
|
||||
|
@ -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"}'
|
||||
|
Loading…
Reference in New Issue
Block a user