datasource.test.ts 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229
  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: {},
  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. expect(templateSrv.replace.mock.calls[0][1]).toEqual({
  869. __interval: {
  870. text: '10s',
  871. value: '10s',
  872. },
  873. __interval_ms: {
  874. text: 10000,
  875. value: 10000,
  876. },
  877. });
  878. });
  879. it('should be min interval when it is greater than auto interval', async () => {
  880. const query = {
  881. // 6 minute range
  882. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  883. targets: [
  884. {
  885. expr: 'rate(test[$__interval])',
  886. interval: '10s',
  887. },
  888. ],
  889. interval: '5s',
  890. scopedVars: {
  891. __interval: { text: '5s', value: '5s' },
  892. __interval_ms: { text: 5 * 1000, value: 5 * 1000 },
  893. },
  894. };
  895. const urlExpected =
  896. 'proxied/api/v1/query_range?query=' +
  897. encodeURIComponent('rate(test[$__interval])') +
  898. '&start=60&end=420&step=10';
  899. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  900. templateSrv.replace = jest.fn(str => str);
  901. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  902. await ctx.ds.query(query);
  903. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  904. expect(res.method).toBe('GET');
  905. expect(res.url).toBe(urlExpected);
  906. expect(templateSrv.replace.mock.calls[0][1]).toEqual({
  907. __interval: {
  908. text: '5s',
  909. value: '5s',
  910. },
  911. __interval_ms: {
  912. text: 5000,
  913. value: 5000,
  914. },
  915. });
  916. });
  917. it('should account for intervalFactor', async () => {
  918. const query = {
  919. // 6 minute range
  920. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  921. targets: [
  922. {
  923. expr: 'rate(test[$__interval])',
  924. interval: '5s',
  925. intervalFactor: 10,
  926. },
  927. ],
  928. interval: '10s',
  929. scopedVars: {
  930. __interval: { text: '10s', value: '10s' },
  931. __interval_ms: { text: 10 * 1000, value: 10 * 1000 },
  932. },
  933. };
  934. const urlExpected =
  935. 'proxied/api/v1/query_range?query=' +
  936. encodeURIComponent('rate(test[$__interval])') +
  937. '&start=0&end=400&step=100';
  938. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  939. templateSrv.replace = jest.fn(str => str);
  940. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  941. await ctx.ds.query(query);
  942. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  943. expect(res.method).toBe('GET');
  944. expect(res.url).toBe(urlExpected);
  945. expect(templateSrv.replace.mock.calls[0][1]).toEqual({
  946. __interval: {
  947. text: '10s',
  948. value: '10s',
  949. },
  950. __interval_ms: {
  951. text: 10000,
  952. value: 10000,
  953. },
  954. });
  955. expect(query.scopedVars.__interval.text).toBe('10s');
  956. expect(query.scopedVars.__interval.value).toBe('10s');
  957. expect(query.scopedVars.__interval_ms.text).toBe(10 * 1000);
  958. expect(query.scopedVars.__interval_ms.value).toBe(10 * 1000);
  959. });
  960. it('should be interval * intervalFactor when greater than min interval', async () => {
  961. const query = {
  962. // 6 minute range
  963. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  964. targets: [
  965. {
  966. expr: 'rate(test[$__interval])',
  967. interval: '10s',
  968. intervalFactor: 10,
  969. },
  970. ],
  971. interval: '5s',
  972. scopedVars: {
  973. __interval: { text: '5s', value: '5s' },
  974. __interval_ms: { text: 5 * 1000, value: 5 * 1000 },
  975. },
  976. };
  977. const urlExpected =
  978. 'proxied/api/v1/query_range?query=' +
  979. encodeURIComponent('rate(test[$__interval])') +
  980. '&start=50&end=400&step=50';
  981. templateSrv.replace = jest.fn(str => str);
  982. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  983. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  984. await ctx.ds.query(query);
  985. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  986. expect(res.method).toBe('GET');
  987. expect(res.url).toBe(urlExpected);
  988. expect(templateSrv.replace.mock.calls[0][1]).toEqual({
  989. __interval: {
  990. text: '5s',
  991. value: '5s',
  992. },
  993. __interval_ms: {
  994. text: 5000,
  995. value: 5000,
  996. },
  997. });
  998. });
  999. it('should be min interval when greater than interval * intervalFactor', async () => {
  1000. const query = {
  1001. // 6 minute range
  1002. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  1003. targets: [
  1004. {
  1005. expr: 'rate(test[$__interval])',
  1006. interval: '15s',
  1007. intervalFactor: 2,
  1008. },
  1009. ],
  1010. interval: '5s',
  1011. scopedVars: {
  1012. __interval: { text: '5s', value: '5s' },
  1013. __interval_ms: { text: 5 * 1000, value: 5 * 1000 },
  1014. },
  1015. };
  1016. const urlExpected =
  1017. 'proxied/api/v1/query_range?query=' +
  1018. encodeURIComponent('rate(test[$__interval])') +
  1019. '&start=60&end=420&step=15';
  1020. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  1021. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  1022. await ctx.ds.query(query);
  1023. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  1024. expect(res.method).toBe('GET');
  1025. expect(res.url).toBe(urlExpected);
  1026. expect(templateSrv.replace.mock.calls[0][1]).toEqual({
  1027. __interval: {
  1028. text: '5s',
  1029. value: '5s',
  1030. },
  1031. __interval_ms: {
  1032. text: 5000,
  1033. value: 5000,
  1034. },
  1035. });
  1036. });
  1037. it('should be determined by the 11000 data points limit, accounting for intervalFactor', async () => {
  1038. const query = {
  1039. // 1 week range
  1040. range: { from: time({}), to: time({ hours: 7 * 24 }) },
  1041. targets: [
  1042. {
  1043. expr: 'rate(test[$__interval])',
  1044. intervalFactor: 10,
  1045. },
  1046. ],
  1047. interval: '5s',
  1048. scopedVars: {
  1049. __interval: { text: '5s', value: '5s' },
  1050. __interval_ms: { text: 5 * 1000, value: 5 * 1000 },
  1051. },
  1052. };
  1053. const end = 7 * 24 * 60 * 60;
  1054. const start = 0;
  1055. const urlExpected =
  1056. 'proxied/api/v1/query_range?query=' +
  1057. encodeURIComponent('rate(test[$__interval])') +
  1058. '&start=' +
  1059. start +
  1060. '&end=' +
  1061. end +
  1062. '&step=60';
  1063. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  1064. templateSrv.replace = jest.fn(str => str);
  1065. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  1066. await ctx.ds.query(query);
  1067. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  1068. expect(res.method).toBe('GET');
  1069. expect(res.url).toBe(urlExpected);
  1070. expect(templateSrv.replace.mock.calls[0][1]).toEqual({
  1071. __interval: {
  1072. text: '5s',
  1073. value: '5s',
  1074. },
  1075. __interval_ms: {
  1076. text: 5000,
  1077. value: 5000,
  1078. },
  1079. });
  1080. });
  1081. });
  1082. });
  1083. describe('PrometheusDatasource for POST', () => {
  1084. // const ctx = new helpers.ServiceTestContext();
  1085. const instanceSettings = {
  1086. url: 'proxied',
  1087. directUrl: 'direct',
  1088. user: 'test',
  1089. password: 'mupp',
  1090. jsonData: { httpMethod: 'POST' },
  1091. };
  1092. describe('When querying prometheus with one target using query editor target spec', () => {
  1093. let results;
  1094. const urlExpected = 'proxied/api/v1/query_range';
  1095. const dataExpected = {
  1096. query: 'test{job="testjob"}',
  1097. start: 1 * 60,
  1098. end: 2 * 60,
  1099. step: 60,
  1100. };
  1101. const query = {
  1102. range: { from: time({ minutes: 1, seconds: 3 }), to: time({ minutes: 2, seconds: 3 }) },
  1103. targets: [{ expr: 'test{job="testjob"}', format: 'time_series' }],
  1104. interval: '60s',
  1105. };
  1106. beforeEach(async () => {
  1107. const response = {
  1108. status: 'success',
  1109. data: {
  1110. data: {
  1111. resultType: 'matrix',
  1112. result: [
  1113. {
  1114. metric: { __name__: 'test', job: 'testjob' },
  1115. values: [[2 * 60, '3846']],
  1116. },
  1117. ],
  1118. },
  1119. },
  1120. };
  1121. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  1122. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  1123. await ctx.ds.query(query).then(data => {
  1124. results = data;
  1125. });
  1126. });
  1127. it('should generate the correct query', () => {
  1128. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  1129. expect(res.method).toBe('POST');
  1130. expect(res.url).toBe(urlExpected);
  1131. expect(res.data).toEqual(dataExpected);
  1132. });
  1133. it('should return series list', () => {
  1134. expect(results.data.length).toBe(1);
  1135. expect(results.data[0].target).toBe('test{job="testjob"}');
  1136. });
  1137. });
  1138. });