reducers.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. import {
  2. calculateResultsFromQueryTransactions,
  3. generateEmptyQuery,
  4. getIntervals,
  5. ensureQueries,
  6. getQueryKeys,
  7. } from 'app/core/utils/explore';
  8. import { ExploreItemState, ExploreState, QueryTransaction } from 'app/types/explore';
  9. import { DataQuery } from '@grafana/ui/src/types';
  10. import { HigherOrderAction, ActionTypes } from './actionTypes';
  11. import { reducerFactory } from 'app/core/redux';
  12. import {
  13. addQueryRowAction,
  14. changeQueryAction,
  15. changeSizeAction,
  16. changeTimeAction,
  17. clearQueriesAction,
  18. highlightLogsExpressionAction,
  19. initializeExploreAction,
  20. updateDatasourceInstanceAction,
  21. loadDatasourceFailureAction,
  22. loadDatasourceMissingAction,
  23. loadDatasourcePendingAction,
  24. loadDatasourceSuccessAction,
  25. modifyQueriesAction,
  26. queryTransactionFailureAction,
  27. queryTransactionStartAction,
  28. queryTransactionSuccessAction,
  29. removeQueryRowAction,
  30. runQueriesEmptyAction,
  31. scanRangeAction,
  32. scanStartAction,
  33. scanStopAction,
  34. setQueriesAction,
  35. toggleGraphAction,
  36. toggleLogsAction,
  37. toggleTableAction,
  38. queriesImportedAction,
  39. updateUIStateAction,
  40. toggleLogLevelAction,
  41. } from './actionTypes';
  42. export const DEFAULT_RANGE = {
  43. from: 'now-6h',
  44. to: 'now',
  45. };
  46. // Millies step for helper bar charts
  47. const DEFAULT_GRAPH_INTERVAL = 15 * 1000;
  48. /**
  49. * Returns a fresh Explore area state
  50. */
  51. export const makeExploreItemState = (): ExploreItemState => ({
  52. StartPage: undefined,
  53. containerWidth: 0,
  54. datasourceInstance: null,
  55. requestedDatasourceName: null,
  56. datasourceError: null,
  57. datasourceLoading: null,
  58. datasourceMissing: false,
  59. exploreDatasources: [],
  60. history: [],
  61. queries: [],
  62. initialized: false,
  63. queryTransactions: [],
  64. queryIntervals: { interval: '15s', intervalMs: DEFAULT_GRAPH_INTERVAL },
  65. range: DEFAULT_RANGE,
  66. scanning: false,
  67. scanRange: null,
  68. showingGraph: true,
  69. showingLogs: true,
  70. showingTable: true,
  71. supportsGraph: null,
  72. supportsLogs: null,
  73. supportsTable: null,
  74. queryKeys: [],
  75. });
  76. /**
  77. * Global Explore state that handles multiple Explore areas and the split state
  78. */
  79. export const initialExploreState: ExploreState = {
  80. split: null,
  81. left: makeExploreItemState(),
  82. right: makeExploreItemState(),
  83. };
  84. /**
  85. * Reducer for an Explore area, to be used by the global Explore reducer.
  86. */
  87. export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemState)
  88. .addMapper({
  89. filter: addQueryRowAction,
  90. mapper: (state, action): ExploreItemState => {
  91. const { queries, queryTransactions } = state;
  92. const { index, query } = action.payload;
  93. // Add to queries, which will cause a new row to be rendered
  94. const nextQueries = [...queries.slice(0, index + 1), { ...query }, ...queries.slice(index + 1)];
  95. // Ongoing transactions need to update their row indices
  96. const nextQueryTransactions = queryTransactions.map(qt => {
  97. if (qt.rowIndex > index) {
  98. return {
  99. ...qt,
  100. rowIndex: qt.rowIndex + 1,
  101. };
  102. }
  103. return qt;
  104. });
  105. return {
  106. ...state,
  107. queries: nextQueries,
  108. logsHighlighterExpressions: undefined,
  109. queryTransactions: nextQueryTransactions,
  110. queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
  111. };
  112. },
  113. })
  114. .addMapper({
  115. filter: changeQueryAction,
  116. mapper: (state, action): ExploreItemState => {
  117. const { queries, queryTransactions } = state;
  118. const { query, index } = action.payload;
  119. // Override path: queries are completely reset
  120. const nextQuery: DataQuery = { ...query, ...generateEmptyQuery(index) };
  121. const nextQueries = [...queries];
  122. nextQueries[index] = nextQuery;
  123. // Discard ongoing transaction related to row query
  124. const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index);
  125. return {
  126. ...state,
  127. queries: nextQueries,
  128. queryTransactions: nextQueryTransactions,
  129. queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
  130. };
  131. },
  132. })
  133. .addMapper({
  134. filter: changeSizeAction,
  135. mapper: (state, action): ExploreItemState => {
  136. const { range, datasourceInstance } = state;
  137. let interval = '1s';
  138. if (datasourceInstance && datasourceInstance.interval) {
  139. interval = datasourceInstance.interval;
  140. }
  141. const containerWidth = action.payload.width;
  142. const queryIntervals = getIntervals(range, interval, containerWidth);
  143. return { ...state, containerWidth, queryIntervals };
  144. },
  145. })
  146. .addMapper({
  147. filter: changeTimeAction,
  148. mapper: (state, action): ExploreItemState => {
  149. return { ...state, range: action.payload.range };
  150. },
  151. })
  152. .addMapper({
  153. filter: clearQueriesAction,
  154. mapper: (state): ExploreItemState => {
  155. const queries = ensureQueries();
  156. return {
  157. ...state,
  158. queries: queries.slice(),
  159. queryTransactions: [],
  160. showingStartPage: Boolean(state.StartPage),
  161. queryKeys: getQueryKeys(queries, state.datasourceInstance),
  162. };
  163. },
  164. })
  165. .addMapper({
  166. filter: highlightLogsExpressionAction,
  167. mapper: (state, action): ExploreItemState => {
  168. const { expressions } = action.payload;
  169. return { ...state, logsHighlighterExpressions: expressions };
  170. },
  171. })
  172. .addMapper({
  173. filter: initializeExploreAction,
  174. mapper: (state, action): ExploreItemState => {
  175. const { containerWidth, eventBridge, exploreDatasources, queries, range, ui } = action.payload;
  176. return {
  177. ...state,
  178. containerWidth,
  179. eventBridge,
  180. exploreDatasources,
  181. range,
  182. queries,
  183. initialized: true,
  184. queryKeys: getQueryKeys(queries, state.datasourceInstance),
  185. ...ui,
  186. };
  187. },
  188. })
  189. .addMapper({
  190. filter: updateDatasourceInstanceAction,
  191. mapper: (state, action): ExploreItemState => {
  192. const { datasourceInstance } = action.payload;
  193. return { ...state, datasourceInstance, queryKeys: getQueryKeys(state.queries, datasourceInstance) };
  194. },
  195. })
  196. .addMapper({
  197. filter: loadDatasourceFailureAction,
  198. mapper: (state, action): ExploreItemState => {
  199. return { ...state, datasourceError: action.payload.error, datasourceLoading: false };
  200. },
  201. })
  202. .addMapper({
  203. filter: loadDatasourceMissingAction,
  204. mapper: (state): ExploreItemState => {
  205. return { ...state, datasourceMissing: true, datasourceLoading: false };
  206. },
  207. })
  208. .addMapper({
  209. filter: loadDatasourcePendingAction,
  210. mapper: (state, action): ExploreItemState => {
  211. return { ...state, datasourceLoading: true, requestedDatasourceName: action.payload.requestedDatasourceName };
  212. },
  213. })
  214. .addMapper({
  215. filter: loadDatasourceSuccessAction,
  216. mapper: (state, action): ExploreItemState => {
  217. const { containerWidth, range } = state;
  218. const {
  219. StartPage,
  220. datasourceInstance,
  221. history,
  222. showingStartPage,
  223. supportsGraph,
  224. supportsLogs,
  225. supportsTable,
  226. } = action.payload;
  227. const queryIntervals = getIntervals(range, datasourceInstance.interval, containerWidth);
  228. return {
  229. ...state,
  230. queryIntervals,
  231. StartPage,
  232. datasourceInstance,
  233. history,
  234. showingStartPage,
  235. supportsGraph,
  236. supportsLogs,
  237. supportsTable,
  238. datasourceLoading: false,
  239. datasourceMissing: false,
  240. datasourceError: null,
  241. logsHighlighterExpressions: undefined,
  242. queryTransactions: [],
  243. };
  244. },
  245. })
  246. .addMapper({
  247. filter: modifyQueriesAction,
  248. mapper: (state, action): ExploreItemState => {
  249. const { queries, queryTransactions } = state;
  250. const { modification, index, modifier } = action.payload;
  251. let nextQueries: DataQuery[];
  252. let nextQueryTransactions;
  253. if (index === undefined) {
  254. // Modify all queries
  255. nextQueries = queries.map((query, i) => ({
  256. ...modifier({ ...query }, modification),
  257. ...generateEmptyQuery(i),
  258. }));
  259. // Discard all ongoing transactions
  260. nextQueryTransactions = [];
  261. } else {
  262. // Modify query only at index
  263. nextQueries = queries.map((query, i) => {
  264. // Synchronize all queries with local query cache to ensure consistency
  265. // TODO still needed?
  266. return i === index ? { ...modifier({ ...query }, modification), ...generateEmptyQuery(i) } : query;
  267. });
  268. nextQueryTransactions = queryTransactions
  269. // Consume the hint corresponding to the action
  270. .map(qt => {
  271. if (qt.hints != null && qt.rowIndex === index) {
  272. qt.hints = qt.hints.filter(hint => hint.fix.action !== modification);
  273. }
  274. return qt;
  275. })
  276. // Preserve previous row query transaction to keep results visible if next query is incomplete
  277. .filter(qt => modification.preventSubmit || qt.rowIndex !== index);
  278. }
  279. return {
  280. ...state,
  281. queries: nextQueries,
  282. queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
  283. queryTransactions: nextQueryTransactions,
  284. };
  285. },
  286. })
  287. .addMapper({
  288. filter: queryTransactionFailureAction,
  289. mapper: (state, action): ExploreItemState => {
  290. const { queryTransactions } = action.payload;
  291. return { ...state, queryTransactions, showingStartPage: false };
  292. },
  293. })
  294. .addMapper({
  295. filter: queryTransactionStartAction,
  296. mapper: (state, action): ExploreItemState => {
  297. const { queryTransactions } = state;
  298. const { resultType, rowIndex, transaction } = action.payload;
  299. // Discarding existing transactions of same type
  300. const remainingTransactions = queryTransactions.filter(
  301. qt => !(qt.resultType === resultType && qt.rowIndex === rowIndex)
  302. );
  303. // Append new transaction
  304. const nextQueryTransactions: QueryTransaction[] = [...remainingTransactions, transaction];
  305. return { ...state, queryTransactions: nextQueryTransactions, showingStartPage: false };
  306. },
  307. })
  308. .addMapper({
  309. filter: queryTransactionSuccessAction,
  310. mapper: (state, action): ExploreItemState => {
  311. const { datasourceInstance, queryIntervals } = state;
  312. const { history, queryTransactions } = action.payload;
  313. const results = calculateResultsFromQueryTransactions(
  314. queryTransactions,
  315. datasourceInstance,
  316. queryIntervals.intervalMs
  317. );
  318. return { ...state, ...results, history, queryTransactions, showingStartPage: false };
  319. },
  320. })
  321. .addMapper({
  322. filter: removeQueryRowAction,
  323. mapper: (state, action): ExploreItemState => {
  324. const { datasourceInstance, queries, queryIntervals, queryTransactions, queryKeys } = state;
  325. const { index } = action.payload;
  326. if (queries.length <= 1) {
  327. return state;
  328. }
  329. const nextQueries = [...queries.slice(0, index), ...queries.slice(index + 1)];
  330. const nextQueryKeys = [...queryKeys.slice(0, index), ...queryKeys.slice(index + 1)];
  331. // Discard transactions related to row query
  332. const nextQueryTransactions = queryTransactions.filter(qt => nextQueries.some(nq => nq.key === qt.query.key));
  333. const results = calculateResultsFromQueryTransactions(
  334. nextQueryTransactions,
  335. datasourceInstance,
  336. queryIntervals.intervalMs
  337. );
  338. return {
  339. ...state,
  340. ...results,
  341. queries: nextQueries,
  342. logsHighlighterExpressions: undefined,
  343. queryTransactions: nextQueryTransactions,
  344. queryKeys: nextQueryKeys,
  345. };
  346. },
  347. })
  348. .addMapper({
  349. filter: runQueriesEmptyAction,
  350. mapper: (state): ExploreItemState => {
  351. return { ...state, queryTransactions: [] };
  352. },
  353. })
  354. .addMapper({
  355. filter: scanRangeAction,
  356. mapper: (state, action): ExploreItemState => {
  357. return { ...state, scanRange: action.payload.range };
  358. },
  359. })
  360. .addMapper({
  361. filter: scanStartAction,
  362. mapper: (state, action): ExploreItemState => {
  363. return { ...state, scanning: true, scanner: action.payload.scanner };
  364. },
  365. })
  366. .addMapper({
  367. filter: scanStopAction,
  368. mapper: (state): ExploreItemState => {
  369. const { queryTransactions } = state;
  370. const nextQueryTransactions = queryTransactions.filter(qt => qt.scanning && !qt.done);
  371. return {
  372. ...state,
  373. queryTransactions: nextQueryTransactions,
  374. scanning: false,
  375. scanRange: undefined,
  376. scanner: undefined,
  377. };
  378. },
  379. })
  380. .addMapper({
  381. filter: setQueriesAction,
  382. mapper: (state, action): ExploreItemState => {
  383. const { queries } = action.payload;
  384. return {
  385. ...state,
  386. queries: queries.slice(),
  387. queryKeys: getQueryKeys(queries, state.datasourceInstance),
  388. };
  389. },
  390. })
  391. .addMapper({
  392. filter: updateUIStateAction,
  393. mapper: (state, action): ExploreItemState => {
  394. return { ...state, ...action.payload };
  395. },
  396. })
  397. .addMapper({
  398. filter: toggleGraphAction,
  399. mapper: (state): ExploreItemState => {
  400. const showingGraph = !state.showingGraph;
  401. let nextQueryTransactions = state.queryTransactions;
  402. if (!showingGraph) {
  403. // Discard transactions related to Graph query
  404. nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Graph');
  405. }
  406. return { ...state, queryTransactions: nextQueryTransactions };
  407. },
  408. })
  409. .addMapper({
  410. filter: toggleLogsAction,
  411. mapper: (state): ExploreItemState => {
  412. const showingLogs = !state.showingLogs;
  413. let nextQueryTransactions = state.queryTransactions;
  414. if (!showingLogs) {
  415. // Discard transactions related to Logs query
  416. nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Logs');
  417. }
  418. return { ...state, queryTransactions: nextQueryTransactions };
  419. },
  420. })
  421. .addMapper({
  422. filter: toggleTableAction,
  423. mapper: (state): ExploreItemState => {
  424. const showingTable = !state.showingTable;
  425. if (showingTable) {
  426. return { ...state, queryTransactions: state.queryTransactions };
  427. }
  428. // Toggle off needs discarding of table queries and results
  429. const nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Table');
  430. const results = calculateResultsFromQueryTransactions(
  431. nextQueryTransactions,
  432. state.datasourceInstance,
  433. state.queryIntervals.intervalMs
  434. );
  435. return { ...state, ...results, queryTransactions: nextQueryTransactions };
  436. },
  437. })
  438. .addMapper({
  439. filter: queriesImportedAction,
  440. mapper: (state, action): ExploreItemState => {
  441. const { queries } = action.payload;
  442. return {
  443. ...state,
  444. queries,
  445. queryKeys: getQueryKeys(queries, state.datasourceInstance),
  446. };
  447. },
  448. })
  449. .addMapper({
  450. filter: toggleLogLevelAction,
  451. mapper: (state, action): ExploreItemState => {
  452. const { hiddenLogLevels } = action.payload;
  453. return {
  454. ...state,
  455. hiddenLogLevels: Array.from(hiddenLogLevels),
  456. };
  457. },
  458. })
  459. .create();
  460. /**
  461. * Global Explore reducer that handles multiple Explore areas (left and right).
  462. * Actions that have an `exploreId` get routed to the ExploreItemReducer.
  463. */
  464. export const exploreReducer = (state = initialExploreState, action: HigherOrderAction): ExploreState => {
  465. switch (action.type) {
  466. case ActionTypes.SplitClose: {
  467. return { ...state, split: false };
  468. }
  469. case ActionTypes.SplitOpen: {
  470. return { ...state, split: true, right: action.payload.itemState };
  471. }
  472. case ActionTypes.InitializeExploreSplit: {
  473. return { ...state, split: true };
  474. }
  475. case ActionTypes.ResetExplore: {
  476. return initialExploreState;
  477. }
  478. }
  479. if (action.payload) {
  480. const { exploreId } = action.payload as any;
  481. if (exploreId !== undefined) {
  482. const exploreItemState = state[exploreId];
  483. return { ...state, [exploreId]: itemReducer(exploreItemState, action) };
  484. }
  485. }
  486. return state;
  487. };
  488. export default {
  489. explore: exploreReducer,
  490. };