datasource.test.ts 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229
  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 a rate hint w/o action for a complex monotonously increasing series', () => {
  220. const series = [{ datapoints: [[23, 1000], [24, 1001]], query: 'sum(metric)', responseIndex: 0 }];
  221. const hints = determineQueryHints(series);
  222. expect(hints.length).toBe(1);
  223. expect(hints[0].label).toContain('rate()');
  224. expect(hints[0].fix).toBeUndefined();
  225. });
  226. it('returns a rate hint for a monotonously increasing series with missing data', () => {
  227. const series = [{ datapoints: [[23, 1000], [null, 1001], [24, 1002]], query: 'metric', responseIndex: 0 }];
  228. const hints = determineQueryHints(series);
  229. expect(hints.length).toBe(1);
  230. expect(hints[0]).toMatchObject({
  231. label: 'Time series is monotonously increasing.',
  232. index: 0,
  233. fix: {
  234. action: {
  235. type: 'ADD_RATE',
  236. query: 'metric',
  237. },
  238. },
  239. });
  240. });
  241. it('returns a histogram hint for a bucket series', () => {
  242. const series = [{ datapoints: [[23, 1000]], query: 'metric_bucket', responseIndex: 0 }];
  243. const hints = determineQueryHints(series);
  244. expect(hints.length).toBe(1);
  245. expect(hints[0]).toMatchObject({
  246. label: 'Time series has buckets, you probably wanted a histogram.',
  247. index: 0,
  248. fix: {
  249. action: {
  250. type: 'ADD_HISTOGRAM_QUANTILE',
  251. query: 'metric_bucket',
  252. },
  253. },
  254. });
  255. });
  256. });
  257. describe('extractRuleMappingFromGroups()', () => {
  258. it('returns empty mapping for no rule groups', () => {
  259. expect(extractRuleMappingFromGroups([])).toEqual({});
  260. });
  261. it('returns a mapping for recording rules only', () => {
  262. const groups = [
  263. {
  264. rules: [
  265. {
  266. name: 'HighRequestLatency',
  267. query: 'job:request_latency_seconds:mean5m{job="myjob"} > 0.5',
  268. type: 'alerting',
  269. },
  270. {
  271. name: 'job:http_inprogress_requests:sum',
  272. query: 'sum(http_inprogress_requests) by (job)',
  273. type: 'recording',
  274. },
  275. ],
  276. file: '/rules.yaml',
  277. interval: 60,
  278. name: 'example',
  279. },
  280. ];
  281. const mapping = extractRuleMappingFromGroups(groups);
  282. expect(mapping).toEqual({ 'job:http_inprogress_requests:sum': 'sum(http_inprogress_requests) by (job)' });
  283. });
  284. });
  285. describe('Prometheus regular escaping', () => {
  286. it('should not escape non-string', () => {
  287. expect(prometheusRegularEscape(12)).toEqual(12);
  288. });
  289. it('should not escape simple string', () => {
  290. expect(prometheusRegularEscape('cryptodepression')).toEqual('cryptodepression');
  291. });
  292. it("should escape '", () => {
  293. expect(prometheusRegularEscape("looking'glass")).toEqual("looking\\\\'glass");
  294. });
  295. it('should escape multiple characters', () => {
  296. expect(prometheusRegularEscape("'looking'glass'")).toEqual("\\\\'looking\\\\'glass\\\\'");
  297. });
  298. });
  299. describe('Prometheus regexes escaping', () => {
  300. it('should not escape simple string', () => {
  301. expect(prometheusSpecialRegexEscape('cryptodepression')).toEqual('cryptodepression');
  302. });
  303. it('should escape $^*+?.()\\', () => {
  304. expect(prometheusSpecialRegexEscape("looking'glass")).toEqual("looking\\\\'glass");
  305. expect(prometheusSpecialRegexEscape('looking{glass')).toEqual('looking\\\\{glass');
  306. expect(prometheusSpecialRegexEscape('looking}glass')).toEqual('looking\\\\}glass');
  307. expect(prometheusSpecialRegexEscape('looking[glass')).toEqual('looking\\\\[glass');
  308. expect(prometheusSpecialRegexEscape('looking]glass')).toEqual('looking\\\\]glass');
  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. });
  319. it('should escape multiple special characters', () => {
  320. expect(prometheusSpecialRegexEscape('+looking$glass?')).toEqual('\\\\+looking\\\\$glass\\\\?');
  321. });
  322. });
  323. describe('metricFindQuery', () => {
  324. beforeEach(() => {
  325. const query = 'query_result(topk(5,rate(http_request_duration_microseconds_count[$__interval])))';
  326. ctx.templateSrvMock.replace = jest.fn();
  327. ctx.timeSrvMock.timeRange = () => {
  328. return {
  329. from: moment(1531468681),
  330. to: moment(1531489712),
  331. };
  332. };
  333. ctx.ds = new PrometheusDatasource(instanceSettings, q, ctx.backendSrvMock, ctx.templateSrvMock, ctx.timeSrvMock);
  334. ctx.ds.metricFindQuery(query);
  335. });
  336. it('should call templateSrv.replace with scopedVars', () => {
  337. expect(ctx.templateSrvMock.replace.mock.calls[0][1]).toBeDefined();
  338. });
  339. it('should have the correct range and range_ms', () => {
  340. const range = ctx.templateSrvMock.replace.mock.calls[0][1].__range;
  341. const rangeMs = ctx.templateSrvMock.replace.mock.calls[0][1].__range_ms;
  342. const rangeS = ctx.templateSrvMock.replace.mock.calls[0][1].__range_s;
  343. expect(range).toEqual({ text: '21s', value: '21s' });
  344. expect(rangeMs).toEqual({ text: 21031, value: 21031 });
  345. expect(rangeS).toEqual({ text: 21, value: 21 });
  346. });
  347. it('should pass the default interval value', () => {
  348. const interval = ctx.templateSrvMock.replace.mock.calls[0][1].__interval;
  349. const intervalMs = ctx.templateSrvMock.replace.mock.calls[0][1].__interval_ms;
  350. expect(interval).toEqual({ text: '15s', value: '15s' });
  351. expect(intervalMs).toEqual({ text: 15000, value: 15000 });
  352. });
  353. });
  354. });
  355. const SECOND = 1000;
  356. const MINUTE = 60 * SECOND;
  357. const HOUR = 60 * MINUTE;
  358. const time = ({ hours = 0, seconds = 0, minutes = 0 }) => moment(hours * HOUR + minutes * MINUTE + seconds * SECOND);
  359. const ctx = {} as any;
  360. const instanceSettings = {
  361. url: 'proxied',
  362. directUrl: 'direct',
  363. user: 'test',
  364. password: 'mupp',
  365. jsonData: { httpMethod: 'GET' },
  366. };
  367. const backendSrv = {
  368. datasourceRequest: jest.fn(),
  369. } as any;
  370. const templateSrv = {
  371. getAdhocFilters: () => [],
  372. replace: jest.fn(str => str),
  373. };
  374. const timeSrv = {
  375. timeRange: () => {
  376. return { to: { diff: () => 2000 }, from: '' };
  377. },
  378. };
  379. describe('PrometheusDatasource', () => {
  380. describe('When querying prometheus with one target using query editor target spec', async () => {
  381. let results;
  382. const query = {
  383. range: { from: time({ seconds: 63 }), to: time({ seconds: 183 }) },
  384. targets: [{ expr: 'test{job="testjob"}', format: 'time_series' }],
  385. interval: '60s',
  386. };
  387. // Interval alignment with step
  388. const urlExpected =
  389. 'proxied/api/v1/query_range?query=' + encodeURIComponent('test{job="testjob"}') + '&start=60&end=240&step=60';
  390. beforeEach(async () => {
  391. const response = {
  392. data: {
  393. status: 'success',
  394. data: {
  395. resultType: 'matrix',
  396. result: [
  397. {
  398. metric: { __name__: 'test', job: 'testjob' },
  399. values: [[60, '3846']],
  400. },
  401. ],
  402. },
  403. },
  404. };
  405. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  406. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  407. await ctx.ds.query(query).then(data => {
  408. results = data;
  409. });
  410. });
  411. it('should generate the correct query', () => {
  412. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  413. expect(res.method).toBe('GET');
  414. expect(res.url).toBe(urlExpected);
  415. });
  416. it('should return series list', async () => {
  417. expect(results.data.length).toBe(1);
  418. expect(results.data[0].target).toBe('test{job="testjob"}');
  419. });
  420. });
  421. describe('When querying prometheus with one target which return multiple series', () => {
  422. let results;
  423. const start = 60;
  424. const end = 360;
  425. const step = 60;
  426. const query = {
  427. range: { from: time({ seconds: start }), to: time({ seconds: end }) },
  428. targets: [{ expr: 'test{job="testjob"}', format: 'time_series' }],
  429. interval: '60s',
  430. };
  431. beforeEach(async () => {
  432. const response = {
  433. status: 'success',
  434. data: {
  435. data: {
  436. resultType: 'matrix',
  437. result: [
  438. {
  439. metric: { __name__: 'test', job: 'testjob', series: 'series 1' },
  440. values: [[start + step * 1, '3846'], [start + step * 3, '3847'], [end - step * 1, '3848']],
  441. },
  442. {
  443. metric: { __name__: 'test', job: 'testjob', series: 'series 2' },
  444. values: [[start + step * 2, '4846']],
  445. },
  446. ],
  447. },
  448. },
  449. };
  450. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  451. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  452. await ctx.ds.query(query).then(data => {
  453. results = data;
  454. });
  455. });
  456. it('should be same length', () => {
  457. expect(results.data.length).toBe(2);
  458. expect(results.data[0].datapoints.length).toBe((end - start) / step + 1);
  459. expect(results.data[1].datapoints.length).toBe((end - start) / step + 1);
  460. });
  461. it('should fill null until first datapoint in response', () => {
  462. expect(results.data[0].datapoints[0][1]).toBe(start * 1000);
  463. expect(results.data[0].datapoints[0][0]).toBe(null);
  464. expect(results.data[0].datapoints[1][1]).toBe((start + step * 1) * 1000);
  465. expect(results.data[0].datapoints[1][0]).toBe(3846);
  466. });
  467. it('should fill null after last datapoint in response', () => {
  468. const length = (end - start) / step + 1;
  469. expect(results.data[0].datapoints[length - 2][1]).toBe((end - step * 1) * 1000);
  470. expect(results.data[0].datapoints[length - 2][0]).toBe(3848);
  471. expect(results.data[0].datapoints[length - 1][1]).toBe(end * 1000);
  472. expect(results.data[0].datapoints[length - 1][0]).toBe(null);
  473. });
  474. it('should fill null at gap between series', () => {
  475. expect(results.data[0].datapoints[2][1]).toBe((start + step * 2) * 1000);
  476. expect(results.data[0].datapoints[2][0]).toBe(null);
  477. expect(results.data[1].datapoints[1][1]).toBe((start + step * 1) * 1000);
  478. expect(results.data[1].datapoints[1][0]).toBe(null);
  479. expect(results.data[1].datapoints[3][1]).toBe((start + step * 3) * 1000);
  480. expect(results.data[1].datapoints[3][0]).toBe(null);
  481. });
  482. });
  483. describe('When querying prometheus with one target and instant = true', () => {
  484. let results;
  485. const urlExpected = 'proxied/api/v1/query?query=' + encodeURIComponent('test{job="testjob"}') + '&time=123';
  486. const query = {
  487. range: { from: time({ seconds: 63 }), to: time({ seconds: 123 }) },
  488. targets: [{ expr: 'test{job="testjob"}', format: 'time_series', instant: true }],
  489. interval: '60s',
  490. };
  491. beforeEach(async () => {
  492. const response = {
  493. status: 'success',
  494. data: {
  495. data: {
  496. resultType: 'vector',
  497. result: [
  498. {
  499. metric: { __name__: 'test', job: 'testjob' },
  500. value: [123, '3846'],
  501. },
  502. ],
  503. },
  504. },
  505. };
  506. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  507. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  508. await ctx.ds.query(query).then(data => {
  509. results = data;
  510. });
  511. });
  512. it('should generate the correct query', () => {
  513. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  514. expect(res.method).toBe('GET');
  515. expect(res.url).toBe(urlExpected);
  516. });
  517. it('should return series list', () => {
  518. expect(results.data.length).toBe(1);
  519. expect(results.data[0].target).toBe('test{job="testjob"}');
  520. });
  521. });
  522. describe('When performing annotationQuery', () => {
  523. let results;
  524. const options = {
  525. annotation: {
  526. expr: 'ALERTS{alertstate="firing"}',
  527. tagKeys: 'job',
  528. titleFormat: '{{alertname}}',
  529. textFormat: '{{instance}}',
  530. },
  531. range: {
  532. from: time({ seconds: 63 }),
  533. to: time({ seconds: 123 }),
  534. },
  535. };
  536. beforeEach(async () => {
  537. const response = {
  538. status: 'success',
  539. data: {
  540. data: {
  541. resultType: 'matrix',
  542. result: [
  543. {
  544. metric: {
  545. __name__: 'ALERTS',
  546. alertname: 'InstanceDown',
  547. alertstate: 'firing',
  548. instance: 'testinstance',
  549. job: 'testjob',
  550. },
  551. values: [[123, '1']],
  552. },
  553. ],
  554. },
  555. },
  556. };
  557. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  558. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  559. await ctx.ds.annotationQuery(options).then(data => {
  560. results = data;
  561. });
  562. });
  563. it('should return annotation list', () => {
  564. expect(results.length).toBe(1);
  565. expect(results[0].tags).toContain('testjob');
  566. expect(results[0].title).toBe('InstanceDown');
  567. expect(results[0].text).toBe('testinstance');
  568. expect(results[0].time).toBe(123 * 1000);
  569. });
  570. it('should return annotation list with seriesValueAsTiemstamp', () => {
  571. const options = {
  572. annotation: {
  573. expr: 'timestamp_seconds',
  574. tagKeys: 'job',
  575. titleFormat: '{{job}}',
  576. textFormat: '{{instance}}',
  577. useValueForTime: true,
  578. },
  579. range: {
  580. from: new Date('2014-04-10T05:20:10Z'),
  581. to: new Date('2014-05-20T03:10:22Z'),
  582. },
  583. };
  584. ctx.backendSrvMock.datasourceRequest.mockReturnValue(
  585. Promise.resolve({
  586. status: 'success',
  587. data: {
  588. resultType: 'matrix',
  589. result: [
  590. {
  591. metric: {
  592. __name__: 'timestamp_milliseconds',
  593. instance: 'testinstance',
  594. job: 'testjob',
  595. },
  596. values: [[1443454528, '1500000000000']],
  597. },
  598. ],
  599. },
  600. })
  601. );
  602. ctx.ds = new PrometheusDatasource(instanceSettings, q, ctx.backendSrvMock, ctx.templateSrvMock, ctx.timeSrvMock);
  603. ctx.ds.annotationQuery(options).then(function (results) {
  604. expect(results[0].time).toEqual(1500000000000);
  605. ctx.backendSrvMock.datasourceRequest.mockReset();
  606. });
  607. });
  608. });
  609. describe('When resultFormat is table and instant = true', () => {
  610. let results;
  611. const query = {
  612. range: { from: time({ seconds: 63 }), to: time({ seconds: 123 }) },
  613. targets: [{ expr: 'test{job="testjob"}', format: 'time_series', instant: true }],
  614. interval: '60s',
  615. };
  616. beforeEach(async () => {
  617. const response = {
  618. status: 'success',
  619. data: {
  620. data: {
  621. resultType: 'vector',
  622. result: [
  623. {
  624. metric: { __name__: 'test', job: 'testjob' },
  625. value: [123, '3846'],
  626. },
  627. ],
  628. },
  629. },
  630. };
  631. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  632. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  633. await ctx.ds.query(query).then(data => {
  634. results = data;
  635. });
  636. });
  637. it('should return result', () => {
  638. expect(results).not.toBe(null);
  639. });
  640. });
  641. describe('The "step" query parameter', () => {
  642. const response = {
  643. status: 'success',
  644. data: {
  645. data: {
  646. resultType: 'matrix',
  647. result: [],
  648. },
  649. },
  650. };
  651. it('should be min interval when greater than auto interval', async () => {
  652. const query = {
  653. // 6 minute range
  654. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  655. targets: [
  656. {
  657. expr: 'test',
  658. interval: '10s',
  659. },
  660. ],
  661. interval: '5s',
  662. };
  663. const urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=10';
  664. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  665. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  666. await ctx.ds.query(query);
  667. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  668. expect(res.method).toBe('GET');
  669. expect(res.url).toBe(urlExpected);
  670. });
  671. it('step should never go below 1', async () => {
  672. const query = {
  673. // 6 minute range
  674. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  675. targets: [{ expr: 'test' }],
  676. interval: '100ms',
  677. };
  678. const urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=1';
  679. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  680. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  681. await ctx.ds.query(query);
  682. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  683. expect(res.method).toBe('GET');
  684. expect(res.url).toBe(urlExpected);
  685. });
  686. it('should be auto interval when greater than min interval', async () => {
  687. const query = {
  688. // 6 minute range
  689. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  690. targets: [
  691. {
  692. expr: 'test',
  693. interval: '5s',
  694. },
  695. ],
  696. interval: '10s',
  697. };
  698. const urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=10';
  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 result in querying fewer than 11000 data points', async () => {
  707. const query = {
  708. // 6 hour range
  709. range: { from: time({ hours: 1 }), to: time({ hours: 7 }) },
  710. targets: [{ expr: 'test' }],
  711. interval: '1s',
  712. };
  713. const end = 7 * 60 * 60;
  714. const start = 60 * 60;
  715. const urlExpected = 'proxied/api/v1/query_range?query=test&start=' + start + '&end=' + end + '&step=2';
  716. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  717. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  718. await ctx.ds.query(query);
  719. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  720. expect(res.method).toBe('GET');
  721. expect(res.url).toBe(urlExpected);
  722. });
  723. it('should not apply min interval when interval * intervalFactor greater', async () => {
  724. const query = {
  725. // 6 minute range
  726. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  727. targets: [
  728. {
  729. expr: 'test',
  730. interval: '10s',
  731. intervalFactor: 10,
  732. },
  733. ],
  734. interval: '5s',
  735. };
  736. // times get rounded up to interval
  737. const urlExpected = 'proxied/api/v1/query_range?query=test&start=50&end=450&step=50';
  738. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  739. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  740. await ctx.ds.query(query);
  741. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  742. expect(res.method).toBe('GET');
  743. expect(res.url).toBe(urlExpected);
  744. });
  745. it('should apply min interval when interval * intervalFactor smaller', async () => {
  746. const query = {
  747. // 6 minute range
  748. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  749. targets: [
  750. {
  751. expr: 'test',
  752. interval: '15s',
  753. intervalFactor: 2,
  754. },
  755. ],
  756. interval: '5s',
  757. };
  758. const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=60&end=420&step=15';
  759. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  760. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  761. await ctx.ds.query(query);
  762. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  763. expect(res.method).toBe('GET');
  764. expect(res.url).toBe(urlExpected);
  765. });
  766. it('should apply intervalFactor to auto interval when greater', async () => {
  767. const query = {
  768. // 6 minute range
  769. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  770. targets: [
  771. {
  772. expr: 'test',
  773. interval: '5s',
  774. intervalFactor: 10,
  775. },
  776. ],
  777. interval: '10s',
  778. };
  779. // times get aligned to interval
  780. const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=0&end=500&step=100';
  781. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  782. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  783. await ctx.ds.query(query);
  784. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  785. expect(res.method).toBe('GET');
  786. expect(res.url).toBe(urlExpected);
  787. });
  788. it('should not not be affected by the 11000 data points limit when large enough', async () => {
  789. const query = {
  790. // 1 week range
  791. range: { from: time({}), to: time({ hours: 7 * 24 }) },
  792. targets: [
  793. {
  794. expr: 'test',
  795. intervalFactor: 10,
  796. },
  797. ],
  798. interval: '10s',
  799. };
  800. const end = 7 * 24 * 60 * 60;
  801. const start = 0;
  802. const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=' + start + '&end=' + end + '&step=100';
  803. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  804. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  805. await ctx.ds.query(query);
  806. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  807. expect(res.method).toBe('GET');
  808. expect(res.url).toBe(urlExpected);
  809. });
  810. it('should be determined by the 11000 data points limit when too small', async () => {
  811. const query = {
  812. // 1 week range
  813. range: { from: time({}), to: time({ hours: 7 * 24 }) },
  814. targets: [
  815. {
  816. expr: 'test',
  817. intervalFactor: 10,
  818. },
  819. ],
  820. interval: '5s',
  821. };
  822. const end = 7 * 24 * 60 * 60;
  823. const start = 0;
  824. const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=' + start + '&end=' + end + '&step=60';
  825. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  826. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  827. await ctx.ds.query(query);
  828. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  829. expect(res.method).toBe('GET');
  830. expect(res.url).toBe(urlExpected);
  831. });
  832. });
  833. describe('The __interval and __interval_ms template variables', () => {
  834. const response = {
  835. status: 'success',
  836. data: {
  837. data: {
  838. resultType: 'matrix',
  839. result: [],
  840. },
  841. },
  842. };
  843. it('should be unchanged when auto interval is greater than min interval', async () => {
  844. const query = {
  845. // 6 minute range
  846. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  847. targets: [
  848. {
  849. expr: 'rate(test[$__interval])',
  850. interval: '5s',
  851. },
  852. ],
  853. interval: '10s',
  854. scopedVars: {
  855. __interval: { text: '10s', value: '10s' },
  856. __interval_ms: { text: 10 * 1000, value: 10 * 1000 },
  857. },
  858. };
  859. const urlExpected =
  860. 'proxied/api/v1/query_range?query=' +
  861. encodeURIComponent('rate(test[$__interval])') +
  862. '&start=60&end=420&step=10';
  863. templateSrv.replace = jest.fn(str => str);
  864. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  865. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  866. await ctx.ds.query(query);
  867. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  868. expect(res.method).toBe('GET');
  869. expect(res.url).toBe(urlExpected);
  870. expect(templateSrv.replace.mock.calls[0][1]).toEqual({
  871. __interval: {
  872. text: '10s',
  873. value: '10s',
  874. },
  875. __interval_ms: {
  876. text: 10000,
  877. value: 10000,
  878. },
  879. });
  880. });
  881. it('should be min interval when it is greater than auto interval', async () => {
  882. const query = {
  883. // 6 minute range
  884. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  885. targets: [
  886. {
  887. expr: 'rate(test[$__interval])',
  888. interval: '10s',
  889. },
  890. ],
  891. interval: '5s',
  892. scopedVars: {
  893. __interval: { text: '5s', value: '5s' },
  894. __interval_ms: { text: 5 * 1000, value: 5 * 1000 },
  895. },
  896. };
  897. const urlExpected =
  898. 'proxied/api/v1/query_range?query=' +
  899. encodeURIComponent('rate(test[$__interval])') +
  900. '&start=60&end=420&step=10';
  901. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  902. templateSrv.replace = jest.fn(str => str);
  903. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  904. await ctx.ds.query(query);
  905. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  906. expect(res.method).toBe('GET');
  907. expect(res.url).toBe(urlExpected);
  908. expect(templateSrv.replace.mock.calls[0][1]).toEqual({
  909. __interval: {
  910. text: '5s',
  911. value: '5s',
  912. },
  913. __interval_ms: {
  914. text: 5000,
  915. value: 5000,
  916. },
  917. });
  918. });
  919. it('should account for intervalFactor', async () => {
  920. const query = {
  921. // 6 minute range
  922. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  923. targets: [
  924. {
  925. expr: 'rate(test[$__interval])',
  926. interval: '5s',
  927. intervalFactor: 10,
  928. },
  929. ],
  930. interval: '10s',
  931. scopedVars: {
  932. __interval: { text: '10s', value: '10s' },
  933. __interval_ms: { text: 10 * 1000, value: 10 * 1000 },
  934. },
  935. };
  936. const urlExpected =
  937. 'proxied/api/v1/query_range?query=' +
  938. encodeURIComponent('rate(test[$__interval])') +
  939. '&start=0&end=500&step=100';
  940. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  941. templateSrv.replace = jest.fn(str => str);
  942. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  943. await ctx.ds.query(query);
  944. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  945. expect(res.method).toBe('GET');
  946. expect(res.url).toBe(urlExpected);
  947. expect(templateSrv.replace.mock.calls[0][1]).toEqual({
  948. __interval: {
  949. text: '10s',
  950. value: '10s',
  951. },
  952. __interval_ms: {
  953. text: 10000,
  954. value: 10000,
  955. },
  956. });
  957. expect(query.scopedVars.__interval.text).toBe('10s');
  958. expect(query.scopedVars.__interval.value).toBe('10s');
  959. expect(query.scopedVars.__interval_ms.text).toBe(10 * 1000);
  960. expect(query.scopedVars.__interval_ms.value).toBe(10 * 1000);
  961. });
  962. it('should be interval * intervalFactor when greater than min interval', async () => {
  963. const query = {
  964. // 6 minute range
  965. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  966. targets: [
  967. {
  968. expr: 'rate(test[$__interval])',
  969. interval: '10s',
  970. intervalFactor: 10,
  971. },
  972. ],
  973. interval: '5s',
  974. scopedVars: {
  975. __interval: { text: '5s', value: '5s' },
  976. __interval_ms: { text: 5 * 1000, value: 5 * 1000 },
  977. },
  978. };
  979. const urlExpected =
  980. 'proxied/api/v1/query_range?query=' +
  981. encodeURIComponent('rate(test[$__interval])') +
  982. '&start=50&end=450&step=50';
  983. templateSrv.replace = jest.fn(str => str);
  984. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  985. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  986. await ctx.ds.query(query);
  987. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  988. expect(res.method).toBe('GET');
  989. expect(res.url).toBe(urlExpected);
  990. expect(templateSrv.replace.mock.calls[0][1]).toEqual({
  991. __interval: {
  992. text: '5s',
  993. value: '5s',
  994. },
  995. __interval_ms: {
  996. text: 5000,
  997. value: 5000,
  998. },
  999. });
  1000. });
  1001. it('should be min interval when greater than interval * intervalFactor', async () => {
  1002. const query = {
  1003. // 6 minute range
  1004. range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) },
  1005. targets: [
  1006. {
  1007. expr: 'rate(test[$__interval])',
  1008. interval: '15s',
  1009. intervalFactor: 2,
  1010. },
  1011. ],
  1012. interval: '5s',
  1013. scopedVars: {
  1014. __interval: { text: '5s', value: '5s' },
  1015. __interval_ms: { text: 5 * 1000, value: 5 * 1000 },
  1016. },
  1017. };
  1018. const urlExpected =
  1019. 'proxied/api/v1/query_range?query=' +
  1020. encodeURIComponent('rate(test[$__interval])') +
  1021. '&start=60&end=420&step=15';
  1022. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  1023. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  1024. await ctx.ds.query(query);
  1025. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  1026. expect(res.method).toBe('GET');
  1027. expect(res.url).toBe(urlExpected);
  1028. expect(templateSrv.replace.mock.calls[0][1]).toEqual({
  1029. __interval: {
  1030. text: '5s',
  1031. value: '5s',
  1032. },
  1033. __interval_ms: {
  1034. text: 5000,
  1035. value: 5000,
  1036. },
  1037. });
  1038. });
  1039. it('should be determined by the 11000 data points limit, accounting for intervalFactor', async () => {
  1040. const query = {
  1041. // 1 week range
  1042. range: { from: time({}), to: time({ hours: 7 * 24 }) },
  1043. targets: [
  1044. {
  1045. expr: 'rate(test[$__interval])',
  1046. intervalFactor: 10,
  1047. },
  1048. ],
  1049. interval: '5s',
  1050. scopedVars: {
  1051. __interval: { text: '5s', value: '5s' },
  1052. __interval_ms: { text: 5 * 1000, value: 5 * 1000 },
  1053. },
  1054. };
  1055. const end = 7 * 24 * 60 * 60;
  1056. const start = 0;
  1057. const urlExpected =
  1058. 'proxied/api/v1/query_range?query=' +
  1059. encodeURIComponent('rate(test[$__interval])') +
  1060. '&start=' +
  1061. start +
  1062. '&end=' +
  1063. end +
  1064. '&step=60';
  1065. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  1066. templateSrv.replace = jest.fn(str => str);
  1067. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  1068. await ctx.ds.query(query);
  1069. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  1070. expect(res.method).toBe('GET');
  1071. expect(res.url).toBe(urlExpected);
  1072. expect(templateSrv.replace.mock.calls[0][1]).toEqual({
  1073. __interval: {
  1074. text: '5s',
  1075. value: '5s',
  1076. },
  1077. __interval_ms: {
  1078. text: 5000,
  1079. value: 5000,
  1080. },
  1081. });
  1082. });
  1083. });
  1084. });
  1085. describe('PrometheusDatasource for POST', () => {
  1086. // const ctx = new helpers.ServiceTestContext();
  1087. const instanceSettings = {
  1088. url: 'proxied',
  1089. directUrl: 'direct',
  1090. user: 'test',
  1091. password: 'mupp',
  1092. jsonData: { httpMethod: 'POST' },
  1093. };
  1094. describe('When querying prometheus with one target using query editor target spec', () => {
  1095. let results;
  1096. const urlExpected = 'proxied/api/v1/query_range';
  1097. const dataExpected = {
  1098. query: 'test{job="testjob"}',
  1099. start: 1 * 60,
  1100. end: 3 * 60,
  1101. step: 60,
  1102. };
  1103. const query = {
  1104. range: { from: time({ minutes: 1, seconds: 3 }), to: time({ minutes: 2, seconds: 3 }) },
  1105. targets: [{ expr: 'test{job="testjob"}', format: 'time_series' }],
  1106. interval: '60s',
  1107. };
  1108. beforeEach(async () => {
  1109. const response = {
  1110. status: 'success',
  1111. data: {
  1112. data: {
  1113. resultType: 'matrix',
  1114. result: [
  1115. {
  1116. metric: { __name__: 'test', job: 'testjob' },
  1117. values: [[2 * 60, '3846']],
  1118. },
  1119. ],
  1120. },
  1121. },
  1122. };
  1123. backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
  1124. ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
  1125. await ctx.ds.query(query).then(data => {
  1126. results = data;
  1127. });
  1128. });
  1129. it('should generate the correct query', () => {
  1130. const res = backendSrv.datasourceRequest.mock.calls[0][0];
  1131. expect(res.method).toBe('POST');
  1132. expect(res.url).toBe(urlExpected);
  1133. expect(res.data).toEqual(dataExpected);
  1134. });
  1135. it('should return series list', () => {
  1136. expect(results.data.length).toBe(1);
  1137. expect(results.data[0].target).toBe('test{job="testjob"}');
  1138. });
  1139. });
  1140. });