reducers.test.ts 20 KB

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