datasource.test.ts 41 KB

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