reducers.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  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(state.queries) };
  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(state.queries),
  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
  267. ? { ...modifier({ ...query }, modification), ...generateEmptyQuery(state.queries) }
  268. : query;
  269. });
  270. nextQueryTransactions = queryTransactions
  271. // Consume the hint corresponding to the action
  272. .map(qt => {
  273. if (qt.hints != null && qt.rowIndex === index) {
  274. qt.hints = qt.hints.filter(hint => hint.fix.action !== modification);
  275. }
  276. return qt;
  277. })
  278. // Preserve previous row query transaction to keep results visible if next query is incomplete
  279. .filter(qt => modification.preventSubmit || qt.rowIndex !== index);
  280. }
  281. return {
  282. ...state,
  283. queries: nextQueries,
  284. queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
  285. queryTransactions: nextQueryTransactions,
  286. };
  287. },
  288. })
  289. .addMapper({
  290. filter: queryTransactionFailureAction,
  291. mapper: (state, action): ExploreItemState => {
  292. const { queryTransactions } = action.payload;
  293. return { ...state, queryTransactions, showingStartPage: false };
  294. },
  295. })
  296. .addMapper({
  297. filter: queryTransactionStartAction,
  298. mapper: (state, action): ExploreItemState => {
  299. const { queryTransactions } = state;
  300. const { resultType, rowIndex, transaction } = action.payload;
  301. // Discarding existing transactions of same type
  302. const remainingTransactions = queryTransactions.filter(
  303. qt => !(qt.resultType === resultType && qt.rowIndex === rowIndex)
  304. );
  305. // Append new transaction
  306. const nextQueryTransactions: QueryTransaction[] = [...remainingTransactions, transaction];
  307. return { ...state, queryTransactions: nextQueryTransactions, showingStartPage: false };
  308. },
  309. })
  310. .addMapper({
  311. filter: queryTransactionSuccessAction,
  312. mapper: (state, action): ExploreItemState => {
  313. const { datasourceInstance, queryIntervals } = state;
  314. const { history, queryTransactions } = action.payload;
  315. const results = calculateResultsFromQueryTransactions(
  316. queryTransactions,
  317. datasourceInstance,
  318. queryIntervals.intervalMs
  319. );
  320. return { ...state, ...results, history, queryTransactions, showingStartPage: false };
  321. },
  322. })
  323. .addMapper({
  324. filter: removeQueryRowAction,
  325. mapper: (state, action): ExploreItemState => {
  326. const { datasourceInstance, queries, queryIntervals, queryTransactions, queryKeys } = state;
  327. const { index } = action.payload;
  328. if (queries.length <= 1) {
  329. return state;
  330. }
  331. const nextQueries = [...queries.slice(0, index), ...queries.slice(index + 1)];
  332. const nextQueryKeys = [...queryKeys.slice(0, index), ...queryKeys.slice(index + 1)];
  333. // Discard transactions related to row query
  334. const nextQueryTransactions = queryTransactions.filter(qt => nextQueries.some(nq => nq.key === qt.query.key));
  335. const results = calculateResultsFromQueryTransactions(
  336. nextQueryTransactions,
  337. datasourceInstance,
  338. queryIntervals.intervalMs
  339. );
  340. return {
  341. ...state,
  342. ...results,
  343. queries: nextQueries,
  344. logsHighlighterExpressions: undefined,
  345. queryTransactions: nextQueryTransactions,
  346. queryKeys: nextQueryKeys,
  347. };
  348. },
  349. })
  350. .addMapper({
  351. filter: runQueriesEmptyAction,
  352. mapper: (state): ExploreItemState => {
  353. return { ...state, queryTransactions: [] };
  354. },
  355. })
  356. .addMapper({
  357. filter: scanRangeAction,
  358. mapper: (state, action): ExploreItemState => {
  359. return { ...state, scanRange: action.payload.range };
  360. },
  361. })
  362. .addMapper({
  363. filter: scanStartAction,
  364. mapper: (state, action): ExploreItemState => {
  365. return { ...state, scanning: true, scanner: action.payload.scanner };
  366. },
  367. })
  368. .addMapper({
  369. filter: scanStopAction,
  370. mapper: (state): ExploreItemState => {
  371. const { queryTransactions } = state;
  372. const nextQueryTransactions = queryTransactions.filter(qt => qt.scanning && !qt.done);
  373. return {
  374. ...state,
  375. queryTransactions: nextQueryTransactions,
  376. scanning: false,
  377. scanRange: undefined,
  378. scanner: undefined,
  379. };
  380. },
  381. })
  382. .addMapper({
  383. filter: setQueriesAction,
  384. mapper: (state, action): ExploreItemState => {
  385. const { queries } = action.payload;
  386. return {
  387. ...state,
  388. queries: queries.slice(),
  389. queryKeys: getQueryKeys(queries, state.datasourceInstance),
  390. };
  391. },
  392. })
  393. .addMapper({
  394. filter: updateUIStateAction,
  395. mapper: (state, action): ExploreItemState => {
  396. return { ...state, ...action.payload };
  397. },
  398. })
  399. .addMapper({
  400. filter: toggleGraphAction,
  401. mapper: (state): ExploreItemState => {
  402. const showingGraph = !state.showingGraph;
  403. let nextQueryTransactions = state.queryTransactions;
  404. if (!showingGraph) {
  405. // Discard transactions related to Graph query
  406. nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Graph');
  407. }
  408. return { ...state, queryTransactions: nextQueryTransactions };
  409. },
  410. })
  411. .addMapper({
  412. filter: toggleLogsAction,
  413. mapper: (state): ExploreItemState => {
  414. const showingLogs = !state.showingLogs;
  415. let nextQueryTransactions = state.queryTransactions;
  416. if (!showingLogs) {
  417. // Discard transactions related to Logs query
  418. nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Logs');
  419. }
  420. return { ...state, queryTransactions: nextQueryTransactions };
  421. },
  422. })
  423. .addMapper({
  424. filter: toggleTableAction,
  425. mapper: (state): ExploreItemState => {
  426. const showingTable = !state.showingTable;
  427. if (showingTable) {
  428. return { ...state, queryTransactions: state.queryTransactions };
  429. }
  430. // Toggle off needs discarding of table queries and results
  431. const nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Table');
  432. const results = calculateResultsFromQueryTransactions(
  433. nextQueryTransactions,
  434. state.datasourceInstance,
  435. state.queryIntervals.intervalMs
  436. );
  437. return { ...state, ...results, queryTransactions: nextQueryTransactions };
  438. },
  439. })
  440. .addMapper({
  441. filter: queriesImportedAction,
  442. mapper: (state, action): ExploreItemState => {
  443. const { queries } = action.payload;
  444. return {
  445. ...state,
  446. queries,
  447. queryKeys: getQueryKeys(queries, state.datasourceInstance),
  448. };
  449. },
  450. })
  451. .addMapper({
  452. filter: toggleLogLevelAction,
  453. mapper: (state, action): ExploreItemState => {
  454. const { hiddenLogLevels } = action.payload;
  455. return {
  456. ...state,
  457. hiddenLogLevels: Array.from(hiddenLogLevels),
  458. };
  459. },
  460. })
  461. .create();
  462. /**
  463. * Global Explore reducer that handles multiple Explore areas (left and right).
  464. * Actions that have an `exploreId` get routed to the ExploreItemReducer.
  465. */
  466. export const exploreReducer = (state = initialExploreState, action: HigherOrderAction): ExploreState => {
  467. switch (action.type) {
  468. case ActionTypes.SplitClose: {
  469. return { ...state, split: false };
  470. }
  471. case ActionTypes.SplitOpen: {
  472. return { ...state, split: true, right: action.payload.itemState };
  473. }
  474. case ActionTypes.InitializeExploreSplit: {
  475. return { ...state, split: true };
  476. }
  477. case ActionTypes.ResetExplore: {
  478. return initialExploreState;
  479. }
  480. }
  481. if (action.payload) {
  482. const { exploreId } = action.payload as any;
  483. if (exploreId !== undefined) {
  484. const exploreItemState = state[exploreId];
  485. return { ...state, [exploreId]: itemReducer(exploreItemState, action) };
  486. }
  487. }
  488. return state;
  489. };
  490. export default {
  491. explore: exploreReducer,
  492. };