reducers.test.ts 18 KB

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