reducers.test.ts 18 KB

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