| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501 |
- import {
- calculateResultsFromQueryTransactions,
- generateEmptyQuery,
- getIntervals,
- ensureQueries,
- getQueryKeys,
- } from 'app/core/utils/explore';
- import { ExploreItemState, ExploreState, QueryTransaction } from 'app/types/explore';
- import { DataQuery } from '@grafana/ui/src/types';
- import { HigherOrderAction, ActionTypes } from './actionTypes';
- import { reducerFactory } from 'app/core/redux';
- import {
- addQueryRowAction,
- changeQueryAction,
- changeSizeAction,
- changeTimeAction,
- clearQueriesAction,
- highlightLogsExpressionAction,
- initializeExploreAction,
- updateDatasourceInstanceAction,
- loadDatasourceFailureAction,
- loadDatasourceMissingAction,
- loadDatasourcePendingAction,
- loadDatasourceSuccessAction,
- modifyQueriesAction,
- queryTransactionFailureAction,
- queryTransactionStartAction,
- queryTransactionSuccessAction,
- removeQueryRowAction,
- runQueriesEmptyAction,
- scanRangeAction,
- scanStartAction,
- scanStopAction,
- setQueriesAction,
- toggleGraphAction,
- toggleLogsAction,
- toggleTableAction,
- queriesImportedAction,
- } from './actionTypes';
- export const DEFAULT_RANGE = {
- from: 'now-6h',
- to: 'now',
- };
- // Millies step for helper bar charts
- const DEFAULT_GRAPH_INTERVAL = 15 * 1000;
- /**
- * Returns a fresh Explore area state
- */
- export const makeExploreItemState = (): ExploreItemState => ({
- StartPage: undefined,
- containerWidth: 0,
- datasourceInstance: null,
- requestedDatasourceName: null,
- datasourceError: null,
- datasourceLoading: null,
- datasourceMissing: false,
- exploreDatasources: [],
- history: [],
- queries: [],
- initialized: false,
- queryTransactions: [],
- queryIntervals: { interval: '15s', intervalMs: DEFAULT_GRAPH_INTERVAL },
- range: DEFAULT_RANGE,
- scanning: false,
- scanRange: null,
- showingGraph: true,
- showingLogs: true,
- showingTable: true,
- supportsGraph: null,
- supportsLogs: null,
- supportsTable: null,
- queryKeys: [],
- });
- /**
- * Global Explore state that handles multiple Explore areas and the split state
- */
- export const initialExploreState: ExploreState = {
- split: null,
- left: makeExploreItemState(),
- right: makeExploreItemState(),
- };
- /**
- * Reducer for an Explore area, to be used by the global Explore reducer.
- */
- export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemState)
- .addMapper({
- filter: addQueryRowAction,
- mapper: (state, action): ExploreItemState => {
- const { queries, queryTransactions } = state;
- const { index, query } = action.payload;
- // Add to queries, which will cause a new row to be rendered
- const nextQueries = [...queries.slice(0, index + 1), { ...query }, ...queries.slice(index + 1)];
- // Ongoing transactions need to update their row indices
- const nextQueryTransactions = queryTransactions.map(qt => {
- if (qt.rowIndex > index) {
- return {
- ...qt,
- rowIndex: qt.rowIndex + 1,
- };
- }
- return qt;
- });
- return {
- ...state,
- queries: nextQueries,
- logsHighlighterExpressions: undefined,
- queryTransactions: nextQueryTransactions,
- queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
- };
- },
- })
- .addMapper({
- filter: changeQueryAction,
- mapper: (state, action): ExploreItemState => {
- const { queries, queryTransactions } = state;
- const { query, index } = action.payload;
- // Override path: queries are completely reset
- const nextQuery: DataQuery = { ...query, ...generateEmptyQuery(index) };
- const nextQueries = [...queries];
- nextQueries[index] = nextQuery;
- // Discard ongoing transaction related to row query
- const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index);
- return {
- ...state,
- queries: nextQueries,
- queryTransactions: nextQueryTransactions,
- queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
- };
- },
- })
- .addMapper({
- filter: changeSizeAction,
- mapper: (state, action): ExploreItemState => {
- const { range, datasourceInstance } = state;
- let interval = '1s';
- if (datasourceInstance && datasourceInstance.interval) {
- interval = datasourceInstance.interval;
- }
- const containerWidth = action.payload.width;
- const queryIntervals = getIntervals(range, interval, containerWidth);
- return { ...state, containerWidth, queryIntervals };
- },
- })
- .addMapper({
- filter: changeTimeAction,
- mapper: (state, action): ExploreItemState => {
- return { ...state, range: action.payload.range };
- },
- })
- .addMapper({
- filter: clearQueriesAction,
- mapper: (state): ExploreItemState => {
- const queries = ensureQueries();
- return {
- ...state,
- queries: queries.slice(),
- queryTransactions: [],
- showingStartPage: Boolean(state.StartPage),
- queryKeys: getQueryKeys(queries, state.datasourceInstance),
- };
- },
- })
- .addMapper({
- filter: highlightLogsExpressionAction,
- mapper: (state, action): ExploreItemState => {
- const { expressions } = action.payload;
- return { ...state, logsHighlighterExpressions: expressions };
- },
- })
- .addMapper({
- filter: initializeExploreAction,
- mapper: (state, action): ExploreItemState => {
- const { containerWidth, eventBridge, exploreDatasources, queries, range, ui } = action.payload;
- return {
- ...state,
- containerWidth,
- eventBridge,
- exploreDatasources,
- range,
- queries,
- initialized: true,
- queryKeys: getQueryKeys(queries, state.datasourceInstance),
- ...ui,
- };
- },
- })
- .addMapper({
- filter: updateDatasourceInstanceAction,
- mapper: (state, action): ExploreItemState => {
- const { datasourceInstance } = action.payload;
- return { ...state, datasourceInstance, queryKeys: getQueryKeys(state.queries, datasourceInstance) };
- },
- })
- .addMapper({
- filter: loadDatasourceFailureAction,
- mapper: (state, action): ExploreItemState => {
- return { ...state, datasourceError: action.payload.error, datasourceLoading: false };
- },
- })
- .addMapper({
- filter: loadDatasourceMissingAction,
- mapper: (state): ExploreItemState => {
- return { ...state, datasourceMissing: true, datasourceLoading: false };
- },
- })
- .addMapper({
- filter: loadDatasourcePendingAction,
- mapper: (state, action): ExploreItemState => {
- return { ...state, datasourceLoading: true, requestedDatasourceName: action.payload.requestedDatasourceName };
- },
- })
- .addMapper({
- filter: loadDatasourceSuccessAction,
- mapper: (state, action): ExploreItemState => {
- const { containerWidth, range } = state;
- const {
- StartPage,
- datasourceInstance,
- history,
- showingStartPage,
- supportsGraph,
- supportsLogs,
- supportsTable,
- } = action.payload;
- const queryIntervals = getIntervals(range, datasourceInstance.interval, containerWidth);
- return {
- ...state,
- queryIntervals,
- StartPage,
- datasourceInstance,
- history,
- showingStartPage,
- supportsGraph,
- supportsLogs,
- supportsTable,
- datasourceLoading: false,
- datasourceMissing: false,
- datasourceError: null,
- logsHighlighterExpressions: undefined,
- queryTransactions: [],
- };
- },
- })
- .addMapper({
- filter: modifyQueriesAction,
- mapper: (state, action): ExploreItemState => {
- const { queries, queryTransactions } = state;
- const { modification, index, modifier } = action.payload;
- let nextQueries: DataQuery[];
- let nextQueryTransactions;
- if (index === undefined) {
- // Modify all queries
- nextQueries = queries.map((query, i) => ({
- ...modifier({ ...query }, modification),
- ...generateEmptyQuery(i),
- }));
- // Discard all ongoing transactions
- nextQueryTransactions = [];
- } else {
- // Modify query only at index
- nextQueries = queries.map((query, i) => {
- // Synchronize all queries with local query cache to ensure consistency
- // TODO still needed?
- return i === index ? { ...modifier({ ...query }, modification), ...generateEmptyQuery(i) } : query;
- });
- nextQueryTransactions = queryTransactions
- // Consume the hint corresponding to the action
- .map(qt => {
- if (qt.hints != null && qt.rowIndex === index) {
- qt.hints = qt.hints.filter(hint => hint.fix.action !== modification);
- }
- return qt;
- })
- // Preserve previous row query transaction to keep results visible if next query is incomplete
- .filter(qt => modification.preventSubmit || qt.rowIndex !== index);
- }
- return {
- ...state,
- queries: nextQueries,
- queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
- queryTransactions: nextQueryTransactions,
- };
- },
- })
- .addMapper({
- filter: queryTransactionFailureAction,
- mapper: (state, action): ExploreItemState => {
- const { queryTransactions } = action.payload;
- return { ...state, queryTransactions, showingStartPage: false };
- },
- })
- .addMapper({
- filter: queryTransactionStartAction,
- mapper: (state, action): ExploreItemState => {
- const { queryTransactions } = state;
- const { resultType, rowIndex, transaction } = action.payload;
- // Discarding existing transactions of same type
- const remainingTransactions = queryTransactions.filter(
- qt => !(qt.resultType === resultType && qt.rowIndex === rowIndex)
- );
- // Append new transaction
- const nextQueryTransactions: QueryTransaction[] = [...remainingTransactions, transaction];
- return { ...state, queryTransactions: nextQueryTransactions, showingStartPage: false };
- },
- })
- .addMapper({
- filter: queryTransactionSuccessAction,
- mapper: (state, action): ExploreItemState => {
- const { datasourceInstance, queryIntervals } = state;
- const { history, queryTransactions } = action.payload;
- const results = calculateResultsFromQueryTransactions(
- queryTransactions,
- datasourceInstance,
- queryIntervals.intervalMs
- );
- return { ...state, ...results, history, queryTransactions, showingStartPage: false };
- },
- })
- .addMapper({
- filter: removeQueryRowAction,
- mapper: (state, action): ExploreItemState => {
- const { datasourceInstance, queries, queryIntervals, queryTransactions, queryKeys } = state;
- const { index } = action.payload;
- if (queries.length <= 1) {
- return state;
- }
- const nextQueries = [...queries.slice(0, index), ...queries.slice(index + 1)];
- const nextQueryKeys = [...queryKeys.slice(0, index), ...queryKeys.slice(index + 1)];
- // Discard transactions related to row query
- const nextQueryTransactions = queryTransactions.filter(qt => nextQueries.some(nq => nq.key === qt.query.key));
- const results = calculateResultsFromQueryTransactions(
- nextQueryTransactions,
- datasourceInstance,
- queryIntervals.intervalMs
- );
- return {
- ...state,
- ...results,
- queries: nextQueries,
- logsHighlighterExpressions: undefined,
- queryTransactions: nextQueryTransactions,
- queryKeys: nextQueryKeys,
- };
- },
- })
- .addMapper({
- filter: runQueriesEmptyAction,
- mapper: (state): ExploreItemState => {
- return { ...state, queryTransactions: [] };
- },
- })
- .addMapper({
- filter: scanRangeAction,
- mapper: (state, action): ExploreItemState => {
- return { ...state, scanRange: action.payload.range };
- },
- })
- .addMapper({
- filter: scanStartAction,
- mapper: (state, action): ExploreItemState => {
- return { ...state, scanning: true, scanner: action.payload.scanner };
- },
- })
- .addMapper({
- filter: scanStopAction,
- mapper: (state): ExploreItemState => {
- const { queryTransactions } = state;
- const nextQueryTransactions = queryTransactions.filter(qt => qt.scanning && !qt.done);
- return {
- ...state,
- queryTransactions: nextQueryTransactions,
- scanning: false,
- scanRange: undefined,
- scanner: undefined,
- };
- },
- })
- .addMapper({
- filter: setQueriesAction,
- mapper: (state, action): ExploreItemState => {
- const { queries } = action.payload;
- return {
- ...state,
- queries: queries.slice(),
- queryKeys: getQueryKeys(queries, state.datasourceInstance),
- };
- },
- })
- .addMapper({
- filter: toggleGraphAction,
- mapper: (state): ExploreItemState => {
- const showingGraph = !state.showingGraph;
- let nextQueryTransactions = state.queryTransactions;
- if (!showingGraph) {
- // Discard transactions related to Graph query
- nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Graph');
- }
- return { ...state, queryTransactions: nextQueryTransactions, showingGraph };
- },
- })
- .addMapper({
- filter: toggleLogsAction,
- mapper: (state): ExploreItemState => {
- const showingLogs = !state.showingLogs;
- let nextQueryTransactions = state.queryTransactions;
- if (!showingLogs) {
- // Discard transactions related to Logs query
- nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Logs');
- }
- return { ...state, queryTransactions: nextQueryTransactions, showingLogs };
- },
- })
- .addMapper({
- filter: toggleTableAction,
- mapper: (state): ExploreItemState => {
- const showingTable = !state.showingTable;
- if (showingTable) {
- return { ...state, showingTable, queryTransactions: state.queryTransactions };
- }
- // Toggle off needs discarding of table queries and results
- const nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Table');
- const results = calculateResultsFromQueryTransactions(
- nextQueryTransactions,
- state.datasourceInstance,
- state.queryIntervals.intervalMs
- );
- return { ...state, ...results, queryTransactions: nextQueryTransactions, showingTable };
- },
- })
- .addMapper({
- filter: queriesImportedAction,
- mapper: (state, action): ExploreItemState => {
- const { queries } = action.payload;
- return {
- ...state,
- queries,
- queryKeys: getQueryKeys(queries, state.datasourceInstance),
- };
- },
- })
- .create();
- /**
- * Global Explore reducer that handles multiple Explore areas (left and right).
- * Actions that have an `exploreId` get routed to the ExploreItemReducer.
- */
- export const exploreReducer = (state = initialExploreState, action: HigherOrderAction): ExploreState => {
- switch (action.type) {
- case ActionTypes.SplitClose: {
- return { ...state, split: false };
- }
- case ActionTypes.SplitOpen: {
- return { ...state, split: true, right: action.payload.itemState };
- }
- case ActionTypes.InitializeExploreSplit: {
- return { ...state, split: true };
- }
- case ActionTypes.ResetExplore: {
- return initialExploreState;
- }
- }
- if (action.payload) {
- const { exploreId } = action.payload as any;
- if (exploreId !== undefined) {
- const exploreItemState = state[exploreId];
- return { ...state, [exploreId]: itemReducer(exploreItemState, action) };
- }
- }
- return state;
- };
- export default {
- explore: exploreReducer,
- };
|