reducers.test.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. import {
  2. itemReducer,
  3. makeExploreItemState,
  4. exploreReducer,
  5. makeInitialUpdateState,
  6. initialExploreState,
  7. } from './reducers';
  8. import {
  9. ExploreId,
  10. ExploreItemState,
  11. ExploreUrlState,
  12. ExploreState,
  13. RangeScanner,
  14. ExploreMode,
  15. } from 'app/types/explore';
  16. import { reducerTester } from 'test/core/redux/reducerTester';
  17. import {
  18. scanStartAction,
  19. scanStopAction,
  20. testDataSourcePendingAction,
  21. testDataSourceSuccessAction,
  22. testDataSourceFailureAction,
  23. updateDatasourceInstanceAction,
  24. splitOpenAction,
  25. splitCloseAction,
  26. changeModeAction,
  27. runQueriesAction,
  28. } from './actionTypes';
  29. import { Reducer } from 'redux';
  30. import { ActionOf } from 'app/core/redux/actionCreatorFactory';
  31. import { updateLocation } from 'app/core/actions/location';
  32. import { LogsDedupStrategy, LogsModel } from 'app/core/logs_model';
  33. import { serializeStateToUrlParam } from 'app/core/utils/explore';
  34. import TableModel from 'app/core/table_model';
  35. import { DataSourceApi, DataQuery } from '@grafana/ui';
  36. describe('Explore item reducer', () => {
  37. describe('scanning', () => {
  38. it('should start scanning', () => {
  39. const scanner = jest.fn();
  40. const initalState = {
  41. ...makeExploreItemState(),
  42. scanning: false,
  43. scanner: undefined as RangeScanner,
  44. };
  45. reducerTester()
  46. .givenReducer(itemReducer as Reducer<ExploreItemState, ActionOf<any>>, initalState)
  47. .whenActionIsDispatched(scanStartAction({ exploreId: ExploreId.left, scanner }))
  48. .thenStateShouldEqual({
  49. ...makeExploreItemState(),
  50. scanning: true,
  51. scanner,
  52. });
  53. });
  54. it('should stop scanning', () => {
  55. const scanner = jest.fn();
  56. const initalState = {
  57. ...makeExploreItemState(),
  58. scanning: true,
  59. scanner,
  60. scanRange: {},
  61. };
  62. reducerTester()
  63. .givenReducer(itemReducer as Reducer<ExploreItemState, ActionOf<any>>, initalState)
  64. .whenActionIsDispatched(scanStopAction({ exploreId: ExploreId.left }))
  65. .thenStateShouldEqual({
  66. ...makeExploreItemState(),
  67. scanning: false,
  68. scanner: undefined,
  69. scanRange: undefined,
  70. });
  71. });
  72. });
  73. describe('testing datasource', () => {
  74. describe('when testDataSourcePendingAction is dispatched', () => {
  75. it('then it should set datasourceError', () => {
  76. reducerTester()
  77. .givenReducer(itemReducer, { datasourceError: {} })
  78. .whenActionIsDispatched(testDataSourcePendingAction({ exploreId: ExploreId.left }))
  79. .thenStateShouldEqual({ datasourceError: null });
  80. });
  81. });
  82. describe('when testDataSourceSuccessAction is dispatched', () => {
  83. it('then it should set datasourceError', () => {
  84. reducerTester()
  85. .givenReducer(itemReducer, { datasourceError: {} })
  86. .whenActionIsDispatched(testDataSourceSuccessAction({ exploreId: ExploreId.left }))
  87. .thenStateShouldEqual({ datasourceError: null });
  88. });
  89. });
  90. describe('when testDataSourceFailureAction is dispatched', () => {
  91. it('then it should set correct state', () => {
  92. const error = 'some error';
  93. const initalState: Partial<ExploreItemState> = {
  94. datasourceError: null,
  95. graphResult: [],
  96. tableResult: {} as TableModel,
  97. logsResult: {} as LogsModel,
  98. update: {
  99. datasource: true,
  100. queries: true,
  101. range: true,
  102. ui: true,
  103. },
  104. };
  105. const expectedState = {
  106. datasourceError: error,
  107. graphResult: undefined as any[],
  108. tableResult: undefined as TableModel,
  109. logsResult: undefined as LogsModel,
  110. update: makeInitialUpdateState(),
  111. };
  112. reducerTester()
  113. .givenReducer(itemReducer, initalState)
  114. .whenActionIsDispatched(testDataSourceFailureAction({ exploreId: ExploreId.left, error }))
  115. .thenStateShouldEqual(expectedState);
  116. });
  117. });
  118. describe('when changeDataType is dispatched', () => {
  119. it('then it should set correct state', () => {
  120. reducerTester()
  121. .givenReducer(itemReducer, {})
  122. .whenActionIsDispatched(changeModeAction({ exploreId: ExploreId.left, mode: ExploreMode.Logs }))
  123. .thenStateShouldEqual({
  124. mode: ExploreMode.Logs,
  125. });
  126. });
  127. });
  128. });
  129. describe('changing datasource', () => {
  130. describe('when updateDatasourceInstanceAction is dispatched', () => {
  131. describe('and datasourceInstance supports graph, logs, table and has a startpage', () => {
  132. it('then it should set correct state', () => {
  133. const StartPage = {};
  134. const datasourceInstance = {
  135. meta: {
  136. metrics: true,
  137. logs: true,
  138. tables: true,
  139. },
  140. components: {
  141. ExploreStartPage: StartPage,
  142. },
  143. } as DataSourceApi;
  144. const queries: DataQuery[] = [];
  145. const queryKeys: string[] = [];
  146. const initalState: Partial<ExploreItemState> = {
  147. datasourceInstance: null,
  148. supportsGraph: false,
  149. supportsLogs: false,
  150. supportsTable: false,
  151. StartPage: null,
  152. showingStartPage: false,
  153. queries,
  154. queryKeys,
  155. };
  156. const expectedState = {
  157. datasourceInstance,
  158. supportsGraph: true,
  159. supportsLogs: true,
  160. supportsTable: true,
  161. StartPage,
  162. showingStartPage: true,
  163. queries,
  164. queryKeys,
  165. supportedModes: [ExploreMode.Metrics, ExploreMode.Logs],
  166. mode: ExploreMode.Metrics,
  167. graphIsLoading: false,
  168. tableIsLoading: false,
  169. logIsLoading: false,
  170. latency: 0,
  171. queryErrors: [],
  172. };
  173. reducerTester()
  174. .givenReducer(itemReducer, initalState)
  175. .whenActionIsDispatched(updateDatasourceInstanceAction({ exploreId: ExploreId.left, datasourceInstance }))
  176. .thenStateShouldEqual(expectedState);
  177. });
  178. });
  179. });
  180. });
  181. describe('run queries', () => {
  182. describe('when runQueriesAction is dispatched', () => {
  183. it('then it should set correct state', () => {
  184. const initalState: Partial<ExploreItemState> = {
  185. showingStartPage: true,
  186. };
  187. const expectedState = {
  188. queryIntervals: {
  189. interval: '1s',
  190. intervalMs: 1000,
  191. },
  192. showingStartPage: false,
  193. };
  194. reducerTester()
  195. .givenReducer(itemReducer, initalState)
  196. .whenActionIsDispatched(runQueriesAction({ exploreId: ExploreId.left }))
  197. .thenStateShouldEqual(expectedState);
  198. });
  199. });
  200. });
  201. });
  202. export const setup = (urlStateOverrides?: any) => {
  203. const update = makeInitialUpdateState();
  204. const urlStateDefaults: ExploreUrlState = {
  205. datasource: 'some-datasource',
  206. queries: [],
  207. range: {
  208. from: '',
  209. to: '',
  210. },
  211. ui: {
  212. dedupStrategy: LogsDedupStrategy.none,
  213. showingGraph: false,
  214. showingTable: false,
  215. showingLogs: false,
  216. },
  217. };
  218. const urlState: ExploreUrlState = { ...urlStateDefaults, ...urlStateOverrides };
  219. const serializedUrlState = serializeStateToUrlParam(urlState);
  220. const initalState = { split: false, left: { urlState, update }, right: { urlState, update } };
  221. return {
  222. initalState,
  223. serializedUrlState,
  224. };
  225. };
  226. describe('Explore reducer', () => {
  227. describe('split view', () => {
  228. it("should make right pane a duplicate of the given item's state on split open", () => {
  229. const leftItemMock = {
  230. containerWidth: 100,
  231. } as ExploreItemState;
  232. const initalState = {
  233. split: null,
  234. left: leftItemMock as ExploreItemState,
  235. right: makeExploreItemState(),
  236. } as ExploreState;
  237. reducerTester()
  238. .givenReducer(exploreReducer as Reducer<ExploreState, ActionOf<any>>, initalState)
  239. .whenActionIsDispatched(splitOpenAction({ itemState: leftItemMock }))
  240. .thenStateShouldEqual({
  241. split: true,
  242. left: leftItemMock,
  243. right: leftItemMock,
  244. });
  245. });
  246. describe('split close', () => {
  247. it('should keep right pane as left when left is closed', () => {
  248. const leftItemMock = {
  249. containerWidth: 100,
  250. } as ExploreItemState;
  251. const rightItemMock = {
  252. containerWidth: 200,
  253. } as ExploreItemState;
  254. const initalState = {
  255. split: null,
  256. left: leftItemMock,
  257. right: rightItemMock,
  258. } as ExploreState;
  259. // closing left item
  260. reducerTester()
  261. .givenReducer(exploreReducer as Reducer<ExploreState, ActionOf<any>>, initalState)
  262. .whenActionIsDispatched(splitCloseAction({ itemId: ExploreId.left }))
  263. .thenStateShouldEqual({
  264. split: false,
  265. left: rightItemMock,
  266. right: initialExploreState.right,
  267. });
  268. });
  269. it('should reset right pane when it is closed ', () => {
  270. const leftItemMock = {
  271. containerWidth: 100,
  272. } as ExploreItemState;
  273. const rightItemMock = {
  274. containerWidth: 200,
  275. } as ExploreItemState;
  276. const initalState = {
  277. split: null,
  278. left: leftItemMock,
  279. right: rightItemMock,
  280. } as ExploreState;
  281. // closing left item
  282. reducerTester()
  283. .givenReducer(exploreReducer as Reducer<ExploreState, ActionOf<any>>, initalState)
  284. .whenActionIsDispatched(splitCloseAction({ itemId: ExploreId.right }))
  285. .thenStateShouldEqual({
  286. split: false,
  287. left: leftItemMock,
  288. right: initialExploreState.right,
  289. });
  290. });
  291. });
  292. });
  293. describe('when updateLocation is dispatched', () => {
  294. describe('and payload does not contain a query', () => {
  295. it('then it should just return state', () => {
  296. reducerTester()
  297. .givenReducer(exploreReducer, {})
  298. .whenActionIsDispatched(updateLocation({ query: null }))
  299. .thenStateShouldEqual({});
  300. });
  301. });
  302. describe('and payload contains a query', () => {
  303. describe("but does not contain 'left'", () => {
  304. it('then it should just return state', () => {
  305. reducerTester()
  306. .givenReducer(exploreReducer, {})
  307. .whenActionIsDispatched(updateLocation({ query: {} }))
  308. .thenStateShouldEqual({});
  309. });
  310. });
  311. describe("and query contains a 'right'", () => {
  312. it('then it should add split in state', () => {
  313. const { initalState, serializedUrlState } = setup();
  314. const expectedState = { ...initalState, split: true };
  315. reducerTester()
  316. .givenReducer(exploreReducer, initalState)
  317. .whenActionIsDispatched(
  318. updateLocation({
  319. query: {
  320. left: serializedUrlState,
  321. right: serializedUrlState,
  322. },
  323. })
  324. )
  325. .thenStateShouldEqual(expectedState);
  326. });
  327. });
  328. describe("and query contains a 'left'", () => {
  329. describe('but urlState is not set in state', () => {
  330. it('then it should just add urlState and update in state', () => {
  331. const { initalState, serializedUrlState } = setup();
  332. const urlState: ExploreUrlState = null;
  333. const stateWithoutUrlState = { ...initalState, left: { urlState } };
  334. const expectedState = { ...initalState };
  335. reducerTester()
  336. .givenReducer(exploreReducer, stateWithoutUrlState)
  337. .whenActionIsDispatched(
  338. updateLocation({
  339. query: {
  340. left: serializedUrlState,
  341. },
  342. path: '/explore',
  343. })
  344. )
  345. .thenStateShouldEqual(expectedState);
  346. });
  347. });
  348. describe("but '/explore' is missing in path", () => {
  349. it('then it should just add urlState and update in state', () => {
  350. const { initalState, serializedUrlState } = setup();
  351. const expectedState = { ...initalState };
  352. reducerTester()
  353. .givenReducer(exploreReducer, initalState)
  354. .whenActionIsDispatched(
  355. updateLocation({
  356. query: {
  357. left: serializedUrlState,
  358. },
  359. path: '/dashboard',
  360. })
  361. )
  362. .thenStateShouldEqual(expectedState);
  363. });
  364. });
  365. describe("and '/explore' is in path", () => {
  366. describe('and datasource differs', () => {
  367. it('then it should return update datasource', () => {
  368. const { initalState, serializedUrlState } = setup();
  369. const expectedState = {
  370. ...initalState,
  371. left: {
  372. ...initalState.left,
  373. update: {
  374. ...initalState.left.update,
  375. datasource: true,
  376. },
  377. },
  378. };
  379. const stateWithDifferentDataSource = {
  380. ...initalState,
  381. left: {
  382. ...initalState.left,
  383. urlState: {
  384. ...initalState.left.urlState,
  385. datasource: 'different datasource',
  386. },
  387. },
  388. };
  389. reducerTester()
  390. .givenReducer(exploreReducer, stateWithDifferentDataSource)
  391. .whenActionIsDispatched(
  392. updateLocation({
  393. query: {
  394. left: serializedUrlState,
  395. },
  396. path: '/explore',
  397. })
  398. )
  399. .thenStateShouldEqual(expectedState);
  400. });
  401. });
  402. describe('and range differs', () => {
  403. it('then it should return update range', () => {
  404. const { initalState, serializedUrlState } = setup();
  405. const expectedState = {
  406. ...initalState,
  407. left: {
  408. ...initalState.left,
  409. update: {
  410. ...initalState.left.update,
  411. range: true,
  412. },
  413. },
  414. };
  415. const stateWithDifferentDataSource = {
  416. ...initalState,
  417. left: {
  418. ...initalState.left,
  419. urlState: {
  420. ...initalState.left.urlState,
  421. range: {
  422. from: 'now',
  423. to: 'now-6h',
  424. },
  425. },
  426. },
  427. };
  428. reducerTester()
  429. .givenReducer(exploreReducer, stateWithDifferentDataSource)
  430. .whenActionIsDispatched(
  431. updateLocation({
  432. query: {
  433. left: serializedUrlState,
  434. },
  435. path: '/explore',
  436. })
  437. )
  438. .thenStateShouldEqual(expectedState);
  439. });
  440. });
  441. describe('and queries differs', () => {
  442. it('then it should return update queries', () => {
  443. const { initalState, serializedUrlState } = setup();
  444. const expectedState = {
  445. ...initalState,
  446. left: {
  447. ...initalState.left,
  448. update: {
  449. ...initalState.left.update,
  450. queries: true,
  451. },
  452. },
  453. };
  454. const stateWithDifferentDataSource = {
  455. ...initalState,
  456. left: {
  457. ...initalState.left,
  458. urlState: {
  459. ...initalState.left.urlState,
  460. queries: [{ expr: '{__filename__="some.log"}' }],
  461. },
  462. },
  463. };
  464. reducerTester()
  465. .givenReducer(exploreReducer, stateWithDifferentDataSource)
  466. .whenActionIsDispatched(
  467. updateLocation({
  468. query: {
  469. left: serializedUrlState,
  470. },
  471. path: '/explore',
  472. })
  473. )
  474. .thenStateShouldEqual(expectedState);
  475. });
  476. });
  477. describe('and ui differs', () => {
  478. it('then it should return update ui', () => {
  479. const { initalState, serializedUrlState } = setup();
  480. const expectedState = {
  481. ...initalState,
  482. left: {
  483. ...initalState.left,
  484. update: {
  485. ...initalState.left.update,
  486. ui: true,
  487. },
  488. },
  489. };
  490. const stateWithDifferentDataSource = {
  491. ...initalState,
  492. left: {
  493. ...initalState.left,
  494. urlState: {
  495. ...initalState.left.urlState,
  496. ui: {
  497. ...initalState.left.urlState.ui,
  498. showingGraph: true,
  499. },
  500. },
  501. },
  502. };
  503. reducerTester()
  504. .givenReducer(exploreReducer, stateWithDifferentDataSource)
  505. .whenActionIsDispatched(
  506. updateLocation({
  507. query: {
  508. left: serializedUrlState,
  509. },
  510. path: '/explore',
  511. })
  512. )
  513. .thenStateShouldEqual(expectedState);
  514. });
  515. });
  516. describe('and nothing differs', () => {
  517. it('then it should return update ui', () => {
  518. const { initalState, serializedUrlState } = setup();
  519. const expectedState = { ...initalState };
  520. reducerTester()
  521. .givenReducer(exploreReducer, initalState)
  522. .whenActionIsDispatched(
  523. updateLocation({
  524. query: {
  525. left: serializedUrlState,
  526. },
  527. path: '/explore',
  528. })
  529. )
  530. .thenStateShouldEqual(expectedState);
  531. });
  532. });
  533. });
  534. });
  535. });
  536. });
  537. });