reducers.test.ts 18 KB

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