queryRunners.test.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. import { of } from 'rxjs';
  2. import { getDefaultTimeRange, VariableSupportType } from '@grafana/data';
  3. import { VariableRefresh } from '../types';
  4. import { QueryRunners, variableDummyRefId } from './queryRunners';
  5. describe('QueryRunners', () => {
  6. describe('when using a legacy data source', () => {
  7. const getLegacyTestContext = (variable?: any) => {
  8. const defaultTimeRange = getDefaultTimeRange();
  9. variable = variable ?? { query: 'A query' };
  10. const timeSrv = {
  11. timeRange: jest.fn().mockReturnValue(defaultTimeRange),
  12. };
  13. const datasource: any = { metricFindQuery: jest.fn().mockResolvedValue([{ text: 'A', value: 'A' }]) };
  14. const runner = new QueryRunners().getRunnerForDatasource(datasource);
  15. const runRequest = jest.fn().mockReturnValue(of({}));
  16. const runnerArgs: any = { datasource, variable, searchFilter: 'A searchFilter', timeSrv, runRequest };
  17. const request: any = {};
  18. return { timeSrv, datasource, runner, variable, runnerArgs, request, defaultTimeRange };
  19. };
  20. describe('and calling getRunnerForDatasource', () => {
  21. it('then it should return LegacyQueryRunner', () => {
  22. const { runner } = getLegacyTestContext();
  23. expect(runner!.type).toEqual(VariableSupportType.Legacy);
  24. });
  25. });
  26. describe('and calling getTarget', () => {
  27. it('then it should return correct target', () => {
  28. const { runner, datasource, variable } = getLegacyTestContext();
  29. const target = runner.getTarget({ datasource, variable });
  30. expect(target).toEqual('A query');
  31. });
  32. });
  33. describe('and calling runRequest with a variable that refreshes when time range changes', () => {
  34. const { datasource, runner, runnerArgs, request, timeSrv, defaultTimeRange } = getLegacyTestContext({
  35. query: 'A query',
  36. refresh: VariableRefresh.onTimeRangeChanged,
  37. });
  38. const observable = runner.runRequest(runnerArgs, request);
  39. it('then it should return correct observable', async () => {
  40. await expect(observable).toEmitValuesWith((received) => {
  41. const value = received[0];
  42. expect(value).toEqual({
  43. series: [{ text: 'A', value: 'A' }],
  44. state: 'Done',
  45. timeRange: defaultTimeRange,
  46. });
  47. });
  48. });
  49. it('and it should call timeSrv.timeRange()', () => {
  50. expect(timeSrv.timeRange).toHaveBeenCalledTimes(1);
  51. });
  52. it('and it should call metricFindQuery with correct options', () => {
  53. expect(datasource.metricFindQuery).toHaveBeenCalledTimes(1);
  54. expect(datasource.metricFindQuery).toHaveBeenCalledWith('A query', {
  55. range: defaultTimeRange,
  56. searchFilter: 'A searchFilter',
  57. variable: {
  58. query: 'A query',
  59. refresh: VariableRefresh.onTimeRangeChanged,
  60. },
  61. });
  62. });
  63. });
  64. describe('and calling runRequest with a variable that refreshes on dashboard load', () => {
  65. const { datasource, runner, runnerArgs, request, timeSrv, defaultTimeRange } = getLegacyTestContext({
  66. query: 'A query',
  67. refresh: VariableRefresh.onDashboardLoad,
  68. });
  69. const observable = runner.runRequest(runnerArgs, request);
  70. it('then it should return correct observable', async () => {
  71. await expect(observable).toEmitValuesWith((received) => {
  72. const value = received[0];
  73. expect(value).toEqual({
  74. series: [{ text: 'A', value: 'A' }],
  75. state: 'Done',
  76. timeRange: defaultTimeRange,
  77. });
  78. });
  79. });
  80. it('and it should call timeSrv.timeRange()', () => {
  81. expect(timeSrv.timeRange).toHaveBeenCalledTimes(1);
  82. });
  83. it('and it should call metricFindQuery with correct options', () => {
  84. expect(datasource.metricFindQuery).toHaveBeenCalledTimes(1);
  85. expect(datasource.metricFindQuery).toHaveBeenCalledWith('A query', {
  86. range: defaultTimeRange,
  87. searchFilter: 'A searchFilter',
  88. variable: {
  89. query: 'A query',
  90. refresh: VariableRefresh.onDashboardLoad,
  91. },
  92. });
  93. });
  94. });
  95. describe('and calling runRequest with a variable that does not refresh when time range changes', () => {
  96. const { datasource, runner, runnerArgs, request, timeSrv } = getLegacyTestContext({
  97. query: 'A query',
  98. refresh: VariableRefresh.never,
  99. });
  100. const observable = runner.runRequest(runnerArgs, request);
  101. it('then it should return correct observable', async () => {
  102. await expect(observable).toEmitValuesWith((received) => {
  103. const values = received[0];
  104. expect(values).toEqual({
  105. series: [{ text: 'A', value: 'A' }],
  106. state: 'Done',
  107. timeRange: undefined,
  108. });
  109. });
  110. });
  111. it('and it should not call timeSrv.timeRange()', () => {
  112. expect(timeSrv.timeRange).not.toHaveBeenCalled();
  113. });
  114. it('and it should call metricFindQuery with correct options', () => {
  115. expect(datasource.metricFindQuery).toHaveBeenCalledTimes(1);
  116. expect(datasource.metricFindQuery).toHaveBeenCalledWith('A query', {
  117. range: undefined,
  118. searchFilter: 'A searchFilter',
  119. variable: {
  120. query: 'A query',
  121. refresh: VariableRefresh.never,
  122. },
  123. });
  124. });
  125. });
  126. });
  127. describe('when using a data source with standard variable support', () => {
  128. const getStandardTestContext = (datasource?: any) => {
  129. const variable: any = { query: { refId: 'A', query: 'A query' } };
  130. const timeSrv = {};
  131. datasource = datasource ?? {
  132. variables: {
  133. getType: () => VariableSupportType.Standard,
  134. toDataQuery: (query: any) => ({ ...query, extra: 'extra' }),
  135. },
  136. };
  137. const runner = new QueryRunners().getRunnerForDatasource(datasource);
  138. const runRequest = jest.fn().mockReturnValue(of({}));
  139. const runnerArgs: any = { datasource, variable, searchFilter: 'A searchFilter', timeSrv, runRequest };
  140. const request: any = {};
  141. return { timeSrv, datasource, runner, variable, runnerArgs, request, runRequest };
  142. };
  143. describe('and calling getRunnerForDatasource', () => {
  144. it('then it should return StandardQueryRunner', () => {
  145. const { runner } = getStandardTestContext();
  146. expect(runner!.type).toEqual(VariableSupportType.Standard);
  147. });
  148. });
  149. describe('and calling getTarget', () => {
  150. it('then it should return correct target', () => {
  151. const { runner, variable, datasource } = getStandardTestContext();
  152. const target = runner.getTarget({ datasource, variable });
  153. expect(target).toEqual({ refId: 'A', query: 'A query', extra: 'extra' });
  154. });
  155. });
  156. describe('and calling runRequest with a datasource that uses a custom query', () => {
  157. const { runner, request, runnerArgs, runRequest, datasource } = getStandardTestContext({
  158. variables: {
  159. getType: () => VariableSupportType.Standard,
  160. toDataQuery: () => undefined,
  161. query: () => undefined,
  162. },
  163. });
  164. const observable = runner.runRequest(runnerArgs, request);
  165. it('then it should return correct observable', async () => {
  166. await expect(observable).toEmitValuesWith((received) => {
  167. const value = received[0];
  168. expect(value).toEqual({});
  169. });
  170. });
  171. it('then it should call runRequest with correct args', () => {
  172. expect(runRequest).toHaveBeenCalledTimes(1);
  173. expect(runRequest).toHaveBeenCalledWith(datasource, {}, datasource.variables.query);
  174. });
  175. });
  176. describe('and calling runRequest with a datasource that has no custom query', () => {
  177. const { runner, request, runnerArgs, runRequest, datasource } = getStandardTestContext({
  178. variables: { getType: () => VariableSupportType.Standard, toDataQuery: () => undefined },
  179. });
  180. const observable = runner.runRequest(runnerArgs, request);
  181. it('then it should return correct observable', async () => {
  182. await expect(observable).toEmitValuesWith((received) => {
  183. const value = received[0];
  184. expect(value).toEqual({});
  185. });
  186. });
  187. it('then it should call runRequest with correct args', () => {
  188. expect(runRequest).toHaveBeenCalledTimes(1);
  189. expect(runRequest).toHaveBeenCalledWith(datasource, {});
  190. });
  191. });
  192. });
  193. describe('when using a data source with custom variable support', () => {
  194. const getCustomTestContext = () => {
  195. const variable: any = { query: { refId: 'A', query: 'A query' } };
  196. const timeSrv = {};
  197. const datasource: any = {
  198. variables: { getType: () => VariableSupportType.Custom, query: () => undefined, editor: {} },
  199. };
  200. const runner = new QueryRunners().getRunnerForDatasource(datasource);
  201. const runRequest = jest.fn().mockReturnValue(of({}));
  202. const runnerArgs: any = { datasource, variable, searchFilter: 'A searchFilter', timeSrv, runRequest };
  203. const request: any = {};
  204. return { timeSrv, datasource, runner, variable, runnerArgs, request, runRequest };
  205. };
  206. describe('and calling getRunnerForDatasource', () => {
  207. it('then it should return CustomQueryRunner', () => {
  208. const { runner } = getCustomTestContext();
  209. expect(runner!.type).toEqual(VariableSupportType.Custom);
  210. });
  211. });
  212. describe('and calling getTarget', () => {
  213. it('then it should return correct target', () => {
  214. const { runner, variable, datasource } = getCustomTestContext();
  215. const target = runner.getTarget({ datasource, variable });
  216. expect(target).toEqual({ refId: 'A', query: 'A query' });
  217. });
  218. });
  219. describe('and calling runRequest', () => {
  220. const { runner, request, runnerArgs, runRequest, datasource } = getCustomTestContext();
  221. const observable = runner.runRequest(runnerArgs, request);
  222. it('then it should return correct observable', async () => {
  223. await expect(observable).toEmitValuesWith((received) => {
  224. const value = received[0];
  225. expect(value).toEqual({});
  226. });
  227. });
  228. it('then it should call runRequest with correct args', () => {
  229. expect(runRequest).toHaveBeenCalledTimes(1);
  230. expect(runRequest).toHaveBeenCalledWith(datasource, {}, datasource.variables.query);
  231. });
  232. });
  233. });
  234. describe('when using a data source with datasource variable support', () => {
  235. const getDatasourceTestContext = () => {
  236. const variable: any = { query: { refId: 'A', query: 'A query' } };
  237. const timeSrv = {};
  238. const datasource: any = {
  239. variables: { getType: () => VariableSupportType.Datasource },
  240. };
  241. const runner = new QueryRunners().getRunnerForDatasource(datasource);
  242. const runRequest = jest.fn().mockReturnValue(of({}));
  243. const runnerArgs: any = { datasource, variable, searchFilter: 'A searchFilter', timeSrv, runRequest };
  244. const request: any = {};
  245. return { timeSrv, datasource, runner, variable, runnerArgs, request, runRequest };
  246. };
  247. describe('and calling getRunnerForDatasource', () => {
  248. it('then it should return DatasourceQueryRunner', () => {
  249. const { runner } = getDatasourceTestContext();
  250. expect(runner!.type).toEqual(VariableSupportType.Datasource);
  251. });
  252. });
  253. describe('and calling getTarget', () => {
  254. it('then it should return correct target', () => {
  255. const { runner, datasource, variable } = getDatasourceTestContext();
  256. const target = runner.getTarget({ datasource, variable });
  257. expect(target).toEqual({ refId: 'A', query: 'A query' });
  258. });
  259. describe('and ref id is missing', () => {
  260. it('then it should return correct target with dummy ref id', () => {
  261. const { runner, datasource, variable } = getDatasourceTestContext();
  262. delete variable.query.refId;
  263. const target = runner.getTarget({ datasource, variable });
  264. expect(target).toEqual({ refId: variableDummyRefId, query: 'A query' });
  265. });
  266. });
  267. });
  268. describe('and calling runRequest', () => {
  269. const { runner, request, runnerArgs, runRequest, datasource } = getDatasourceTestContext();
  270. const observable = runner.runRequest(runnerArgs, request);
  271. it('then it should return correct observable', async () => {
  272. await expect(observable).toEmitValuesWith((received) => {
  273. const value = received[0];
  274. expect(value).toEqual({});
  275. });
  276. });
  277. it('then it should call runRequest with correct args', () => {
  278. expect(runRequest).toHaveBeenCalledTimes(1);
  279. expect(runRequest).toHaveBeenCalledWith(datasource, {});
  280. });
  281. });
  282. });
  283. describe('when using a data source with unknown variable support', () => {
  284. describe('and calling getRunnerForDatasource', () => {
  285. it('then it should throw', () => {
  286. const datasource: any = {
  287. variables: {},
  288. };
  289. expect(() => new QueryRunners().getRunnerForDatasource(datasource)).toThrow();
  290. });
  291. });
  292. });
  293. });