datasource.jest.ts 37 KB

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