epics.test.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. import { liveOption } from '@grafana/ui/src/components/RefreshPicker/RefreshPicker';
  2. import { DataSourceApi, DataQuery } from '@grafana/ui/src/types/datasource';
  3. import { ExploreId, ExploreState } from 'app/types';
  4. import { actionCreatorFactory } from 'app/core/redux/actionCreatorFactory';
  5. import {
  6. startSubscriptionsEpic,
  7. startSubscriptionsAction,
  8. SubscriptionDataReceivedPayload,
  9. startSubscriptionAction,
  10. startSubscriptionEpic,
  11. limitMessageRatePayloadAction,
  12. } from './epics';
  13. import { makeExploreItemState } from './reducers';
  14. import { epicTester } from 'test/core/redux/epicTester';
  15. import {
  16. resetExploreAction,
  17. updateDatasourceInstanceAction,
  18. changeRefreshIntervalAction,
  19. clearQueriesAction,
  20. } from './actionTypes';
  21. const setup = (options: any = {}) => {
  22. const url = '/api/datasources/proxy/20/api/prom/tail?query=%7Bfilename%3D%22%2Fvar%2Flog%2Fdocker.log%22%7D';
  23. const webSocketUrl = 'ws://localhost' + url;
  24. const refId = options.refId || 'A';
  25. const exploreId = ExploreId.left;
  26. const datasourceInstance: DataSourceApi = options.datasourceInstance || {
  27. id: 1337,
  28. query: jest.fn(),
  29. name: 'test',
  30. testDatasource: jest.fn(),
  31. convertToStreamTargets: () => [
  32. {
  33. url,
  34. refId,
  35. },
  36. ],
  37. resultToSeriesData: data => [data],
  38. };
  39. const itemState = makeExploreItemState();
  40. const explore: Partial<ExploreState> = {
  41. [exploreId]: {
  42. ...itemState,
  43. datasourceInstance,
  44. refreshInterval: options.refreshInterval || liveOption.value,
  45. queries: [{} as DataQuery],
  46. },
  47. };
  48. const state: any = {
  49. explore,
  50. };
  51. return { url, state, refId, webSocketUrl, exploreId };
  52. };
  53. const dataReceivedActionCreator = actionCreatorFactory<SubscriptionDataReceivedPayload>('test').create();
  54. describe('startSubscriptionsEpic', () => {
  55. describe('when startSubscriptionsAction is dispatched', () => {
  56. describe('and datasource supports convertToStreamTargets', () => {
  57. describe('and explore is Live', () => {
  58. it('then correct actions should be dispatched', () => {
  59. const { state, refId, webSocketUrl, exploreId } = setup();
  60. epicTester(startSubscriptionsEpic, state)
  61. .whenActionIsDispatched(startSubscriptionsAction({ exploreId, dataReceivedActionCreator }))
  62. .thenResultingActionsEqual(
  63. startSubscriptionAction({
  64. exploreId,
  65. refId,
  66. url: webSocketUrl,
  67. dataReceivedActionCreator,
  68. })
  69. );
  70. });
  71. });
  72. describe('and explore is not Live', () => {
  73. it('then no actions should be dispatched', () => {
  74. const { state, exploreId } = setup({ refreshInterval: '10s' });
  75. epicTester(startSubscriptionsEpic, state)
  76. .whenActionIsDispatched(startSubscriptionsAction({ exploreId, dataReceivedActionCreator }))
  77. .thenNoActionsWhereDispatched();
  78. });
  79. });
  80. });
  81. describe('and datasource does not support streaming', () => {
  82. it('then no actions should be dispatched', () => {
  83. const { state, exploreId } = setup({ datasourceInstance: {} });
  84. epicTester(startSubscriptionsEpic, state)
  85. .whenActionIsDispatched(startSubscriptionsAction({ exploreId, dataReceivedActionCreator }))
  86. .thenNoActionsWhereDispatched();
  87. });
  88. });
  89. });
  90. });
  91. describe('startSubscriptionEpic', () => {
  92. describe('when startSubscriptionAction is dispatched', () => {
  93. describe('and datasource supports resultToSeriesData', () => {
  94. it('then correct actions should be dispatched', () => {
  95. const { state, webSocketUrl, refId, exploreId } = setup();
  96. epicTester(startSubscriptionEpic, state)
  97. .whenActionIsDispatched(
  98. startSubscriptionAction({ url: webSocketUrl, refId, exploreId, dataReceivedActionCreator })
  99. )
  100. .thenNoActionsWhereDispatched()
  101. .whenWebSocketReceivesData({ data: [1, 2, 3] })
  102. .thenResultingActionsEqual(
  103. limitMessageRatePayloadAction({
  104. exploreId,
  105. data: { data: [1, 2, 3] } as any,
  106. dataReceivedActionCreator,
  107. })
  108. )
  109. .whenWebSocketReceivesData({ data: [4, 5, 6] })
  110. .thenResultingActionsEqual(
  111. limitMessageRatePayloadAction({
  112. exploreId,
  113. data: { data: [1, 2, 3] } as any,
  114. dataReceivedActionCreator,
  115. }),
  116. limitMessageRatePayloadAction({
  117. exploreId,
  118. data: { data: [4, 5, 6] } as any,
  119. dataReceivedActionCreator,
  120. })
  121. );
  122. });
  123. });
  124. describe('and datasource does not support resultToSeriesData', () => {
  125. it('then no actions should be dispatched', () => {
  126. const { state, webSocketUrl, refId, exploreId } = setup({ datasourceInstance: {} });
  127. epicTester(startSubscriptionEpic, state)
  128. .whenActionIsDispatched(
  129. startSubscriptionAction({ url: webSocketUrl, refId, exploreId, dataReceivedActionCreator })
  130. )
  131. .thenNoActionsWhereDispatched()
  132. .whenWebSocketReceivesData({ data: [1, 2, 3] })
  133. .thenNoActionsWhereDispatched();
  134. });
  135. });
  136. });
  137. describe('when an subscription is active', () => {
  138. describe('and resetExploreAction is dispatched', () => {
  139. it('then subscription should be unsubscribed', () => {
  140. const { state, webSocketUrl, refId, exploreId } = setup();
  141. epicTester(startSubscriptionEpic, state)
  142. .whenActionIsDispatched(
  143. startSubscriptionAction({ url: webSocketUrl, refId, exploreId, dataReceivedActionCreator })
  144. )
  145. .thenNoActionsWhereDispatched()
  146. .whenWebSocketReceivesData({ data: [1, 2, 3] })
  147. .thenResultingActionsEqual(
  148. limitMessageRatePayloadAction({
  149. exploreId,
  150. data: { data: [1, 2, 3] } as any,
  151. dataReceivedActionCreator,
  152. })
  153. )
  154. .whenActionIsDispatched(resetExploreAction())
  155. .whenWebSocketReceivesData({ data: [4, 5, 6] })
  156. .thenResultingActionsEqual(
  157. limitMessageRatePayloadAction({
  158. exploreId,
  159. data: { data: [1, 2, 3] } as any,
  160. dataReceivedActionCreator,
  161. })
  162. );
  163. });
  164. });
  165. describe('and updateDatasourceInstanceAction is dispatched', () => {
  166. describe('and exploreId matches the websockets', () => {
  167. it('then subscription should be unsubscribed', () => {
  168. const { state, webSocketUrl, refId, exploreId } = setup();
  169. epicTester(startSubscriptionEpic, state)
  170. .whenActionIsDispatched(
  171. startSubscriptionAction({
  172. url: webSocketUrl,
  173. refId,
  174. exploreId,
  175. dataReceivedActionCreator,
  176. })
  177. )
  178. .thenNoActionsWhereDispatched()
  179. .whenWebSocketReceivesData({ data: [1, 2, 3] })
  180. .thenResultingActionsEqual(
  181. limitMessageRatePayloadAction({
  182. exploreId,
  183. data: { data: [1, 2, 3] } as any,
  184. dataReceivedActionCreator,
  185. })
  186. )
  187. .whenActionIsDispatched(updateDatasourceInstanceAction({ exploreId, datasourceInstance: null }))
  188. .whenWebSocketReceivesData({ data: [4, 5, 6] })
  189. .thenResultingActionsEqual(
  190. limitMessageRatePayloadAction({
  191. exploreId,
  192. data: { data: [1, 2, 3] } as any,
  193. dataReceivedActionCreator,
  194. })
  195. );
  196. });
  197. });
  198. describe('and exploreId does not match the websockets', () => {
  199. it('then subscription should not be unsubscribed', () => {
  200. const { state, webSocketUrl, refId, exploreId } = setup();
  201. epicTester(startSubscriptionEpic, state)
  202. .whenActionIsDispatched(
  203. startSubscriptionAction({
  204. url: webSocketUrl,
  205. refId,
  206. exploreId,
  207. dataReceivedActionCreator,
  208. })
  209. )
  210. .thenNoActionsWhereDispatched()
  211. .whenWebSocketReceivesData({ data: [1, 2, 3] })
  212. .thenResultingActionsEqual(
  213. limitMessageRatePayloadAction({
  214. exploreId,
  215. data: { data: [1, 2, 3] } as any,
  216. dataReceivedActionCreator,
  217. })
  218. )
  219. .whenActionIsDispatched(
  220. updateDatasourceInstanceAction({ exploreId: ExploreId.right, datasourceInstance: null })
  221. )
  222. .whenWebSocketReceivesData({ data: [4, 5, 6] })
  223. .thenResultingActionsEqual(
  224. limitMessageRatePayloadAction({
  225. exploreId,
  226. data: { data: [1, 2, 3] } as any,
  227. dataReceivedActionCreator,
  228. }),
  229. limitMessageRatePayloadAction({
  230. exploreId,
  231. data: { data: [4, 5, 6] } as any,
  232. dataReceivedActionCreator,
  233. })
  234. );
  235. });
  236. });
  237. });
  238. describe('and changeRefreshIntervalAction is dispatched', () => {
  239. describe('and exploreId matches the websockets', () => {
  240. describe('and refreshinterval is not "Live"', () => {
  241. it('then subscription should be unsubscribed', () => {
  242. const { state, webSocketUrl, refId, exploreId } = setup();
  243. epicTester(startSubscriptionEpic, state)
  244. .whenActionIsDispatched(
  245. startSubscriptionAction({
  246. url: webSocketUrl,
  247. refId,
  248. exploreId,
  249. dataReceivedActionCreator,
  250. })
  251. )
  252. .thenNoActionsWhereDispatched()
  253. .whenWebSocketReceivesData({ data: [1, 2, 3] })
  254. .thenResultingActionsEqual(
  255. limitMessageRatePayloadAction({
  256. exploreId,
  257. data: { data: [1, 2, 3] } as any,
  258. dataReceivedActionCreator,
  259. })
  260. )
  261. .whenActionIsDispatched(changeRefreshIntervalAction({ exploreId, refreshInterval: '10s' }))
  262. .whenWebSocketReceivesData({ data: [4, 5, 6] })
  263. .thenResultingActionsEqual(
  264. limitMessageRatePayloadAction({
  265. exploreId,
  266. data: { data: [1, 2, 3] } as any,
  267. dataReceivedActionCreator,
  268. })
  269. );
  270. });
  271. });
  272. describe('and refreshinterval is "Live"', () => {
  273. it('then subscription should not be unsubscribed', () => {
  274. const { state, webSocketUrl, refId, exploreId } = setup();
  275. epicTester(startSubscriptionEpic, state)
  276. .whenActionIsDispatched(
  277. startSubscriptionAction({
  278. url: webSocketUrl,
  279. refId,
  280. exploreId,
  281. dataReceivedActionCreator,
  282. })
  283. )
  284. .thenNoActionsWhereDispatched()
  285. .whenWebSocketReceivesData({ data: [1, 2, 3] })
  286. .thenResultingActionsEqual(
  287. limitMessageRatePayloadAction({
  288. exploreId,
  289. data: { data: [1, 2, 3] } as any,
  290. dataReceivedActionCreator,
  291. })
  292. )
  293. .whenActionIsDispatched(changeRefreshIntervalAction({ exploreId, refreshInterval: liveOption.value }))
  294. .whenWebSocketReceivesData({ data: [4, 5, 6] })
  295. .thenResultingActionsEqual(
  296. limitMessageRatePayloadAction({
  297. exploreId,
  298. data: { data: [1, 2, 3] } as any,
  299. dataReceivedActionCreator,
  300. }),
  301. limitMessageRatePayloadAction({
  302. exploreId,
  303. data: { data: [4, 5, 6] } as any,
  304. dataReceivedActionCreator,
  305. })
  306. );
  307. });
  308. });
  309. });
  310. describe('and exploreId does not match the websockets', () => {
  311. it('then subscription should not be unsubscribed', () => {
  312. const { state, webSocketUrl, refId, exploreId } = setup();
  313. epicTester(startSubscriptionEpic, state)
  314. .whenActionIsDispatched(
  315. startSubscriptionAction({
  316. url: webSocketUrl,
  317. refId,
  318. exploreId,
  319. dataReceivedActionCreator,
  320. })
  321. )
  322. .thenNoActionsWhereDispatched()
  323. .whenWebSocketReceivesData({ data: [1, 2, 3] })
  324. .thenResultingActionsEqual(
  325. limitMessageRatePayloadAction({
  326. exploreId,
  327. data: { data: [1, 2, 3] } as any,
  328. dataReceivedActionCreator,
  329. })
  330. )
  331. .whenActionIsDispatched(changeRefreshIntervalAction({ exploreId: ExploreId.right, refreshInterval: '10s' }))
  332. .whenWebSocketReceivesData({ data: [4, 5, 6] })
  333. .thenResultingActionsEqual(
  334. limitMessageRatePayloadAction({
  335. exploreId,
  336. data: { data: [1, 2, 3] } as any,
  337. dataReceivedActionCreator,
  338. }),
  339. limitMessageRatePayloadAction({
  340. exploreId,
  341. data: { data: [4, 5, 6] } as any,
  342. dataReceivedActionCreator,
  343. })
  344. );
  345. });
  346. });
  347. });
  348. describe('and clearQueriesAction is dispatched', () => {
  349. describe('and exploreId matches the websockets', () => {
  350. it('then subscription should be unsubscribed', () => {
  351. const { state, webSocketUrl, refId, exploreId } = setup();
  352. epicTester(startSubscriptionEpic, state)
  353. .whenActionIsDispatched(
  354. startSubscriptionAction({
  355. url: webSocketUrl,
  356. refId,
  357. exploreId,
  358. dataReceivedActionCreator,
  359. })
  360. )
  361. .thenNoActionsWhereDispatched()
  362. .whenWebSocketReceivesData({ data: [1, 2, 3] })
  363. .thenResultingActionsEqual(
  364. limitMessageRatePayloadAction({
  365. exploreId,
  366. data: { data: [1, 2, 3] } as any,
  367. dataReceivedActionCreator,
  368. })
  369. )
  370. .whenActionIsDispatched(clearQueriesAction({ exploreId }))
  371. .whenWebSocketReceivesData({ data: [4, 5, 6] })
  372. .thenResultingActionsEqual(
  373. limitMessageRatePayloadAction({
  374. exploreId,
  375. data: { data: [1, 2, 3] } as any,
  376. dataReceivedActionCreator,
  377. })
  378. );
  379. });
  380. });
  381. describe('and exploreId does not match the websockets', () => {
  382. it('then subscription should not be unsubscribed', () => {
  383. const { state, webSocketUrl, refId, exploreId } = setup();
  384. epicTester(startSubscriptionEpic, state)
  385. .whenActionIsDispatched(
  386. startSubscriptionAction({
  387. url: webSocketUrl,
  388. refId,
  389. exploreId,
  390. dataReceivedActionCreator,
  391. })
  392. )
  393. .thenNoActionsWhereDispatched()
  394. .whenWebSocketReceivesData({ data: [1, 2, 3] })
  395. .thenResultingActionsEqual(
  396. limitMessageRatePayloadAction({
  397. exploreId,
  398. data: { data: [1, 2, 3] } as any,
  399. dataReceivedActionCreator,
  400. })
  401. )
  402. .whenActionIsDispatched(clearQueriesAction({ exploreId: ExploreId.right }))
  403. .whenWebSocketReceivesData({ data: [4, 5, 6] })
  404. .thenResultingActionsEqual(
  405. limitMessageRatePayloadAction({
  406. exploreId,
  407. data: { data: [1, 2, 3] } as any,
  408. dataReceivedActionCreator,
  409. }),
  410. limitMessageRatePayloadAction({
  411. exploreId,
  412. data: { data: [4, 5, 6] } as any,
  413. dataReceivedActionCreator,
  414. })
  415. );
  416. });
  417. });
  418. });
  419. describe('and startSubscriptionAction is dispatched', () => {
  420. describe('and exploreId and refId matches the websockets', () => {
  421. it('then subscription should be unsubscribed', () => {
  422. const { state, webSocketUrl, refId, exploreId } = setup();
  423. epicTester(startSubscriptionEpic, state)
  424. .whenActionIsDispatched(
  425. startSubscriptionAction({
  426. url: webSocketUrl,
  427. refId,
  428. exploreId,
  429. dataReceivedActionCreator,
  430. })
  431. )
  432. .thenNoActionsWhereDispatched()
  433. .whenWebSocketReceivesData({ data: [1, 2, 3] })
  434. .thenResultingActionsEqual(
  435. limitMessageRatePayloadAction({
  436. exploreId,
  437. data: { data: [1, 2, 3] } as any,
  438. dataReceivedActionCreator,
  439. })
  440. )
  441. .whenActionIsDispatched(
  442. startSubscriptionAction({
  443. url: webSocketUrl,
  444. refId,
  445. exploreId,
  446. dataReceivedActionCreator,
  447. })
  448. )
  449. .whenWebSocketReceivesData({ data: [4, 5, 6] })
  450. .thenResultingActionsEqual(
  451. limitMessageRatePayloadAction({
  452. exploreId,
  453. data: { data: [1, 2, 3] } as any,
  454. dataReceivedActionCreator,
  455. }),
  456. limitMessageRatePayloadAction({
  457. exploreId,
  458. data: { data: [4, 5, 6] } as any,
  459. dataReceivedActionCreator,
  460. })
  461. // This looks like we haven't stopped the subscription but we actually started the same again
  462. );
  463. });
  464. describe('and exploreId or refId does not match the websockets', () => {
  465. it('then subscription should not be unsubscribed and another websocket is started', () => {
  466. const { state, webSocketUrl, refId, exploreId } = setup();
  467. epicTester(startSubscriptionEpic, state)
  468. .whenActionIsDispatched(
  469. startSubscriptionAction({
  470. url: webSocketUrl,
  471. refId,
  472. exploreId,
  473. dataReceivedActionCreator,
  474. })
  475. )
  476. .thenNoActionsWhereDispatched()
  477. .whenWebSocketReceivesData({ data: [1, 2, 3] })
  478. .thenResultingActionsEqual(
  479. limitMessageRatePayloadAction({
  480. exploreId,
  481. data: { data: [1, 2, 3] } as any,
  482. dataReceivedActionCreator,
  483. })
  484. )
  485. .whenActionIsDispatched(
  486. startSubscriptionAction({
  487. url: webSocketUrl,
  488. refId: 'B',
  489. exploreId,
  490. dataReceivedActionCreator,
  491. })
  492. )
  493. .whenWebSocketReceivesData({ data: [4, 5, 6] })
  494. .thenResultingActionsEqual(
  495. limitMessageRatePayloadAction({
  496. exploreId,
  497. data: { data: [1, 2, 3] } as any,
  498. dataReceivedActionCreator,
  499. }),
  500. limitMessageRatePayloadAction({
  501. exploreId,
  502. data: { data: [4, 5, 6] } as any,
  503. dataReceivedActionCreator,
  504. }),
  505. limitMessageRatePayloadAction({
  506. exploreId,
  507. data: { data: [4, 5, 6] } as any,
  508. dataReceivedActionCreator,
  509. })
  510. );
  511. });
  512. });
  513. });
  514. });
  515. });
  516. });