datasource.test.ts 42 KB

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