| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- import {
- calculateResultsFromQueryTransactions,
- generateEmptyQuery,
- getIntervals,
- ensureQueries,
- } from 'app/core/utils/explore';
- import { ExploreItemState, ExploreState, QueryTransaction } from 'app/types/explore';
- import { DataQuery } from '@grafana/ui/src/types';
- import { Action, ActionTypes } 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: [],
- initialQueries: [],
- initialized: false,
- modifiedQueries: [],
- 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,
- });
- /**
- * 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 = (state, action: Action): ExploreItemState => {
- switch (action.type) {
- case ActionTypes.AddQueryRow: {
- const { initialQueries, modifiedQueries, queryTransactions } = state;
- const { index, query } = action.payload;
- // Add new query row after given index, keep modifications of existing rows
- const nextModifiedQueries = [
- ...modifiedQueries.slice(0, index + 1),
- { ...query },
- ...initialQueries.slice(index + 1),
- ];
- // Add to initialQueries, which will cause a new row to be rendered
- const nextQueries = [...initialQueries.slice(0, index + 1), { ...query }, ...initialQueries.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,
- initialQueries: nextQueries,
- logsHighlighterExpressions: undefined,
- modifiedQueries: nextModifiedQueries,
- queryTransactions: nextQueryTransactions,
- };
- }
- case ActionTypes.ChangeQuery: {
- const { initialQueries, queryTransactions } = state;
- let { modifiedQueries } = state;
- const { query, index, override } = action.payload;
- // Fast path: only change modifiedQueries to not trigger an update
- modifiedQueries[index] = query;
- if (!override) {
- return {
- ...state,
- modifiedQueries,
- };
- }
- // Override path: queries are completely reset
- const nextQuery: DataQuery = {
- ...query,
- ...generateEmptyQuery(index),
- };
- const nextQueries = [...initialQueries];
- nextQueries[index] = nextQuery;
- modifiedQueries = [...nextQueries];
- // Discard ongoing transaction related to row query
- const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index);
- return {
- ...state,
- initialQueries: nextQueries,
- modifiedQueries: nextQueries.slice(),
- queryTransactions: nextQueryTransactions,
- };
- }
- case ActionTypes.ChangeSize: {
- 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 };
- }
- case ActionTypes.ChangeTime: {
- return {
- ...state,
- range: action.payload.range,
- };
- }
- case ActionTypes.ClearQueries: {
- const queries = ensureQueries();
- return {
- ...state,
- initialQueries: queries.slice(),
- modifiedQueries: queries.slice(),
- queryTransactions: [],
- showingStartPage: Boolean(state.StartPage),
- };
- }
- case ActionTypes.HighlightLogsExpression: {
- const { expressions } = action.payload;
- return { ...state, logsHighlighterExpressions: expressions };
- }
- case ActionTypes.InitializeExplore: {
- const { containerWidth, eventBridge, exploreDatasources, queries, range } = action.payload;
- return {
- ...state,
- containerWidth,
- eventBridge,
- exploreDatasources,
- range,
- initialQueries: queries,
- initialized: true,
- modifiedQueries: queries.slice(),
- };
- }
- case ActionTypes.UpdateDatasourceInstance: {
- const { datasourceInstance } = action.payload;
- return {
- ...state,
- datasourceInstance,
- datasourceName: datasourceInstance.name,
- };
- }
- case ActionTypes.LoadDatasourceFailure: {
- return { ...state, datasourceError: action.payload.error, datasourceLoading: false };
- }
- case ActionTypes.LoadDatasourceMissing: {
- return { ...state, datasourceMissing: true, datasourceLoading: false };
- }
- case ActionTypes.LoadDatasourcePending: {
- return { ...state, datasourceLoading: true, requestedDatasourceName: action.payload.requestedDatasourceName };
- }
- case ActionTypes.LoadDatasourceSuccess: {
- 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: [],
- };
- }
- case ActionTypes.ModifyQueries: {
- const { initialQueries, modifiedQueries, queryTransactions } = state;
- const { modification, index, modifier } = action.payload;
- let nextQueries: DataQuery[];
- let nextQueryTransactions;
- if (index === undefined) {
- // Modify all queries
- nextQueries = initialQueries.map((query, i) => ({
- ...modifier(modifiedQueries[i], modification),
- ...generateEmptyQuery(i),
- }));
- // Discard all ongoing transactions
- nextQueryTransactions = [];
- } else {
- // Modify query only at index
- nextQueries = initialQueries.map((query, i) => {
- // Synchronize all queries with local query cache to ensure consistency
- // TODO still needed?
- return i === index
- ? {
- ...modifier(modifiedQueries[i], 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,
- initialQueries: nextQueries,
- modifiedQueries: nextQueries.slice(),
- queryTransactions: nextQueryTransactions,
- };
- }
- case ActionTypes.QueryTransactionFailure: {
- const { queryTransactions } = action.payload;
- return {
- ...state,
- queryTransactions,
- showingStartPage: false,
- };
- }
- case ActionTypes.QueryTransactionStart: {
- 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,
- };
- }
- case ActionTypes.QueryTransactionSuccess: {
- const { datasourceInstance, queryIntervals } = state;
- const { history, queryTransactions } = action.payload;
- const results = calculateResultsFromQueryTransactions(
- queryTransactions,
- datasourceInstance,
- queryIntervals.intervalMs
- );
- return {
- ...state,
- ...results,
- history,
- queryTransactions,
- showingStartPage: false,
- };
- }
- case ActionTypes.RemoveQueryRow: {
- const { datasourceInstance, initialQueries, queryIntervals, queryTransactions } = state;
- let { modifiedQueries } = state;
- const { index } = action.payload;
- modifiedQueries = [...modifiedQueries.slice(0, index), ...modifiedQueries.slice(index + 1)];
- if (initialQueries.length <= 1) {
- return state;
- }
- const nextQueries = [...initialQueries.slice(0, index), ...initialQueries.slice(index + 1)];
- // Discard transactions related to row query
- const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index);
- const results = calculateResultsFromQueryTransactions(
- nextQueryTransactions,
- datasourceInstance,
- queryIntervals.intervalMs
- );
- return {
- ...state,
- ...results,
- initialQueries: nextQueries,
- logsHighlighterExpressions: undefined,
- modifiedQueries: nextQueries.slice(),
- queryTransactions: nextQueryTransactions,
- };
- }
- case ActionTypes.RunQueriesEmpty: {
- return { ...state, queryTransactions: [] };
- }
- case ActionTypes.ScanRange: {
- return { ...state, scanRange: action.payload.range };
- }
- case ActionTypes.ScanStart: {
- return { ...state, scanning: true, scanner: action.payload.scanner };
- }
- case ActionTypes.ScanStop: {
- const { queryTransactions } = state;
- const nextQueryTransactions = queryTransactions.filter(qt => qt.scanning && !qt.done);
- return {
- ...state,
- queryTransactions: nextQueryTransactions,
- scanning: false,
- scanRange: undefined,
- scanner: undefined,
- };
- }
- case ActionTypes.SetQueries: {
- const { queries } = action.payload;
- return { ...state, initialQueries: queries.slice(), modifiedQueries: queries.slice() };
- }
- case ActionTypes.ToggleGraph: {
- 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 };
- }
- case ActionTypes.ToggleLogs: {
- 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 };
- }
- case ActionTypes.ToggleTable: {
- 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 };
- }
- case ActionTypes.QueriesImported: {
- return {
- ...state,
- initialQueries: action.payload.queries,
- modifiedQueries: action.payload.queries.slice(),
- };
- }
- }
- return state;
- };
- /**
- * 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: Action): 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,
- };
|