datasource.test.ts 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365
  1. import _ from 'lodash';
  2. import q from 'q';
  3. import {
  4. alignRange,
  5. extractRuleMappingFromGroups,
  6. PrometheusDatasource,
  7. prometheusRegularEscape,
  8. prometheusSpecialRegexEscape,
  9. } from '../datasource';
  10. import { dateTime } from '@grafana/ui/src/utils/moment_wrapper';
  11. import { DataSourceInstanceSettings } from '@grafana/ui';
  12. import { PromOptions } from '../types';
  13. import { TemplateSrv } from 'app/features/templating/template_srv';
  14. import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
  15. import { CustomVariable } from 'app/features/templating/custom_variable';
  16. jest.mock('../metric_find_query');
  17. const DEFAULT_TEMPLATE_SRV_MOCK = {
  18. getAdhocFilters: () => [],
  19. replace: a => a,
  20. };
  21. describe('PrometheusDatasource', () => {
  22. const ctx: any = {};
  23. const instanceSettings = ({
  24. url: 'proxied',
  25. directUrl: 'direct',
  26. user: 'test',
  27. password: 'mupp',
  28. jsonData: {} as any,
  29. } as unknown) as DataSourceInstanceSettings<PromOptions>;
  30. ctx.backendSrvMock = {};
  31. ctx.templateSrvMock = DEFAULT_TEMPLATE_SRV_MOCK;
  32. ctx.timeSrvMock = {
  33. timeRange: () => {
  34. return {
  35. from: dateTime(1531468681),
  36. to: dateTime(1531489712),
  37. };
  38. },
  39. };
  40. beforeEach(() => {
  41. ctx.ds = new PrometheusDatasource(instanceSettings, q, ctx.backendSrvMock, ctx.templateSrvMock, ctx.timeSrvMock);
  42. });
  43. describe('Datasource metadata requests', () => {
  44. it('should perform a GET request with the default config', () => {
  45. ctx.backendSrvMock.datasourceRequest = jest.fn();
  46. ctx.ds.metadataRequest('/foo');
  47. expect(ctx.backendSrvMock.datasourceRequest.mock.calls.length).toBe(1);
  48. expect(ctx.backendSrvMock.datasourceRequest.mock.calls[0][0].method).toBe('GET');
  49. });
  50. it('should still perform a GET request with the DS HTTP method set to POST', () => {
  51. ctx.backendSrvMock.datasourceRequest = jest.fn();
  52. const postSettings = _.cloneDeep(instanceSettings);
  53. postSettings.jsonData.httpMethod = 'POST';
  54. const ds = new PrometheusDatasource(postSettings, q, ctx.backendSrvMock, ctx.templateSrvMock, ctx.timeSrvMock);
  55. ds.metadataRequest('/foo');
  56. expect(ctx.backendSrvMock.datasourceRequest.mock.calls.length).toBe(1);
  57. expect(ctx.backendSrvMock.datasourceRequest.mock.calls[0][0].method).toBe('GET');
  58. });
  59. });
  60. describe('When using adhoc filters', () => {
  61. const DEFAULT_QUERY_EXPRESSION = 'metric{job="foo"} - metric';
  62. const target = { expr: DEFAULT_QUERY_EXPRESSION };
  63. afterEach(() => {
  64. ctx.templateSrvMock.getAdhocFilters = DEFAULT_TEMPLATE_SRV_MOCK.getAdhocFilters;
  65. });
  66. it('should not modify expression with no filters', () => {
  67. const result = ctx.ds.createQuery(target, { interval: '15s' });
  68. expect(result).toMatchObject({ expr: DEFAULT_QUERY_EXPRESSION });
  69. });
  70. it('should add filters to expression', () => {
  71. ctx.templateSrvMock.getAdhocFilters = () => [
  72. {
  73. key: 'k1',
  74. operator: '=',
  75. value: 'v1',
  76. },
  77. {
  78. key: 'k2',
  79. operator: '!=',
  80. value: 'v2',
  81. },
  82. ];
  83. const result = ctx.ds.createQuery(target, { interval: '15s' });
  84. expect(result).toMatchObject({ expr: 'metric{job="foo",k1="v1",k2!="v2"} - metric{k1="v1",k2!="v2"}' });
  85. });
  86. it('should add escaping if needed to regex filter expressions', () => {
  87. ctx.templateSrvMock.getAdhocFilters = () => [
  88. {
  89. key: 'k1',
  90. operator: '=~',
  91. value: 'v.*',
  92. },
  93. {
  94. key: 'k2',
  95. operator: '=~',
  96. value: `v'.*`,
  97. },
  98. ];
  99. const result = ctx.ds.createQuery(target, { interval: '15s' });
  100. expect(result).toMatchObject({
  101. expr: `metric{job="foo",k1=~"v.*",k2=~"v\\\\'.*"} - metric{k1=~"v.*",k2=~"v\\\\'.*"}`,
  102. });
  103. });
  104. });
  105. describe('When performing performSuggestQuery', () => {
  106. it('should cache response', async () => {
  107. ctx.backendSrvMock.datasourceRequest.mockReturnValue(
  108. Promise.resolve({
  109. status: 'success',
  110. data: { data: ['value1', 'value2', 'value3'] },
  111. })
  112. );
  113. let results = await ctx.ds.performSuggestQuery('value', true);
  114. expect(results).toHaveLength(3);
  115. ctx.backendSrvMock.datasourceRequest.mockReset();
  116. results = await ctx.ds.performSuggestQuery('value', true);
  117. expect(results).toHaveLength(3);
  118. });
  119. });
  120. describe('When converting prometheus histogram to heatmap format', () => {
  121. beforeEach(() => {
  122. ctx.query = {
  123. range: { from: dateTime(1443454528000), to: dateTime(1443454528000) },
  124. targets: [{ expr: 'test{job="testjob"}', format: 'heatmap', legendFormat: '{{le}}' }],
  125. interval: '1s',
  126. };
  127. });
  128. it('should convert cumullative histogram to ordinary', () => {
  129. const resultMock = [
  130. {
  131. metric: { __name__: 'metric', job: 'testjob', le: '10' },
  132. values: [[1443454528.0, '10'], [1443454528.0, '10']],
  133. },
  134. {
  135. metric: { __name__: 'metric', job: 'testjob', le: '20' },
  136. values: [[1443454528.0, '20'], [1443454528.0, '10']],
  137. },
  138. {
  139. metric: { __name__: 'metric', job: 'testjob', le: '30' },
  140. values: [[1443454528.0, '25'], [1443454528.0, '10']],
  141. },
  142. ];
  143. const responseMock = { data: { data: { result: resultMock } } };
  144. const expected = [
  145. {
  146. target: '10',
  147. datapoints: [[10, 1443454528000], [10, 1443454528000]],
  148. },
  149. {
  150. target: '20',
  151. datapoints: [[10, 1443454528000], [0, 1443454528000]],
  152. },
  153. {
  154. target: '30',
  155. datapoints: [[5, 1443454528000], [0, 1443454528000]],
  156. },
  157. ];
  158. ctx.ds.performTimeSeriesQuery = jest.fn().mockReturnValue(responseMock);
  159. return ctx.ds.query(ctx.query).then(result => {
  160. const results = result.data;
  161. return expect(results).toMatchObject(expected);
  162. });
  163. });
  164. it('should sort series by label value', () => {
  165. const resultMock = [
  166. {
  167. metric: { __name__: 'metric', job: 'testjob', le: '2' },
  168. values: [[1443454528.0, '10'], [1443454528.0, '10']],
  169. },
  170. {
  171. metric: { __name__: 'metric', job: 'testjob', le: '4' },
  172. values: [[1443454528.0, '20'], [1443454528.0, '10']],
  173. },
  174. {
  175. metric: { __name__: 'metric', job: 'testjob', le: '+Inf' },
  176. values: [[1443454528.0, '25'], [1443454528.0, '10']],
  177. },
  178. {
  179. metric: { __name__: 'metric', job: 'testjob', le: '1' },
  180. values: [[1443454528.0, '25'], [1443454528.0, '10']],
  181. },
  182. ];
  183. const responseMock = { data: { data: { result: resultMock } } };
  184. const expected = ['1', '2', '4', '+Inf'];
  185. ctx.ds.performTimeSeriesQuery = jest.fn().mockReturnValue(responseMock);
  186. return ctx.ds.query(ctx.query).then(result => {
  187. const seriesLabels = _.map(result.data, 'target');
  188. return expect(seriesLabels).toEqual(expected);
  189. });
  190. });
  191. });
  192. describe('alignRange', () => {
  193. it('does not modify already aligned intervals with perfect step', () => {
  194. const range = alignRange(0, 3, 3);
  195. expect(range.start).toEqual(0);
  196. expect(range.end).toEqual(3);
  197. });
  198. it('does modify end-aligned intervals to reflect number of steps possible', () => {
  199. const range = alignRange(1, 6, 3);
  200. expect(range.start).toEqual(0);
  201. expect(range.end).toEqual(6);
  202. });
  203. it('does align intervals that are a multiple of steps', () => {
  204. const range = alignRange(1, 4, 3);
  205. expect(range.start).toEqual(0);
  206. expect(range.end).toEqual(3);
  207. });
  208. it('does align intervals that are not a multiple of steps', () => {
  209. const range = alignRange(1, 5, 3);
  210. expect(range.start).toEqual(0);
  211. expect(range.end).toEqual(3);
  212. });
  213. });
  214. describe('extractRuleMappingFromGroups()', () => {
  215. it('returns empty mapping for no rule groups', () => {
  216. expect(extractRuleMappingFromGroups([])).toEqual({});
  217. });
  218. it('returns a mapping for recording rules only', () => {
  219. const groups = [
  220. {
  221. rules: [
  222. {
  223. name: 'HighRequestLatency',
  224. query: 'job:request_latency_seconds:mean5m{job="myjob"} > 0.5',
  225. type: 'alerting',
  226. },
  227. {
  228. name: 'job:http_inprogress_requests:sum',
  229. query: 'sum(http_inprogress_requests) by (job)',
  230. type: 'recording',
  231. },
  232. ],
  233. file: '/rules.yaml',
  234. interval: 60,
  235. name: 'example',
  236. },
  237. ];
  238. const mapping = extractRuleMappingFromGroups(groups);
  239. expect(mapping).toEqual({ 'job:http_inprogress_requests:sum': 'sum(http_inprogress_requests) by (job)' });
  240. });
  241. });
  242. describe('Prometheus regular escaping', () => {
  243. it('should not escape non-string', () => {
  244. expect(prometheusRegularEscape(12)).toEqual(12);
  245. });
  246. it('should not escape simple string', () => {
  247. expect(prometheusRegularEscape('cryptodepression')).toEqual('cryptodepression');
  248. });
  249. it("should escape '", () => {
  250. expect(prometheusRegularEscape("looking'glass")).toEqual("looking\\\\'glass");
  251. });
  252. it('should escape multiple characters', () => {
  253. expect(prometheusRegularEscape("'looking'glass'")).toEqual("\\\\'looking\\\\'glass\\\\'");
  254. });
  255. });
  256. describe('Prometheus regexes escaping', () => {
  257. it('should not escape simple string', () => {
  258. expect(prometheusSpecialRegexEscape('cryptodepression')).toEqual('cryptodepression');
  259. });
  260. it('should escape $^*+?.()|\\', () => {
  261. expect(prometheusSpecialRegexEscape("looking'glass")).toEqual("looking\\\\'glass");
  262. expect(prometheusSpecialRegexEscape('looking{glass')).toEqual('looking\\\\{glass');
  263. expect(prometheusSpecialRegexEscape('looking}glass')).toEqual('looking\\\\}glass');
  264. expect(prometheusSpecialRegexEscape('looking[glass')).toEqual('looking\\\\[glass');
  265. expect(prometheusSpecialRegexEscape('looking]glass')).toEqual('looking\\\\]glass');
  266. expect(prometheusSpecialRegexEscape('looking$glass')).toEqual('looking\\\\$glass');
  267. expect(prometheusSpecialRegexEscape('looking^glass')).toEqual('looking\\\\^glass');
  268. expect(prometheusSpecialRegexEscape('looking*glass')).toEqual('looking\\\\*glass');
  269. expect(prometheusSpecialRegexEscape('looking+glass')).toEqual('looking\\\\+glass');
  270. expect(prometheusSpecialRegexEscape('looking?glass')).toEqual('looking\\\\?glass');
  271. expect(prometheusSpecialRegexEscape('looking.glass')).toEqual('looking\\\\.glass');
  272. expect(prometheusSpecialRegexEscape('looking(glass')).toEqual('looking\\\\(glass');
  273. expect(prometheusSpecialRegexEscape('looking)glass')).toEqual('looking\\\\)glass');
  274. expect(prometheusSpecialRegexEscape('looking\\glass')).toEqual('looking\\\\\\\\glass');
  275. expect(prometheusSpecialRegexEscape('looking|glass')).toEqual('looking\\\\|glass');
  276. });
  277. it('should escape multiple special characters', () => {
  278. expect(prometheusSpecialRegexEscape('+looking$glass?')).toEqual('\\\\+looking\\\\$glass\\\\?');
  279. });
  280. });
  281. describe('When interpolating variables', () => {
  282. beforeEach(() => {
  283. ctx.ds = new PrometheusDatasource(instanceSettings, q, ctx.backendSrvMock, ctx.templateSrvMock, ctx.timeSrvMock);
  284. ctx.variable = new CustomVariable({}, {});
  285. });
  286. describe('and value is a string', () => {
  287. it('should only escape single quotes', () => {
  288. expect(ctx.ds.interpolateQueryExpr("abc'$^*{}[]+?.()|", ctx.variable)).toEqual("abc\\\\'$^*{}[]+?.()|");
  289. });
  290. });
  291. describe('and value is a number', () => {
  292. it('should return a number', () => {
  293. expect(ctx.ds.interpolateQueryExpr(1000, ctx.variable)).toEqual(1000);
  294. });
  295. });
  296. describe('and variable allows multi-value', () => {
  297. beforeEach(() => {
  298. ctx.variable.multi = true;
  299. });
  300. it('should regex escape values if the value is a string', () => {
  301. expect(ctx.ds.interpolateQueryExpr('looking*glass', ctx.variable)).toEqual('looking\\\\*glass');
  302. });
  303. it('should return pipe separated values if the value is an array of strings', () => {
  304. expect(ctx.ds.interpolateQueryExpr(['a|bc', 'de|f'], ctx.variable)).toEqual('a\\\\|bc|de\\\\|f');
  305. });
  306. });
  307. describe('and variable allows all', () => {
  308. beforeEach(() => {
  309. ctx.variable.includeAll = true;
  310. });
  311. it('should regex escape values if the array is a string', () => {
  312. expect(ctx.ds.interpolateQueryExpr('looking*glass', ctx.variable)).toEqual('looking\\\\*glass');
  313. });
  314. it('should return pipe separated values if the value is an array of strings', () => {
  315. expect(ctx.ds.interpolateQueryExpr(['a|bc', 'de|f'], ctx.variable)).toEqual('a\\\\|bc|de\\\\|f');
  316. });
  317. });
  318. });
  319. describe('metricFindQuery', () => {
  320. beforeEach(() => {
  321. const query = 'query_result(topk(5,rate(http_request_duration_microseconds_count[$__interval])))';
  322. ctx.templateSrvMock.replace = jest.fn();
  323. ctx.timeSrvMock.timeRange = () => {
  324. return {
  325. from: dateTime(1531468681),
  326. to: dateTime(1531489712),
  327. };
  328. };
  329. ctx.ds = new PrometheusDatasource(instanceSettings, q, ctx.backendSrvMock, ctx.templateSrvMock, ctx.timeSrvMock);
  330. ctx.ds.metricFindQuery(query);
  331. });
  332. it('should call templateSrv.replace with scopedVars', () => {
  333. expect(ctx.templateSrvMock.replace.mock.calls[0][1]).toBeDefined();
  334. });
  335. it('should have the correct range and range_ms', () => {
  336. const range = ctx.templateSrvMock.replace.mock.calls[0][1].__range;
  337. const rangeMs = ctx.templateSrvMock.replace.mock.calls[0][1].__range_ms;
  338. const rangeS = ctx.templateSrvMock.replace.mock.calls[0][1].__range_s;
  339. expect(range).toEqual({ text: '21s', value: '21s' });
  340. expect(rangeMs).toEqual({ text: 21031, value: 21031 });
  341. expect(rangeS).toEqual({ text: 21, value: 21 });
  342. });
  343. it('should pass the default interval value', () => {
  344. const interval = ctx.templateSrvMock.replace.mock.calls[0][1].__interval;
  345. const intervalMs = ctx.templateSrvMock.replace.mock.calls[0][1].__interval_ms;
  346. expect(interval).toEqual({ text: '15s', value: '15s' });
  347. expect(intervalMs).toEqual({ text: 15000, value: 15000 });
  348. });
  349. });
  350. });
  351. const SECOND = 1000;
  352. const MINUTE = 60 * SECOND;
  353. const HOUR = 60 * MINUTE;
  354. const time = ({ hours = 0, seconds = 0, minutes = 0 }) => dateTime(hours * HOUR + minutes * MINUTE + seconds * SECOND);
  355. const ctx = {} as any;
  356. const instanceSettings = ({
  357. url: 'proxied',
  358. directUrl: 'direct',
  359. user: 'test',
  360. password: 'mupp',
  361. jsonData: { httpMethod: 'GET' },
  362. } as unknown) as DataSourceInstanceSettings<PromOptions>;
  363. const backendSrv = {
  364. datasourceRequest: jest.fn(),
  365. } as any;
  366. const templateSrv = ({
  367. getAdhocFilters: () => [],
  368. replace: jest.fn(str => str),
  369. } as unknown) as TemplateSrv;
  370. const timeSrv = ({
  371. timeRange: () => {
  372. return { to: { diff: () => 2000 }, from: '' };
  373. },
  374. } as unknown) as TimeSrv;
  375. describe('PrometheusDatasource', () => {
  376. describe('When querying prometheus with one target using query editor target spec', () => {
  377. let results;
  378. const query = {
  379. range: { from: time({ seconds: 63 }), to: time({ seconds: 183 }) },
  380. targets: [{ expr: 'test{job="testjob"}', format: 'time_series' }],
  381. interval: '60s',
  382. };
  383. // Interval alignment with step
  384. const urlExpected =
  385. 'proxied/api/v1/query_range?query=' + encodeURIComponent('test{job="testjob"}') + '&start=60&end=180&step=60';
  386. beforeEach(async () => {
  387. const response = {
  388. data: {
  389. status: 'success',
  390. data: {
  391. resultType: 'matrix',
  392. result: [
  393. {
  394. metric: { __name__: 'test', job: 'testjob' },
  395. values: [[60, '3846']],
  396. },
  397. ],
  398. },
  399. },
  400. };
  401. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  402. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  403. await ctx.ds.query(query).then(data => {
  404. results = data;
  405. });
  406. });
  407. it('should generate the correct query', () => {
  408. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  409. expect(res.method).toBe('GET');
  410. expect(res.url).toBe(urlExpected);
  411. });
  412. it('should return series list', async () => {
  413. expect(results.data.length).toBe(1);
  414. expect(results.data[0].target).toBe('test{job="testjob"}');
  415. });
  416. });
  417. describe('When querying prometheus with one target which returns multiple series', () => {
  418. let results;
  419. const start = 60;
  420. const end = 360;
  421. const step = 60;
  422. const query = {
  423. range: { from: time({ seconds: start }), to: time({ seconds: end }) },
  424. targets: [{ expr: 'test{job="testjob"}', format: 'time_series' }],
  425. interval: '60s',
  426. };
  427. beforeEach(async () => {
  428. const response = {
  429. status: 'success',
  430. data: {
  431. data: {
  432. resultType: 'matrix',
  433. result: [
  434. {
  435. metric: { __name__: 'test', job: 'testjob', series: 'series 1' },
  436. values: [[start + step * 1, '3846'], [start + step * 3, '3847'], [end - step * 1, '3848']],
  437. },
  438. {
  439. metric: { __name__: 'test', job: 'testjob', series: 'series 2' },
  440. values: [[start + step * 2, '4846']],
  441. },
  442. ],
  443. },
  444. },
  445. };
  446. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  447. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  448. await ctx.ds.query(query).then(data => {
  449. results = data;
  450. });
  451. });
  452. it('should be same length', () => {
  453. expect(results.data.length).toBe(2);
  454. expect(results.data[0].datapoints.length).toBe((end - start) / step + 1);
  455. expect(results.data[1].datapoints.length).toBe((end - start) / step + 1);
  456. });
  457. it('should fill null until first datapoint in response', () => {
  458. expect(results.data[0].datapoints[0][1]).toBe(start * 1000);
  459. expect(results.data[0].datapoints[0][0]).toBe(null);
  460. expect(results.data[0].datapoints[1][1]).toBe((start + step * 1) * 1000);
  461. expect(results.data[0].datapoints[1][0]).toBe(3846);
  462. });
  463. it('should fill null after last datapoint in response', () => {
  464. const length = (end - start) / step + 1;
  465. expect(results.data[0].datapoints[length - 2][1]).toBe((end - step * 1) * 1000);
  466. expect(results.data[0].datapoints[length - 2][0]).toBe(3848);
  467. expect(results.data[0].datapoints[length - 1][1]).toBe(end * 1000);
  468. expect(results.data[0].datapoints[length - 1][0]).toBe(null);
  469. });
  470. it('should fill null at gap between series', () => {
  471. expect(results.data[0].datapoints[2][1]).toBe((start + step * 2) * 1000);
  472. expect(results.data[0].datapoints[2][0]).toBe(null);
  473. expect(results.data[1].datapoints[1][1]).toBe((start + step * 1) * 1000);
  474. expect(results.data[1].datapoints[1][0]).toBe(null);
  475. expect(results.data[1].datapoints[3][1]).toBe((start + step * 3) * 1000);
  476. expect(results.data[1].datapoints[3][0]).toBe(null);
  477. });
  478. });
  479. describe('When querying prometheus with one target and instant = true', () => {
  480. let results;
  481. const urlExpected = 'proxied/api/v1/query?query=' + encodeURIComponent('test{job="testjob"}') + '&time=123';
  482. const query = {
  483. range: { from: time({ seconds: 63 }), to: time({ seconds: 123 }) },
  484. targets: [{ expr: 'test{job="testjob"}', format: 'time_series', instant: true }],
  485. interval: '60s',
  486. };
  487. beforeEach(async () => {
  488. const response = {
  489. status: 'success',
  490. data: {
  491. data: {
  492. resultType: 'vector',
  493. result: [
  494. {
  495. metric: { __name__: 'test', job: 'testjob' },
  496. value: [123, '3846'],
  497. },
  498. ],
  499. },
  500. },
  501. };
  502. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  503. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  504. await ctx.ds.query(query).then(data => {
  505. results = data;
  506. });
  507. });
  508. it('should generate the correct query', () => {
  509. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  510. expect(res.method).toBe('GET');
  511. expect(res.url).toBe(urlExpected);
  512. });
  513. it('should return series list', () => {
  514. expect(results.data.length).toBe(1);
  515. expect(results.data[0].target).toBe('test{job="testjob"}');
  516. });
  517. });
  518. describe('When performing annotationQuery', () => {
  519. let results;
  520. const options: any = {
  521. annotation: {
  522. expr: 'ALERTS{alertstate="firing"}',
  523. tagKeys: 'job',
  524. titleFormat: '{{alertname}}',
  525. textFormat: '{{instance}}',
  526. },
  527. range: {
  528. from: time({ seconds: 63 }),
  529. to: time({ seconds: 123 }),
  530. },
  531. };
  532. const response = {
  533. status: 'success',
  534. data: {
  535. data: {
  536. resultType: 'matrix',
  537. result: [
  538. {
  539. metric: {
  540. __name__: 'ALERTS',
  541. alertname: 'InstanceDown',
  542. alertstate: 'firing',
  543. instance: 'testinstance',
  544. job: 'testjob',
  545. },
  546. values: [[123, '1']],
  547. },
  548. ],
  549. },
  550. },
  551. };
  552. describe('not use useValueForTime', () => {
  553. beforeEach(async () => {
  554. options.annotation.useValueForTime = false;
  555. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  556. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  557. await ctx.ds.annotationQuery(options).then(data => {
  558. results = data;
  559. });
  560. });
  561. it('should return annotation list', () => {
  562. expect(results.length).toBe(1);
  563. expect(results[0].tags).toContain('testjob');
  564. expect(results[0].title).toBe('InstanceDown');
  565. expect(results[0].text).toBe('testinstance');
  566. expect(results[0].time).toBe(123 * 1000);
  567. });
  568. });
  569. describe('use useValueForTime', () => {
  570. beforeEach(async () => {
  571. options.annotation.useValueForTime = true;
  572. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  573. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  574. await ctx.ds.annotationQuery(options).then(data => {
  575. results = data;
  576. });
  577. });
  578. it('should return annotation list', () => {
  579. expect(results[0].time).toEqual(1);
  580. });
  581. });
  582. describe('step parameter', () => {
  583. beforeEach(() => {
  584. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  585. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  586. });
  587. it('should use default step for short range if no interval is given', () => {
  588. const query = {
  589. ...options,
  590. range: {
  591. from: time({ seconds: 63 }),
  592. to: time({ seconds: 123 }),
  593. },
  594. };
  595. ctx.ds.annotationQuery(query);
  596. const req = backendSrv.datasourceRequest.mock.calls[0][0];
  597. expect(req.url).toContain('step=60');
  598. });
  599. it('should use custom step for short range', () => {
  600. const annotation = {
  601. ...options.annotation,
  602. step: '10s',
  603. };
  604. const query = {
  605. ...options,
  606. annotation,
  607. range: {
  608. from: time({ seconds: 63 }),
  609. to: time({ seconds: 123 }),
  610. },
  611. };
  612. ctx.ds.annotationQuery(query);
  613. const req = backendSrv.datasourceRequest.mock.calls[0][0];
  614. expect(req.url).toContain('step=10');
  615. });
  616. it('should use custom step for short range', () => {
  617. const annotation = {
  618. ...options.annotation,
  619. step: '10s',
  620. };
  621. const query = {
  622. ...options,
  623. annotation,
  624. range: {
  625. from: time({ seconds: 63 }),
  626. to: time({ seconds: 123 }),
  627. },
  628. };
  629. ctx.ds.annotationQuery(query);
  630. const req = backendSrv.datasourceRequest.mock.calls[0][0];
  631. expect(req.url).toContain('step=10');
  632. });
  633. it('should use dynamic step on long ranges if no option was given', () => {
  634. const query = {
  635. ...options,
  636. range: {
  637. from: time({ seconds: 63 }),
  638. to: time({ hours: 24 * 30, seconds: 63 }),
  639. },
  640. };
  641. ctx.ds.annotationQuery(query);
  642. const req = backendSrv.datasourceRequest.mock.calls[0][0];
  643. // Range in seconds: (to - from) / 1000
  644. // Max_datapoints: 11000
  645. // Step: range / max_datapoints
  646. const step = 236;
  647. expect(req.url).toContain(`step=${step}`);
  648. });
  649. });
  650. });
  651. describe('When resultFormat is table and instant = true', () => {
  652. let results;
  653. const query = {
  654. range: { from: time({ seconds: 63 }), to: time({ seconds: 123 }) },
  655. targets: [{ expr: 'test{job="testjob"}', format: 'time_series', instant: true }],
  656. interval: '60s',
  657. };
  658. beforeEach(async () => {
  659. const response = {
  660. status: 'success',
  661. data: {
  662. data: {
  663. resultType: 'vector',
  664. result: [
  665. {
  666. metric: { __name__: 'test', job: 'testjob' },
  667. value: [123, '3846'],
  668. },
  669. ],
  670. },
  671. },
  672. };
  673. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  674. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  675. await ctx.ds.query(query).then(data => {
  676. results = data;
  677. });
  678. });
  679. it('should return result', () => {
  680. expect(results).not.toBe(null);
  681. });
  682. });
  683. describe('The "step" query parameter', () => {
  684. const response = {
  685. status: 'success',
  686. data: {
  687. data: {
  688. resultType: 'matrix',
  689. result: [],
  690. },
  691. },
  692. };
  693. it('should be min interval when greater than auto interval', async () => {
  694. const query = {
  695. // 6 minute range
  696. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  697. targets: [
  698. {
  699. expr: 'test',
  700. interval: '10s',
  701. },
  702. ],
  703. interval: '5s',
  704. };
  705. const urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=10';
  706. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  707. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  708. await ctx.ds.query(query);
  709. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  710. expect(res.method).toBe('GET');
  711. expect(res.url).toBe(urlExpected);
  712. });
  713. it('step should never go below 1', async () => {
  714. const query = {
  715. // 6 minute range
  716. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  717. targets: [{ expr: 'test' }],
  718. interval: '100ms',
  719. };
  720. const urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=1';
  721. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  722. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  723. await ctx.ds.query(query);
  724. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  725. expect(res.method).toBe('GET');
  726. expect(res.url).toBe(urlExpected);
  727. });
  728. it('should be auto interval when greater than min interval', async () => {
  729. const query = {
  730. // 6 minute range
  731. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  732. targets: [
  733. {
  734. expr: 'test',
  735. interval: '5s',
  736. },
  737. ],
  738. interval: '10s',
  739. };
  740. const urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=10';
  741. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  742. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  743. await ctx.ds.query(query);
  744. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  745. expect(res.method).toBe('GET');
  746. expect(res.url).toBe(urlExpected);
  747. });
  748. it('should result in querying fewer than 11000 data points', async () => {
  749. const query = {
  750. // 6 hour range
  751. range: { from: time({ hours: 1 }), to: time({ hours: 7 }) },
  752. targets: [{ expr: 'test' }],
  753. interval: '1s',
  754. };
  755. const end = 7 * 60 * 60;
  756. const start = 60 * 60;
  757. const urlExpected = 'proxied/api/v1/query_range?query=test&start=' + start + '&end=' + end + '&step=2';
  758. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  759. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  760. await ctx.ds.query(query);
  761. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  762. expect(res.method).toBe('GET');
  763. expect(res.url).toBe(urlExpected);
  764. });
  765. it('should not apply min interval when interval * intervalFactor greater', async () => {
  766. const query = {
  767. // 6 minute range
  768. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  769. targets: [
  770. {
  771. expr: 'test',
  772. interval: '10s',
  773. intervalFactor: 10,
  774. },
  775. ],
  776. interval: '5s',
  777. };
  778. // times get rounded up to interval
  779. const urlExpected = 'proxied/api/v1/query_range?query=test&start=50&end=400&step=50';
  780. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  781. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  782. await ctx.ds.query(query);
  783. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  784. expect(res.method).toBe('GET');
  785. expect(res.url).toBe(urlExpected);
  786. });
  787. it('should apply min interval when interval * intervalFactor smaller', async () => {
  788. const query = {
  789. // 6 minute range
  790. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  791. targets: [
  792. {
  793. expr: 'test',
  794. interval: '15s',
  795. intervalFactor: 2,
  796. },
  797. ],
  798. interval: '5s',
  799. };
  800. const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=60&end=420&step=15';
  801. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  802. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  803. await ctx.ds.query(query);
  804. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  805. expect(res.method).toBe('GET');
  806. expect(res.url).toBe(urlExpected);
  807. });
  808. it('should apply intervalFactor to auto interval when greater', async () => {
  809. const query = {
  810. // 6 minute range
  811. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  812. targets: [
  813. {
  814. expr: 'test',
  815. interval: '5s',
  816. intervalFactor: 10,
  817. },
  818. ],
  819. interval: '10s',
  820. };
  821. // times get aligned to interval
  822. const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=0&end=400&step=100';
  823. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  824. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  825. await ctx.ds.query(query);
  826. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  827. expect(res.method).toBe('GET');
  828. expect(res.url).toBe(urlExpected);
  829. });
  830. it('should not not be affected by the 11000 data points limit when large enough', async () => {
  831. const query = {
  832. // 1 week range
  833. range: { from: time({}), to: time({ hours: 7 * 24 }) },
  834. targets: [
  835. {
  836. expr: 'test',
  837. intervalFactor: 10,
  838. },
  839. ],
  840. interval: '10s',
  841. };
  842. const end = 7 * 24 * 60 * 60;
  843. const start = 0;
  844. const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=' + start + '&end=' + end + '&step=100';
  845. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  846. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  847. await ctx.ds.query(query);
  848. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  849. expect(res.method).toBe('GET');
  850. expect(res.url).toBe(urlExpected);
  851. });
  852. it('should be determined by the 11000 data points limit when too small', async () => {
  853. const query = {
  854. // 1 week range
  855. range: { from: time({}), to: time({ hours: 7 * 24 }) },
  856. targets: [
  857. {
  858. expr: 'test',
  859. intervalFactor: 10,
  860. },
  861. ],
  862. interval: '5s',
  863. };
  864. const end = 7 * 24 * 60 * 60;
  865. const start = 0;
  866. const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=' + start + '&end=' + end + '&step=60';
  867. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  868. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  869. await ctx.ds.query(query);
  870. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  871. expect(res.method).toBe('GET');
  872. expect(res.url).toBe(urlExpected);
  873. });
  874. });
  875. describe('The __interval and __interval_ms template variables', () => {
  876. const response = {
  877. status: 'success',
  878. data: {
  879. data: {
  880. resultType: 'matrix',
  881. result: [],
  882. },
  883. },
  884. };
  885. it('should be unchanged when auto interval is greater than min interval', async () => {
  886. const query = {
  887. // 6 minute range
  888. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  889. targets: [
  890. {
  891. expr: 'rate(test[$__interval])',
  892. interval: '5s',
  893. },
  894. ],
  895. interval: '10s',
  896. scopedVars: {
  897. __interval: { text: '10s', value: '10s' },
  898. __interval_ms: { text: 10 * 1000, value: 10 * 1000 },
  899. },
  900. };
  901. const urlExpected =
  902. 'proxied/api/v1/query_range?query=' +
  903. encodeURIComponent('rate(test[$__interval])') +
  904. '&start=60&end=420&step=10';
  905. templateSrv.replace = jest.fn(str => str);
  906. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  907. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  908. await ctx.ds.query(query);
  909. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  910. expect(res.method).toBe('GET');
  911. expect(res.url).toBe(urlExpected);
  912. // @ts-ignore
  913. expect(templateSrv.replace.mock.calls[0][1]).toEqual({
  914. __interval: {
  915. text: '10s',
  916. value: '10s',
  917. },
  918. __interval_ms: {
  919. text: 10000,
  920. value: 10000,
  921. },
  922. });
  923. });
  924. it('should be min interval when it is greater than auto interval', async () => {
  925. const query = {
  926. // 6 minute range
  927. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  928. targets: [
  929. {
  930. expr: 'rate(test[$__interval])',
  931. interval: '10s',
  932. },
  933. ],
  934. interval: '5s',
  935. scopedVars: {
  936. __interval: { text: '5s', value: '5s' },
  937. __interval_ms: { text: 5 * 1000, value: 5 * 1000 },
  938. },
  939. };
  940. const urlExpected =
  941. 'proxied/api/v1/query_range?query=' +
  942. encodeURIComponent('rate(test[$__interval])') +
  943. '&start=60&end=420&step=10';
  944. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  945. templateSrv.replace = jest.fn(str => str);
  946. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  947. await ctx.ds.query(query);
  948. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  949. expect(res.method).toBe('GET');
  950. expect(res.url).toBe(urlExpected);
  951. // @ts-ignore
  952. expect(templateSrv.replace.mock.calls[0][1]).toEqual({
  953. __interval: {
  954. text: '5s',
  955. value: '5s',
  956. },
  957. __interval_ms: {
  958. text: 5000,
  959. value: 5000,
  960. },
  961. });
  962. });
  963. it('should account for intervalFactor', async () => {
  964. const query = {
  965. // 6 minute range
  966. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  967. targets: [
  968. {
  969. expr: 'rate(test[$__interval])',
  970. interval: '5s',
  971. intervalFactor: 10,
  972. },
  973. ],
  974. interval: '10s',
  975. scopedVars: {
  976. __interval: { text: '10s', value: '10s' },
  977. __interval_ms: { text: 10 * 1000, value: 10 * 1000 },
  978. },
  979. };
  980. const urlExpected =
  981. 'proxied/api/v1/query_range?query=' +
  982. encodeURIComponent('rate(test[$__interval])') +
  983. '&start=0&end=400&step=100';
  984. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  985. templateSrv.replace = jest.fn(str => str);
  986. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  987. await ctx.ds.query(query);
  988. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  989. expect(res.method).toBe('GET');
  990. expect(res.url).toBe(urlExpected);
  991. // @ts-ignore
  992. expect(templateSrv.replace.mock.calls[0][1]).toEqual({
  993. __interval: {
  994. text: '10s',
  995. value: '10s',
  996. },
  997. __interval_ms: {
  998. text: 10000,
  999. value: 10000,
  1000. },
  1001. });
  1002. expect(query.scopedVars.__interval.text).toBe('10s');
  1003. expect(query.scopedVars.__interval.value).toBe('10s');
  1004. expect(query.scopedVars.__interval_ms.text).toBe(10 * 1000);
  1005. expect(query.scopedVars.__interval_ms.value).toBe(10 * 1000);
  1006. });
  1007. it('should be interval * intervalFactor when greater than min interval', async () => {
  1008. const query = {
  1009. // 6 minute range
  1010. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  1011. targets: [
  1012. {
  1013. expr: 'rate(test[$__interval])',
  1014. interval: '10s',
  1015. intervalFactor: 10,
  1016. },
  1017. ],
  1018. interval: '5s',
  1019. scopedVars: {
  1020. __interval: { text: '5s', value: '5s' },
  1021. __interval_ms: { text: 5 * 1000, value: 5 * 1000 },
  1022. },
  1023. };
  1024. const urlExpected =
  1025. 'proxied/api/v1/query_range?query=' +
  1026. encodeURIComponent('rate(test[$__interval])') +
  1027. '&start=50&end=400&step=50';
  1028. templateSrv.replace = jest.fn(str => str);
  1029. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  1030. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  1031. await ctx.ds.query(query);
  1032. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  1033. expect(res.method).toBe('GET');
  1034. expect(res.url).toBe(urlExpected);
  1035. // @ts-ignore
  1036. expect(templateSrv.replace.mock.calls[0][1]).toEqual({
  1037. __interval: {
  1038. text: '5s',
  1039. value: '5s',
  1040. },
  1041. __interval_ms: {
  1042. text: 5000,
  1043. value: 5000,
  1044. },
  1045. });
  1046. });
  1047. it('should be min interval when greater than interval * intervalFactor', async () => {
  1048. const query = {
  1049. // 6 minute range
  1050. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  1051. targets: [
  1052. {
  1053. expr: 'rate(test[$__interval])',
  1054. interval: '15s',
  1055. intervalFactor: 2,
  1056. },
  1057. ],
  1058. interval: '5s',
  1059. scopedVars: {
  1060. __interval: { text: '5s', value: '5s' },
  1061. __interval_ms: { text: 5 * 1000, value: 5 * 1000 },
  1062. },
  1063. };
  1064. const urlExpected =
  1065. 'proxied/api/v1/query_range?query=' +
  1066. encodeURIComponent('rate(test[$__interval])') +
  1067. '&start=60&end=420&step=15';
  1068. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  1069. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  1070. await ctx.ds.query(query);
  1071. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  1072. expect(res.method).toBe('GET');
  1073. expect(res.url).toBe(urlExpected);
  1074. // @ts-ignore
  1075. expect(templateSrv.replace.mock.calls[0][1]).toEqual({
  1076. __interval: {
  1077. text: '5s',
  1078. value: '5s',
  1079. },
  1080. __interval_ms: {
  1081. text: 5000,
  1082. value: 5000,
  1083. },
  1084. });
  1085. });
  1086. it('should be determined by the 11000 data points limit, accounting for intervalFactor', async () => {
  1087. const query = {
  1088. // 1 week range
  1089. range: { from: time({}), to: time({ hours: 7 * 24 }) },
  1090. targets: [
  1091. {
  1092. expr: 'rate(test[$__interval])',
  1093. intervalFactor: 10,
  1094. },
  1095. ],
  1096. interval: '5s',
  1097. scopedVars: {
  1098. __interval: { text: '5s', value: '5s' },
  1099. __interval_ms: { text: 5 * 1000, value: 5 * 1000 },
  1100. },
  1101. };
  1102. const end = 7 * 24 * 60 * 60;
  1103. const start = 0;
  1104. const urlExpected =
  1105. 'proxied/api/v1/query_range?query=' +
  1106. encodeURIComponent('rate(test[$__interval])') +
  1107. '&start=' +
  1108. start +
  1109. '&end=' +
  1110. end +
  1111. '&step=60';
  1112. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  1113. templateSrv.replace = jest.fn(str => str);
  1114. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  1115. await ctx.ds.query(query);
  1116. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  1117. expect(res.method).toBe('GET');
  1118. expect(res.url).toBe(urlExpected);
  1119. // @ts-ignore
  1120. expect(templateSrv.replace.mock.calls[0][1]).toEqual({
  1121. __interval: {
  1122. text: '5s',
  1123. value: '5s',
  1124. },
  1125. __interval_ms: {
  1126. text: 5000,
  1127. value: 5000,
  1128. },
  1129. });
  1130. });
  1131. });
  1132. describe('The __range, __range_s and __range_ms variables', () => {
  1133. const response = {
  1134. status: 'success',
  1135. data: {
  1136. data: {
  1137. resultType: 'matrix',
  1138. result: [],
  1139. },
  1140. },
  1141. };
  1142. it('should use overridden ranges, not dashboard ranges', async () => {
  1143. const expectedRangeSecond = 3600;
  1144. const expectedRangeString = '1h';
  1145. const query = {
  1146. range: {
  1147. from: time({}),
  1148. to: time({ hours: 1 }),
  1149. },
  1150. targets: [
  1151. {
  1152. expr: 'test[${__range_s}s]',
  1153. },
  1154. ],
  1155. interval: '60s',
  1156. };
  1157. const urlExpected = `proxied/api/v1/query_range?query=${encodeURIComponent(
  1158. query.targets[0].expr
  1159. )}&start=0&end=3600&step=60`;
  1160. templateSrv.replace = jest.fn(str => str);
  1161. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  1162. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  1163. await ctx.ds.query(query);
  1164. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  1165. expect(res.url).toBe(urlExpected);
  1166. // @ts-ignore
  1167. expect(templateSrv.replace.mock.calls[1][1]).toEqual({
  1168. __range_s: {
  1169. text: expectedRangeSecond,
  1170. value: expectedRangeSecond,
  1171. },
  1172. __range: {
  1173. text: expectedRangeString,
  1174. value: expectedRangeString,
  1175. },
  1176. __range_ms: {
  1177. text: expectedRangeSecond * 1000,
  1178. value: expectedRangeSecond * 1000,
  1179. },
  1180. });
  1181. });
  1182. });
  1183. });
  1184. describe('PrometheusDatasource for POST', () => {
  1185. // const ctx = new helpers.ServiceTestContext();
  1186. const instanceSettings = ({
  1187. url: 'proxied',
  1188. directUrl: 'direct',
  1189. user: 'test',
  1190. password: 'mupp',
  1191. jsonData: { httpMethod: 'POST' },
  1192. } as unknown) as DataSourceInstanceSettings<PromOptions>;
  1193. describe('When querying prometheus with one target using query editor target spec', () => {
  1194. let results;
  1195. const urlExpected = 'proxied/api/v1/query_range';
  1196. const dataExpected = {
  1197. query: 'test{job="testjob"}',
  1198. start: 1 * 60,
  1199. end: 2 * 60,
  1200. step: 60,
  1201. };
  1202. const query = {
  1203. range: { from: time({ minutes: 1, seconds: 3 }), to: time({ minutes: 2, seconds: 3 }) },
  1204. targets: [{ expr: 'test{job="testjob"}', format: 'time_series' }],
  1205. interval: '60s',
  1206. };
  1207. beforeEach(async () => {
  1208. const response = {
  1209. status: 'success',
  1210. data: {
  1211. data: {
  1212. resultType: 'matrix',
  1213. result: [
  1214. {
  1215. metric: { __name__: 'test', job: 'testjob' },
  1216. values: [[2 * 60, '3846']],
  1217. },
  1218. ],
  1219. },
  1220. },
  1221. };
  1222. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  1223. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  1224. await ctx.ds.query(query).then(data => {
  1225. results = data;
  1226. });
  1227. });
  1228. it('should generate the correct query', () => {
  1229. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  1230. expect(res.method).toBe('POST');
  1231. expect(res.url).toBe(urlExpected);
  1232. expect(res.data).toEqual(dataExpected);
  1233. });
  1234. it('should return series list', () => {
  1235. expect(results.data.length).toBe(1);
  1236. expect(results.data[0].target).toBe('test{job="testjob"}');
  1237. });
  1238. });
  1239. describe('When querying prometheus via check headers X-Dashboard-Id and X-Panel-Id', () => {
  1240. const options = { dashboardId: 1, panelId: 2 };
  1241. const httpOptions = {
  1242. headers: {},
  1243. };
  1244. it('with proxy access tracing headers should be added', () => {
  1245. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  1246. ctx.ds._addTracingHeaders(httpOptions, options);
  1247. expect(httpOptions.headers['X-Dashboard-Id']).toBe(1);
  1248. expect(httpOptions.headers['X-Panel-Id']).toBe(2);
  1249. });
  1250. it('with direct access tracing headers should not be added', () => {
  1251. instanceSettings.url = 'http://127.0.0.1:8000';
  1252. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
  1253. ctx.ds._addTracingHeaders(httpOptions, options);
  1254. expect(httpOptions.headers['X-Dashboard-Id']).toBe(undefined);
  1255. expect(httpOptions.headers['X-Panel-Id']).toBe(undefined);
  1256. });
  1257. });
  1258. });