datasource.test.ts 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. import { lastValueFrom, of } from 'rxjs';
  2. import { TemplateSrvStub } from 'test/specs/helpers';
  3. import { ScopedVars } from '@grafana/data/src';
  4. import { FetchResponse } from '@grafana/runtime';
  5. import config from 'app/core/config';
  6. import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
  7. import InfluxDatasource from '../datasource';
  8. //@ts-ignore
  9. const templateSrv = new TemplateSrvStub();
  10. jest.mock('@grafana/runtime', () => ({
  11. ...(jest.requireActual('@grafana/runtime') as unknown as object),
  12. getBackendSrv: () => backendSrv,
  13. }));
  14. describe('InfluxDataSource', () => {
  15. const ctx: any = {
  16. instanceSettings: { url: 'url', name: 'influxDb', jsonData: { httpMode: 'GET' } },
  17. };
  18. const fetchMock = jest.spyOn(backendSrv, 'fetch');
  19. beforeEach(() => {
  20. jest.clearAllMocks();
  21. ctx.instanceSettings.url = '/api/datasources/proxy/1';
  22. ctx.ds = new InfluxDatasource(ctx.instanceSettings, templateSrv);
  23. });
  24. describe('When issuing metricFindQuery', () => {
  25. const query = 'SELECT max(value) FROM measurement WHERE $timeFilter';
  26. const queryOptions: any = {
  27. range: {
  28. from: '2018-01-01T00:00:00Z',
  29. to: '2018-01-02T00:00:00Z',
  30. },
  31. };
  32. let requestQuery: any, requestMethod: any, requestData: any, response: any;
  33. beforeEach(async () => {
  34. fetchMock.mockImplementation((req: any) => {
  35. requestMethod = req.method;
  36. requestQuery = req.params.q;
  37. requestData = req.data;
  38. return of({
  39. data: {
  40. status: 'success',
  41. results: [
  42. {
  43. series: [
  44. {
  45. name: 'measurement',
  46. columns: ['name'],
  47. values: [['cpu']],
  48. },
  49. ],
  50. },
  51. ],
  52. },
  53. } as FetchResponse);
  54. });
  55. response = await ctx.ds.metricFindQuery(query, queryOptions);
  56. });
  57. it('should replace $timefilter', () => {
  58. expect(requestQuery).toMatch('time >= 1514764800000ms and time <= 1514851200000ms');
  59. });
  60. it('should use the HTTP GET method', () => {
  61. expect(requestMethod).toBe('GET');
  62. });
  63. it('should not have any data in request body', () => {
  64. expect(requestData).toBeNull();
  65. });
  66. it('parse response correctly', () => {
  67. expect(response).toEqual([{ text: 'cpu' }]);
  68. });
  69. });
  70. describe('When getting error on 200 after issuing a query', () => {
  71. const queryOptions = {
  72. range: {
  73. from: '2018-01-01T00:00:00Z',
  74. to: '2018-01-02T00:00:00Z',
  75. },
  76. rangeRaw: {
  77. from: '2018-01-01T00:00:00Z',
  78. to: '2018-01-02T00:00:00Z',
  79. },
  80. targets: [{}],
  81. timezone: 'UTC',
  82. scopedVars: {
  83. interval: { text: '1m', value: '1m' },
  84. __interval: { text: '1m', value: '1m' },
  85. __interval_ms: { text: 60000, value: 60000 },
  86. },
  87. };
  88. it('throws an error', async () => {
  89. fetchMock.mockImplementation(() => {
  90. return of({
  91. data: {
  92. results: [
  93. {
  94. error: 'Query timeout',
  95. },
  96. ],
  97. },
  98. } as FetchResponse);
  99. });
  100. try {
  101. await lastValueFrom(ctx.ds.query(queryOptions));
  102. } catch (err) {
  103. expect(err.message).toBe('InfluxDB Error: Query timeout');
  104. }
  105. });
  106. });
  107. describe('InfluxDataSource in POST query mode', () => {
  108. const ctx: any = {
  109. instanceSettings: { url: 'url', name: 'influxDb', jsonData: { httpMode: 'POST' } },
  110. };
  111. beforeEach(() => {
  112. ctx.instanceSettings.url = '/api/datasources/proxy/1';
  113. ctx.ds = new InfluxDatasource(ctx.instanceSettings, templateSrv);
  114. });
  115. describe('When issuing metricFindQuery', () => {
  116. const query = 'SELECT max(value) FROM measurement';
  117. const queryOptions: any = {};
  118. let requestMethod: any, requestQueryParameter: any, queryEncoded: any, requestQuery: any;
  119. beforeEach(async () => {
  120. fetchMock.mockImplementation((req: any) => {
  121. requestMethod = req.method;
  122. requestQueryParameter = req.params;
  123. requestQuery = req.data;
  124. return of({
  125. data: {
  126. results: [
  127. {
  128. series: [
  129. {
  130. name: 'measurement',
  131. columns: ['max'],
  132. values: [[1]],
  133. },
  134. ],
  135. },
  136. ],
  137. },
  138. } as FetchResponse);
  139. });
  140. queryEncoded = await ctx.ds.serializeParams({ q: query });
  141. await ctx.ds.metricFindQuery(query, queryOptions).then(() => {});
  142. });
  143. it('should have the query form urlencoded', () => {
  144. expect(requestQuery).toBe(queryEncoded);
  145. });
  146. it('should use the HTTP POST method', () => {
  147. expect(requestMethod).toBe('POST');
  148. });
  149. it('should not have q as a query parameter', () => {
  150. expect(requestQueryParameter).not.toHaveProperty('q');
  151. });
  152. });
  153. });
  154. describe('Variables should be interpolated correctly', () => {
  155. const instanceSettings: any = {};
  156. const text = 'interpolationText';
  157. const text2 = 'interpolationText2';
  158. const textWithoutFormatRegex = 'interpolationText,interpolationText2';
  159. const textWithFormatRegex = 'interpolationText|interpolationText2';
  160. const variableMap: Record<string, string> = {
  161. $interpolationVar: text,
  162. $interpolationVar2: text2,
  163. };
  164. const templateSrv: any = {
  165. replace: jest.fn((target?: string, scopedVars?: ScopedVars, format?: string | Function): string => {
  166. if (!format) {
  167. return variableMap[target!] || '';
  168. }
  169. if (format === 'regex') {
  170. return textWithFormatRegex;
  171. }
  172. return textWithoutFormatRegex;
  173. }),
  174. };
  175. const ds = new InfluxDatasource(instanceSettings, templateSrv);
  176. const influxQuery = {
  177. refId: 'x',
  178. alias: '$interpolationVar',
  179. measurement: '$interpolationVar',
  180. policy: '$interpolationVar',
  181. limit: '$interpolationVar',
  182. slimit: '$interpolationVar',
  183. tz: '$interpolationVar',
  184. tags: [
  185. {
  186. key: 'cpu',
  187. operator: '=~',
  188. value: '/^$interpolationVar,$interpolationVar2$/',
  189. },
  190. ],
  191. groupBy: [
  192. {
  193. params: ['$interpolationVar'],
  194. type: 'tag',
  195. },
  196. ],
  197. select: [
  198. [
  199. {
  200. params: ['$interpolationVar'],
  201. type: 'field',
  202. },
  203. ],
  204. ],
  205. };
  206. function influxChecks(query: any) {
  207. expect(templateSrv.replace).toBeCalledTimes(10);
  208. expect(query.alias).toBe(text);
  209. expect(query.measurement).toBe(textWithFormatRegex);
  210. expect(query.policy).toBe(textWithFormatRegex);
  211. expect(query.limit).toBe(textWithFormatRegex);
  212. expect(query.slimit).toBe(textWithFormatRegex);
  213. expect(query.tz).toBe(text);
  214. expect(query.tags![0].value).toBe(textWithFormatRegex);
  215. expect(query.groupBy![0].params![0]).toBe(textWithFormatRegex);
  216. expect(query.select![0][0].params![0]).toBe(textWithFormatRegex);
  217. }
  218. describe('when interpolating query variables for dashboard->explore', () => {
  219. it('should interpolate all variables with Flux mode', () => {
  220. ds.isFlux = true;
  221. const fluxQuery = {
  222. refId: 'x',
  223. query: '$interpolationVar,$interpolationVar2',
  224. };
  225. const queries = ds.interpolateVariablesInQueries([fluxQuery], {
  226. interpolationVar: { text: text, value: text },
  227. interpolationVar2: { text: text2, value: text2 },
  228. });
  229. expect(templateSrv.replace).toBeCalledTimes(1);
  230. expect(queries[0].query).toBe(textWithFormatRegex);
  231. });
  232. it('should interpolate all variables with InfluxQL mode', () => {
  233. ds.isFlux = false;
  234. const queries = ds.interpolateVariablesInQueries([influxQuery], {
  235. interpolationVar: { text: text, value: text },
  236. interpolationVar2: { text: text2, value: text2 },
  237. });
  238. influxChecks(queries[0]);
  239. });
  240. });
  241. describe('when interpolating template variables', () => {
  242. it('should apply all template variables with Flux mode', () => {
  243. ds.isFlux = true;
  244. const fluxQuery = {
  245. refId: 'x',
  246. query: '$interpolationVar',
  247. };
  248. const query = ds.applyTemplateVariables(fluxQuery, {
  249. interpolationVar: {
  250. text: text,
  251. value: text,
  252. },
  253. });
  254. expect(templateSrv.replace).toBeCalledTimes(1);
  255. expect(query.query).toBe(text);
  256. });
  257. it('should apply all template variables with InfluxQL mode', () => {
  258. ds.isFlux = false;
  259. ds.access = 'proxy';
  260. config.featureToggles.influxdbBackendMigration = true;
  261. const query = ds.applyTemplateVariables(influxQuery, {
  262. interpolationVar: { text: text, value: text },
  263. interpolationVar2: { text: 'interpolationText2', value: 'interpolationText2' },
  264. });
  265. influxChecks(query);
  266. });
  267. });
  268. });
  269. });