runQueriesBatchEpic.test.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. import { mockExploreState } from 'test/mocks/mockExploreState';
  2. import { epicTester } from 'test/core/redux/epicTester';
  3. import { runQueriesBatchEpic } from './runQueriesBatchEpic';
  4. import {
  5. runQueriesBatchAction,
  6. queryStartAction,
  7. historyUpdatedAction,
  8. processQueryResultsAction,
  9. processQueryErrorsAction,
  10. limitMessageRatePayloadAction,
  11. resetExploreAction,
  12. updateDatasourceInstanceAction,
  13. changeRefreshIntervalAction,
  14. clearQueriesAction,
  15. stateSaveAction,
  16. } from '../actionTypes';
  17. import { LoadingState, DataFrame, FieldType } from '@grafana/data';
  18. import { DataQueryRequest } from '@grafana/ui';
  19. const testContext = () => {
  20. const series: DataFrame[] = [
  21. {
  22. fields: [
  23. {
  24. name: 'Value',
  25. },
  26. {
  27. name: 'Time',
  28. type: FieldType.time,
  29. unit: 'dateTimeAsIso',
  30. },
  31. ],
  32. rows: [],
  33. refId: 'A',
  34. },
  35. ];
  36. const response = { data: series };
  37. return {
  38. response,
  39. series,
  40. };
  41. };
  42. describe('runQueriesBatchEpic', () => {
  43. let originalDateNow = Date.now;
  44. beforeEach(() => {
  45. originalDateNow = Date.now;
  46. Date.now = () => 1337;
  47. });
  48. afterEach(() => {
  49. Date.now = originalDateNow;
  50. });
  51. describe('when runQueriesBatchAction is dispatched', () => {
  52. describe('and query targets are not live', () => {
  53. describe('and query is successful', () => {
  54. it('then correct actions are dispatched', () => {
  55. const { response, series } = testContext();
  56. const { exploreId, state, history, datasourceId } = mockExploreState();
  57. epicTester(runQueriesBatchEpic, state)
  58. .whenActionIsDispatched(
  59. runQueriesBatchAction({ exploreId, queryOptions: { live: false, interval: '', maxDataPoints: 1980 } })
  60. )
  61. .whenQueryReceivesResponse(response)
  62. .thenResultingActionsEqual(
  63. queryStartAction({ exploreId }),
  64. historyUpdatedAction({ exploreId, history }),
  65. processQueryResultsAction({
  66. exploreId,
  67. delta: null,
  68. series,
  69. latency: 0,
  70. datasourceId,
  71. loadingState: LoadingState.Done,
  72. }),
  73. stateSaveAction()
  74. );
  75. });
  76. });
  77. describe('and query is not successful', () => {
  78. it('then correct actions are dispatched', () => {
  79. const error = {
  80. message: 'Error parsing line x',
  81. };
  82. const { exploreId, state, datasourceId } = mockExploreState();
  83. epicTester(runQueriesBatchEpic, state)
  84. .whenActionIsDispatched(
  85. runQueriesBatchAction({ exploreId, queryOptions: { live: false, interval: '', maxDataPoints: 1980 } })
  86. )
  87. .whenQueryThrowsError(error)
  88. .thenResultingActionsEqual(
  89. queryStartAction({ exploreId }),
  90. processQueryErrorsAction({ exploreId, response: error, datasourceId })
  91. );
  92. });
  93. });
  94. });
  95. describe('and query targets are live', () => {
  96. describe('and state equals Streaming', () => {
  97. it('then correct actions are dispatched', () => {
  98. const { exploreId, state, datasourceId } = mockExploreState();
  99. const unsubscribe = jest.fn();
  100. const serieA: any = {
  101. fields: [],
  102. rows: [],
  103. refId: 'A',
  104. };
  105. const serieB: any = {
  106. fields: [],
  107. rows: [],
  108. refId: 'B',
  109. };
  110. epicTester(runQueriesBatchEpic, state)
  111. .whenActionIsDispatched(
  112. runQueriesBatchAction({ exploreId, queryOptions: { live: true, interval: '', maxDataPoints: 1980 } })
  113. )
  114. .whenQueryObserverReceivesEvent({
  115. state: LoadingState.Streaming,
  116. delta: [serieA],
  117. key: 'some key',
  118. request: {} as DataQueryRequest,
  119. unsubscribe,
  120. })
  121. .whenQueryObserverReceivesEvent({
  122. state: LoadingState.Streaming,
  123. delta: [serieB],
  124. key: 'some key',
  125. request: {} as DataQueryRequest,
  126. unsubscribe,
  127. })
  128. .thenResultingActionsEqual(
  129. queryStartAction({ exploreId }),
  130. limitMessageRatePayloadAction({ exploreId, series: [serieA], datasourceId }),
  131. limitMessageRatePayloadAction({ exploreId, series: [serieB], datasourceId })
  132. );
  133. });
  134. });
  135. describe('and state equals Error', () => {
  136. it('then correct actions are dispatched', () => {
  137. const { exploreId, state, datasourceId } = mockExploreState();
  138. const unsubscribe = jest.fn();
  139. const error = { message: 'Something went really wrong!' };
  140. epicTester(runQueriesBatchEpic, state)
  141. .whenActionIsDispatched(
  142. runQueriesBatchAction({ exploreId, queryOptions: { live: true, interval: '', maxDataPoints: 1980 } })
  143. )
  144. .whenQueryObserverReceivesEvent({
  145. state: LoadingState.Error,
  146. error,
  147. key: 'some key',
  148. request: {} as DataQueryRequest,
  149. unsubscribe,
  150. })
  151. .thenResultingActionsEqual(
  152. queryStartAction({ exploreId }),
  153. processQueryErrorsAction({ exploreId, response: error, datasourceId })
  154. );
  155. });
  156. });
  157. describe('and state equals Done', () => {
  158. it('then correct actions are dispatched', () => {
  159. const { exploreId, state, datasourceId, history } = mockExploreState();
  160. const unsubscribe = jest.fn();
  161. const serieA: any = {
  162. fields: [],
  163. rows: [],
  164. refId: 'A',
  165. };
  166. const serieB: any = {
  167. fields: [],
  168. rows: [],
  169. refId: 'B',
  170. };
  171. const delta = [serieA, serieB];
  172. epicTester(runQueriesBatchEpic, state)
  173. .whenActionIsDispatched(
  174. runQueriesBatchAction({ exploreId, queryOptions: { live: true, interval: '', maxDataPoints: 1980 } })
  175. )
  176. .whenQueryObserverReceivesEvent({
  177. state: LoadingState.Done,
  178. data: null,
  179. delta,
  180. key: 'some key',
  181. request: {} as DataQueryRequest,
  182. unsubscribe,
  183. })
  184. .thenResultingActionsEqual(
  185. queryStartAction({ exploreId }),
  186. historyUpdatedAction({ exploreId, history }),
  187. processQueryResultsAction({
  188. exploreId,
  189. delta,
  190. series: null,
  191. latency: 0,
  192. datasourceId,
  193. loadingState: LoadingState.Done,
  194. }),
  195. stateSaveAction()
  196. );
  197. });
  198. });
  199. });
  200. describe('and another runQueriesBatchAction is dispatched', () => {
  201. it('then the observable should be unsubscribed', () => {
  202. const { response, series } = testContext();
  203. const { exploreId, state, history, datasourceId } = mockExploreState();
  204. const unsubscribe = jest.fn();
  205. epicTester(runQueriesBatchEpic, state)
  206. .whenActionIsDispatched(
  207. runQueriesBatchAction({ exploreId, queryOptions: { live: false, interval: '', maxDataPoints: 1980 } }) // first observable
  208. )
  209. .whenQueryReceivesResponse(response)
  210. .whenQueryObserverReceivesEvent({
  211. key: 'some key',
  212. request: {} as DataQueryRequest,
  213. state: LoadingState.Loading, // fake just to setup and test unsubscribe
  214. unsubscribe,
  215. })
  216. .whenActionIsDispatched(
  217. // second observable and unsubscribes the first observable
  218. runQueriesBatchAction({ exploreId, queryOptions: { live: true, interval: '', maxDataPoints: 800 } })
  219. )
  220. .whenQueryReceivesResponse(response)
  221. .whenQueryObserverReceivesEvent({
  222. key: 'some key',
  223. request: {} as DataQueryRequest,
  224. state: LoadingState.Loading, // fake just to setup and test unsubscribe
  225. unsubscribe,
  226. })
  227. .thenResultingActionsEqual(
  228. queryStartAction({ exploreId }), // output from first observable
  229. historyUpdatedAction({ exploreId, history }), // output from first observable
  230. processQueryResultsAction({
  231. exploreId,
  232. delta: null,
  233. series,
  234. latency: 0,
  235. datasourceId,
  236. loadingState: LoadingState.Done,
  237. }),
  238. stateSaveAction(),
  239. // output from first observable
  240. queryStartAction({ exploreId }), // output from second observable
  241. historyUpdatedAction({ exploreId, history }), // output from second observable
  242. processQueryResultsAction({
  243. exploreId,
  244. delta: null,
  245. series,
  246. latency: 0,
  247. datasourceId,
  248. loadingState: LoadingState.Done,
  249. }),
  250. stateSaveAction()
  251. // output from second observable
  252. );
  253. expect(unsubscribe).toBeCalledTimes(1); // first unsubscribe should be called but not second as that isn't unsubscribed
  254. });
  255. });
  256. describe('and resetExploreAction is dispatched', () => {
  257. it('then the observable should be unsubscribed', () => {
  258. const { response, series } = testContext();
  259. const { exploreId, state, history, datasourceId } = mockExploreState();
  260. const unsubscribe = jest.fn();
  261. epicTester(runQueriesBatchEpic, state)
  262. .whenActionIsDispatched(
  263. runQueriesBatchAction({ exploreId, queryOptions: { live: false, interval: '', maxDataPoints: 1980 } })
  264. )
  265. .whenQueryReceivesResponse(response)
  266. .whenQueryObserverReceivesEvent({
  267. key: 'some key',
  268. request: {} as DataQueryRequest,
  269. state: LoadingState.Loading, // fake just to setup and test unsubscribe
  270. unsubscribe,
  271. })
  272. .whenActionIsDispatched(resetExploreAction()) // unsubscribes the observable
  273. .whenQueryReceivesResponse(response) // new updates will not reach anywhere
  274. .thenResultingActionsEqual(
  275. queryStartAction({ exploreId }),
  276. historyUpdatedAction({ exploreId, history }),
  277. processQueryResultsAction({
  278. exploreId,
  279. delta: null,
  280. series,
  281. latency: 0,
  282. datasourceId,
  283. loadingState: LoadingState.Done,
  284. }),
  285. stateSaveAction()
  286. );
  287. expect(unsubscribe).toBeCalledTimes(1);
  288. });
  289. });
  290. describe('and updateDatasourceInstanceAction is dispatched', () => {
  291. it('then the observable should be unsubscribed', () => {
  292. const { response, series } = testContext();
  293. const { exploreId, state, history, datasourceId, datasourceInstance } = mockExploreState();
  294. const unsubscribe = jest.fn();
  295. epicTester(runQueriesBatchEpic, state)
  296. .whenActionIsDispatched(
  297. runQueriesBatchAction({ exploreId, queryOptions: { live: false, interval: '', maxDataPoints: 1980 } })
  298. )
  299. .whenQueryReceivesResponse(response)
  300. .whenQueryObserverReceivesEvent({
  301. key: 'some key',
  302. request: {} as DataQueryRequest,
  303. state: LoadingState.Loading, // fake just to setup and test unsubscribe
  304. unsubscribe,
  305. })
  306. .whenActionIsDispatched(updateDatasourceInstanceAction({ exploreId, datasourceInstance })) // unsubscribes the observable
  307. .whenQueryReceivesResponse(response) // new updates will not reach anywhere
  308. .thenResultingActionsEqual(
  309. queryStartAction({ exploreId }),
  310. historyUpdatedAction({ exploreId, history }),
  311. processQueryResultsAction({
  312. exploreId,
  313. delta: null,
  314. series,
  315. latency: 0,
  316. datasourceId,
  317. loadingState: LoadingState.Done,
  318. }),
  319. stateSaveAction()
  320. );
  321. expect(unsubscribe).toBeCalledTimes(1);
  322. });
  323. });
  324. describe('and changeRefreshIntervalAction is dispatched', () => {
  325. it('then the observable should be unsubscribed', () => {
  326. const { response, series } = testContext();
  327. const { exploreId, state, history, datasourceId } = mockExploreState();
  328. const unsubscribe = jest.fn();
  329. epicTester(runQueriesBatchEpic, state)
  330. .whenActionIsDispatched(
  331. runQueriesBatchAction({ exploreId, queryOptions: { live: false, interval: '', maxDataPoints: 1980 } })
  332. )
  333. .whenQueryReceivesResponse(response)
  334. .whenQueryObserverReceivesEvent({
  335. key: 'some key',
  336. request: {} as DataQueryRequest,
  337. state: LoadingState.Loading, // fake just to setup and test unsubscribe
  338. unsubscribe,
  339. })
  340. .whenActionIsDispatched(changeRefreshIntervalAction({ exploreId, refreshInterval: '' })) // unsubscribes the observable
  341. .whenQueryReceivesResponse(response) // new updates will not reach anywhere
  342. .thenResultingActionsEqual(
  343. queryStartAction({ exploreId }),
  344. historyUpdatedAction({ exploreId, history }),
  345. processQueryResultsAction({
  346. exploreId,
  347. delta: null,
  348. series,
  349. latency: 0,
  350. datasourceId,
  351. loadingState: LoadingState.Done,
  352. }),
  353. stateSaveAction()
  354. );
  355. expect(unsubscribe).toBeCalledTimes(1);
  356. });
  357. });
  358. describe('and clearQueriesAction is dispatched', () => {
  359. it('then the observable should be unsubscribed', () => {
  360. const { response, series } = testContext();
  361. const { exploreId, state, history, datasourceId } = mockExploreState();
  362. const unsubscribe = jest.fn();
  363. epicTester(runQueriesBatchEpic, state)
  364. .whenActionIsDispatched(
  365. runQueriesBatchAction({ exploreId, queryOptions: { live: false, interval: '', maxDataPoints: 1980 } })
  366. )
  367. .whenQueryReceivesResponse(response)
  368. .whenQueryObserverReceivesEvent({
  369. key: 'some key',
  370. request: {} as DataQueryRequest,
  371. state: LoadingState.Loading, // fake just to setup and test unsubscribe
  372. unsubscribe,
  373. })
  374. .whenActionIsDispatched(clearQueriesAction({ exploreId })) // unsubscribes the observable
  375. .whenQueryReceivesResponse(response) // new updates will not reach anywhere
  376. .thenResultingActionsEqual(
  377. queryStartAction({ exploreId }),
  378. historyUpdatedAction({ exploreId, history }),
  379. processQueryResultsAction({
  380. exploreId,
  381. delta: null,
  382. series,
  383. latency: 0,
  384. datasourceId,
  385. loadingState: LoadingState.Done,
  386. }),
  387. stateSaveAction()
  388. );
  389. expect(unsubscribe).toBeCalledTimes(1);
  390. });
  391. });
  392. });
  393. });