datasource.jest.ts 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042
  1. import _ from 'lodash';
  2. import moment from 'moment';
  3. import q from 'q';
  4. import { alignRange, PrometheusDatasource, prometheusSpecialRegexEscape, prometheusRegularEscape } from '../datasource';
  5. jest.mock('../metric_find_query');
  6. describe('PrometheusDatasource', () => {
  7. let ctx: any = {};
  8. let instanceSettings = {
  9. url: 'proxied',
  10. directUrl: 'direct',
  11. user: 'test',
  12. password: 'mupp',
  13. jsonData: {},
  14. };
  15. ctx.backendSrvMock = {};
  16. ctx.templateSrvMock = {
  17. replace: a => a,
  18. };
  19. ctx.timeSrvMock = {
  20. timeRange: () => {
  21. return {
  22. from: moment(1531468681),
  23. to: moment(1531489712),
  24. };
  25. },
  26. };
  27. beforeEach(() => {
  28. ctx.ds = new PrometheusDatasource(instanceSettings, q, ctx.backendSrvMock, ctx.templateSrvMock, ctx.timeSrvMock);
  29. });
  30. describe('Datasource metadata requests', () => {
  31. it('should perform a GET request with the default config', () => {
  32. ctx.backendSrvMock.datasourceRequest = jest.fn();
  33. ctx.ds.metadataRequest('/foo');
  34. expect(ctx.backendSrvMock.datasourceRequest.mock.calls.length).toBe(1);
  35. expect(ctx.backendSrvMock.datasourceRequest.mock.calls[0][0].method).toBe('GET');
  36. });
  37. it('should still perform a GET request with the DS HTTP method set to POST', () => {
  38. ctx.backendSrvMock.datasourceRequest = jest.fn();
  39. const postSettings = _.cloneDeep(instanceSettings);
  40. postSettings.jsonData.httpMethod = 'POST';
  41. const ds = new PrometheusDatasource(postSettings, q, ctx.backendSrvMock, ctx.templateSrvMock, ctx.timeSrvMock);
  42. ds.metadataRequest('/foo');
  43. expect(ctx.backendSrvMock.datasourceRequest.mock.calls.length).toBe(1);
  44. expect(ctx.backendSrvMock.datasourceRequest.mock.calls[0][0].method).toBe('GET');
  45. });
  46. });
  47. describe('When performing performSuggestQuery', () => {
  48. it('should cache response', async () => {
  49. ctx.backendSrvMock.datasourceRequest.mockReturnValue(
  50. Promise.resolve({
  51. status: 'success',
  52. data: { data: ['value1', 'value2', 'value3'] },
  53. })
  54. );
  55. let results = await ctx.ds.performSuggestQuery('value', true);
  56. expect(results).toHaveLength(3);
  57. ctx.backendSrvMock.datasourceRequest.mockReset();
  58. results = await ctx.ds.performSuggestQuery('value', true);
  59. expect(results).toHaveLength(3);
  60. });
  61. });
  62. describe('When converting prometheus histogram to heatmap format', () => {
  63. beforeEach(() => {
  64. ctx.query = {
  65. range: { from: moment(1443454528000), to: moment(1443454528000) },
  66. targets: [{ expr: 'test{job="testjob"}', format: 'heatmap', legendFormat: '{{le}}' }],
  67. interval: '1s',
  68. };
  69. });
  70. it('should convert cumullative histogram to ordinary', () => {
  71. const resultMock = [
  72. {
  73. metric: { __name__: 'metric', job: 'testjob', le: '10' },
  74. values: [[1443454528.0, '10'], [1443454528.0, '10']],
  75. },
  76. {
  77. metric: { __name__: 'metric', job: 'testjob', le: '20' },
  78. values: [[1443454528.0, '20'], [1443454528.0, '10']],
  79. },
  80. {
  81. metric: { __name__: 'metric', job: 'testjob', le: '30' },
  82. values: [[1443454528.0, '25'], [1443454528.0, '10']],
  83. },
  84. ];
  85. const responseMock = { data: { data: { result: resultMock } } };
  86. const expected = [
  87. {
  88. target: '10',
  89. datapoints: [[10, 1443454528000], [10, 1443454528000]],
  90. },
  91. {
  92. target: '20',
  93. datapoints: [[10, 1443454528000], [0, 1443454528000]],
  94. },
  95. {
  96. target: '30',
  97. datapoints: [[5, 1443454528000], [0, 1443454528000]],
  98. },
  99. ];
  100. ctx.ds.performTimeSeriesQuery = jest.fn().mockReturnValue(responseMock);
  101. return ctx.ds.query(ctx.query).then(result => {
  102. let results = result.data;
  103. return expect(results).toEqual(expected);
  104. });
  105. });
  106. it('should sort series by label value', () => {
  107. const resultMock = [
  108. {
  109. metric: { __name__: 'metric', job: 'testjob', le: '2' },
  110. values: [[1443454528.0, '10'], [1443454528.0, '10']],
  111. },
  112. {
  113. metric: { __name__: 'metric', job: 'testjob', le: '4' },
  114. values: [[1443454528.0, '20'], [1443454528.0, '10']],
  115. },
  116. {
  117. metric: { __name__: 'metric', job: 'testjob', le: '+Inf' },
  118. values: [[1443454528.0, '25'], [1443454528.0, '10']],
  119. },
  120. {
  121. metric: { __name__: 'metric', job: 'testjob', le: '1' },
  122. values: [[1443454528.0, '25'], [1443454528.0, '10']],
  123. },
  124. ];
  125. const responseMock = { data: { data: { result: resultMock } } };
  126. const expected = ['1', '2', '4', '+Inf'];
  127. ctx.ds.performTimeSeriesQuery = jest.fn().mockReturnValue(responseMock);
  128. return ctx.ds.query(ctx.query).then(result => {
  129. let seriesLabels = _.map(result.data, 'target');
  130. return expect(seriesLabels).toEqual(expected);
  131. });
  132. });
  133. });
  134. describe('alignRange', () => {
  135. it('does not modify already aligned intervals with perfect step', () => {
  136. const range = alignRange(0, 3, 3);
  137. expect(range.start).toEqual(0);
  138. expect(range.end).toEqual(3);
  139. });
  140. it('does modify end-aligned intervals to reflect number of steps possible', () => {
  141. const range = alignRange(1, 6, 3);
  142. expect(range.start).toEqual(0);
  143. expect(range.end).toEqual(6);
  144. });
  145. it('does align intervals that are a multiple of steps', () => {
  146. const range = alignRange(1, 4, 3);
  147. expect(range.start).toEqual(0);
  148. expect(range.end).toEqual(6);
  149. });
  150. it('does align intervals that are not a multiple of steps', () => {
  151. const range = alignRange(1, 5, 3);
  152. expect(range.start).toEqual(0);
  153. expect(range.end).toEqual(6);
  154. });
  155. });
  156. describe('Prometheus regular escaping', () => {
  157. it('should not escape non-string', () => {
  158. expect(prometheusRegularEscape(12)).toEqual(12);
  159. });
  160. it('should not escape simple string', () => {
  161. expect(prometheusRegularEscape('cryptodepression')).toEqual('cryptodepression');
  162. });
  163. it("should escape '", () => {
  164. expect(prometheusRegularEscape("looking'glass")).toEqual("looking\\\\'glass");
  165. });
  166. it('should escape multiple characters', () => {
  167. expect(prometheusRegularEscape("'looking'glass'")).toEqual("\\\\'looking\\\\'glass\\\\'");
  168. });
  169. });
  170. describe('Prometheus regexes escaping', () => {
  171. it('should not escape simple string', () => {
  172. expect(prometheusSpecialRegexEscape('cryptodepression')).toEqual('cryptodepression');
  173. });
  174. it('should escape $^*+?.()\\', () => {
  175. expect(prometheusSpecialRegexEscape("looking'glass")).toEqual("looking\\\\'glass");
  176. expect(prometheusSpecialRegexEscape('looking{glass')).toEqual('looking\\\\{glass');
  177. expect(prometheusSpecialRegexEscape('looking}glass')).toEqual('looking\\\\}glass');
  178. expect(prometheusSpecialRegexEscape('looking[glass')).toEqual('looking\\\\[glass');
  179. expect(prometheusSpecialRegexEscape('looking]glass')).toEqual('looking\\\\]glass');
  180. expect(prometheusSpecialRegexEscape('looking$glass')).toEqual('looking\\\\$glass');
  181. expect(prometheusSpecialRegexEscape('looking^glass')).toEqual('looking\\\\^glass');
  182. expect(prometheusSpecialRegexEscape('looking*glass')).toEqual('looking\\\\*glass');
  183. expect(prometheusSpecialRegexEscape('looking+glass')).toEqual('looking\\\\+glass');
  184. expect(prometheusSpecialRegexEscape('looking?glass')).toEqual('looking\\\\?glass');
  185. expect(prometheusSpecialRegexEscape('looking.glass')).toEqual('looking\\\\.glass');
  186. expect(prometheusSpecialRegexEscape('looking(glass')).toEqual('looking\\\\(glass');
  187. expect(prometheusSpecialRegexEscape('looking)glass')).toEqual('looking\\\\)glass');
  188. expect(prometheusSpecialRegexEscape('looking\\glass')).toEqual('looking\\\\\\\\glass');
  189. });
  190. it('should escape multiple special characters', () => {
  191. expect(prometheusSpecialRegexEscape('+looking$glass?')).toEqual('\\\\+looking\\\\$glass\\\\?');
  192. });
  193. });
  194. describe('metricFindQuery', () => {
  195. beforeEach(() => {
  196. let query = 'query_result(topk(5,rate(http_request_duration_microseconds_count[$__interval])))';
  197. ctx.templateSrvMock.replace = jest.fn();
  198. ctx.timeSrvMock.timeRange = () => {
  199. return {
  200. from: moment(1531468681),
  201. to: moment(1531489712),
  202. };
  203. };
  204. ctx.ds = new PrometheusDatasource(instanceSettings, q, ctx.backendSrvMock, ctx.templateSrvMock, ctx.timeSrvMock);
  205. ctx.ds.metricFindQuery(query);
  206. });
  207. it('should call templateSrv.replace with scopedVars', () => {
  208. expect(ctx.templateSrvMock.replace.mock.calls[0][1]).toBeDefined();
  209. });
  210. it('should have the correct range and range_ms', () => {
  211. let range = ctx.templateSrvMock.replace.mock.calls[0][1].__range;
  212. let rangeMs = ctx.templateSrvMock.replace.mock.calls[0][1].__range_ms;
  213. expect(range).toEqual({ text: '21s', value: '21s' });
  214. expect(rangeMs).toEqual({ text: 21031, value: 21031 });
  215. });
  216. it('should pass the default interval value', () => {
  217. let interval = ctx.templateSrvMock.replace.mock.calls[0][1].__interval;
  218. let intervalMs = ctx.templateSrvMock.replace.mock.calls[0][1].__interval_ms;
  219. expect(interval).toEqual({ text: '15s', value: '15s' });
  220. expect(intervalMs).toEqual({ text: 15000, value: 15000 });
  221. });
  222. });
  223. });
  224. const SECOND = 1000;
  225. const MINUTE = 60 * SECOND;
  226. const HOUR = 60 * MINUTE;
  227. const time = ({ hours = 0, seconds = 0, minutes = 0 }) => moment(hours * HOUR + minutes * MINUTE + seconds * SECOND);
  228. let ctx = <any>{};
  229. let instanceSettings = {
  230. url: 'proxied',
  231. directUrl: 'direct',
  232. user: 'test',
  233. password: 'mupp',
  234. jsonData: { httpMethod: 'GET' },
  235. };
  236. let backendSrv = <any>{
  237. datasourceRequest: jest.fn(),
  238. };
  239. let templateSrv = {
  240. replace: jest.fn(str => str),
  241. };
  242. let timeSrv = {
  243. timeRange: () => {
  244. return { to: { diff: () => 2000 }, from: '' };
  245. },
  246. };
  247. describe('PrometheusDatasource', () => {
  248. describe('When querying prometheus with one target using query editor target spec', async () => {
  249. var results;
  250. var query = {
  251. range: { from: time({ seconds: 63 }), to: time({ seconds: 183 }) },
  252. targets: [{ expr: 'test{job="testjob"}', format: 'time_series' }],
  253. interval: '60s',
  254. };
  255. // Interval alignment with step
  256. var urlExpected =
  257. 'proxied/api/v1/query_range?query=' + encodeURIComponent('test{job="testjob"}') + '&start=60&end=240&step=60';
  258. beforeEach(async () => {
  259. let response = {
  260. data: {
  261. status: 'success',
  262. data: {
  263. resultType: 'matrix',
  264. result: [
  265. {
  266. metric: { __name__: 'test', job: 'testjob' },
  267. values: [[60, '3846']],
  268. },
  269. ],
  270. },
  271. },
  272. };
  273. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  274. ctx.ds = new PrometheusDatasource(instanceSettings, q, <any>backendSrv, templateSrv, timeSrv);
  275. await ctx.ds.query(query).then(function(data) {
  276. results = data;
  277. });
  278. });
  279. it('should generate the correct query', () => {
  280. let res = backendSrv.datasourceRequest.mock.calls[0][0];
  281. expect(res.method).toBe('GET');
  282. expect(res.url).toBe(urlExpected);
  283. });
  284. it('should return series list', async () => {
  285. expect(results.data.length).toBe(1);
  286. expect(results.data[0].target).toBe('test{job="testjob"}');
  287. });
  288. });
  289. describe('When querying prometheus with one target which return multiple series', () => {
  290. var results;
  291. var start = 60;
  292. var end = 360;
  293. var step = 60;
  294. var query = {
  295. range: { from: time({ seconds: start }), to: time({ seconds: end }) },
  296. targets: [{ expr: 'test{job="testjob"}', format: 'time_series' }],
  297. interval: '60s',
  298. };
  299. beforeEach(async () => {
  300. let response = {
  301. status: 'success',
  302. data: {
  303. data: {
  304. resultType: 'matrix',
  305. result: [
  306. {
  307. metric: { __name__: 'test', job: 'testjob', series: 'series 1' },
  308. values: [[start + step * 1, '3846'], [start + step * 3, '3847'], [end - step * 1, '3848']],
  309. },
  310. {
  311. metric: { __name__: 'test', job: 'testjob', series: 'series 2' },
  312. values: [[start + step * 2, '4846']],
  313. },
  314. ],
  315. },
  316. },
  317. };
  318. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  319. ctx.ds = new PrometheusDatasource(instanceSettings, q, <any>backendSrv, templateSrv, timeSrv);
  320. await ctx.ds.query(query).then(function(data) {
  321. results = data;
  322. });
  323. });
  324. it('should be same length', () => {
  325. expect(results.data.length).toBe(2);
  326. expect(results.data[0].datapoints.length).toBe((end - start) / step + 1);
  327. expect(results.data[1].datapoints.length).toBe((end - start) / step + 1);
  328. });
  329. it('should fill null until first datapoint in response', () => {
  330. expect(results.data[0].datapoints[0][1]).toBe(start * 1000);
  331. expect(results.data[0].datapoints[0][0]).toBe(null);
  332. expect(results.data[0].datapoints[1][1]).toBe((start + step * 1) * 1000);
  333. expect(results.data[0].datapoints[1][0]).toBe(3846);
  334. });
  335. it('should fill null after last datapoint in response', () => {
  336. var length = (end - start) / step + 1;
  337. expect(results.data[0].datapoints[length - 2][1]).toBe((end - step * 1) * 1000);
  338. expect(results.data[0].datapoints[length - 2][0]).toBe(3848);
  339. expect(results.data[0].datapoints[length - 1][1]).toBe(end * 1000);
  340. expect(results.data[0].datapoints[length - 1][0]).toBe(null);
  341. });
  342. it('should fill null at gap between series', () => {
  343. expect(results.data[0].datapoints[2][1]).toBe((start + step * 2) * 1000);
  344. expect(results.data[0].datapoints[2][0]).toBe(null);
  345. expect(results.data[1].datapoints[1][1]).toBe((start + step * 1) * 1000);
  346. expect(results.data[1].datapoints[1][0]).toBe(null);
  347. expect(results.data[1].datapoints[3][1]).toBe((start + step * 3) * 1000);
  348. expect(results.data[1].datapoints[3][0]).toBe(null);
  349. });
  350. });
  351. describe('When querying prometheus with one target and instant = true', () => {
  352. var results;
  353. var urlExpected = 'proxied/api/v1/query?query=' + encodeURIComponent('test{job="testjob"}') + '&time=123';
  354. var query = {
  355. range: { from: time({ seconds: 63 }), to: time({ seconds: 123 }) },
  356. targets: [{ expr: 'test{job="testjob"}', format: 'time_series', instant: true }],
  357. interval: '60s',
  358. };
  359. beforeEach(async () => {
  360. let response = {
  361. status: 'success',
  362. data: {
  363. data: {
  364. resultType: 'vector',
  365. result: [
  366. {
  367. metric: { __name__: 'test', job: 'testjob' },
  368. value: [123, '3846'],
  369. },
  370. ],
  371. },
  372. },
  373. };
  374. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  375. ctx.ds = new PrometheusDatasource(instanceSettings, q, <any>backendSrv, templateSrv, timeSrv);
  376. await ctx.ds.query(query).then(function(data) {
  377. results = data;
  378. });
  379. });
  380. it('should generate the correct query', () => {
  381. let res = backendSrv.datasourceRequest.mock.calls[0][0];
  382. expect(res.method).toBe('GET');
  383. expect(res.url).toBe(urlExpected);
  384. });
  385. it('should return series list', () => {
  386. expect(results.data.length).toBe(1);
  387. expect(results.data[0].target).toBe('test{job="testjob"}');
  388. });
  389. });
  390. describe('When performing annotationQuery', () => {
  391. var results;
  392. var options = {
  393. annotation: {
  394. expr: 'ALERTS{alertstate="firing"}',
  395. tagKeys: 'job',
  396. titleFormat: '{{alertname}}',
  397. textFormat: '{{instance}}',
  398. },
  399. range: {
  400. from: time({ seconds: 63 }),
  401. to: time({ seconds: 123 }),
  402. },
  403. };
  404. beforeEach(async () => {
  405. let response = {
  406. status: 'success',
  407. data: {
  408. data: {
  409. resultType: 'matrix',
  410. result: [
  411. {
  412. metric: {
  413. __name__: 'ALERTS',
  414. alertname: 'InstanceDown',
  415. alertstate: 'firing',
  416. instance: 'testinstance',
  417. job: 'testjob',
  418. },
  419. values: [[123, '1']],
  420. },
  421. ],
  422. },
  423. },
  424. };
  425. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  426. ctx.ds = new PrometheusDatasource(instanceSettings, q, <any>backendSrv, templateSrv, timeSrv);
  427. await ctx.ds.annotationQuery(options).then(function(data) {
  428. results = data;
  429. });
  430. });
  431. it('should return annotation list', () => {
  432. expect(results.length).toBe(1);
  433. expect(results[0].tags).toContain('testjob');
  434. expect(results[0].title).toBe('InstanceDown');
  435. expect(results[0].text).toBe('testinstance');
  436. expect(results[0].time).toBe(123 * 1000);
  437. });
  438. });
  439. describe('When resultFormat is table and instant = true', () => {
  440. var results;
  441. var query = {
  442. range: { from: time({ seconds: 63 }), to: time({ seconds: 123 }) },
  443. targets: [{ expr: 'test{job="testjob"}', format: 'time_series', instant: true }],
  444. interval: '60s',
  445. };
  446. beforeEach(async () => {
  447. let response = {
  448. status: 'success',
  449. data: {
  450. data: {
  451. resultType: 'vector',
  452. result: [
  453. {
  454. metric: { __name__: 'test', job: 'testjob' },
  455. value: [123, '3846'],
  456. },
  457. ],
  458. },
  459. },
  460. };
  461. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  462. ctx.ds = new PrometheusDatasource(instanceSettings, q, <any>backendSrv, templateSrv, timeSrv);
  463. await ctx.ds.query(query).then(function(data) {
  464. results = data;
  465. });
  466. });
  467. it('should return result', () => {
  468. expect(results).not.toBe(null);
  469. });
  470. });
  471. describe('The "step" query parameter', () => {
  472. var response = {
  473. status: 'success',
  474. data: {
  475. data: {
  476. resultType: 'matrix',
  477. result: [],
  478. },
  479. },
  480. };
  481. it('should be min interval when greater than auto interval', async () => {
  482. let query = {
  483. // 6 minute range
  484. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  485. targets: [
  486. {
  487. expr: 'test',
  488. interval: '10s',
  489. },
  490. ],
  491. interval: '5s',
  492. };
  493. let urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=10';
  494. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  495. ctx.ds = new PrometheusDatasource(instanceSettings, q, <any>backendSrv, templateSrv, timeSrv);
  496. await ctx.ds.query(query);
  497. let res = backendSrv.datasourceRequest.mock.calls[0][0];
  498. expect(res.method).toBe('GET');
  499. expect(res.url).toBe(urlExpected);
  500. });
  501. it('step should never go below 1', async () => {
  502. var query = {
  503. // 6 minute range
  504. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  505. targets: [{ expr: 'test' }],
  506. interval: '100ms',
  507. };
  508. var urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=1';
  509. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  510. ctx.ds = new PrometheusDatasource(instanceSettings, q, <any>backendSrv, templateSrv, timeSrv);
  511. await ctx.ds.query(query);
  512. let res = backendSrv.datasourceRequest.mock.calls[0][0];
  513. expect(res.method).toBe('GET');
  514. expect(res.url).toBe(urlExpected);
  515. });
  516. it('should be auto interval when greater than min interval', async () => {
  517. var query = {
  518. // 6 minute range
  519. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  520. targets: [
  521. {
  522. expr: 'test',
  523. interval: '5s',
  524. },
  525. ],
  526. interval: '10s',
  527. };
  528. var urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=10';
  529. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  530. ctx.ds = new PrometheusDatasource(instanceSettings, q, <any>backendSrv, templateSrv, timeSrv);
  531. await ctx.ds.query(query);
  532. let res = backendSrv.datasourceRequest.mock.calls[0][0];
  533. expect(res.method).toBe('GET');
  534. expect(res.url).toBe(urlExpected);
  535. });
  536. it('should result in querying fewer than 11000 data points', async () => {
  537. var query = {
  538. // 6 hour range
  539. range: { from: time({ hours: 1 }), to: time({ hours: 7 }) },
  540. targets: [{ expr: 'test' }],
  541. interval: '1s',
  542. };
  543. var end = 7 * 60 * 60;
  544. var start = 60 * 60;
  545. var urlExpected = 'proxied/api/v1/query_range?query=test&start=' + start + '&end=' + end + '&step=2';
  546. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  547. ctx.ds = new PrometheusDatasource(instanceSettings, q, <any>backendSrv, templateSrv, timeSrv);
  548. await ctx.ds.query(query);
  549. let res = backendSrv.datasourceRequest.mock.calls[0][0];
  550. expect(res.method).toBe('GET');
  551. expect(res.url).toBe(urlExpected);
  552. });
  553. it('should not apply min interval when interval * intervalFactor greater', async () => {
  554. var query = {
  555. // 6 minute range
  556. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  557. targets: [
  558. {
  559. expr: 'test',
  560. interval: '10s',
  561. intervalFactor: 10,
  562. },
  563. ],
  564. interval: '5s',
  565. };
  566. // times get rounded up to interval
  567. var urlExpected = 'proxied/api/v1/query_range?query=test&start=50&end=450&step=50';
  568. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  569. ctx.ds = new PrometheusDatasource(instanceSettings, q, <any>backendSrv, templateSrv, timeSrv);
  570. await ctx.ds.query(query);
  571. let res = backendSrv.datasourceRequest.mock.calls[0][0];
  572. expect(res.method).toBe('GET');
  573. expect(res.url).toBe(urlExpected);
  574. });
  575. it('should apply min interval when interval * intervalFactor smaller', async () => {
  576. var query = {
  577. // 6 minute range
  578. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  579. targets: [
  580. {
  581. expr: 'test',
  582. interval: '15s',
  583. intervalFactor: 2,
  584. },
  585. ],
  586. interval: '5s',
  587. };
  588. var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=60&end=420&step=15';
  589. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  590. ctx.ds = new PrometheusDatasource(instanceSettings, q, <any>backendSrv, templateSrv, timeSrv);
  591. await ctx.ds.query(query);
  592. let res = backendSrv.datasourceRequest.mock.calls[0][0];
  593. expect(res.method).toBe('GET');
  594. expect(res.url).toBe(urlExpected);
  595. });
  596. it('should apply intervalFactor to auto interval when greater', async () => {
  597. var query = {
  598. // 6 minute range
  599. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  600. targets: [
  601. {
  602. expr: 'test',
  603. interval: '5s',
  604. intervalFactor: 10,
  605. },
  606. ],
  607. interval: '10s',
  608. };
  609. // times get aligned to interval
  610. var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=0&end=500&step=100';
  611. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  612. ctx.ds = new PrometheusDatasource(instanceSettings, q, <any>backendSrv, templateSrv, timeSrv);
  613. await ctx.ds.query(query);
  614. let res = backendSrv.datasourceRequest.mock.calls[0][0];
  615. expect(res.method).toBe('GET');
  616. expect(res.url).toBe(urlExpected);
  617. });
  618. it('should not not be affected by the 11000 data points limit when large enough', async () => {
  619. var query = {
  620. // 1 week range
  621. range: { from: time({}), to: time({ hours: 7 * 24 }) },
  622. targets: [
  623. {
  624. expr: 'test',
  625. intervalFactor: 10,
  626. },
  627. ],
  628. interval: '10s',
  629. };
  630. var end = 7 * 24 * 60 * 60;
  631. var start = 0;
  632. var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=' + start + '&end=' + end + '&step=100';
  633. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  634. ctx.ds = new PrometheusDatasource(instanceSettings, q, <any>backendSrv, templateSrv, timeSrv);
  635. await ctx.ds.query(query);
  636. let res = backendSrv.datasourceRequest.mock.calls[0][0];
  637. expect(res.method).toBe('GET');
  638. expect(res.url).toBe(urlExpected);
  639. });
  640. it('should be determined by the 11000 data points limit when too small', async () => {
  641. var query = {
  642. // 1 week range
  643. range: { from: time({}), to: time({ hours: 7 * 24 }) },
  644. targets: [
  645. {
  646. expr: 'test',
  647. intervalFactor: 10,
  648. },
  649. ],
  650. interval: '5s',
  651. };
  652. var end = 7 * 24 * 60 * 60;
  653. var start = 0;
  654. var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=' + start + '&end=' + end + '&step=60';
  655. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  656. ctx.ds = new PrometheusDatasource(instanceSettings, q, <any>backendSrv, templateSrv, timeSrv);
  657. await ctx.ds.query(query);
  658. let res = backendSrv.datasourceRequest.mock.calls[0][0];
  659. expect(res.method).toBe('GET');
  660. expect(res.url).toBe(urlExpected);
  661. });
  662. });
  663. describe('The __interval and __interval_ms template variables', () => {
  664. var response = {
  665. status: 'success',
  666. data: {
  667. data: {
  668. resultType: 'matrix',
  669. result: [],
  670. },
  671. },
  672. };
  673. it('should be unchanged when auto interval is greater than min interval', async () => {
  674. var query = {
  675. // 6 minute range
  676. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  677. targets: [
  678. {
  679. expr: 'rate(test[$__interval])',
  680. interval: '5s',
  681. },
  682. ],
  683. interval: '10s',
  684. scopedVars: {
  685. __interval: { text: '10s', value: '10s' },
  686. __interval_ms: { text: 10 * 1000, value: 10 * 1000 },
  687. },
  688. };
  689. var urlExpected =
  690. 'proxied/api/v1/query_range?query=' +
  691. encodeURIComponent('rate(test[$__interval])') +
  692. '&start=60&end=420&step=10';
  693. templateSrv.replace = jest.fn(str => str);
  694. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  695. ctx.ds = new PrometheusDatasource(instanceSettings, q, <any>backendSrv, templateSrv, timeSrv);
  696. await ctx.ds.query(query);
  697. let res = backendSrv.datasourceRequest.mock.calls[0][0];
  698. expect(res.method).toBe('GET');
  699. expect(res.url).toBe(urlExpected);
  700. expect(templateSrv.replace.mock.calls[0][1]).toEqual({
  701. __interval: {
  702. text: '10s',
  703. value: '10s',
  704. },
  705. __interval_ms: {
  706. text: 10000,
  707. value: 10000,
  708. },
  709. });
  710. });
  711. it('should be min interval when it is greater than auto interval', async () => {
  712. var query = {
  713. // 6 minute range
  714. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  715. targets: [
  716. {
  717. expr: 'rate(test[$__interval])',
  718. interval: '10s',
  719. },
  720. ],
  721. interval: '5s',
  722. scopedVars: {
  723. __interval: { text: '5s', value: '5s' },
  724. __interval_ms: { text: 5 * 1000, value: 5 * 1000 },
  725. },
  726. };
  727. var urlExpected =
  728. 'proxied/api/v1/query_range?query=' +
  729. encodeURIComponent('rate(test[$__interval])') +
  730. '&start=60&end=420&step=10';
  731. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  732. templateSrv.replace = jest.fn(str => str);
  733. ctx.ds = new PrometheusDatasource(instanceSettings, q, <any>backendSrv, templateSrv, timeSrv);
  734. await ctx.ds.query(query);
  735. let res = backendSrv.datasourceRequest.mock.calls[0][0];
  736. expect(res.method).toBe('GET');
  737. expect(res.url).toBe(urlExpected);
  738. expect(templateSrv.replace.mock.calls[0][1]).toEqual({
  739. __interval: {
  740. text: '5s',
  741. value: '5s',
  742. },
  743. __interval_ms: {
  744. text: 5000,
  745. value: 5000,
  746. },
  747. });
  748. });
  749. it('should account for intervalFactor', async () => {
  750. var query = {
  751. // 6 minute range
  752. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  753. targets: [
  754. {
  755. expr: 'rate(test[$__interval])',
  756. interval: '5s',
  757. intervalFactor: 10,
  758. },
  759. ],
  760. interval: '10s',
  761. scopedVars: {
  762. __interval: { text: '10s', value: '10s' },
  763. __interval_ms: { text: 10 * 1000, value: 10 * 1000 },
  764. },
  765. };
  766. var urlExpected =
  767. 'proxied/api/v1/query_range?query=' +
  768. encodeURIComponent('rate(test[$__interval])') +
  769. '&start=0&end=500&step=100';
  770. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  771. templateSrv.replace = jest.fn(str => str);
  772. ctx.ds = new PrometheusDatasource(instanceSettings, q, <any>backendSrv, templateSrv, timeSrv);
  773. await ctx.ds.query(query);
  774. let res = backendSrv.datasourceRequest.mock.calls[0][0];
  775. expect(res.method).toBe('GET');
  776. expect(res.url).toBe(urlExpected);
  777. expect(templateSrv.replace.mock.calls[0][1]).toEqual({
  778. __interval: {
  779. text: '10s',
  780. value: '10s',
  781. },
  782. __interval_ms: {
  783. text: 10000,
  784. value: 10000,
  785. },
  786. });
  787. expect(query.scopedVars.__interval.text).toBe('10s');
  788. expect(query.scopedVars.__interval.value).toBe('10s');
  789. expect(query.scopedVars.__interval_ms.text).toBe(10 * 1000);
  790. expect(query.scopedVars.__interval_ms.value).toBe(10 * 1000);
  791. });
  792. it('should be interval * intervalFactor when greater than min interval', async () => {
  793. var query = {
  794. // 6 minute range
  795. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  796. targets: [
  797. {
  798. expr: 'rate(test[$__interval])',
  799. interval: '10s',
  800. intervalFactor: 10,
  801. },
  802. ],
  803. interval: '5s',
  804. scopedVars: {
  805. __interval: { text: '5s', value: '5s' },
  806. __interval_ms: { text: 5 * 1000, value: 5 * 1000 },
  807. },
  808. };
  809. var urlExpected =
  810. 'proxied/api/v1/query_range?query=' +
  811. encodeURIComponent('rate(test[$__interval])') +
  812. '&start=50&end=450&step=50';
  813. templateSrv.replace = jest.fn(str => str);
  814. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  815. ctx.ds = new PrometheusDatasource(instanceSettings, q, <any>backendSrv, templateSrv, timeSrv);
  816. await ctx.ds.query(query);
  817. let res = backendSrv.datasourceRequest.mock.calls[0][0];
  818. expect(res.method).toBe('GET');
  819. expect(res.url).toBe(urlExpected);
  820. expect(templateSrv.replace.mock.calls[0][1]).toEqual({
  821. __interval: {
  822. text: '5s',
  823. value: '5s',
  824. },
  825. __interval_ms: {
  826. text: 5000,
  827. value: 5000,
  828. },
  829. });
  830. });
  831. it('should be min interval when greater than interval * intervalFactor', async () => {
  832. var query = {
  833. // 6 minute range
  834. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  835. targets: [
  836. {
  837. expr: 'rate(test[$__interval])',
  838. interval: '15s',
  839. intervalFactor: 2,
  840. },
  841. ],
  842. interval: '5s',
  843. scopedVars: {
  844. __interval: { text: '5s', value: '5s' },
  845. __interval_ms: { text: 5 * 1000, value: 5 * 1000 },
  846. },
  847. };
  848. var urlExpected =
  849. 'proxied/api/v1/query_range?query=' +
  850. encodeURIComponent('rate(test[$__interval])') +
  851. '&start=60&end=420&step=15';
  852. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  853. ctx.ds = new PrometheusDatasource(instanceSettings, q, <any>backendSrv, templateSrv, timeSrv);
  854. await ctx.ds.query(query);
  855. let res = backendSrv.datasourceRequest.mock.calls[0][0];
  856. expect(res.method).toBe('GET');
  857. expect(res.url).toBe(urlExpected);
  858. expect(templateSrv.replace.mock.calls[0][1]).toEqual({
  859. __interval: {
  860. text: '5s',
  861. value: '5s',
  862. },
  863. __interval_ms: {
  864. text: 5000,
  865. value: 5000,
  866. },
  867. });
  868. });
  869. it('should be determined by the 11000 data points limit, accounting for intervalFactor', async () => {
  870. var query = {
  871. // 1 week range
  872. range: { from: time({}), to: time({ hours: 7 * 24 }) },
  873. targets: [
  874. {
  875. expr: 'rate(test[$__interval])',
  876. intervalFactor: 10,
  877. },
  878. ],
  879. interval: '5s',
  880. scopedVars: {
  881. __interval: { text: '5s', value: '5s' },
  882. __interval_ms: { text: 5 * 1000, value: 5 * 1000 },
  883. },
  884. };
  885. var end = 7 * 24 * 60 * 60;
  886. var start = 0;
  887. var urlExpected =
  888. 'proxied/api/v1/query_range?query=' +
  889. encodeURIComponent('rate(test[$__interval])') +
  890. '&start=' +
  891. start +
  892. '&end=' +
  893. end +
  894. '&step=60';
  895. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  896. templateSrv.replace = jest.fn(str => str);
  897. ctx.ds = new PrometheusDatasource(instanceSettings, q, <any>backendSrv, templateSrv, timeSrv);
  898. await ctx.ds.query(query);
  899. let res = backendSrv.datasourceRequest.mock.calls[0][0];
  900. expect(res.method).toBe('GET');
  901. expect(res.url).toBe(urlExpected);
  902. expect(templateSrv.replace.mock.calls[0][1]).toEqual({
  903. __interval: {
  904. text: '5s',
  905. value: '5s',
  906. },
  907. __interval_ms: {
  908. text: 5000,
  909. value: 5000,
  910. },
  911. });
  912. });
  913. });
  914. });
  915. describe('PrometheusDatasource for POST', () => {
  916. // var ctx = new helpers.ServiceTestContext();
  917. let instanceSettings = {
  918. url: 'proxied',
  919. directUrl: 'direct',
  920. user: 'test',
  921. password: 'mupp',
  922. jsonData: { httpMethod: 'POST' },
  923. };
  924. describe('When querying prometheus with one target using query editor target spec', () => {
  925. var results;
  926. var urlExpected = 'proxied/api/v1/query_range';
  927. var dataExpected = {
  928. query: 'test{job="testjob"}',
  929. start: 1 * 60,
  930. end: 3 * 60,
  931. step: 60,
  932. };
  933. var query = {
  934. range: { from: time({ minutes: 1, seconds: 3 }), to: time({ minutes: 2, seconds: 3 }) },
  935. targets: [{ expr: 'test{job="testjob"}', format: 'time_series' }],
  936. interval: '60s',
  937. };
  938. beforeEach(async () => {
  939. let response = {
  940. status: 'success',
  941. data: {
  942. data: {
  943. resultType: 'matrix',
  944. result: [
  945. {
  946. metric: { __name__: 'test', job: 'testjob' },
  947. values: [[2 * 60, '3846']],
  948. },
  949. ],
  950. },
  951. },
  952. };
  953. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  954. ctx.ds = new PrometheusDatasource(instanceSettings, q, <any>backendSrv, templateSrv, timeSrv);
  955. await ctx.ds.query(query).then(function(data) {
  956. results = data;
  957. });
  958. });
  959. it('should generate the correct query', () => {
  960. let res = backendSrv.datasourceRequest.mock.calls[0][0];
  961. expect(res.method).toBe('POST');
  962. expect(res.url).toBe(urlExpected);
  963. expect(res.data).toEqual(dataExpected);
  964. });
  965. it('should return series list', () => {
  966. expect(results.data.length).toBe(1);
  967. expect(results.data[0].target).toBe('test{job="testjob"}');
  968. });
  969. });
  970. });