reducers.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791
  1. import _ from 'lodash';
  2. import {
  3. ensureQueries,
  4. getQueryKeys,
  5. parseUrlState,
  6. DEFAULT_UI_STATE,
  7. generateNewKeyAndAddRefIdIfMissing,
  8. sortLogsResult,
  9. stopQueryState,
  10. refreshIntervalToSortOrder,
  11. } from 'app/core/utils/explore';
  12. import { ExploreItemState, ExploreState, ExploreId, ExploreUpdateState, ExploreMode } from 'app/types/explore';
  13. import { LoadingState, toLegacyResponseData } from '@grafana/data';
  14. import { DataQuery, DataSourceApi, PanelData, DataQueryRequest } from '@grafana/ui';
  15. import {
  16. HigherOrderAction,
  17. ActionTypes,
  18. testDataSourcePendingAction,
  19. testDataSourceSuccessAction,
  20. testDataSourceFailureAction,
  21. splitCloseAction,
  22. SplitCloseActionPayload,
  23. loadExploreDatasources,
  24. historyUpdatedAction,
  25. changeModeAction,
  26. setUrlReplacedAction,
  27. scanStopAction,
  28. queryStartAction,
  29. changeRangeAction,
  30. clearOriginAction,
  31. addQueryRowAction,
  32. changeQueryAction,
  33. changeSizeAction,
  34. changeRefreshIntervalAction,
  35. clearQueriesAction,
  36. highlightLogsExpressionAction,
  37. initializeExploreAction,
  38. updateDatasourceInstanceAction,
  39. loadDatasourceMissingAction,
  40. loadDatasourcePendingAction,
  41. loadDatasourceReadyAction,
  42. modifyQueriesAction,
  43. removeQueryRowAction,
  44. scanStartAction,
  45. setQueriesAction,
  46. toggleTableAction,
  47. queriesImportedAction,
  48. updateUIStateAction,
  49. toggleLogLevelAction,
  50. changeLoadingStateAction,
  51. resetExploreAction,
  52. queryStreamUpdatedAction,
  53. QueryEndedPayload,
  54. queryStoreSubscriptionAction,
  55. setPausedStateAction,
  56. toggleGraphAction,
  57. } from './actionTypes';
  58. import { reducerFactory, ActionOf } from 'app/core/redux';
  59. import { updateLocation } from 'app/core/actions/location';
  60. import { LocationUpdate } from '@grafana/runtime';
  61. import TableModel from 'app/core/table_model';
  62. import { isLive } from '@grafana/ui/src/components/RefreshPicker/RefreshPicker';
  63. import { ResultProcessor } from '../utils/ResultProcessor';
  64. export const DEFAULT_RANGE = {
  65. from: 'now-6h',
  66. to: 'now',
  67. };
  68. export const makeInitialUpdateState = (): ExploreUpdateState => ({
  69. datasource: false,
  70. queries: false,
  71. range: false,
  72. mode: false,
  73. ui: false,
  74. });
  75. /**
  76. * Returns a fresh Explore area state
  77. */
  78. export const makeExploreItemState = (): ExploreItemState => ({
  79. StartPage: undefined,
  80. containerWidth: 0,
  81. datasourceInstance: null,
  82. requestedDatasourceName: null,
  83. datasourceError: null,
  84. datasourceLoading: null,
  85. datasourceMissing: false,
  86. exploreDatasources: [],
  87. history: [],
  88. queries: [],
  89. initialized: false,
  90. range: {
  91. from: null,
  92. to: null,
  93. raw: DEFAULT_RANGE,
  94. },
  95. absoluteRange: {
  96. from: null,
  97. to: null,
  98. },
  99. scanning: false,
  100. scanRange: null,
  101. showingGraph: true,
  102. showingTable: true,
  103. loading: false,
  104. queryKeys: [],
  105. urlState: null,
  106. update: makeInitialUpdateState(),
  107. latency: 0,
  108. supportedModes: [],
  109. mode: null,
  110. isLive: false,
  111. isPaused: false,
  112. urlReplaced: false,
  113. queryResponse: createEmptyQueryResponse(),
  114. });
  115. export const createEmptyQueryResponse = (): PanelData => ({
  116. state: LoadingState.NotStarted,
  117. request: {} as DataQueryRequest<DataQuery>,
  118. series: [],
  119. error: null,
  120. });
  121. /**
  122. * Global Explore state that handles multiple Explore areas and the split state
  123. */
  124. export const initialExploreItemState = makeExploreItemState();
  125. export const initialExploreState: ExploreState = {
  126. split: null,
  127. left: initialExploreItemState,
  128. right: initialExploreItemState,
  129. };
  130. /**
  131. * Reducer for an Explore area, to be used by the global Explore reducer.
  132. */
  133. export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemState)
  134. .addMapper({
  135. filter: addQueryRowAction,
  136. mapper: (state, action): ExploreItemState => {
  137. const { queries } = state;
  138. const { index, query } = action.payload;
  139. // Add to queries, which will cause a new row to be rendered
  140. const nextQueries = [...queries.slice(0, index + 1), { ...query }, ...queries.slice(index + 1)];
  141. return {
  142. ...state,
  143. queries: nextQueries,
  144. logsHighlighterExpressions: undefined,
  145. queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
  146. };
  147. },
  148. })
  149. .addMapper({
  150. filter: changeQueryAction,
  151. mapper: (state, action): ExploreItemState => {
  152. const { queries } = state;
  153. const { query, index } = action.payload;
  154. // Override path: queries are completely reset
  155. const nextQuery: DataQuery = generateNewKeyAndAddRefIdIfMissing(query, queries, index);
  156. const nextQueries = [...queries];
  157. nextQueries[index] = nextQuery;
  158. return {
  159. ...state,
  160. queries: nextQueries,
  161. queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
  162. };
  163. },
  164. })
  165. .addMapper({
  166. filter: changeSizeAction,
  167. mapper: (state, action): ExploreItemState => {
  168. const containerWidth = action.payload.width;
  169. return { ...state, containerWidth };
  170. },
  171. })
  172. .addMapper({
  173. filter: changeModeAction,
  174. mapper: (state, action): ExploreItemState => {
  175. const mode = action.payload.mode;
  176. return { ...state, mode };
  177. },
  178. })
  179. .addMapper({
  180. filter: changeRefreshIntervalAction,
  181. mapper: (state, action): ExploreItemState => {
  182. const { refreshInterval } = action.payload;
  183. const live = isLive(refreshInterval);
  184. const sortOrder = refreshIntervalToSortOrder(refreshInterval);
  185. const logsResult = sortLogsResult(state.logsResult, sortOrder);
  186. if (isLive(state.refreshInterval) && !live) {
  187. stopQueryState(state.querySubscription);
  188. }
  189. return {
  190. ...state,
  191. refreshInterval,
  192. queryResponse: {
  193. ...state.queryResponse,
  194. state: live ? LoadingState.Streaming : LoadingState.NotStarted,
  195. },
  196. isLive: live,
  197. isPaused: false,
  198. loading: live,
  199. logsResult,
  200. };
  201. },
  202. })
  203. .addMapper({
  204. filter: clearQueriesAction,
  205. mapper: (state): ExploreItemState => {
  206. const queries = ensureQueries();
  207. stopQueryState(state.querySubscription);
  208. return {
  209. ...state,
  210. queries: queries.slice(),
  211. graphResult: null,
  212. tableResult: null,
  213. logsResult: null,
  214. showingStartPage: Boolean(state.StartPage),
  215. queryKeys: getQueryKeys(queries, state.datasourceInstance),
  216. queryResponse: createEmptyQueryResponse(),
  217. loading: false,
  218. };
  219. },
  220. })
  221. .addMapper({
  222. filter: clearOriginAction,
  223. mapper: (state): ExploreItemState => {
  224. return {
  225. ...state,
  226. originPanelId: undefined,
  227. };
  228. },
  229. })
  230. .addMapper({
  231. filter: highlightLogsExpressionAction,
  232. mapper: (state, action): ExploreItemState => {
  233. const { expressions } = action.payload;
  234. return { ...state, logsHighlighterExpressions: expressions };
  235. },
  236. })
  237. .addMapper({
  238. filter: initializeExploreAction,
  239. mapper: (state, action): ExploreItemState => {
  240. const { containerWidth, eventBridge, queries, range, mode, ui, originPanelId } = action.payload;
  241. return {
  242. ...state,
  243. containerWidth,
  244. eventBridge,
  245. range,
  246. mode,
  247. queries,
  248. initialized: true,
  249. queryKeys: getQueryKeys(queries, state.datasourceInstance),
  250. ...ui,
  251. originPanelId,
  252. update: makeInitialUpdateState(),
  253. };
  254. },
  255. })
  256. .addMapper({
  257. filter: updateDatasourceInstanceAction,
  258. mapper: (state, action): ExploreItemState => {
  259. const { datasourceInstance } = action.payload;
  260. const [supportedModes, mode] = getModesForDatasource(datasourceInstance, state.mode);
  261. const originPanelId = state.urlState && state.urlState.originPanelId;
  262. // Custom components
  263. const StartPage = datasourceInstance.components.ExploreStartPage;
  264. stopQueryState(state.querySubscription);
  265. return {
  266. ...state,
  267. datasourceInstance,
  268. graphResult: null,
  269. tableResult: null,
  270. logsResult: null,
  271. latency: 0,
  272. queryResponse: createEmptyQueryResponse(),
  273. loading: false,
  274. StartPage,
  275. showingStartPage: Boolean(StartPage),
  276. queryKeys: [],
  277. supportedModes,
  278. mode,
  279. originPanelId,
  280. };
  281. },
  282. })
  283. .addMapper({
  284. filter: loadDatasourceMissingAction,
  285. mapper: (state): ExploreItemState => {
  286. return {
  287. ...state,
  288. datasourceMissing: true,
  289. datasourceLoading: false,
  290. update: makeInitialUpdateState(),
  291. };
  292. },
  293. })
  294. .addMapper({
  295. filter: loadDatasourcePendingAction,
  296. mapper: (state, action): ExploreItemState => {
  297. return {
  298. ...state,
  299. datasourceLoading: true,
  300. requestedDatasourceName: action.payload.requestedDatasourceName,
  301. };
  302. },
  303. })
  304. .addMapper({
  305. filter: loadDatasourceReadyAction,
  306. mapper: (state, action): ExploreItemState => {
  307. const { history } = action.payload;
  308. return {
  309. ...state,
  310. history,
  311. datasourceLoading: false,
  312. datasourceMissing: false,
  313. logsHighlighterExpressions: undefined,
  314. update: makeInitialUpdateState(),
  315. };
  316. },
  317. })
  318. .addMapper({
  319. filter: modifyQueriesAction,
  320. mapper: (state, action): ExploreItemState => {
  321. const { queries } = state;
  322. const { modification, index, modifier } = action.payload;
  323. let nextQueries: DataQuery[];
  324. if (index === undefined) {
  325. // Modify all queries
  326. nextQueries = queries.map((query, i) => {
  327. const nextQuery = modifier({ ...query }, modification);
  328. return generateNewKeyAndAddRefIdIfMissing(nextQuery, queries, i);
  329. });
  330. } else {
  331. // Modify query only at index
  332. nextQueries = queries.map((query, i) => {
  333. if (i === index) {
  334. const nextQuery = modifier({ ...query }, modification);
  335. return generateNewKeyAndAddRefIdIfMissing(nextQuery, queries, i);
  336. }
  337. return query;
  338. });
  339. }
  340. return {
  341. ...state,
  342. queries: nextQueries,
  343. queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
  344. };
  345. },
  346. })
  347. .addMapper({
  348. filter: queryStartAction,
  349. mapper: (state): ExploreItemState => {
  350. return {
  351. ...state,
  352. latency: 0,
  353. queryResponse: {
  354. ...state.queryResponse,
  355. state: LoadingState.Loading,
  356. error: null,
  357. },
  358. loading: true,
  359. update: makeInitialUpdateState(),
  360. };
  361. },
  362. })
  363. .addMapper({
  364. filter: removeQueryRowAction,
  365. mapper: (state, action): ExploreItemState => {
  366. const { queries, queryKeys } = state;
  367. const { index } = action.payload;
  368. if (queries.length <= 1) {
  369. return state;
  370. }
  371. const nextQueries = [...queries.slice(0, index), ...queries.slice(index + 1)];
  372. const nextQueryKeys = [...queryKeys.slice(0, index), ...queryKeys.slice(index + 1)];
  373. return {
  374. ...state,
  375. queries: nextQueries,
  376. logsHighlighterExpressions: undefined,
  377. queryKeys: nextQueryKeys,
  378. };
  379. },
  380. })
  381. .addMapper({
  382. filter: scanStartAction,
  383. mapper: (state, action): ExploreItemState => {
  384. return { ...state, scanning: true };
  385. },
  386. })
  387. .addMapper({
  388. filter: scanStopAction,
  389. mapper: (state): ExploreItemState => {
  390. return {
  391. ...state,
  392. scanning: false,
  393. scanRange: undefined,
  394. update: makeInitialUpdateState(),
  395. };
  396. },
  397. })
  398. .addMapper({
  399. filter: setQueriesAction,
  400. mapper: (state, action): ExploreItemState => {
  401. const { queries } = action.payload;
  402. return {
  403. ...state,
  404. queries: queries.slice(),
  405. queryKeys: getQueryKeys(queries, state.datasourceInstance),
  406. };
  407. },
  408. })
  409. .addMapper({
  410. filter: updateUIStateAction,
  411. mapper: (state, action): ExploreItemState => {
  412. return { ...state, ...action.payload };
  413. },
  414. })
  415. .addMapper({
  416. filter: toggleGraphAction,
  417. mapper: (state): ExploreItemState => {
  418. const showingGraph = !state.showingGraph;
  419. if (showingGraph) {
  420. return { ...state, showingGraph };
  421. }
  422. return { ...state, showingGraph, graphResult: null };
  423. },
  424. })
  425. .addMapper({
  426. filter: toggleTableAction,
  427. mapper: (state): ExploreItemState => {
  428. const showingTable = !state.showingTable;
  429. if (showingTable) {
  430. return { ...state, showingTable };
  431. }
  432. return { ...state, showingTable, tableResult: new TableModel() };
  433. },
  434. })
  435. .addMapper({
  436. filter: queriesImportedAction,
  437. mapper: (state, action): ExploreItemState => {
  438. const { queries } = action.payload;
  439. return {
  440. ...state,
  441. queries,
  442. queryKeys: getQueryKeys(queries, state.datasourceInstance),
  443. };
  444. },
  445. })
  446. .addMapper({
  447. filter: toggleLogLevelAction,
  448. mapper: (state, action): ExploreItemState => {
  449. const { hiddenLogLevels } = action.payload;
  450. return {
  451. ...state,
  452. hiddenLogLevels: Array.from(hiddenLogLevels),
  453. };
  454. },
  455. })
  456. .addMapper({
  457. filter: testDataSourcePendingAction,
  458. mapper: (state): ExploreItemState => {
  459. return {
  460. ...state,
  461. datasourceError: null,
  462. };
  463. },
  464. })
  465. .addMapper({
  466. filter: testDataSourceSuccessAction,
  467. mapper: (state): ExploreItemState => {
  468. return {
  469. ...state,
  470. datasourceError: null,
  471. };
  472. },
  473. })
  474. .addMapper({
  475. filter: testDataSourceFailureAction,
  476. mapper: (state, action): ExploreItemState => {
  477. return {
  478. ...state,
  479. datasourceError: action.payload.error,
  480. graphResult: undefined,
  481. tableResult: undefined,
  482. logsResult: undefined,
  483. update: makeInitialUpdateState(),
  484. };
  485. },
  486. })
  487. .addMapper({
  488. filter: loadExploreDatasources,
  489. mapper: (state, action): ExploreItemState => {
  490. return {
  491. ...state,
  492. exploreDatasources: action.payload.exploreDatasources,
  493. };
  494. },
  495. })
  496. .addMapper({
  497. filter: historyUpdatedAction,
  498. mapper: (state, action): ExploreItemState => {
  499. return {
  500. ...state,
  501. history: action.payload.history,
  502. };
  503. },
  504. })
  505. .addMapper({
  506. filter: setUrlReplacedAction,
  507. mapper: (state): ExploreItemState => {
  508. return {
  509. ...state,
  510. urlReplaced: true,
  511. };
  512. },
  513. })
  514. .addMapper({
  515. filter: changeRangeAction,
  516. mapper: (state, action): ExploreItemState => {
  517. const { range, absoluteRange } = action.payload;
  518. return {
  519. ...state,
  520. range,
  521. absoluteRange,
  522. };
  523. },
  524. })
  525. .addMapper({
  526. filter: changeLoadingStateAction,
  527. mapper: (state, action): ExploreItemState => {
  528. const { loadingState } = action.payload;
  529. return {
  530. ...state,
  531. queryResponse: {
  532. ...state.queryResponse,
  533. state: loadingState,
  534. },
  535. loading: loadingState === LoadingState.Loading || loadingState === LoadingState.Streaming,
  536. };
  537. },
  538. })
  539. .addMapper({
  540. filter: setPausedStateAction,
  541. mapper: (state, action): ExploreItemState => {
  542. const { isPaused } = action.payload;
  543. return {
  544. ...state,
  545. isPaused: isPaused,
  546. };
  547. },
  548. })
  549. .addMapper({
  550. filter: queryStoreSubscriptionAction,
  551. mapper: (state, action): ExploreItemState => {
  552. const { querySubscription } = action.payload;
  553. return {
  554. ...state,
  555. querySubscription,
  556. };
  557. },
  558. })
  559. .addMapper({
  560. filter: queryStreamUpdatedAction,
  561. mapper: (state, action): ExploreItemState => {
  562. return processQueryResponse(state, action);
  563. },
  564. })
  565. .create();
  566. export const processQueryResponse = (
  567. state: ExploreItemState,
  568. action: ActionOf<QueryEndedPayload>
  569. ): ExploreItemState => {
  570. const { response } = action.payload;
  571. const { request, state: loadingState, series, error } = response;
  572. if (error) {
  573. if (error.cancelled) {
  574. return state;
  575. }
  576. // For Angular editors
  577. state.eventBridge.emit('data-error', error);
  578. return {
  579. ...state,
  580. loading: false,
  581. queryResponse: response,
  582. graphResult: null,
  583. tableResult: null,
  584. logsResult: null,
  585. showingStartPage: false,
  586. update: makeInitialUpdateState(),
  587. };
  588. }
  589. const latency = request.endTime ? request.endTime - request.startTime : 0;
  590. const processor = new ResultProcessor(state, series, request.intervalMs);
  591. const graphResult = processor.getGraphResult();
  592. const tableResult = processor.getTableResult();
  593. const logsResult = processor.getLogsResult();
  594. // Send legacy data to Angular editors
  595. if (state.datasourceInstance.components.QueryCtrl) {
  596. const legacy = series.map(v => toLegacyResponseData(v));
  597. state.eventBridge.emit('data-received', legacy);
  598. }
  599. return {
  600. ...state,
  601. latency,
  602. queryResponse: response,
  603. graphResult,
  604. tableResult,
  605. logsResult,
  606. loading: loadingState === LoadingState.Loading || loadingState === LoadingState.Streaming,
  607. showingStartPage: false,
  608. update: makeInitialUpdateState(),
  609. };
  610. };
  611. export const updateChildRefreshState = (
  612. state: Readonly<ExploreItemState>,
  613. payload: LocationUpdate,
  614. exploreId: ExploreId
  615. ): ExploreItemState => {
  616. const path = payload.path || '';
  617. const queryState = payload.query[exploreId] as string;
  618. if (!queryState) {
  619. return state;
  620. }
  621. const urlState = parseUrlState(queryState);
  622. if (!state.urlState || path !== '/explore') {
  623. // we only want to refresh when browser back/forward
  624. return {
  625. ...state,
  626. urlState,
  627. update: { datasource: false, queries: false, range: false, mode: false, ui: false },
  628. };
  629. }
  630. const datasource = _.isEqual(urlState ? urlState.datasource : '', state.urlState.datasource) === false;
  631. const queries = _.isEqual(urlState ? urlState.queries : [], state.urlState.queries) === false;
  632. const range = _.isEqual(urlState ? urlState.range : DEFAULT_RANGE, state.urlState.range) === false;
  633. const mode = _.isEqual(urlState ? urlState.mode : ExploreMode.Metrics, state.urlState.mode) === false;
  634. const ui = _.isEqual(urlState ? urlState.ui : DEFAULT_UI_STATE, state.urlState.ui) === false;
  635. return {
  636. ...state,
  637. urlState,
  638. update: {
  639. ...state.update,
  640. datasource,
  641. queries,
  642. range,
  643. mode,
  644. ui,
  645. },
  646. };
  647. };
  648. const getModesForDatasource = (dataSource: DataSourceApi, currentMode: ExploreMode): [ExploreMode[], ExploreMode] => {
  649. // Temporary hack here. We want Loki to work in dashboards for which it needs to have metrics = true which is weird
  650. // for Explore.
  651. // TODO: need to figure out a better way to handle this situation
  652. const supportsGraph = dataSource.meta.name === 'Loki' ? false : dataSource.meta.metrics;
  653. const supportsLogs = dataSource.meta.logs;
  654. let mode = currentMode || ExploreMode.Metrics;
  655. const supportedModes: ExploreMode[] = [];
  656. if (supportsGraph) {
  657. supportedModes.push(ExploreMode.Metrics);
  658. }
  659. if (supportsLogs) {
  660. supportedModes.push(ExploreMode.Logs);
  661. }
  662. if (supportedModes.length === 1) {
  663. mode = supportedModes[0];
  664. }
  665. return [supportedModes, mode];
  666. };
  667. /**
  668. * Global Explore reducer that handles multiple Explore areas (left and right).
  669. * Actions that have an `exploreId` get routed to the ExploreItemReducer.
  670. */
  671. export const exploreReducer = (state = initialExploreState, action: HigherOrderAction): ExploreState => {
  672. switch (action.type) {
  673. case splitCloseAction.type: {
  674. const { itemId } = action.payload as SplitCloseActionPayload;
  675. const targetSplit = {
  676. left: itemId === ExploreId.left ? state.right : state.left,
  677. right: initialExploreState.right,
  678. };
  679. return {
  680. ...state,
  681. ...targetSplit,
  682. split: false,
  683. };
  684. }
  685. case ActionTypes.SplitOpen: {
  686. return { ...state, split: true, right: { ...action.payload.itemState } };
  687. }
  688. case ActionTypes.ResetExplore: {
  689. if (action.payload.force || !Number.isInteger(state.left.originPanelId)) {
  690. return initialExploreState;
  691. }
  692. return {
  693. ...initialExploreState,
  694. left: {
  695. ...initialExploreItemState,
  696. queries: state.left.queries,
  697. originPanelId: state.left.originPanelId,
  698. },
  699. };
  700. }
  701. case updateLocation.type: {
  702. const { query } = action.payload;
  703. if (!query || !query[ExploreId.left]) {
  704. return state;
  705. }
  706. const split = query[ExploreId.right] ? true : false;
  707. const leftState = state[ExploreId.left];
  708. const rightState = state[ExploreId.right];
  709. return {
  710. ...state,
  711. split,
  712. [ExploreId.left]: updateChildRefreshState(leftState, action.payload, ExploreId.left),
  713. [ExploreId.right]: updateChildRefreshState(rightState, action.payload, ExploreId.right),
  714. };
  715. }
  716. case resetExploreAction.type: {
  717. const leftState = state[ExploreId.left];
  718. const rightState = state[ExploreId.right];
  719. stopQueryState(leftState.querySubscription);
  720. stopQueryState(rightState.querySubscription);
  721. return {
  722. ...state,
  723. [ExploreId.left]: updateChildRefreshState(leftState, action.payload, ExploreId.left),
  724. [ExploreId.right]: updateChildRefreshState(rightState, action.payload, ExploreId.right),
  725. };
  726. }
  727. }
  728. if (action.payload) {
  729. const { exploreId } = action.payload as any;
  730. if (exploreId !== undefined) {
  731. // @ts-ignore
  732. const exploreItemState = state[exploreId];
  733. return { ...state, [exploreId]: itemReducer(exploreItemState, action) };
  734. }
  735. }
  736. return state;
  737. };
  738. export default {
  739. explore: exploreReducer,
  740. };