runQueriesBatchEpic.test.ts 15 KB

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