diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 06a6ae24cac..31ffdf4ab24 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -18,15 +18,7 @@ import TableContainer from './TableContainer'; import TimePicker, { parseTime } from './TimePicker'; // Actions -import { - changeSize, - changeTime, - initializeExplore, - modifyQueries, - scanStart, - scanStop, - setQueries, -} from './state/actions'; +import { changeSize, changeTime, initializeExplore, modifyQueries, scanStart, setQueries } from './state/actions'; // Types import { RawTimeRange, TimeRange, DataQuery } from '@grafana/ui'; @@ -35,6 +27,7 @@ import { StoreState } from 'app/types'; import { LAST_USED_DATASOURCE_KEY, ensureQueries, DEFAULT_RANGE } from 'app/core/utils/explore'; import { Emitter } from 'app/core/utils/emitter'; import { ExploreToolbar } from './ExploreToolbar'; +import { scanStopAction } from './state/actionTypes'; interface ExploreProps { StartPage?: any; @@ -54,7 +47,7 @@ interface ExploreProps { scanning?: boolean; scanRange?: RawTimeRange; scanStart: typeof scanStart; - scanStop: typeof scanStop; + scanStopAction: typeof scanStopAction; setQueries: typeof setQueries; split: boolean; showingStartPage?: boolean; @@ -171,7 +164,7 @@ export class Explore extends React.PureComponent<ExploreProps> { }; onStopScanning = () => { - this.props.scanStop(this.props.exploreId); + this.props.scanStopAction({ exploreId: this.props.exploreId }); }; render() { @@ -281,7 +274,7 @@ const mapDispatchToProps = { initializeExplore, modifyQueries, scanStart, - scanStop, + scanStopAction, setQueries, }; diff --git a/public/app/features/explore/QueryRow.tsx b/public/app/features/explore/QueryRow.tsx index bbc0bf0d101..5e2e8442e54 100644 --- a/public/app/features/explore/QueryRow.tsx +++ b/public/app/features/explore/QueryRow.tsx @@ -9,20 +9,14 @@ import QueryEditor from './QueryEditor'; import QueryTransactionStatus from './QueryTransactionStatus'; // Actions -import { - addQueryRow, - changeQuery, - highlightLogsExpression, - modifyQueries, - removeQueryRow, - runQueries, -} from './state/actions'; +import { changeQuery, modifyQueries, runQueries, addQueryRow } from './state/actions'; // Types import { StoreState } from 'app/types'; import { RawTimeRange, DataQuery, ExploreDataSourceApi, QueryHint, QueryFixAction } from '@grafana/ui'; import { QueryTransaction, HistoryItem, ExploreItemState, ExploreId } from 'app/types/explore'; import { Emitter } from 'app/core/utils/emitter'; +import { highlightLogsExpressionAction, removeQueryRowAction } from './state/actionTypes'; function getFirstHintFromTransactions(transactions: QueryTransaction[]): QueryHint { const transaction = transactions.find(qt => qt.hints && qt.hints.length > 0); @@ -38,7 +32,7 @@ interface QueryRowProps { className?: string; exploreId: ExploreId; datasourceInstance: ExploreDataSourceApi; - highlightLogsExpression: typeof highlightLogsExpression; + highlightLogsExpressionAction: typeof highlightLogsExpressionAction; history: HistoryItem[]; index: number; initialQuery: DataQuery; @@ -46,7 +40,7 @@ interface QueryRowProps { queryTransactions: QueryTransaction[]; exploreEvents: Emitter; range: RawTimeRange; - removeQueryRow: typeof removeQueryRow; + removeQueryRowAction: typeof removeQueryRowAction; runQueries: typeof runQueries; } @@ -88,14 +82,15 @@ export class QueryRow extends PureComponent<QueryRowProps> { onClickRemoveButton = () => { const { exploreId, index } = this.props; - this.props.removeQueryRow(exploreId, index); + this.props.removeQueryRowAction({ exploreId, index }); }; updateLogsHighlights = _.debounce((value: DataQuery) => { const { datasourceInstance } = this.props; if (datasourceInstance.getHighlighterExpression) { + const { exploreId } = this.props; const expressions = [datasourceInstance.getHighlighterExpression(value)]; - this.props.highlightLogsExpression(this.props.exploreId, expressions); + this.props.highlightLogsExpressionAction({ exploreId, expressions }); } }, 500); @@ -168,9 +163,9 @@ function mapStateToProps(state: StoreState, { exploreId, index }) { const mapDispatchToProps = { addQueryRow, changeQuery, - highlightLogsExpression, + highlightLogsExpressionAction, modifyQueries, - removeQueryRow, + removeQueryRowAction, runQueries, }; diff --git a/public/app/features/explore/Wrapper.tsx b/public/app/features/explore/Wrapper.tsx index aca2e6d8cbd..f64b2704b71 100644 --- a/public/app/features/explore/Wrapper.tsx +++ b/public/app/features/explore/Wrapper.tsx @@ -7,16 +7,16 @@ import { StoreState } from 'app/types'; import { ExploreId, ExploreUrlState } from 'app/types/explore'; import { parseUrlState } from 'app/core/utils/explore'; -import { initializeExploreSplit, resetExplore } from './state/actions'; import ErrorBoundary from './ErrorBoundary'; import Explore from './Explore'; import { CustomScrollbar } from '@grafana/ui'; +import { initializeExploreSplitAction, resetExploreAction } from './state/actionTypes'; interface WrapperProps { - initializeExploreSplit: typeof initializeExploreSplit; + initializeExploreSplitAction: typeof initializeExploreSplitAction; split: boolean; updateLocation: typeof updateLocation; - resetExplore: typeof resetExplore; + resetExploreAction: typeof resetExploreAction; urlStates: { [key: string]: string }; } @@ -39,12 +39,12 @@ export class Wrapper extends Component<WrapperProps> { componentDidMount() { if (this.initialSplit) { - this.props.initializeExploreSplit(); + this.props.initializeExploreSplitAction(); } } componentWillUnmount() { - this.props.resetExplore(); + this.props.resetExploreAction(); } render() { @@ -77,9 +77,9 @@ const mapStateToProps = (state: StoreState) => { }; const mapDispatchToProps = { - initializeExploreSplit, + initializeExploreSplitAction, updateLocation, - resetExplore, + resetExploreAction, }; export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(Wrapper)); diff --git a/public/app/features/explore/state/actionTypes.ts b/public/app/features/explore/state/actionTypes.ts index 53954f4dc2b..05ef661a8e5 100644 --- a/public/app/features/explore/state/actionTypes.ts +++ b/public/app/features/explore/state/actionTypes.ts @@ -1,6 +1,13 @@ // Types import { Emitter } from 'app/core/core'; -import { RawTimeRange, TimeRange, DataQuery, DataSourceSelectItem, DataSourceApi, QueryFixAction } from '@grafana/ui/src/types'; +import { + RawTimeRange, + TimeRange, + DataQuery, + DataSourceSelectItem, + DataSourceApi, + QueryFixAction, +} from '@grafana/ui/src/types'; import { ExploreId, ExploreItemState, @@ -9,233 +16,26 @@ import { ResultType, QueryTransaction, } from 'app/types/explore'; +import { actionCreatorFactory, noPayloadActionCreatorFactory, ActionOf } from 'app/core/redux/actionCreatorFactory'; +/** Higher order actions + * + */ export enum ActionTypes { - AddQueryRow = 'explore/ADD_QUERY_ROW', - ChangeDatasource = 'explore/CHANGE_DATASOURCE', - ChangeQuery = 'explore/CHANGE_QUERY', - ChangeSize = 'explore/CHANGE_SIZE', - ChangeTime = 'explore/CHANGE_TIME', - ClearQueries = 'explore/CLEAR_QUERIES', - HighlightLogsExpression = 'explore/HIGHLIGHT_LOGS_EXPRESSION', - InitializeExplore = 'explore/INITIALIZE_EXPLORE', InitializeExploreSplit = 'explore/INITIALIZE_EXPLORE_SPLIT', - LoadDatasourceFailure = 'explore/LOAD_DATASOURCE_FAILURE', - LoadDatasourceMissing = 'explore/LOAD_DATASOURCE_MISSING', - LoadDatasourcePending = 'explore/LOAD_DATASOURCE_PENDING', - LoadDatasourceSuccess = 'explore/LOAD_DATASOURCE_SUCCESS', - ModifyQueries = 'explore/MODIFY_QUERIES', - QueryTransactionFailure = 'explore/QUERY_TRANSACTION_FAILURE', - QueryTransactionStart = 'explore/QUERY_TRANSACTION_START', - QueryTransactionSuccess = 'explore/QUERY_TRANSACTION_SUCCESS', - RemoveQueryRow = 'explore/REMOVE_QUERY_ROW', - RunQueries = 'explore/RUN_QUERIES', - RunQueriesEmpty = 'explore/RUN_QUERIES_EMPTY', - ScanRange = 'explore/SCAN_RANGE', - ScanStart = 'explore/SCAN_START', - ScanStop = 'explore/SCAN_STOP', - SetQueries = 'explore/SET_QUERIES', SplitClose = 'explore/SPLIT_CLOSE', SplitOpen = 'explore/SPLIT_OPEN', - StateSave = 'explore/STATE_SAVE', - ToggleGraph = 'explore/TOGGLE_GRAPH', - ToggleLogs = 'explore/TOGGLE_LOGS', - ToggleTable = 'explore/TOGGLE_TABLE', - UpdateDatasourceInstance = 'explore/UPDATE_DATASOURCE_INSTANCE', ResetExplore = 'explore/RESET_EXPLORE', - QueriesImported = 'explore/QueriesImported', -} - -export interface AddQueryRowAction { - type: ActionTypes.AddQueryRow; - payload: { - exploreId: ExploreId; - index: number; - query: DataQuery; - }; -} - -export interface ChangeQueryAction { - type: ActionTypes.ChangeQuery; - payload: { - exploreId: ExploreId; - query: DataQuery; - index: number; - override: boolean; - }; -} - -export interface ChangeSizeAction { - type: ActionTypes.ChangeSize; - payload: { - exploreId: ExploreId; - width: number; - height: number; - }; -} - -export interface ChangeTimeAction { - type: ActionTypes.ChangeTime; - payload: { - exploreId: ExploreId; - range: TimeRange; - }; -} - -export interface ClearQueriesAction { - type: ActionTypes.ClearQueries; - payload: { - exploreId: ExploreId; - }; -} - -export interface HighlightLogsExpressionAction { - type: ActionTypes.HighlightLogsExpression; - payload: { - exploreId: ExploreId; - expressions: string[]; - }; -} - -export interface InitializeExploreAction { - type: ActionTypes.InitializeExplore; - payload: { - exploreId: ExploreId; - containerWidth: number; - eventBridge: Emitter; - exploreDatasources: DataSourceSelectItem[]; - queries: DataQuery[]; - range: RawTimeRange; - }; } export interface InitializeExploreSplitAction { type: ActionTypes.InitializeExploreSplit; -} - -export interface LoadDatasourceFailureAction { - type: ActionTypes.LoadDatasourceFailure; - payload: { - exploreId: ExploreId; - error: string; - }; -} - -export interface LoadDatasourcePendingAction { - type: ActionTypes.LoadDatasourcePending; - payload: { - exploreId: ExploreId; - requestedDatasourceName: string; - }; -} - -export interface LoadDatasourceMissingAction { - type: ActionTypes.LoadDatasourceMissing; - payload: { - exploreId: ExploreId; - }; -} - -export interface LoadDatasourceSuccessAction { - type: ActionTypes.LoadDatasourceSuccess; - payload: { - exploreId: ExploreId; - StartPage?: any; - datasourceInstance: any; - history: HistoryItem[]; - logsHighlighterExpressions?: any[]; - showingStartPage: boolean; - supportsGraph: boolean; - supportsLogs: boolean; - supportsTable: boolean; - }; -} - -export interface ModifyQueriesAction { - type: ActionTypes.ModifyQueries; - payload: { - exploreId: ExploreId; - modification: QueryFixAction; - index: number; - modifier: (queries: DataQuery[], modification: QueryFixAction) => DataQuery[]; - }; -} - -export interface QueryTransactionFailureAction { - type: ActionTypes.QueryTransactionFailure; - payload: { - exploreId: ExploreId; - queryTransactions: QueryTransaction[]; - }; -} - -export interface QueryTransactionStartAction { - type: ActionTypes.QueryTransactionStart; - payload: { - exploreId: ExploreId; - resultType: ResultType; - rowIndex: number; - transaction: QueryTransaction; - }; -} - -export interface QueryTransactionSuccessAction { - type: ActionTypes.QueryTransactionSuccess; - payload: { - exploreId: ExploreId; - history: HistoryItem[]; - queryTransactions: QueryTransaction[]; - }; -} - -export interface RemoveQueryRowAction { - type: ActionTypes.RemoveQueryRow; - payload: { - exploreId: ExploreId; - index: number; - }; -} - -export interface RunQueriesEmptyAction { - type: ActionTypes.RunQueriesEmpty; - payload: { - exploreId: ExploreId; - }; -} - -export interface ScanStartAction { - type: ActionTypes.ScanStart; - payload: { - exploreId: ExploreId; - scanner: RangeScanner; - }; -} - -export interface ScanRangeAction { - type: ActionTypes.ScanRange; - payload: { - exploreId: ExploreId; - range: RawTimeRange; - }; -} - -export interface ScanStopAction { - type: ActionTypes.ScanStop; - payload: { - exploreId: ExploreId; - }; -} - -export interface SetQueriesAction { - type: ActionTypes.SetQueries; - payload: { - exploreId: ExploreId; - queries: DataQuery[]; - }; + payload: {}; } export interface SplitCloseAction { type: ActionTypes.SplitClose; + payload: {}; } export interface SplitOpenAction { @@ -245,80 +45,384 @@ export interface SplitOpenAction { }; } -export interface StateSaveAction { - type: ActionTypes.StateSave; -} - -export interface ToggleTableAction { - type: ActionTypes.ToggleTable; - payload: { - exploreId: ExploreId; - }; -} - -export interface ToggleGraphAction { - type: ActionTypes.ToggleGraph; - payload: { - exploreId: ExploreId; - }; -} - -export interface ToggleLogsAction { - type: ActionTypes.ToggleLogs; - payload: { - exploreId: ExploreId; - }; -} - -export interface UpdateDatasourceInstanceAction { - type: ActionTypes.UpdateDatasourceInstance; - payload: { - exploreId: ExploreId; - datasourceInstance: DataSourceApi; - }; -} - export interface ResetExploreAction { type: ActionTypes.ResetExplore; payload: {}; } -export interface QueriesImported { - type: ActionTypes.QueriesImported; - payload: { - exploreId: ExploreId; - queries: DataQuery[]; - }; +/** Lower order actions + * + */ +export interface AddQueryRowPayload { + exploreId: ExploreId; + index: number; + query: DataQuery; } -export type Action = - | AddQueryRowAction - | ChangeQueryAction - | ChangeSizeAction - | ChangeTimeAction - | ClearQueriesAction - | HighlightLogsExpressionAction - | InitializeExploreAction +export interface ChangeQueryPayload { + exploreId: ExploreId; + query: DataQuery; + index: number; + override: boolean; +} + +export interface ChangeSizePayload { + exploreId: ExploreId; + width: number; + height: number; +} + +export interface ChangeTimePayload { + exploreId: ExploreId; + range: TimeRange; +} + +export interface ClearQueriesPayload { + exploreId: ExploreId; +} + +export interface HighlightLogsExpressionPayload { + exploreId: ExploreId; + expressions: string[]; +} + +export interface InitializeExplorePayload { + exploreId: ExploreId; + containerWidth: number; + eventBridge: Emitter; + exploreDatasources: DataSourceSelectItem[]; + queries: DataQuery[]; + range: RawTimeRange; +} + +export interface LoadDatasourceFailurePayload { + exploreId: ExploreId; + error: string; +} + +export interface LoadDatasourceMissingPayload { + exploreId: ExploreId; +} + +export interface LoadDatasourcePendingPayload { + exploreId: ExploreId; + requestedDatasourceName: string; +} + +export interface LoadDatasourceSuccessPayload { + exploreId: ExploreId; + StartPage?: any; + datasourceInstance: any; + history: HistoryItem[]; + logsHighlighterExpressions?: any[]; + showingStartPage: boolean; + supportsGraph: boolean; + supportsLogs: boolean; + supportsTable: boolean; +} + +export interface ModifyQueriesPayload { + exploreId: ExploreId; + modification: QueryFixAction; + index: number; + modifier: (query: DataQuery, modification: QueryFixAction) => DataQuery; +} + +export interface QueryTransactionFailurePayload { + exploreId: ExploreId; + queryTransactions: QueryTransaction[]; +} + +export interface QueryTransactionStartPayload { + exploreId: ExploreId; + resultType: ResultType; + rowIndex: number; + transaction: QueryTransaction; +} + +export interface QueryTransactionSuccessPayload { + exploreId: ExploreId; + history: HistoryItem[]; + queryTransactions: QueryTransaction[]; +} + +export interface RemoveQueryRowPayload { + exploreId: ExploreId; + index: number; +} + +export interface RunQueriesEmptyPayload { + exploreId: ExploreId; +} + +export interface ScanStartPayload { + exploreId: ExploreId; + scanner: RangeScanner; +} + +export interface ScanRangePayload { + exploreId: ExploreId; + range: RawTimeRange; +} + +export interface ScanStopPayload { + exploreId: ExploreId; +} + +export interface SetQueriesPayload { + exploreId: ExploreId; + queries: DataQuery[]; +} + +export interface SplitOpenPayload { + itemState: ExploreItemState; +} + +export interface ToggleTablePayload { + exploreId: ExploreId; +} + +export interface ToggleGraphPayload { + exploreId: ExploreId; +} + +export interface ToggleLogsPayload { + exploreId: ExploreId; +} + +export interface UpdateDatasourceInstancePayload { + exploreId: ExploreId; + datasourceInstance: DataSourceApi; +} + +export interface QueriesImportedPayload { + exploreId: ExploreId; + queries: DataQuery[]; +} + +/** + * Adds a query row after the row with the given index. + */ +export const addQueryRowAction = actionCreatorFactory<AddQueryRowPayload>('explore/ADD_QUERY_ROW').create(); + +/** + * Loads a new datasource identified by the given name. + */ +export const changeDatasourceAction = noPayloadActionCreatorFactory('explore/CHANGE_DATASOURCE').create(); + +/** + * Query change handler for the query row with the given index. + * If `override` is reset the query modifications and run the queries. Use this to set queries via a link. + */ +export const changeQueryAction = actionCreatorFactory<ChangeQueryPayload>('explore/CHANGE_QUERY').create(); + +/** + * Keep track of the Explore container size, in particular the width. + * The width will be used to calculate graph intervals (number of datapoints). + */ +export const changeSizeAction = actionCreatorFactory<ChangeSizePayload>('explore/CHANGE_SIZE').create(); + +/** + * Change the time range of Explore. Usually called from the Timepicker or a graph interaction. + */ +export const changeTimeAction = actionCreatorFactory<ChangeTimePayload>('explore/CHANGE_TIME').create(); + +/** + * Clear all queries and results. + */ +export const clearQueriesAction = actionCreatorFactory<ClearQueriesPayload>('explore/CLEAR_QUERIES').create(); + +/** + * Highlight expressions in the log results + */ +export const highlightLogsExpressionAction = actionCreatorFactory<HighlightLogsExpressionPayload>( + 'explore/HIGHLIGHT_LOGS_EXPRESSION' +).create(); + +/** + * Initialize Explore state with state from the URL and the React component. + * Call this only on components for with the Explore state has not been initialized. + */ +export const initializeExploreAction = actionCreatorFactory<InitializeExplorePayload>( + 'explore/INITIALIZE_EXPLORE' +).create(); + +/** + * Initialize the wrapper split state + */ +export const initializeExploreSplitAction = noPayloadActionCreatorFactory('explore/INITIALIZE_EXPLORE_SPLIT').create(); + +/** + * Display an error that happened during the selection of a datasource + */ +export const loadDatasourceFailureAction = actionCreatorFactory<LoadDatasourceFailurePayload>( + 'explore/LOAD_DATASOURCE_FAILURE' +).create(); + +/** + * Display an error when no datasources have been configured + */ +export const loadDatasourceMissingAction = actionCreatorFactory<LoadDatasourceMissingPayload>( + 'explore/LOAD_DATASOURCE_MISSING' +).create(); + +/** + * Start the async process of loading a datasource to display a loading indicator + */ +export const loadDatasourcePendingAction = actionCreatorFactory<LoadDatasourcePendingPayload>( + 'explore/LOAD_DATASOURCE_PENDING' +).create(); + +/** + * Datasource loading was successfully completed. The instance is stored in the state as well in case we need to + * run datasource-specific code. Existing queries are imported to the new datasource if an importer exists, + * e.g., Prometheus -> Loki queries. + */ +export const loadDatasourceSuccessAction = actionCreatorFactory<LoadDatasourceSuccessPayload>( + 'explore/LOAD_DATASOURCE_SUCCESS' +).create(); + +/** + * Action to modify a query given a datasource-specific modifier action. + * @param exploreId Explore area + * @param modification Action object with a type, e.g., ADD_FILTER + * @param index Optional query row index. If omitted, the modification is applied to all query rows. + * @param modifier Function that executes the modification, typically `datasourceInstance.modifyQueries`. + */ +export const modifyQueriesAction = actionCreatorFactory<ModifyQueriesPayload>('explore/MODIFY_QUERIES').create(); + +/** + * Mark a query transaction as failed with an error extracted from the query response. + * The transaction will be marked as `done`. + */ +export const queryTransactionFailureAction = actionCreatorFactory<QueryTransactionFailurePayload>( + 'explore/QUERY_TRANSACTION_FAILURE' +).create(); + +/** + * Start a query transaction for the given result type. + * @param exploreId Explore area + * @param transaction Query options and `done` status. + * @param resultType Associate the transaction with a result viewer, e.g., Graph + * @param rowIndex Index is used to associate latency for this transaction with a query row + */ +export const queryTransactionStartAction = actionCreatorFactory<QueryTransactionStartPayload>( + 'explore/QUERY_TRANSACTION_START' +).create(); + +/** + * Complete a query transaction, mark the transaction as `done` and store query state in URL. + * If the transaction was started by a scanner, it keeps on scanning for more results. + * Side-effect: the query is stored in localStorage. + * @param exploreId Explore area + * @param transactionId ID + * @param result Response from `datasourceInstance.query()` + * @param latency Duration between request and response + * @param queries Queries from all query rows + * @param datasourceId Origin datasource instance, used to discard results if current datasource is different + */ +export const queryTransactionSuccessAction = actionCreatorFactory<QueryTransactionSuccessPayload>( + 'explore/QUERY_TRANSACTION_SUCCESS' +).create(); + +/** + * Remove query row of the given index, as well as associated query results. + */ +export const removeQueryRowAction = actionCreatorFactory<RemoveQueryRowPayload>('explore/REMOVE_QUERY_ROW').create(); +export const runQueriesAction = noPayloadActionCreatorFactory('explore/RUN_QUERIES').create(); +export const runQueriesEmptyAction = actionCreatorFactory<RunQueriesEmptyPayload>('explore/RUN_QUERIES_EMPTY').create(); + +/** + * Start a scan for more results using the given scanner. + * @param exploreId Explore area + * @param scanner Function that a) returns a new time range and b) triggers a query run for the new range + */ +export const scanStartAction = actionCreatorFactory<ScanStartPayload>('explore/SCAN_START').create(); +export const scanRangeAction = actionCreatorFactory<ScanRangePayload>('explore/SCAN_RANGE').create(); + +/** + * Stop any scanning for more results. + */ +export const scanStopAction = actionCreatorFactory<ScanStopPayload>('explore/SCAN_STOP').create(); + +/** + * Reset queries to the given queries. Any modifications will be discarded. + * Use this action for clicks on query examples. Triggers a query run. + */ +export const setQueriesAction = actionCreatorFactory<SetQueriesPayload>('explore/SET_QUERIES').create(); + +/** + * Close the split view and save URL state. + */ +export const splitCloseAction = noPayloadActionCreatorFactory('explore/SPLIT_CLOSE').create(); + +/** + * Open the split view and copy the left state to be the right state. + * The right state is automatically initialized. + * The copy keeps all query modifications but wipes the query results. + */ +export const splitOpenAction = actionCreatorFactory<SplitOpenPayload>('explore/SPLIT_OPEN').create(); +export const stateSaveAction = noPayloadActionCreatorFactory('explore/STATE_SAVE').create(); + +/** + * Expand/collapse the table result viewer. When collapsed, table queries won't be run. + */ +export const toggleTableAction = actionCreatorFactory<ToggleTablePayload>('explore/TOGGLE_TABLE').create(); + +/** + * Expand/collapse the graph result viewer. When collapsed, graph queries won't be run. + */ +export const toggleGraphAction = actionCreatorFactory<ToggleGraphPayload>('explore/TOGGLE_GRAPH').create(); + +/** + * Expand/collapse the logs result viewer. When collapsed, log queries won't be run. + */ +export const toggleLogsAction = actionCreatorFactory<ToggleLogsPayload>('explore/TOGGLE_LOGS').create(); + +/** + * Updates datasource instance before datasouce loading has started + */ +export const updateDatasourceInstanceAction = actionCreatorFactory<UpdateDatasourceInstancePayload>( + 'explore/UPDATE_DATASOURCE_INSTANCE' +).create(); + +/** + * Resets state for explore. + */ +export const resetExploreAction = noPayloadActionCreatorFactory('explore/RESET_EXPLORE').create(); +export const queriesImportedAction = actionCreatorFactory<QueriesImportedPayload>('explore/QueriesImported').create(); + +export type HigherOrderAction = | InitializeExploreSplitAction - | LoadDatasourceFailureAction - | LoadDatasourceMissingAction - | LoadDatasourcePendingAction - | LoadDatasourceSuccessAction - | ModifyQueriesAction - | QueryTransactionFailureAction - | QueryTransactionStartAction - | QueryTransactionSuccessAction - | RemoveQueryRowAction - | RunQueriesEmptyAction - | ScanRangeAction - | ScanStartAction - | ScanStopAction - | SetQueriesAction | SplitCloseAction | SplitOpenAction - | ToggleGraphAction - | ToggleLogsAction - | ToggleTableAction - | UpdateDatasourceInstanceAction | ResetExploreAction - | QueriesImported; + | ActionOf<any>; + +export type Action = + | ActionOf<AddQueryRowPayload> + | ActionOf<ChangeQueryPayload> + | ActionOf<ChangeSizePayload> + | ActionOf<ChangeTimePayload> + | ActionOf<ClearQueriesPayload> + | ActionOf<HighlightLogsExpressionPayload> + | ActionOf<InitializeExplorePayload> + | ActionOf<LoadDatasourceFailurePayload> + | ActionOf<LoadDatasourceMissingPayload> + | ActionOf<LoadDatasourcePendingPayload> + | ActionOf<LoadDatasourceSuccessPayload> + | ActionOf<ModifyQueriesPayload> + | ActionOf<QueryTransactionFailurePayload> + | ActionOf<QueryTransactionStartPayload> + | ActionOf<QueryTransactionSuccessPayload> + | ActionOf<RemoveQueryRowPayload> + | ActionOf<RunQueriesEmptyPayload> + | ActionOf<ScanStartPayload> + | ActionOf<ScanRangePayload> + | ActionOf<SetQueriesPayload> + | ActionOf<SplitOpenPayload> + | ActionOf<ToggleTablePayload> + | ActionOf<ToggleGraphPayload> + | ActionOf<ToggleLogsPayload> + | ActionOf<UpdateDatasourceInstancePayload> + | ActionOf<QueriesImportedPayload>; diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index 63432e9c516..f32575edda5 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -32,40 +32,49 @@ import { QueryHint, QueryFixAction, } from '@grafana/ui/src/types'; +import { ExploreId, ExploreUrlState, RangeScanner, ResultType, QueryOptions } from 'app/types/explore'; import { - ExploreId, - ExploreUrlState, - RangeScanner, - ResultType, - QueryOptions, - QueryTransaction, -} from 'app/types/explore'; - -import { - Action as ThunkableAction, - ActionTypes, - AddQueryRowAction, - ChangeSizeAction, - HighlightLogsExpressionAction, - LoadDatasourceFailureAction, - LoadDatasourceMissingAction, - LoadDatasourcePendingAction, - LoadDatasourceSuccessAction, - QueryTransactionStartAction, - ScanStopAction, - UpdateDatasourceInstanceAction, - QueriesImported, - ModifyQueriesAction, + Action, + updateDatasourceInstanceAction, + changeQueryAction, + changeSizeAction, + ChangeSizePayload, + changeTimeAction, + scanStopAction, + clearQueriesAction, + initializeExploreAction, + loadDatasourceMissingAction, + loadDatasourceFailureAction, + loadDatasourcePendingAction, + queriesImportedAction, + LoadDatasourceSuccessPayload, + loadDatasourceSuccessAction, + modifyQueriesAction, + queryTransactionFailureAction, + queryTransactionStartAction, + queryTransactionSuccessAction, + scanRangeAction, + runQueriesEmptyAction, + scanStartAction, + setQueriesAction, + splitCloseAction, + splitOpenAction, + toggleGraphAction, + toggleLogsAction, + toggleTableAction, + addQueryRowAction, + AddQueryRowPayload, } from './actionTypes'; +import { ActionOf } from 'app/core/redux/actionCreatorFactory'; -type ThunkResult<R> = ThunkAction<R, StoreState, undefined, ThunkableAction>; +type ThunkResult<R> = ThunkAction<R, StoreState, undefined, Action>; -/** - * Adds a query row after the row with the given index. - */ -export function addQueryRow(exploreId: ExploreId, index: number): AddQueryRowAction { +// /** +// * Adds a query row after the row with the given index. +// */ +export function addQueryRow(exploreId: ExploreId, index: number): ActionOf<AddQueryRowPayload> { const query = generateEmptyQuery(index + 1); - return { type: ActionTypes.AddQueryRow, payload: { exploreId, index, query } }; + return addQueryRowAction({ exploreId, index, query }); } /** @@ -79,7 +88,7 @@ export function changeDatasource(exploreId: ExploreId, datasource: string): Thun await dispatch(importQueries(exploreId, modifiedQueries, currentDataSourceInstance, newDataSourceInstance)); - dispatch(updateDatasourceInstance(exploreId, newDataSourceInstance)); + dispatch(updateDatasourceInstanceAction({ exploreId, datasourceInstance: newDataSourceInstance })); dispatch(loadDatasource(exploreId, newDataSourceInstance)); }; } @@ -100,7 +109,7 @@ export function changeQuery( query = { ...generateEmptyQuery(index) }; } - dispatch({ type: ActionTypes.ChangeQuery, payload: { exploreId, query, index, override } }); + dispatch(changeQueryAction({ exploreId, query, index, override })); if (override) { dispatch(runQueries(exploreId)); } @@ -114,8 +123,8 @@ export function changeQuery( export function changeSize( exploreId: ExploreId, { height, width }: { height: number; width: number } -): ChangeSizeAction { - return { type: ActionTypes.ChangeSize, payload: { exploreId, height, width } }; +): ActionOf<ChangeSizePayload> { + return changeSizeAction({ exploreId, height, width }); } /** @@ -123,7 +132,7 @@ export function changeSize( */ export function changeTime(exploreId: ExploreId, range: TimeRange): ThunkResult<void> { return dispatch => { - dispatch({ type: ActionTypes.ChangeTime, payload: { exploreId, range } }); + dispatch(changeTimeAction({ exploreId, range })); dispatch(runQueries(exploreId)); }; } @@ -133,19 +142,12 @@ export function changeTime(exploreId: ExploreId, range: TimeRange): ThunkResult< */ export function clearQueries(exploreId: ExploreId): ThunkResult<void> { return dispatch => { - dispatch(scanStop(exploreId)); - dispatch({ type: ActionTypes.ClearQueries, payload: { exploreId } }); + dispatch(scanStopAction({ exploreId })); + dispatch(clearQueriesAction({ exploreId })); dispatch(stateSave()); }; } -/** - * Highlight expressions in the log results - */ -export function highlightLogsExpression(exploreId: ExploreId, expressions: string[]): HighlightLogsExpressionAction { - return { type: ActionTypes.HighlightLogsExpression, payload: { exploreId, expressions } }; -} - /** * Initialize Explore state with state from the URL and the React component. * Call this only on components for with the Explore state has not been initialized. @@ -167,18 +169,16 @@ export function initializeExplore( meta: ds.meta, })); - dispatch({ - type: ActionTypes.InitializeExplore, - payload: { + dispatch( + initializeExploreAction({ exploreId, containerWidth, - datasourceName, eventBridge, exploreDatasources, queries, range, - }, - }); + }) + ); if (exploreDatasources.length >= 1) { let instance; @@ -195,75 +195,20 @@ export function initializeExplore( instance = await getDatasourceSrv().get(); } - dispatch(updateDatasourceInstance(exploreId, instance)); + dispatch(updateDatasourceInstanceAction({ exploreId, datasourceInstance: instance })); dispatch(loadDatasource(exploreId, instance)); } else { - dispatch(loadDatasourceMissing(exploreId)); + dispatch(loadDatasourceMissingAction({ exploreId })); } }; } -/** - * Initialize the wrapper split state - */ -export function initializeExploreSplit() { - return async dispatch => { - dispatch({ type: ActionTypes.InitializeExploreSplit }); - }; -} - -/** - * Display an error that happened during the selection of a datasource - */ -export const loadDatasourceFailure = (exploreId: ExploreId, error: string): LoadDatasourceFailureAction => ({ - type: ActionTypes.LoadDatasourceFailure, - payload: { - exploreId, - error, - }, -}); - -/** - * Display an error when no datasources have been configured - */ -export const loadDatasourceMissing = (exploreId: ExploreId): LoadDatasourceMissingAction => ({ - type: ActionTypes.LoadDatasourceMissing, - payload: { exploreId }, -}); - -/** - * Start the async process of loading a datasource to display a loading indicator - */ -export const loadDatasourcePending = ( - exploreId: ExploreId, - requestedDatasourceName: string -): LoadDatasourcePendingAction => ({ - type: ActionTypes.LoadDatasourcePending, - payload: { - exploreId, - requestedDatasourceName, - }, -}); - -export const queriesImported = (exploreId: ExploreId, queries: DataQuery[]): QueriesImported => { - return { - type: ActionTypes.QueriesImported, - payload: { - exploreId, - queries, - }, - }; -}; - /** * Datasource loading was successfully completed. The instance is stored in the state as well in case we need to * run datasource-specific code. Existing queries are imported to the new datasource if an importer exists, * e.g., Prometheus -> Loki queries. */ -export const loadDatasourceSuccess = ( - exploreId: ExploreId, - instance: any, -): LoadDatasourceSuccessAction => { +export const loadDatasourceSuccess = (exploreId: ExploreId, instance: any): ActionOf<LoadDatasourceSuccessPayload> => { // Capabilities const supportsGraph = instance.meta.metrics; const supportsLogs = instance.meta.logs; @@ -276,37 +221,18 @@ export const loadDatasourceSuccess = ( // Save last-used datasource store.set(LAST_USED_DATASOURCE_KEY, instance.name); - return { - type: ActionTypes.LoadDatasourceSuccess, - payload: { - exploreId, - StartPage, - datasourceInstance: instance, - history, - showingStartPage: Boolean(StartPage), - supportsGraph, - supportsLogs, - supportsTable, - }, - }; + return loadDatasourceSuccessAction({ + exploreId, + StartPage, + datasourceInstance: instance, + history, + showingStartPage: Boolean(StartPage), + supportsGraph, + supportsLogs, + supportsTable, + }); }; -/** - * Updates datasource instance before datasouce loading has started - */ -export function updateDatasourceInstance( - exploreId: ExploreId, - instance: DataSourceApi -): UpdateDatasourceInstanceAction { - return { - type: ActionTypes.UpdateDatasourceInstance, - payload: { - exploreId, - datasourceInstance: instance, - }, - }; -} - export function importQueries( exploreId: ExploreId, queries: DataQuery[], @@ -332,7 +258,7 @@ export function importQueries( ...generateEmptyQuery(i), })); - dispatch(queriesImported(exploreId, nextQueries)); + dispatch(queriesImportedAction({ exploreId, queries: nextQueries })); }; } @@ -344,7 +270,7 @@ export function loadDatasource(exploreId: ExploreId, instance: DataSourceApi): T const datasourceName = instance.name; // Keep ID to track selection - dispatch(loadDatasourcePending(exploreId, datasourceName)); + dispatch(loadDatasourcePendingAction({ exploreId, requestedDatasourceName: datasourceName })); let datasourceError = null; try { @@ -355,7 +281,7 @@ export function loadDatasource(exploreId: ExploreId, instance: DataSourceApi): T } if (datasourceError) { - dispatch(loadDatasourceFailure(exploreId, datasourceError)); + dispatch(loadDatasourceFailureAction({ exploreId, error: datasourceError })); return; } @@ -392,11 +318,7 @@ export function modifyQueries( modifier: any ): ThunkResult<void> { return dispatch => { - const modifyQueryAction: ModifyQueriesAction = { - type: ActionTypes.ModifyQueries, - payload: { exploreId, modification, index, modifier }, - }; - dispatch(modifyQueryAction); + dispatch(modifyQueriesAction({ exploreId, modification, index, modifier })); if (!modification.preventSubmit) { dispatch(runQueries(exploreId)); } @@ -461,29 +383,10 @@ export function queryTransactionFailure( return qt; }); - dispatch({ - type: ActionTypes.QueryTransactionFailure, - payload: { exploreId, queryTransactions: nextQueryTransactions }, - }); + dispatch(queryTransactionFailureAction({ exploreId, queryTransactions: nextQueryTransactions })); }; } -/** - * Start a query transaction for the given result type. - * @param exploreId Explore area - * @param transaction Query options and `done` status. - * @param resultType Associate the transaction with a result viewer, e.g., Graph - * @param rowIndex Index is used to associate latency for this transaction with a query row - */ -export function queryTransactionStart( - exploreId: ExploreId, - transaction: QueryTransaction, - resultType: ResultType, - rowIndex: number -): QueryTransactionStartAction { - return { type: ActionTypes.QueryTransactionStart, payload: { exploreId, resultType, rowIndex, transaction } }; -} - /** * Complete a query transaction, mark the transaction as `done` and store query state in URL. * If the transaction was started by a scanner, it keeps on scanning for more results. @@ -540,14 +443,13 @@ export function queryTransactionSuccess( // Side-effect: Saving history in localstorage const nextHistory = updateHistory(history, datasourceId, queries); - dispatch({ - type: ActionTypes.QueryTransactionSuccess, - payload: { + dispatch( + queryTransactionSuccessAction({ exploreId, history: nextHistory, queryTransactions: nextQueryTransactions, - }, - }); + }) + ); // Keep scanning for results if this was the last scanning transaction if (scanning) { @@ -555,26 +457,16 @@ export function queryTransactionSuccess( const other = nextQueryTransactions.find(qt => qt.scanning && !qt.done); if (!other) { const range = scanner(); - dispatch({ type: ActionTypes.ScanRange, payload: { exploreId, range } }); + dispatch(scanRangeAction({ exploreId, range })); } } else { // We can stop scanning if we have a result - dispatch(scanStop(exploreId)); + dispatch(scanStopAction({ exploreId })); } } }; } -/** - * Remove query row of the given index, as well as associated query results. - */ -export function removeQueryRow(exploreId: ExploreId, index: number): ThunkResult<void> { - return dispatch => { - dispatch({ type: ActionTypes.RemoveQueryRow, payload: { exploreId, index } }); - dispatch(runQueries(exploreId)); - }; -} - /** * Main action to run queries and dispatches sub-actions based on which result viewers are active */ @@ -592,7 +484,7 @@ export function runQueries(exploreId: ExploreId) { } = getState().explore[exploreId]; if (!hasNonEmptyQuery(modifiedQueries)) { - dispatch({ type: ActionTypes.RunQueriesEmpty, payload: { exploreId } }); + dispatch(runQueriesEmptyAction({ exploreId })); dispatch(stateSave()); // Remember to saves to state and update location return; } @@ -673,7 +565,7 @@ function runQueriesForType( queryIntervals, scanning ); - dispatch(queryTransactionStart(exploreId, transaction, resultType, rowIndex)); + dispatch(queryTransactionStartAction({ exploreId, resultType, rowIndex, transaction })); try { const now = Date.now(); const res = await datasourceInstance.query(transaction.options); @@ -697,21 +589,14 @@ function runQueriesForType( export function scanStart(exploreId: ExploreId, scanner: RangeScanner): ThunkResult<void> { return dispatch => { // Register the scanner - dispatch({ type: ActionTypes.ScanStart, payload: { exploreId, scanner } }); + dispatch(scanStartAction({ exploreId, scanner })); // Scanning must trigger query run, and return the new range const range = scanner(); // Set the new range to be displayed - dispatch({ type: ActionTypes.ScanRange, payload: { exploreId, range } }); + dispatch(scanRangeAction({ exploreId, range })); }; } -/** - * Stop any scanning for more results. - */ -export function scanStop(exploreId: ExploreId): ScanStopAction { - return { type: ActionTypes.ScanStop, payload: { exploreId } }; -} - /** * Reset queries to the given queries. Any modifications will be discarded. * Use this action for clicks on query examples. Triggers a query run. @@ -720,13 +605,7 @@ export function setQueries(exploreId: ExploreId, rawQueries: DataQuery[]): Thunk return dispatch => { // Inject react keys into query objects const queries = rawQueries.map(q => ({ ...q, ...generateEmptyQuery() })); - dispatch({ - type: ActionTypes.SetQueries, - payload: { - exploreId, - queries, - }, - }); + dispatch(setQueriesAction({ exploreId, queries })); dispatch(runQueries(exploreId)); }; } @@ -736,7 +615,7 @@ export function setQueries(exploreId: ExploreId, rawQueries: DataQuery[]): Thunk */ export function splitClose(): ThunkResult<void> { return dispatch => { - dispatch({ type: ActionTypes.SplitClose }); + dispatch(splitCloseAction()); dispatch(stateSave()); }; } @@ -755,7 +634,7 @@ export function splitOpen(): ThunkResult<void> { queryTransactions: [], initialQueries: leftState.modifiedQueries.slice(), }; - dispatch({ type: ActionTypes.SplitOpen, payload: { itemState } }); + dispatch(splitOpenAction({ itemState })); dispatch(stateSave()); }; } @@ -791,7 +670,7 @@ export function stateSave() { */ export function toggleGraph(exploreId: ExploreId): ThunkResult<void> { return (dispatch, getState) => { - dispatch({ type: ActionTypes.ToggleGraph, payload: { exploreId } }); + dispatch(toggleGraphAction({ exploreId })); if (getState().explore[exploreId].showingGraph) { dispatch(runQueries(exploreId)); } @@ -803,7 +682,7 @@ export function toggleGraph(exploreId: ExploreId): ThunkResult<void> { */ export function toggleLogs(exploreId: ExploreId): ThunkResult<void> { return (dispatch, getState) => { - dispatch({ type: ActionTypes.ToggleLogs, payload: { exploreId } }); + dispatch(toggleLogsAction({ exploreId })); if (getState().explore[exploreId].showingLogs) { dispatch(runQueries(exploreId)); } @@ -815,18 +694,9 @@ export function toggleLogs(exploreId: ExploreId): ThunkResult<void> { */ export function toggleTable(exploreId: ExploreId): ThunkResult<void> { return (dispatch, getState) => { - dispatch({ type: ActionTypes.ToggleTable, payload: { exploreId } }); + dispatch(toggleTableAction({ exploreId })); if (getState().explore[exploreId].showingTable) { dispatch(runQueries(exploreId)); } }; } - -/** - * Resets state for explore. - */ -export function resetExplore(): ThunkResult<void> { - return dispatch => { - dispatch({ type: ActionTypes.ResetExplore, payload: {} }); - }; -} diff --git a/public/app/features/explore/state/reducers.test.ts b/public/app/features/explore/state/reducers.test.ts index 8227a947c5b..44079313c04 100644 --- a/public/app/features/explore/state/reducers.test.ts +++ b/public/app/features/explore/state/reducers.test.ts @@ -1,42 +1,47 @@ -import { Action, ActionTypes } from './actionTypes'; import { itemReducer, makeExploreItemState } from './reducers'; -import { ExploreId } from 'app/types/explore'; +import { ExploreId, ExploreItemState } from 'app/types/explore'; +import { reducerTester } from 'test/core/redux/reducerTester'; +import { scanStartAction, scanStopAction } from './actionTypes'; +import { Reducer } from 'redux'; +import { ActionOf } from 'app/core/redux/actionCreatorFactory'; describe('Explore item reducer', () => { describe('scanning', () => { test('should start scanning', () => { - let state = makeExploreItemState(); - const action: Action = { - type: ActionTypes.ScanStart, - payload: { - exploreId: ExploreId.left, - scanner: jest.fn(), - }, + const scanner = jest.fn(); + const initalState = { + ...makeExploreItemState(), + scanning: false, + scanner: undefined, }; - state = itemReducer(state, action); - expect(state.scanning).toBeTruthy(); - expect(state.scanner).toBe(action.payload.scanner); + + reducerTester() + .givenReducer(itemReducer as Reducer<ExploreItemState, ActionOf<any>>, initalState) + .whenActionIsDispatched(scanStartAction({ exploreId: ExploreId.left, scanner })) + .thenStateShouldEqual({ + ...makeExploreItemState(), + scanning: true, + scanner, + }); }); test('should stop scanning', () => { - let state = makeExploreItemState(); - const start: Action = { - type: ActionTypes.ScanStart, - payload: { - exploreId: ExploreId.left, - scanner: jest.fn(), - }, + const scanner = jest.fn(); + const initalState = { + ...makeExploreItemState(), + scanning: true, + scanner, + scanRange: {}, }; - state = itemReducer(state, start); - expect(state.scanning).toBeTruthy(); - const action: Action = { - type: ActionTypes.ScanStop, - payload: { - exploreId: ExploreId.left, - }, - }; - state = itemReducer(state, action); - expect(state.scanning).toBeFalsy(); - expect(state.scanner).toBeUndefined(); + + reducerTester() + .givenReducer(itemReducer as Reducer<ExploreItemState, ActionOf<any>>, initalState) + .whenActionIsDispatched(scanStopAction({ exploreId: ExploreId.left })) + .thenStateShouldEqual({ + ...makeExploreItemState(), + scanning: false, + scanner: undefined, + scanRange: undefined, + }); }); }); }); diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index 14c8d87bbd2..fc9be0c28b8 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -7,7 +7,36 @@ import { import { ExploreItemState, ExploreState, QueryTransaction } from 'app/types/explore'; import { DataQuery } from '@grafana/ui/src/types'; -import { Action, ActionTypes } from './actionTypes'; +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', @@ -58,9 +87,10 @@ export const initialExploreState: ExploreState = { /** * 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: { +export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemState) + .addMapper({ + filter: addQueryRowAction, + mapper: (state, action): ExploreItemState => { const { initialQueries, modifiedQueries, queryTransactions } = state; const { index, query } = action.payload; @@ -77,10 +107,7 @@ export const itemReducer = (state, action: Action): ExploreItemState => { // 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, rowIndex: qt.rowIndex + 1 }; } return qt; }); @@ -92,9 +119,11 @@ export const itemReducer = (state, action: Action): ExploreItemState => { modifiedQueries: nextModifiedQueries, queryTransactions: nextQueryTransactions, }; - } - - case ActionTypes.ChangeQuery: { + }, + }) + .addMapper({ + filter: changeQueryAction, + mapper: (state, action): ExploreItemState => { const { initialQueries, queryTransactions } = state; let { modifiedQueries } = state; const { query, index, override } = action.payload; @@ -102,17 +131,11 @@ export const itemReducer = (state, action: Action): ExploreItemState => { // Fast path: only change modifiedQueries to not trigger an update modifiedQueries[index] = query; if (!override) { - return { - ...state, - modifiedQueries, - }; + return { ...state, modifiedQueries }; } // Override path: queries are completely reset - const nextQuery: DataQuery = { - ...query, - ...generateEmptyQuery(index), - }; + const nextQuery: DataQuery = { ...query, ...generateEmptyQuery(index) }; const nextQueries = [...initialQueries]; nextQueries[index] = nextQuery; modifiedQueries = [...nextQueries]; @@ -126,9 +149,11 @@ export const itemReducer = (state, action: Action): ExploreItemState => { modifiedQueries: nextQueries.slice(), queryTransactions: nextQueryTransactions, }; - } - - case ActionTypes.ChangeSize: { + }, + }) + .addMapper({ + filter: changeSizeAction, + mapper: (state, action): ExploreItemState => { const { range, datasourceInstance } = state; let interval = '1s'; if (datasourceInstance && datasourceInstance.interval) { @@ -137,16 +162,17 @@ export const itemReducer = (state, action: Action): ExploreItemState => { 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: { + }, + }) + .addMapper({ + filter: changeTimeAction, + mapper: (state, action): ExploreItemState => { + return { ...state, range: action.payload.range }; + }, + }) + .addMapper({ + filter: clearQueriesAction, + mapper: (state): ExploreItemState => { const queries = ensureQueries(); return { ...state, @@ -155,14 +181,18 @@ export const itemReducer = (state, action: Action): ExploreItemState => { queryTransactions: [], showingStartPage: Boolean(state.StartPage), }; - } - - case ActionTypes.HighlightLogsExpression: { + }, + }) + .addMapper({ + filter: highlightLogsExpressionAction, + mapper: (state, action): ExploreItemState => { const { expressions } = action.payload; return { ...state, logsHighlighterExpressions: expressions }; - } - - case ActionTypes.InitializeExplore: { + }, + }) + .addMapper({ + filter: initializeExploreAction, + mapper: (state, action): ExploreItemState => { const { containerWidth, eventBridge, exploreDatasources, queries, range } = action.payload; return { ...state, @@ -174,30 +204,37 @@ export const itemReducer = (state, action: Action): ExploreItemState => { initialized: true, modifiedQueries: queries.slice(), }; - } - - case ActionTypes.UpdateDatasourceInstance: { + }, + }) + .addMapper({ + filter: updateDatasourceInstanceAction, + mapper: (state, action): ExploreItemState => { const { datasourceInstance } = action.payload; - return { - ...state, - datasourceInstance, - datasourceName: datasourceInstance.name, - }; - } - - case ActionTypes.LoadDatasourceFailure: { + return { ...state, datasourceInstance }; + /*datasourceName: datasourceInstance.name removed after refactor, datasourceName does not exists on ExploreItemState */ + }, + }) + .addMapper({ + filter: loadDatasourceFailureAction, + mapper: (state, action): ExploreItemState => { return { ...state, datasourceError: action.payload.error, datasourceLoading: false }; - } - - case ActionTypes.LoadDatasourceMissing: { + }, + }) + .addMapper({ + filter: loadDatasourceMissingAction, + mapper: (state): ExploreItemState => { return { ...state, datasourceMissing: true, datasourceLoading: false }; - } - - case ActionTypes.LoadDatasourcePending: { + }, + }) + .addMapper({ + filter: loadDatasourcePendingAction, + mapper: (state, action): ExploreItemState => { return { ...state, datasourceLoading: true, requestedDatasourceName: action.payload.requestedDatasourceName }; - } - - case ActionTypes.LoadDatasourceSuccess: { + }, + }) + .addMapper({ + filter: loadDatasourceSuccessAction, + mapper: (state, action): ExploreItemState => { const { containerWidth, range } = state; const { StartPage, @@ -226,9 +263,11 @@ export const itemReducer = (state, action: Action): ExploreItemState => { logsHighlighterExpressions: undefined, queryTransactions: [], }; - } - - case ActionTypes.ModifyQueries: { + }, + }) + .addMapper({ + filter: modifyQueriesAction, + mapper: (state, action): ExploreItemState => { const { initialQueries, modifiedQueries, queryTransactions } = state; const { modification, index, modifier } = action.payload; let nextQueries: DataQuery[]; @@ -246,12 +285,7 @@ export const itemReducer = (state, action: Action): ExploreItemState => { 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; + return i === index ? { ...modifier(modifiedQueries[i], modification), ...generateEmptyQuery(i) } : query; }); nextQueryTransactions = queryTransactions // Consume the hint corresponding to the action @@ -270,18 +304,18 @@ export const itemReducer = (state, action: Action): ExploreItemState => { modifiedQueries: nextQueries.slice(), queryTransactions: nextQueryTransactions, }; - } - - case ActionTypes.QueryTransactionFailure: { + }, + }) + .addMapper({ + filter: queryTransactionFailureAction, + mapper: (state, action): ExploreItemState => { const { queryTransactions } = action.payload; - return { - ...state, - queryTransactions, - showingStartPage: false, - }; - } - - case ActionTypes.QueryTransactionStart: { + 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 @@ -292,14 +326,12 @@ export const itemReducer = (state, action: Action): ExploreItemState => { // Append new transaction const nextQueryTransactions: QueryTransaction[] = [...remainingTransactions, transaction]; - return { - ...state, - queryTransactions: nextQueryTransactions, - showingStartPage: false, - }; - } - - case ActionTypes.QueryTransactionSuccess: { + 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( @@ -308,16 +340,12 @@ export const itemReducer = (state, action: Action): ExploreItemState => { queryIntervals.intervalMs ); - return { - ...state, - ...results, - history, - queryTransactions, - showingStartPage: false, - }; - } - - case ActionTypes.RemoveQueryRow: { + return { ...state, ...results, history, queryTransactions, showingStartPage: false }; + }, + }) + .addMapper({ + filter: removeQueryRowAction, + mapper: (state, action): ExploreItemState => { const { datasourceInstance, initialQueries, queryIntervals, queryTransactions } = state; let { modifiedQueries } = state; const { index } = action.payload; @@ -346,21 +374,29 @@ export const itemReducer = (state, action: Action): ExploreItemState => { modifiedQueries: nextQueries.slice(), queryTransactions: nextQueryTransactions, }; - } - - case ActionTypes.RunQueriesEmpty: { + }, + }) + .addMapper({ + filter: runQueriesEmptyAction, + mapper: (state): ExploreItemState => { return { ...state, queryTransactions: [] }; - } - - case ActionTypes.ScanRange: { + }, + }) + .addMapper({ + filter: scanRangeAction, + mapper: (state, action): ExploreItemState => { return { ...state, scanRange: action.payload.range }; - } - - case ActionTypes.ScanStart: { + }, + }) + .addMapper({ + filter: scanStartAction, + mapper: (state, action): ExploreItemState => { return { ...state, scanning: true, scanner: action.payload.scanner }; - } - - case ActionTypes.ScanStop: { + }, + }) + .addMapper({ + filter: scanStopAction, + mapper: (state): ExploreItemState => { const { queryTransactions } = state; const nextQueryTransactions = queryTransactions.filter(qt => qt.scanning && !qt.done); return { @@ -370,14 +406,18 @@ export const itemReducer = (state, action: Action): ExploreItemState => { scanRange: undefined, scanner: undefined, }; - } - - case ActionTypes.SetQueries: { + }, + }) + .addMapper({ + filter: setQueriesAction, + mapper: (state, action): ExploreItemState => { const { queries } = action.payload; return { ...state, initialQueries: queries.slice(), modifiedQueries: queries.slice() }; - } - - case ActionTypes.ToggleGraph: { + }, + }) + .addMapper({ + filter: toggleGraphAction, + mapper: (state): ExploreItemState => { const showingGraph = !state.showingGraph; let nextQueryTransactions = state.queryTransactions; if (!showingGraph) { @@ -385,9 +425,11 @@ export const itemReducer = (state, action: Action): ExploreItemState => { nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Graph'); } return { ...state, queryTransactions: nextQueryTransactions, showingGraph }; - } - - case ActionTypes.ToggleLogs: { + }, + }) + .addMapper({ + filter: toggleLogsAction, + mapper: (state): ExploreItemState => { const showingLogs = !state.showingLogs; let nextQueryTransactions = state.queryTransactions; if (!showingLogs) { @@ -395,9 +437,11 @@ export const itemReducer = (state, action: Action): ExploreItemState => { nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Logs'); } return { ...state, queryTransactions: nextQueryTransactions, showingLogs }; - } - - case ActionTypes.ToggleTable: { + }, + }) + .addMapper({ + filter: toggleTableAction, + mapper: (state): ExploreItemState => { const showingTable = !state.showingTable; if (showingTable) { return { ...state, showingTable, queryTransactions: state.queryTransactions }; @@ -412,25 +456,21 @@ export const itemReducer = (state, action: Action): ExploreItemState => { ); return { ...state, ...results, queryTransactions: nextQueryTransactions, showingTable }; - } - - case ActionTypes.QueriesImported: { - return { - ...state, - initialQueries: action.payload.queries, - modifiedQueries: action.payload.queries.slice(), - }; - } - } - - return state; -}; + }, + }) + .addMapper({ + filter: queriesImportedAction, + mapper: (state, action): ExploreItemState => { + return { ...state, initialQueries: action.payload.queries, modifiedQueries: action.payload.queries.slice() }; + }, + }) + .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: Action): ExploreState => { +export const exploreReducer = (state = initialExploreState, action: HigherOrderAction): ExploreState => { switch (action.type) { case ActionTypes.SplitClose: { return { ...state, split: false }; @@ -453,10 +493,7 @@ export const exploreReducer = (state = initialExploreState, action: Action): Exp const { exploreId } = action.payload as any; if (exploreId !== undefined) { const exploreItemState = state[exploreId]; - return { - ...state, - [exploreId]: itemReducer(exploreItemState, action), - }; + return { ...state, [exploreId]: itemReducer(exploreItemState, action) }; } }