runQueriesBatchEpic.test.ts 15 KB

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