From 43f8098981e0328d6d06e75cd746cf5f4b1e9912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Mon, 28 Jan 2019 17:41:33 +0100 Subject: [PATCH 01/14] Removed the on every key change event --- public/app/features/explore/QueryField.tsx | 68 ++++++++++++---------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/public/app/features/explore/QueryField.tsx b/public/app/features/explore/QueryField.tsx index 85315d2bdef..db6efb88f52 100644 --- a/public/app/features/explore/QueryField.tsx +++ b/public/app/features/explore/QueryField.tsx @@ -132,11 +132,11 @@ export class QueryField extends React.PureComponent { + onChange = ({ value }, invokeParentOnValueChanged?: boolean) => { const documentChanged = value.document !== this.state.value.document; const prevValue = this.state.value; @@ -144,7 +144,7 @@ export class QueryField extends React.PureComponent { if (documentChanged) { const textChanged = Plain.serialize(prevValue) !== Plain.serialize(value); - if (textChanged) { + if (textChanged && invokeParentOnValueChanged) { this.handleChangeValue(); } } @@ -288,8 +288,37 @@ export class QueryField extends React.PureComponent { + handleEnterAndTabKey = change => { const { typeaheadIndex, suggestions } = this.state; + if (this.menuEl) { + // Dont blur input + event.preventDefault(); + if (!suggestions || suggestions.length === 0) { + return undefined; + } + + const suggestion = getSuggestionByIndex(suggestions, typeaheadIndex); + const nextChange = this.applyTypeahead(change, suggestion); + + const insertTextOperation = nextChange.operations.find(operation => operation.type === 'insert_text'); + if (insertTextOperation) { + const suggestionText = insertTextOperation.text; + this.placeholdersBuffer.setNextPlaceholderValue(suggestionText); + if (this.placeholdersBuffer.hasPlaceholders()) { + nextChange.move(this.placeholdersBuffer.getNextMoveOffset()).focus(); + } + } + + return true; + } else { + this.handleChangeValue(); + + return undefined; + } + }; + + onKeyDown = (event, change) => { + const { typeaheadIndex } = this.state; switch (event.key) { case 'Escape': { @@ -312,27 +341,7 @@ export class QueryField extends React.PureComponent operation.type === 'insert_text'); - if (insertTextOperation) { - const suggestionText = insertTextOperation.text; - this.placeholdersBuffer.setNextPlaceholderValue(suggestionText); - if (this.placeholdersBuffer.hasPlaceholders()) { - nextChange.move(this.placeholdersBuffer.getNextMoveOffset()).focus(); - } - } - - return true; - } + return this.handleEnterAndTabKey(change); break; } @@ -364,12 +373,7 @@ export class QueryField extends React.PureComponent { if (this.mounted) { - this.setState({ - suggestions: [], - typeaheadIndex: 0, - typeaheadPrefix: '', - typeaheadContext: null, - }); + this.setState({ suggestions: [], typeaheadIndex: 0, typeaheadPrefix: '', typeaheadContext: null }); this.resetTimer = null; } }; @@ -396,7 +400,7 @@ export class QueryField extends React.PureComponent { // Manually triggering change const change = this.applyTypeahead(this.state.value.change(), item); - this.onChange(change); + this.onChange(change, true); }; updateMenu = () => { From acea1d7f0015d0014364d84d190f5e5b0eafae71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Fri, 1 Feb 2019 11:55:01 +0100 Subject: [PATCH 02/14] Alignment of interfaces and components --- packages/grafana-ui/src/types/plugin.ts | 18 ++++++++- public/app/features/explore/QueryField.tsx | 34 ++++++++--------- public/app/features/explore/QueryRow.tsx | 14 ++++--- .../loki/components/LokiQueryField.tsx | 38 +++++++++---------- .../prometheus/components/PromQueryField.tsx | 35 +++++++++-------- public/app/store/configureStore.ts | 4 +- 6 files changed, 77 insertions(+), 66 deletions(-) diff --git a/packages/grafana-ui/src/types/plugin.ts b/packages/grafana-ui/src/types/plugin.ts index 00735827825..1be862e17f3 100644 --- a/packages/grafana-ui/src/types/plugin.ts +++ b/packages/grafana-ui/src/types/plugin.ts @@ -41,6 +41,12 @@ export interface DataSourceApi { pluginExports?: PluginExports; } +export interface ExploreDataSourceApi extends DataSourceApi { + modifyQuery?(query: TQuery, action: any): TQuery; + getHighlighterExpression?(query: TQuery): string; + languageProvider?: any; +} + export interface QueryEditorProps { datasource: DSType; query: TQuery; @@ -48,6 +54,16 @@ export interface QueryEditorProps void; } +export interface ExploreQueryFieldProps { + datasource: DSType; + initialQuery: TQuery; + error?: string | JSX.Element; + hint?: QueryHint; + history: any[]; + onExecuteQuery?: () => void; + onQueryChange?: (value: TQuery) => void; +} + export interface PluginExports { Datasource?: DataSourceApi; QueryCtrl?: any; @@ -55,7 +71,7 @@ export interface PluginExports { ConfigCtrl?: any; AnnotationsQueryCtrl?: any; VariableQueryEditor?: any; - ExploreQueryField?: any; + ExploreQueryField?: ComponentClass>; ExploreStartPage?: any; // Panel plugin diff --git a/public/app/features/explore/QueryField.tsx b/public/app/features/explore/QueryField.tsx index db6efb88f52..880bedd7905 100644 --- a/public/app/features/explore/QueryField.tsx +++ b/public/app/features/explore/QueryField.tsx @@ -33,10 +33,9 @@ export interface QueryFieldProps { cleanText?: (text: string) => string; disabled?: boolean; initialQuery: string | null; - onBlur?: () => void; - onFocus?: () => void; + onExecuteQuery?: () => void; + onQueryChange?: (value: string) => void; onTypeahead?: (typeahead: TypeaheadInput) => TypeaheadOutput; - onValueChanged?: (value: string) => void; onWillApplySuggestion?: (suggestion: string, state: QueryFieldState) => string; placeholder?: string; portalOrigin?: string; @@ -145,7 +144,7 @@ export class QueryField extends React.PureComponent { + executeOnQueryChangeAndExecuteQueries = () => { // Send text change to parent - const { onValueChanged } = this.props; - if (onValueChanged) { - onValueChanged(Plain.serialize(this.state.value)); + const { onQueryChange, onExecuteQuery } = this.props; + if (onQueryChange) { + onQueryChange(Plain.serialize(this.state.value)); + } + + if (onExecuteQuery) { + onExecuteQuery(); } }; @@ -311,7 +314,7 @@ export class QueryField extends React.PureComponent { - const { onBlur } = this.props; // If we dont wait here, menu clicks wont work because the menu // will be gone. this.resetTimer = setTimeout(this.resetTypeahead, 100); // Disrupting placeholder entry wipes all remaining placeholders needing input this.placeholdersBuffer.clearPlaceholders(); - if (onBlur) { - onBlur(); - } + + this.executeOnQueryChangeAndExecuteQueries(); }; - handleFocus = () => { - const { onFocus } = this.props; - if (onFocus) { - onFocus(); - } - }; + handleFocus = () => {}; onClickMenu = (item: CompletionItem) => { // Manually triggering change diff --git a/public/app/features/explore/QueryRow.tsx b/public/app/features/explore/QueryRow.tsx index f6181161d56..7de728edb99 100644 --- a/public/app/features/explore/QueryRow.tsx +++ b/public/app/features/explore/QueryRow.tsx @@ -20,7 +20,7 @@ import { // Types import { StoreState } from 'app/types'; -import { RawTimeRange, DataQuery, QueryHint } from '@grafana/ui'; +import { RawTimeRange, DataQuery, ExploreDataSourceApi, QueryHint } from '@grafana/ui'; import { QueryTransaction, HistoryItem, ExploreItemState, ExploreId } from 'app/types/explore'; import { Emitter } from 'app/core/utils/emitter'; @@ -37,7 +37,7 @@ interface QueryRowProps { changeQuery: typeof changeQuery; className?: string; exploreId: ExploreId; - datasourceInstance: any; + datasourceInstance: ExploreDataSourceApi; highlightLogsExpression: typeof highlightLogsExpression; history: HistoryItem[]; index: number; @@ -115,13 +115,15 @@ export class QueryRow extends PureComponent { {QueryField ? ( ) : ( void; - onPressEnter?: () => void; - onQueryChange?: (value: LokiQuery, override?: boolean) => void; +interface LokiQueryFieldProps extends ExploreQueryFieldProps { + history: HistoryItem[]; } interface LokiQueryFieldState { @@ -98,14 +91,14 @@ export class LokiQueryField extends React.PureComponent node.type === 'code_block', getSyntax: node => 'promql', }), ]; - this.pluginsSearch = [RunnerPlugin({ handler: props.onPressEnter })]; + this.pluginsSearch = [RunnerPlugin({ handler: props.onExecuteQuery })]; this.state = { logLabelOptions: [], @@ -169,21 +162,25 @@ export class LokiQueryField extends React.PureComponent { // Send text change to parent - const { initialQuery, onQueryChange } = this.props; + const { initialQuery, onQueryChange, onExecuteQuery } = this.props; if (onQueryChange) { const query = { ...initialQuery, expr: value, }; - onQueryChange(query, override); + onQueryChange(query); + + if (override && onExecuteQuery) { + onExecuteQuery(); + } } }; onClickHintFix = () => { - const { hint, onClickHintFix } = this.props; - if (onClickHintFix && hint && hint.fix) { - onClickHintFix(hint.fix.action); - } + // const { hint, onClickHintFix } = this.props; + // if (onClickHintFix && hint && hint.fix) { + // onClickHintFix(hint.fix.action); + // } }; onUpdateLanguage = () => { @@ -243,7 +240,8 @@ export class LokiQueryField extends React.PureComponent void; - onPressEnter?: () => void; - onQueryChange?: (value: PromQuery, override?: boolean) => void; +interface PromQueryFieldProps extends ExploreQueryFieldProps { + history: HistoryItem[]; } interface PromQueryFieldState { @@ -116,7 +110,7 @@ class PromQueryField extends React.PureComponent node.type === 'code_block', getSyntax: node => 'promql', @@ -174,21 +168,25 @@ class PromQueryField extends React.PureComponent { // Send text change to parent - const { initialQuery, onQueryChange } = this.props; + const { initialQuery, onQueryChange, onExecuteQuery } = this.props; if (onQueryChange) { const query: PromQuery = { ...initialQuery, expr: value, }; - onQueryChange(query, override); + onQueryChange(query); + + if (override && onExecuteQuery) { + onExecuteQuery(); + } } }; onClickHintFix = () => { - const { hint, onClickHintFix } = this.props; - if (onClickHintFix && hint && hint.fix) { - onClickHintFix(hint.fix.action); - } + // const { hint, onClickHintFix } = this.props; + // if (onClickHintFix && hint && hint.fix) { + // onClickHintFix(hint.fix.action); + // } }; onUpdateLanguage = () => { @@ -264,7 +262,8 @@ class PromQueryField extends React.PureComponent Date: Fri, 1 Feb 2019 12:54:16 +0100 Subject: [PATCH 03/14] More types and some refactoring --- packages/grafana-ui/src/types/plugin.ts | 5 +++-- public/app/features/explore/QueryField.tsx | 2 -- public/app/features/explore/QueryRow.tsx | 14 ++++++-------- public/app/features/explore/state/actionTypes.ts | 6 +++--- public/app/features/explore/state/actions.ts | 10 ++++++++-- public/app/features/explore/state/reducers.ts | 2 +- .../datasource/loki/components/LokiQueryField.tsx | 8 ++++---- .../prometheus/components/PromQueryField.tsx | 8 ++++---- 8 files changed, 29 insertions(+), 26 deletions(-) diff --git a/packages/grafana-ui/src/types/plugin.ts b/packages/grafana-ui/src/types/plugin.ts index 1be862e17f3..e951e91a223 100644 --- a/packages/grafana-ui/src/types/plugin.ts +++ b/packages/grafana-ui/src/types/plugin.ts @@ -1,6 +1,6 @@ import { ComponentClass } from 'react'; import { PanelProps, PanelOptionsProps } from './panel'; -import { DataQueryOptions, DataQuery, DataQueryResponse, QueryHint } from './datasource'; +import { DataQueryOptions, DataQuery, DataQueryResponse, QueryHint, QueryFixAction } from './datasource'; export interface DataSourceApi { /** @@ -42,7 +42,7 @@ export interface DataSourceApi { } export interface ExploreDataSourceApi extends DataSourceApi { - modifyQuery?(query: TQuery, action: any): TQuery; + modifyQuery?(query: TQuery, action: QueryFixAction): TQuery; getHighlighterExpression?(query: TQuery): string; languageProvider?: any; } @@ -62,6 +62,7 @@ export interface ExploreQueryFieldProps void; onQueryChange?: (value: TQuery) => void; + onExecuteHint?: (action: QueryFixAction) => void; } export interface PluginExports { diff --git a/public/app/features/explore/QueryField.tsx b/public/app/features/explore/QueryField.tsx index 880bedd7905..a0e70e8066c 100644 --- a/public/app/features/explore/QueryField.tsx +++ b/public/app/features/explore/QueryField.tsx @@ -387,8 +387,6 @@ export class QueryField extends React.PureComponent {}; diff --git a/public/app/features/explore/QueryRow.tsx b/public/app/features/explore/QueryRow.tsx index 7de728edb99..bbc0bf0d101 100644 --- a/public/app/features/explore/QueryRow.tsx +++ b/public/app/features/explore/QueryRow.tsx @@ -20,7 +20,7 @@ import { // Types import { StoreState } from 'app/types'; -import { RawTimeRange, DataQuery, ExploreDataSourceApi, QueryHint } from '@grafana/ui'; +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'; @@ -78,10 +78,10 @@ export class QueryRow extends PureComponent { this.onChangeQuery(null, true); }; - onClickHintFix = action => { + onClickHintFix = (action: QueryFixAction) => { const { datasourceInstance, exploreId, index } = this.props; if (datasourceInstance && datasourceInstance.modifyQuery) { - const modifier = (queries: DataQuery, action: any) => datasourceInstance.modifyQuery(queries, action); + const modifier = (queries: DataQuery, action: QueryFixAction) => datasourceInstance.modifyQuery(queries, action); this.props.modifyQueries(exploreId, action, index, modifier); } }; @@ -116,14 +116,12 @@ export class QueryRow extends PureComponent { ) : ( DataQuery[]; + modifier: (queries: DataQuery[], modification: QueryFixAction) => DataQuery[]; }; } diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index 1a11b7fcac9..63432e9c516 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -30,6 +30,7 @@ import { DataQuery, DataSourceSelectItem, QueryHint, + QueryFixAction, } from '@grafana/ui/src/types'; import { ExploreId, @@ -54,6 +55,7 @@ import { ScanStopAction, UpdateDatasourceInstanceAction, QueriesImported, + ModifyQueriesAction, } from './actionTypes'; type ThunkResult = ThunkAction; @@ -385,12 +387,16 @@ export function loadDatasource(exploreId: ExploreId, instance: DataSourceApi): T */ export function modifyQueries( exploreId: ExploreId, - modification: any, + modification: QueryFixAction, index: number, modifier: any ): ThunkResult { return dispatch => { - dispatch({ type: ActionTypes.ModifyQueries, payload: { exploreId, modification, index, modifier } }); + const modifyQueryAction: ModifyQueriesAction = { + type: ActionTypes.ModifyQueries, + payload: { exploreId, modification, index, modifier }, + }; + dispatch(modifyQueryAction); if (!modification.preventSubmit) { dispatch(runQueries(exploreId)); } diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index eb67beee3b3..14c8d87bbd2 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -230,7 +230,7 @@ export const itemReducer = (state, action: Action): ExploreItemState => { case ActionTypes.ModifyQueries: { const { initialQueries, modifiedQueries, queryTransactions } = state; - const { modification, index, modifier } = action.payload as any; + const { modification, index, modifier } = action.payload; let nextQueries: DataQuery[]; let nextQueryTransactions; if (index === undefined) { diff --git a/public/app/plugins/datasource/loki/components/LokiQueryField.tsx b/public/app/plugins/datasource/loki/components/LokiQueryField.tsx index 76d2facc5b6..5046c353f17 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryField.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryField.tsx @@ -177,10 +177,10 @@ export class LokiQueryField extends React.PureComponent { - // const { hint, onClickHintFix } = this.props; - // if (onClickHintFix && hint && hint.fix) { - // onClickHintFix(hint.fix.action); - // } + const { hint, onExecuteHint } = this.props; + if (onExecuteHint && hint && hint.fix) { + onExecuteHint(hint.fix.action); + } }; onUpdateLanguage = () => { diff --git a/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx b/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx index 4bdd9f17392..c86ea5c4072 100644 --- a/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx @@ -183,10 +183,10 @@ class PromQueryField extends React.PureComponent { - // const { hint, onClickHintFix } = this.props; - // if (onClickHintFix && hint && hint.fix) { - // onClickHintFix(hint.fix.action); - // } + const { hint, onExecuteHint } = this.props; + if (onExecuteHint && hint && hint.fix) { + onExecuteHint(hint.fix.action); + } }; onUpdateLanguage = () => { From 1f5bb767186b8b0c36594771d1602bffef2af68d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Mon, 4 Feb 2019 07:47:10 +0100 Subject: [PATCH 04/14] Refactor of action, actionTypes and reducer --- public/app/features/explore/Explore.tsx | 17 +- public/app/features/explore/QueryRow.tsx | 23 +- public/app/features/explore/Wrapper.tsx | 14 +- .../app/features/explore/state/actionTypes.ts | 664 ++++++++++-------- public/app/features/explore/state/actions.ts | 296 +++----- .../features/explore/state/reducers.test.ts | 65 +- public/app/features/explore/state/reducers.ts | 313 +++++---- 7 files changed, 698 insertions(+), 694 deletions(-) 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 { }; 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 { 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 { 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('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('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('explore/CHANGE_SIZE').create(); + +/** + * Change the time range of Explore. Usually called from the Timepicker or a graph interaction. + */ +export const changeTimeAction = actionCreatorFactory('explore/CHANGE_TIME').create(); + +/** + * Clear all queries and results. + */ +export const clearQueriesAction = actionCreatorFactory('explore/CLEAR_QUERIES').create(); + +/** + * Highlight expressions in the log results + */ +export const highlightLogsExpressionAction = actionCreatorFactory( + '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( + '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( + 'explore/LOAD_DATASOURCE_FAILURE' +).create(); + +/** + * Display an error when no datasources have been configured + */ +export const loadDatasourceMissingAction = actionCreatorFactory( + 'explore/LOAD_DATASOURCE_MISSING' +).create(); + +/** + * Start the async process of loading a datasource to display a loading indicator + */ +export const loadDatasourcePendingAction = actionCreatorFactory( + '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( + '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('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( + '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( + '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( + 'explore/QUERY_TRANSACTION_SUCCESS' +).create(); + +/** + * Remove query row of the given index, as well as associated query results. + */ +export const removeQueryRowAction = actionCreatorFactory('explore/REMOVE_QUERY_ROW').create(); +export const runQueriesAction = noPayloadActionCreatorFactory('explore/RUN_QUERIES').create(); +export const runQueriesEmptyAction = actionCreatorFactory('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('explore/SCAN_START').create(); +export const scanRangeAction = actionCreatorFactory('explore/SCAN_RANGE').create(); + +/** + * Stop any scanning for more results. + */ +export const scanStopAction = actionCreatorFactory('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('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('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('explore/TOGGLE_TABLE').create(); + +/** + * Expand/collapse the graph result viewer. When collapsed, graph queries won't be run. + */ +export const toggleGraphAction = actionCreatorFactory('explore/TOGGLE_GRAPH').create(); + +/** + * Expand/collapse the logs result viewer. When collapsed, log queries won't be run. + */ +export const toggleLogsAction = actionCreatorFactory('explore/TOGGLE_LOGS').create(); + +/** + * Updates datasource instance before datasouce loading has started + */ +export const updateDatasourceInstanceAction = actionCreatorFactory( + 'explore/UPDATE_DATASOURCE_INSTANCE' +).create(); + +/** + * Resets state for explore. + */ +export const resetExploreAction = noPayloadActionCreatorFactory('explore/RESET_EXPLORE').create(); +export const queriesImportedAction = actionCreatorFactory('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; + +export type Action = + | ActionOf + | ActionOf + | ActionOf + | ActionOf + | ActionOf + | ActionOf + | ActionOf + | ActionOf + | ActionOf + | ActionOf + | ActionOf + | ActionOf + | ActionOf + | ActionOf + | ActionOf + | ActionOf + | ActionOf + | ActionOf + | ActionOf + | ActionOf + | ActionOf + | ActionOf + | ActionOf + | ActionOf + | ActionOf + | ActionOf; 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 = ThunkAction; +type ThunkResult = ThunkAction; -/** - * 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 { 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 { + return changeSizeAction({ exploreId, height, width }); } /** @@ -123,7 +132,7 @@ export function changeSize( */ export function changeTime(exploreId: ExploreId, range: TimeRange): ThunkResult { 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 { 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 => { // 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 { 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 { - 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 { 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 { return dispatch => { - dispatch({ type: ActionTypes.SplitClose }); + dispatch(splitCloseAction()); dispatch(stateSave()); }; } @@ -755,7 +634,7 @@ export function splitOpen(): ThunkResult { 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 { 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 { */ export function toggleLogs(exploreId: ExploreId): ThunkResult { 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 { */ export function toggleTable(exploreId: ExploreId): ThunkResult { 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 { - 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>, 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>, 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({} 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) }; } } From d9578bc48505c890a75c9e6c7ef996fe0886531d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Mon, 4 Feb 2019 08:17:18 +0100 Subject: [PATCH 05/14] Merge with master --- .../datasource/loki/components/LokiQueryEditor.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx index 634a642c65e..e9912522f16 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx @@ -33,7 +33,7 @@ export class LokiQueryEditor extends PureComponent { query: { ...this.state.query, expr: query.expr, - } + }, }); }; @@ -61,12 +61,18 @@ export class LokiQueryEditor extends PureComponent { datasource={datasource} initialQuery={query} onQueryChange={this.onFieldChange} - onPressEnter={this.onRunQuery} + onExecuteQuery={this.onRunQuery} + history={[]} />
Format as
-
From 6b98b05976fb837433370ff45d214b6889e1bc14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Mon, 4 Feb 2019 11:07:32 +0100 Subject: [PATCH 06/14] Removed modifiedQueries from state --- public/app/features/explore/QueryEditor.tsx | 11 ++---- public/app/features/explore/QueryRows.tsx | 9 +++-- public/app/features/explore/state/actions.ts | 18 ++++----- public/app/features/explore/state/reducers.ts | 39 ++++--------------- public/app/types/explore.ts | 8 +--- 5 files changed, 26 insertions(+), 59 deletions(-) diff --git a/public/app/features/explore/QueryEditor.tsx b/public/app/features/explore/QueryEditor.tsx index 083cd8a2e17..1d329f1c56e 100644 --- a/public/app/features/explore/QueryEditor.tsx +++ b/public/app/features/explore/QueryEditor.tsx @@ -14,7 +14,7 @@ interface QueryEditorProps { datasource: any; error?: string | JSX.Element; onExecuteQuery?: () => void; - onQueryChange?: (value: DataQuery, override?: boolean) => void; + onQueryChange?: (value: DataQuery) => void; initialQuery: DataQuery; exploreEvents: Emitter; range: RawTimeRange; @@ -40,20 +40,17 @@ export default class QueryEditor extends PureComponent { datasource, target, refresh: () => { - this.props.onQueryChange(target, false); + this.props.onQueryChange(target); this.props.onExecuteQuery(); }, events: exploreEvents, - panel: { - datasource, - targets: [target], - }, + panel: { datasource, targets: [target] }, dashboard: {}, }, }; this.component = loader.load(this.element, scopeProps, template); - this.props.onQueryChange(target, false); + this.props.onQueryChange(target); } componentWillUnmount() { diff --git a/public/app/features/explore/QueryRows.tsx b/public/app/features/explore/QueryRows.tsx index f8bb6e5ce6b..d65c1283bd6 100644 --- a/public/app/features/explore/QueryRows.tsx +++ b/public/app/features/explore/QueryRows.tsx @@ -21,10 +21,11 @@ export default class QueryRows extends PureComponent { const { className = '', exploreEvents, exploreId, initialQueries } = this.props; return (
- {initialQueries.map((query, index) => ( - // TODO instead of relying on initialQueries, move to react key list in redux - - ))} + {initialQueries.map((query, index) => { + // using query.key will introduce infinite loop because QueryEditor#53 + const key = query.datasource ? `${query.datasource}-${index}` : query.key; + return ; + })}
); } diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index f32575edda5..8530e7678ad 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -84,9 +84,9 @@ export function changeDatasource(exploreId: ExploreId, datasource: string): Thun return async (dispatch, getState) => { const newDataSourceInstance = await getDatasourceSrv().get(datasource); const currentDataSourceInstance = getState().explore[exploreId].datasourceInstance; - const modifiedQueries = getState().explore[exploreId].modifiedQueries; + const queries = getState().explore[exploreId].initialQueries; - await dispatch(importQueries(exploreId, modifiedQueries, currentDataSourceInstance, newDataSourceInstance)); + await dispatch(importQueries(exploreId, queries, currentDataSourceInstance, newDataSourceInstance)); dispatch(updateDatasourceInstanceAction({ exploreId, datasourceInstance: newDataSourceInstance })); dispatch(loadDatasource(exploreId, newDataSourceInstance)); @@ -254,7 +254,7 @@ export function importQueries( } const nextQueries = importedQueries.map((q, i) => ({ - ...importedQueries[i], + ...q, ...generateEmptyQuery(i), })); @@ -474,7 +474,7 @@ export function runQueries(exploreId: ExploreId) { return (dispatch, getState) => { const { datasourceInstance, - modifiedQueries, + initialQueries, showingLogs, showingGraph, showingTable, @@ -483,7 +483,7 @@ export function runQueries(exploreId: ExploreId) { supportsTable, } = getState().explore[exploreId]; - if (!hasNonEmptyQuery(modifiedQueries)) { + if (!hasNonEmptyQuery(initialQueries)) { dispatch(runQueriesEmptyAction({ exploreId })); dispatch(stateSave()); // Remember to saves to state and update location return; @@ -547,7 +547,7 @@ function runQueriesForType( const { datasourceInstance, eventBridge, - modifiedQueries: queries, + initialQueries: queries, queryIntervals, range, scanning, @@ -632,7 +632,7 @@ export function splitOpen(): ThunkResult { const itemState = { ...leftState, queryTransactions: [], - initialQueries: leftState.modifiedQueries.slice(), + initialQueries: leftState.initialQueries.slice(), }; dispatch(splitOpenAction({ itemState })); dispatch(stateSave()); @@ -649,14 +649,14 @@ export function stateSave() { const urlStates: { [index: string]: string } = {}; const leftUrlState: ExploreUrlState = { datasource: left.datasourceInstance.name, - queries: left.modifiedQueries.map(clearQueryKeys), + queries: left.initialQueries.map(clearQueryKeys), range: left.range, }; urlStates.left = serializeStateToUrlParam(leftUrlState, true); if (split) { const rightUrlState: ExploreUrlState = { datasource: right.datasourceInstance.name, - queries: right.modifiedQueries.map(clearQueryKeys), + queries: right.initialQueries.map(clearQueryKeys), range: right.range, }; urlStates.right = serializeStateToUrlParam(rightUrlState, true); diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index fc9be0c28b8..9343cf0ec57 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -61,7 +61,6 @@ export const makeExploreItemState = (): ExploreItemState => ({ history: [], initialQueries: [], initialized: false, - modifiedQueries: [], queryTransactions: [], queryIntervals: { interval: '15s', intervalMs: DEFAULT_GRAPH_INTERVAL }, range: DEFAULT_RANGE, @@ -91,16 +90,9 @@ export const itemReducer = reducerFactory({} as ExploreItemSta .addMapper({ filter: addQueryRowAction, mapper: (state, action): ExploreItemState => { - const { initialQueries, modifiedQueries, queryTransactions } = state; + const { initialQueries, 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)]; @@ -116,7 +108,6 @@ export const itemReducer = reducerFactory({} as ExploreItemSta ...state, initialQueries: nextQueries, logsHighlighterExpressions: undefined, - modifiedQueries: nextModifiedQueries, queryTransactions: nextQueryTransactions, }; }, @@ -125,20 +116,12 @@ export const itemReducer = reducerFactory({} as ExploreItemSta filter: changeQueryAction, mapper: (state, action): ExploreItemState => { 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 }; - } + const { query, index } = action.payload; // 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); @@ -146,7 +129,6 @@ export const itemReducer = reducerFactory({} as ExploreItemSta return { ...state, initialQueries: nextQueries, - modifiedQueries: nextQueries.slice(), queryTransactions: nextQueryTransactions, }; }, @@ -177,7 +159,6 @@ export const itemReducer = reducerFactory({} as ExploreItemSta return { ...state, initialQueries: queries.slice(), - modifiedQueries: queries.slice(), queryTransactions: [], showingStartPage: Boolean(state.StartPage), }; @@ -202,7 +183,6 @@ export const itemReducer = reducerFactory({} as ExploreItemSta range, initialQueries: queries, initialized: true, - modifiedQueries: queries.slice(), }; }, }) @@ -268,14 +248,14 @@ export const itemReducer = reducerFactory({} as ExploreItemSta .addMapper({ filter: modifyQueriesAction, mapper: (state, action): ExploreItemState => { - const { initialQueries, modifiedQueries, queryTransactions } = state; + const { initialQueries, 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), + ...modifier({ ...query }, modification), ...generateEmptyQuery(i), })); // Discard all ongoing transactions @@ -285,7 +265,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta 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({ ...query }, modification), ...generateEmptyQuery(i) } : query; }); nextQueryTransactions = queryTransactions // Consume the hint corresponding to the action @@ -301,7 +281,6 @@ export const itemReducer = reducerFactory({} as ExploreItemSta return { ...state, initialQueries: nextQueries, - modifiedQueries: nextQueries.slice(), queryTransactions: nextQueryTransactions, }; }, @@ -347,11 +326,8 @@ export const itemReducer = reducerFactory({} as ExploreItemSta filter: removeQueryRowAction, mapper: (state, action): ExploreItemState => { 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; } @@ -371,7 +347,6 @@ export const itemReducer = reducerFactory({} as ExploreItemSta ...results, initialQueries: nextQueries, logsHighlighterExpressions: undefined, - modifiedQueries: nextQueries.slice(), queryTransactions: nextQueryTransactions, }; }, @@ -412,7 +387,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta filter: setQueriesAction, mapper: (state, action): ExploreItemState => { const { queries } = action.payload; - return { ...state, initialQueries: queries.slice(), modifiedQueries: queries.slice() }; + return { ...state, initialQueries: queries.slice() }; }, }) .addMapper({ @@ -461,7 +436,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta .addMapper({ filter: queriesImportedAction, mapper: (state, action): ExploreItemState => { - return { ...state, initialQueries: action.payload.queries, modifiedQueries: action.payload.queries.slice() }; + return { ...state, initialQueries: action.payload.queries }; }, }) .create(); diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 34b7ff08c99..92145dc2324 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -145,7 +145,7 @@ export interface ExploreItemState { history: HistoryItem[]; /** * Initial queries for this Explore, e.g., set via URL. Each query will be - * converted to a query row. Query edits should be tracked in `modifiedQueries` though. + * converted to a query row. */ initialQueries: DataQuery[]; /** @@ -162,12 +162,6 @@ export interface ExploreItemState { * Log query result to be displayed in the logs result viewer. */ logsResult?: LogsModel; - /** - * Copy of `initialQueries` that tracks user edits. - * Don't connect this property to a react component as it is updated on every query change. - * Used when running queries. Needs to be reset to `initialQueries` when those are reset as well. - */ - modifiedQueries: DataQuery[]; /** * Query intervals for graph queries to determine how many datapoints to return. * Needs to be updated when `datasourceInstance` or `containerWidth` is changed. From 5e2b9e40a2f6a858bef3ba9ccabc7fff6d96c47d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Mon, 4 Feb 2019 11:25:07 +0100 Subject: [PATCH 07/14] Added more typings --- packages/grafana-ui/src/types/plugin.ts | 10 ++++++---- public/app/features/explore/Explore.tsx | 6 +++--- .../datasource/loki/components/LokiStartPage.tsx | 7 ++----- .../datasource/prometheus/components/PromStart.tsx | 7 ++----- public/app/types/explore.ts | 13 +++++++++++-- 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/packages/grafana-ui/src/types/plugin.ts b/packages/grafana-ui/src/types/plugin.ts index e951e91a223..e674c9fbc32 100644 --- a/packages/grafana-ui/src/types/plugin.ts +++ b/packages/grafana-ui/src/types/plugin.ts @@ -65,15 +65,19 @@ export interface ExploreQueryFieldProps void; } +export interface ExploreStartPageProps { + onClickExample: (query: DataQuery) => void; +} + export interface PluginExports { Datasource?: DataSourceApi; QueryCtrl?: any; - QueryEditor?: ComponentClass>; + QueryEditor?: ComponentClass>; ConfigCtrl?: any; AnnotationsQueryCtrl?: any; VariableQueryEditor?: any; ExploreQueryField?: ComponentClass>; - ExploreStartPage?: any; + ExploreStartPage?: ComponentClass; // Panel plugin PanelCtrl?: any; @@ -131,5 +135,3 @@ export interface PluginMetaInfo { updated: string; version: string; } - - diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 31ffdf4ab24..36c1f7f5ad7 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -1,5 +1,5 @@ // Libraries -import React from 'react'; +import React, { ComponentClass } from 'react'; import { hot } from 'react-hot-loader'; import { connect } from 'react-redux'; import _ from 'lodash'; @@ -21,7 +21,7 @@ import TimePicker, { parseTime } from './TimePicker'; import { changeSize, changeTime, initializeExplore, modifyQueries, scanStart, setQueries } from './state/actions'; // Types -import { RawTimeRange, TimeRange, DataQuery } from '@grafana/ui'; +import { RawTimeRange, TimeRange, DataQuery, ExploreStartPageProps } from '@grafana/ui'; import { ExploreItemState, ExploreUrlState, RangeScanner, ExploreId } from 'app/types/explore'; import { StoreState } from 'app/types'; import { LAST_USED_DATASOURCE_KEY, ensureQueries, DEFAULT_RANGE } from 'app/core/utils/explore'; @@ -30,7 +30,7 @@ import { ExploreToolbar } from './ExploreToolbar'; import { scanStopAction } from './state/actionTypes'; interface ExploreProps { - StartPage?: any; + StartPage?: ComponentClass; changeSize: typeof changeSize; changeTime: typeof changeTime; datasourceError: string; diff --git a/public/app/plugins/datasource/loki/components/LokiStartPage.tsx b/public/app/plugins/datasource/loki/components/LokiStartPage.tsx index da20661fe1b..62063a790ec 100644 --- a/public/app/plugins/datasource/loki/components/LokiStartPage.tsx +++ b/public/app/plugins/datasource/loki/components/LokiStartPage.tsx @@ -1,11 +1,8 @@ import React, { PureComponent } from 'react'; import LokiCheatSheet from './LokiCheatSheet'; +import { ExploreStartPageProps } from '@grafana/ui'; -interface Props { - onClickExample: () => void; -} - -export default class LokiStartPage extends PureComponent { +export default class LokiStartPage extends PureComponent { render() { return (
diff --git a/public/app/plugins/datasource/prometheus/components/PromStart.tsx b/public/app/plugins/datasource/prometheus/components/PromStart.tsx index 9acfc534853..de545e826e3 100644 --- a/public/app/plugins/datasource/prometheus/components/PromStart.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromStart.tsx @@ -1,11 +1,8 @@ import React, { PureComponent } from 'react'; import PromCheatSheet from './PromCheatSheet'; +import { ExploreStartPageProps } from '@grafana/ui'; -interface Props { - onClickExample: () => void; -} - -export default class PromStart extends PureComponent { +export default class PromStart extends PureComponent { render() { return (
diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 92145dc2324..4e099480cf0 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -1,5 +1,14 @@ +import { ComponentClass } from 'react'; import { Value } from 'slate'; -import { RawTimeRange, TimeRange, DataQuery, DataSourceSelectItem, DataSourceApi, QueryHint } from '@grafana/ui'; +import { + RawTimeRange, + TimeRange, + DataQuery, + DataSourceSelectItem, + DataSourceApi, + QueryHint, + ExploreStartPageProps, +} from '@grafana/ui'; import { Emitter } from 'app/core/core'; import { LogsModel } from 'app/core/logs_model'; @@ -102,7 +111,7 @@ export interface ExploreItemState { /** * React component to be shown when no queries have been run yet, e.g., for a query language cheat sheet. */ - StartPage?: any; + StartPage?: ComponentClass; /** * Width used for calculating the graph interval (can't have more datapoints than pixels) */ From efa48390b71d6d6397bc518cdda9ffb270ea8544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Mon, 4 Feb 2019 12:09:06 +0100 Subject: [PATCH 08/14] Reverted redux-logger --- public/app/store/configureStore.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/store/configureStore.ts b/public/app/store/configureStore.ts index 570a387cd74..dc9a478adf3 100644 --- a/public/app/store/configureStore.ts +++ b/public/app/store/configureStore.ts @@ -1,6 +1,6 @@ import { createStore, applyMiddleware, compose, combineReducers } from 'redux'; import thunk from 'redux-thunk'; -import { createLogger } from 'redux-logger'; +// import { createLogger } from 'redux-logger'; import sharedReducers from 'app/core/reducers'; import alertingReducers from 'app/features/alerting/state/reducers'; import teamsReducers from 'app/features/teams/state/reducers'; @@ -39,7 +39,7 @@ export function configureStore() { if (process.env.NODE_ENV !== 'production') { // DEV builds we had the logger middleware - setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk, createLogger())))); + setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk)))); } else { setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk)))); } From 96aef3bab878644a16091d4522302361ebea99f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Mon, 4 Feb 2019 13:41:29 +0100 Subject: [PATCH 09/14] Replaced intialQueris with queryKeys --- public/app/core/utils/explore.ts | 11 ++++++++- public/app/features/explore/Explore.tsx | 10 ++++---- public/app/features/explore/QueryRows.tsx | 9 +++---- public/app/features/explore/state/reducers.ts | 24 +++++++++++++++---- public/app/types/explore.ts | 5 ++++ 5 files changed, 43 insertions(+), 16 deletions(-) diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts index 7a9f54a0cae..efa54b7bc23 100644 --- a/public/app/core/utils/explore.ts +++ b/public/app/core/utils/explore.ts @@ -11,7 +11,7 @@ import { colors } from '@grafana/ui'; import TableModel, { mergeTablesIntoModel } from 'app/core/table_model'; // Types -import { RawTimeRange, IntervalValues, DataQuery } from '@grafana/ui/src/types'; +import { RawTimeRange, IntervalValues, DataQuery, DataSourceApi } from '@grafana/ui/src/types'; import TimeSeries from 'app/core/time_series2'; import { ExploreUrlState, @@ -304,3 +304,12 @@ export function clearHistory(datasourceId: string) { const historyKey = `grafana.explore.history.${datasourceId}`; store.delete(historyKey); } + +export const getQueryKeys = (queries: DataQuery[], datasourceInstance: DataSourceApi): string[] => { + const queryKeys = queries.reduce((newQueryKeys, query, index) => { + const primaryKey = datasourceInstance && datasourceInstance.name ? datasourceInstance.name : query.key; + return newQueryKeys.concat(`${primaryKey}-${index}`); + }, []); + + return queryKeys; +}; diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 36c1f7f5ad7..2012a52c338 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -38,7 +38,6 @@ interface ExploreProps { datasourceLoading: boolean | null; datasourceMissing: boolean; exploreId: ExploreId; - initialQueries: DataQuery[]; initializeExplore: typeof initializeExplore; initialized: boolean; modifyQueries: typeof modifyQueries; @@ -55,6 +54,7 @@ interface ExploreProps { supportsLogs: boolean | null; supportsTable: boolean | null; urlState: ExploreUrlState; + queryKeys: string[]; } /** @@ -175,12 +175,12 @@ export class Explore extends React.PureComponent { datasourceLoading, datasourceMissing, exploreId, - initialQueries, showingStartPage, split, supportsGraph, supportsLogs, supportsTable, + queryKeys, } = this.props; const exploreClass = split ? 'explore explore-split' : 'explore'; @@ -201,7 +201,7 @@ export class Explore extends React.PureComponent { {datasourceInstance && !datasourceError && (
- + {({ width }) => (
@@ -243,13 +243,13 @@ function mapStateToProps(state: StoreState, { exploreId }) { datasourceInstance, datasourceLoading, datasourceMissing, - initialQueries, initialized, range, showingStartPage, supportsGraph, supportsLogs, supportsTable, + queryKeys, } = item; return { StartPage, @@ -257,7 +257,6 @@ function mapStateToProps(state: StoreState, { exploreId }) { datasourceInstance, datasourceLoading, datasourceMissing, - initialQueries, initialized, range, showingStartPage, @@ -265,6 +264,7 @@ function mapStateToProps(state: StoreState, { exploreId }) { supportsGraph, supportsLogs, supportsTable, + queryKeys, }; } diff --git a/public/app/features/explore/QueryRows.tsx b/public/app/features/explore/QueryRows.tsx index d65c1283bd6..4b5a16ef781 100644 --- a/public/app/features/explore/QueryRows.tsx +++ b/public/app/features/explore/QueryRows.tsx @@ -6,24 +6,21 @@ import QueryRow from './QueryRow'; // Types import { Emitter } from 'app/core/utils/emitter'; -import { DataQuery } from '@grafana/ui/src/types'; import { ExploreId } from 'app/types/explore'; interface QueryRowsProps { className?: string; exploreEvents: Emitter; exploreId: ExploreId; - initialQueries: DataQuery[]; + queryKeys: string[]; } export default class QueryRows extends PureComponent { render() { - const { className = '', exploreEvents, exploreId, initialQueries } = this.props; + const { className = '', exploreEvents, exploreId, queryKeys } = this.props; return (
- {initialQueries.map((query, index) => { - // using query.key will introduce infinite loop because QueryEditor#53 - const key = query.datasource ? `${query.datasource}-${index}` : query.key; + {queryKeys.map((key, index) => { return ; })}
diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index 9343cf0ec57..f7eca489b6e 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -3,6 +3,7 @@ import { generateEmptyQuery, getIntervals, ensureQueries, + getQueryKeys, } from 'app/core/utils/explore'; import { ExploreItemState, ExploreState, QueryTransaction } from 'app/types/explore'; import { DataQuery } from '@grafana/ui/src/types'; @@ -72,6 +73,7 @@ export const makeExploreItemState = (): ExploreItemState => ({ supportsGraph: null, supportsLogs: null, supportsTable: null, + queryKeys: [], }); /** @@ -109,6 +111,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta initialQueries: nextQueries, logsHighlighterExpressions: undefined, queryTransactions: nextQueryTransactions, + queryKeys: getQueryKeys(nextQueries, state.datasourceInstance), }; }, }) @@ -130,6 +133,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta ...state, initialQueries: nextQueries, queryTransactions: nextQueryTransactions, + queryKeys: getQueryKeys(nextQueries, state.datasourceInstance), }; }, }) @@ -161,6 +165,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta initialQueries: queries.slice(), queryTransactions: [], showingStartPage: Boolean(state.StartPage), + queryKeys: getQueryKeys(queries, state.datasourceInstance), }; }, }) @@ -183,6 +188,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta range, initialQueries: queries, initialized: true, + queryKeys: getQueryKeys(queries, state.datasourceInstance), }; }, }) @@ -190,8 +196,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta filter: updateDatasourceInstanceAction, mapper: (state, action): ExploreItemState => { const { datasourceInstance } = action.payload; - return { ...state, datasourceInstance }; - /*datasourceName: datasourceInstance.name removed after refactor, datasourceName does not exists on ExploreItemState */ + return { ...state, datasourceInstance, queryKeys: getQueryKeys(state.initialQueries, datasourceInstance) }; }, }) .addMapper({ @@ -281,6 +286,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta return { ...state, initialQueries: nextQueries, + queryKeys: getQueryKeys(nextQueries, state.datasourceInstance), queryTransactions: nextQueryTransactions, }; }, @@ -348,6 +354,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta initialQueries: nextQueries, logsHighlighterExpressions: undefined, queryTransactions: nextQueryTransactions, + queryKeys: getQueryKeys(nextQueries, state.datasourceInstance), }; }, }) @@ -387,7 +394,11 @@ export const itemReducer = reducerFactory({} as ExploreItemSta filter: setQueriesAction, mapper: (state, action): ExploreItemState => { const { queries } = action.payload; - return { ...state, initialQueries: queries.slice() }; + return { + ...state, + initialQueries: queries.slice(), + queryKeys: getQueryKeys(queries, state.datasourceInstance), + }; }, }) .addMapper({ @@ -436,7 +447,12 @@ export const itemReducer = reducerFactory({} as ExploreItemSta .addMapper({ filter: queriesImportedAction, mapper: (state, action): ExploreItemState => { - return { ...state, initialQueries: action.payload.queries }; + const { queries } = action.payload; + return { + ...state, + initialQueries: queries, + queryKeys: getQueryKeys(queries, state.datasourceInstance), + }; }, }) .create(); diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 4e099480cf0..8faf0d2ed09 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -232,6 +232,11 @@ export interface ExploreItemState { * Table model that combines all query table results into a single table. */ tableResult?: TableModel; + + /** + * React keys for rendering of QueryRows + */ + queryKeys: string[]; } export interface ExploreUrlState { From 34dd1a22ab51e145211bef9a8414e55baee164c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Mon, 4 Feb 2019 14:16:15 +0100 Subject: [PATCH 10/14] Fixed bug with removing a QueryRow thats not part of nextQueries --- public/app/features/explore/state/reducers.ts | 7 ++++--- public/app/store/configureStore.ts | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index f7eca489b6e..86c263e39e9 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -331,7 +331,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta .addMapper({ filter: removeQueryRowAction, mapper: (state, action): ExploreItemState => { - const { datasourceInstance, initialQueries, queryIntervals, queryTransactions } = state; + const { datasourceInstance, initialQueries, queryIntervals, queryTransactions, queryKeys } = state; const { index } = action.payload; if (initialQueries.length <= 1) { @@ -339,9 +339,10 @@ export const itemReducer = reducerFactory({} as ExploreItemSta } const nextQueries = [...initialQueries.slice(0, index), ...initialQueries.slice(index + 1)]; + const nextQueryKeys = [...queryKeys.slice(0, index), ...queryKeys.slice(index + 1)]; // Discard transactions related to row query - const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index); + const nextQueryTransactions = queryTransactions.filter(qt => nextQueries.some(nq => nq.key === qt.query.key)); const results = calculateResultsFromQueryTransactions( nextQueryTransactions, datasourceInstance, @@ -354,7 +355,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta initialQueries: nextQueries, logsHighlighterExpressions: undefined, queryTransactions: nextQueryTransactions, - queryKeys: getQueryKeys(nextQueries, state.datasourceInstance), + queryKeys: nextQueryKeys, }; }, }) diff --git a/public/app/store/configureStore.ts b/public/app/store/configureStore.ts index dc9a478adf3..570a387cd74 100644 --- a/public/app/store/configureStore.ts +++ b/public/app/store/configureStore.ts @@ -1,6 +1,6 @@ import { createStore, applyMiddleware, compose, combineReducers } from 'redux'; import thunk from 'redux-thunk'; -// import { createLogger } from 'redux-logger'; +import { createLogger } from 'redux-logger'; import sharedReducers from 'app/core/reducers'; import alertingReducers from 'app/features/alerting/state/reducers'; import teamsReducers from 'app/features/teams/state/reducers'; @@ -39,7 +39,7 @@ export function configureStore() { if (process.env.NODE_ENV !== 'production') { // DEV builds we had the logger middleware - setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk)))); + setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk, createLogger())))); } else { setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk)))); } From f74ebdade663d727d153fb0b15a76c9cb0de6693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Mon, 4 Feb 2019 15:11:19 +0100 Subject: [PATCH 11/14] Missed to save --- public/app/features/explore/state/actions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index a41c0701994..a0357315484 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -710,7 +710,7 @@ const togglePanelActionCreator = ( ) => (exploreId: ExploreId) => { return (dispatch, getState) => { let shouldRunQueries; - dispatch(actionCreator); + dispatch(actionCreator({ exploreId })); dispatch(stateSave()); switch (actionCreator.type) { From 2c255fd85a8646303e6b97455bc2869e25420609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Tue, 5 Feb 2019 06:19:40 +0100 Subject: [PATCH 12/14] Renamed initialQueries to queries --- packages/grafana-ui/src/types/plugin.ts | 2 +- public/app/features/explore/QueryRow.tsx | 14 +++--- public/app/features/explore/state/actions.ts | 27 ++++------- public/app/features/explore/state/reducers.ts | 47 ++++++++++--------- .../loki/components/LokiQueryEditor.tsx | 2 +- .../loki/components/LokiQueryField.tsx | 13 ++--- .../prometheus/components/PromQueryField.tsx | 13 ++--- public/app/types/explore.ts | 4 +- 8 files changed, 54 insertions(+), 68 deletions(-) diff --git a/packages/grafana-ui/src/types/plugin.ts b/packages/grafana-ui/src/types/plugin.ts index e674c9fbc32..c8f156c08dc 100644 --- a/packages/grafana-ui/src/types/plugin.ts +++ b/packages/grafana-ui/src/types/plugin.ts @@ -56,7 +56,7 @@ export interface QueryEditorProps { datasource: DSType; - initialQuery: TQuery; + query: TQuery; error?: string | JSX.Element; hint?: QueryHint; history: any[]; diff --git a/public/app/features/explore/QueryRow.tsx b/public/app/features/explore/QueryRow.tsx index 5e2e8442e54..bcb980e49e4 100644 --- a/public/app/features/explore/QueryRow.tsx +++ b/public/app/features/explore/QueryRow.tsx @@ -35,7 +35,7 @@ interface QueryRowProps { highlightLogsExpressionAction: typeof highlightLogsExpressionAction; history: HistoryItem[]; index: number; - initialQuery: DataQuery; + query: DataQuery; modifyQueries: typeof modifyQueries; queryTransactions: QueryTransaction[]; exploreEvents: Emitter; @@ -95,7 +95,7 @@ export class QueryRow extends PureComponent { }, 500); render() { - const { datasourceInstance, history, index, initialQuery, queryTransactions, exploreEvents, range } = this.props; + const { datasourceInstance, history, index, query, queryTransactions, exploreEvents, range } = this.props; const transactions = queryTransactions.filter(t => t.rowIndex === index); const transactionWithError = transactions.find(t => t.error !== undefined); const hint = getFirstHintFromTransactions(transactions); @@ -110,7 +110,7 @@ export class QueryRow extends PureComponent { {QueryField ? ( { error={queryError} onQueryChange={this.onChangeQuery} onExecuteQuery={this.onExecuteQuery} - initialQuery={initialQuery} + initialQuery={query} exploreEvents={exploreEvents} range={range} /> @@ -155,9 +155,9 @@ export class QueryRow extends PureComponent { function mapStateToProps(state: StoreState, { exploreId, index }) { const explore = state.explore; const item: ExploreItemState = explore[exploreId]; - const { datasourceInstance, history, initialQueries, queryTransactions, range } = item; - const initialQuery = initialQueries[index]; - return { datasourceInstance, history, initialQuery, queryTransactions, range }; + const { datasourceInstance, history, queries, queryTransactions, range } = item; + const query = queries[index]; + return { datasourceInstance, history, query, queryTransactions, range }; } const mapDispatchToProps = { diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index a0357315484..f6fa5c05d63 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -87,7 +87,7 @@ export function changeDatasource(exploreId: ExploreId, datasource: string): Thun return async (dispatch, getState) => { const newDataSourceInstance = await getDatasourceSrv().get(datasource); const currentDataSourceInstance = getState().explore[exploreId].datasourceInstance; - const queries = getState().explore[exploreId].initialQueries; + const queries = getState().explore[exploreId].queries; await dispatch(importQueries(exploreId, queries, currentDataSourceInstance, newDataSourceInstance)); @@ -494,7 +494,7 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false) { return (dispatch, getState) => { const { datasourceInstance, - initialQueries, + queries, showingLogs, showingGraph, showingTable, @@ -503,7 +503,7 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false) { supportsTable, } = getState().explore[exploreId]; - if (!hasNonEmptyQuery(initialQueries)) { + if (!hasNonEmptyQuery(queries)) { dispatch(runQueriesEmptyAction({ exploreId })); dispatch(stateSave()); // Remember to saves to state and update location return; @@ -565,14 +565,7 @@ function runQueriesForType( resultGetter?: any ) { return async (dispatch, getState) => { - const { - datasourceInstance, - eventBridge, - initialQueries: queries, - queryIntervals, - range, - scanning, - } = getState().explore[exploreId]; + const { datasourceInstance, eventBridge, queries, queryIntervals, range, scanning } = getState().explore[exploreId]; const datasourceId = datasourceInstance.meta.id; // Run all queries concurrently @@ -653,7 +646,7 @@ export function splitOpen(): ThunkResult { const itemState = { ...leftState, queryTransactions: [], - initialQueries: leftState.initialQueries.slice(), + queries: leftState.queries.slice(), }; dispatch(splitOpenAction({ itemState })); dispatch(stateSave()); @@ -670,7 +663,7 @@ export function stateSave() { const urlStates: { [index: string]: string } = {}; const leftUrlState: ExploreUrlState = { datasource: left.datasourceInstance.name, - queries: left.initialQueries.map(clearQueryKeys), + queries: left.queries.map(clearQueryKeys), range: left.range, ui: { showingGraph: left.showingGraph, @@ -682,13 +675,9 @@ export function stateSave() { if (split) { const rightUrlState: ExploreUrlState = { datasource: right.datasourceInstance.name, - queries: right.initialQueries.map(clearQueryKeys), + queries: right.queries.map(clearQueryKeys), range: right.range, - ui: { - showingGraph: right.showingGraph, - showingLogs: right.showingLogs, - showingTable: right.showingTable, - }, + ui: { showingGraph: right.showingGraph, showingLogs: right.showingLogs, showingTable: right.showingTable }, }; urlStates.right = serializeStateToUrlParam(rightUrlState, true); diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index 7c0a729d0ed..76fc7d5de32 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -60,7 +60,7 @@ export const makeExploreItemState = (): ExploreItemState => ({ datasourceMissing: false, exploreDatasources: [], history: [], - initialQueries: [], + queries: [], initialized: false, queryTransactions: [], queryIntervals: { interval: '15s', intervalMs: DEFAULT_GRAPH_INTERVAL }, @@ -92,23 +92,26 @@ export const itemReducer = reducerFactory({} as ExploreItemSta .addMapper({ filter: addQueryRowAction, mapper: (state, action): ExploreItemState => { - const { initialQueries, queryTransactions } = state; + const { queries, queryTransactions } = state; const { index, query } = action.payload; - // Add to initialQueries, which will cause a new row to be rendered - const nextQueries = [...initialQueries.slice(0, index + 1), { ...query }, ...initialQueries.slice(index + 1)]; + // 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, + rowIndex: qt.rowIndex + 1, + }; } return qt; }); return { ...state, - initialQueries: nextQueries, + queries: nextQueries, logsHighlighterExpressions: undefined, queryTransactions: nextQueryTransactions, queryKeys: getQueryKeys(nextQueries, state.datasourceInstance), @@ -118,12 +121,12 @@ export const itemReducer = reducerFactory({} as ExploreItemSta .addMapper({ filter: changeQueryAction, mapper: (state, action): ExploreItemState => { - const { initialQueries, queryTransactions } = state; + const { queries, queryTransactions } = state; const { query, index } = action.payload; // Override path: queries are completely reset const nextQuery: DataQuery = { ...query, ...generateEmptyQuery(index) }; - const nextQueries = [...initialQueries]; + const nextQueries = [...queries]; nextQueries[index] = nextQuery; // Discard ongoing transaction related to row query @@ -131,7 +134,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta return { ...state, - initialQueries: nextQueries, + queries: nextQueries, queryTransactions: nextQueryTransactions, queryKeys: getQueryKeys(nextQueries, state.datasourceInstance), }; @@ -162,7 +165,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta const queries = ensureQueries(); return { ...state, - initialQueries: queries.slice(), + queries: queries.slice(), queryTransactions: [], showingStartPage: Boolean(state.StartPage), queryKeys: getQueryKeys(queries, state.datasourceInstance), @@ -186,7 +189,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta eventBridge, exploreDatasources, range, - initialQueries: queries, + queries, initialized: true, queryKeys: getQueryKeys(queries, state.datasourceInstance), ...ui, @@ -197,7 +200,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta filter: updateDatasourceInstanceAction, mapper: (state, action): ExploreItemState => { const { datasourceInstance } = action.payload; - return { ...state, datasourceInstance, queryKeys: getQueryKeys(state.initialQueries, datasourceInstance) }; + return { ...state, datasourceInstance, queryKeys: getQueryKeys(state.queries, datasourceInstance) }; }, }) .addMapper({ @@ -254,13 +257,13 @@ export const itemReducer = reducerFactory({} as ExploreItemSta .addMapper({ filter: modifyQueriesAction, mapper: (state, action): ExploreItemState => { - const { initialQueries, queryTransactions } = state; + const { queries, queryTransactions } = state; const { modification, index, modifier } = action.payload; let nextQueries: DataQuery[]; let nextQueryTransactions; if (index === undefined) { // Modify all queries - nextQueries = initialQueries.map((query, i) => ({ + nextQueries = queries.map((query, i) => ({ ...modifier({ ...query }, modification), ...generateEmptyQuery(i), })); @@ -268,7 +271,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta nextQueryTransactions = []; } else { // Modify query only at index - nextQueries = initialQueries.map((query, i) => { + 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; @@ -286,7 +289,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta } return { ...state, - initialQueries: nextQueries, + queries: nextQueries, queryKeys: getQueryKeys(nextQueries, state.datasourceInstance), queryTransactions: nextQueryTransactions, }; @@ -332,14 +335,14 @@ export const itemReducer = reducerFactory({} as ExploreItemSta .addMapper({ filter: removeQueryRowAction, mapper: (state, action): ExploreItemState => { - const { datasourceInstance, initialQueries, queryIntervals, queryTransactions, queryKeys } = state; + const { datasourceInstance, queries, queryIntervals, queryTransactions, queryKeys } = state; const { index } = action.payload; - if (initialQueries.length <= 1) { + if (queries.length <= 1) { return state; } - const nextQueries = [...initialQueries.slice(0, index), ...initialQueries.slice(index + 1)]; + 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 @@ -353,7 +356,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta return { ...state, ...results, - initialQueries: nextQueries, + queries: nextQueries, logsHighlighterExpressions: undefined, queryTransactions: nextQueryTransactions, queryKeys: nextQueryKeys, @@ -398,7 +401,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta const { queries } = action.payload; return { ...state, - initialQueries: queries.slice(), + queries: queries.slice(), queryKeys: getQueryKeys(queries, state.datasourceInstance), }; }, @@ -452,7 +455,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta const { queries } = action.payload; return { ...state, - initialQueries: queries, + queries, queryKeys: getQueryKeys(queries, state.datasourceInstance), }; }, diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx index e9912522f16..a1b9e7a5df9 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx @@ -59,7 +59,7 @@ export class LokiQueryEditor extends PureComponent {
{ // Send text change to parent - const { initialQuery, onQueryChange, onExecuteQuery } = this.props; + const { query, onQueryChange, onExecuteQuery } = this.props; if (onQueryChange) { - const query = { - ...initialQuery, - expr: value, - }; - onQueryChange(query); + const nextQuery = { ...query, expr: value }; + onQueryChange(nextQuery); if (override && onExecuteQuery) { onExecuteQuery(); @@ -217,7 +214,7 @@ export class LokiQueryField extends React.PureComponent 0; @@ -237,7 +234,7 @@ export class LokiQueryField extends React.PureComponent { // Send text change to parent - const { initialQuery, onQueryChange, onExecuteQuery } = this.props; + const { query, onQueryChange, onExecuteQuery } = this.props; if (onQueryChange) { - const query: PromQuery = { - ...initialQuery, - expr: value, - }; - onQueryChange(query); + const nextQuery: PromQuery = { ...query, expr: value }; + onQueryChange(nextQuery); if (override && onExecuteQuery) { onExecuteQuery(); @@ -240,7 +237,7 @@ class PromQueryField extends React.PureComponent Date: Tue, 5 Feb 2019 07:03:16 +0100 Subject: [PATCH 13/14] Fixed so onBlur event trigger an QueryChange and QueryExecute if values differ --- public/app/features/explore/QueryField.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/public/app/features/explore/QueryField.tsx b/public/app/features/explore/QueryField.tsx index a0e70e8066c..8ab7e56dc5a 100644 --- a/public/app/features/explore/QueryField.tsx +++ b/public/app/features/explore/QueryField.tsx @@ -50,6 +50,7 @@ export interface QueryFieldState { typeaheadPrefix: string; typeaheadText: string; value: Value; + lastExecutedValue: Value; } export interface TypeaheadInput { @@ -89,6 +90,7 @@ export class QueryField extends React.PureComponent { + handleBlur = (event, change) => { + const { lastExecutedValue } = this.state; + const previousValue = lastExecutedValue ? Plain.serialize(this.state.lastExecutedValue) : null; + const currentValue = Plain.serialize(change.value); + // If we dont wait here, menu clicks wont work because the menu // will be gone. this.resetTimer = setTimeout(this.resetTypeahead, 100); // Disrupting placeholder entry wipes all remaining placeholders needing input this.placeholdersBuffer.clearPlaceholders(); + + if (previousValue !== currentValue) { + this.executeOnQueryChangeAndExecuteQueries(); + } }; handleFocus = () => {}; From bfdfb215f329eb5a04b5318db938a81bdddb3a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 5 Feb 2019 09:32:42 +0100 Subject: [PATCH 14/14] added missing typing to explore props --- public/app/features/explore/Explore.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 8eb177b8ad4..b210bcccc18 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -21,7 +21,7 @@ import TimePicker, { parseTime } from './TimePicker'; import { changeSize, changeTime, initializeExplore, modifyQueries, scanStart, setQueries } from './state/actions'; // Types -import { RawTimeRange, TimeRange, DataQuery, ExploreStartPageProps } from '@grafana/ui'; +import { RawTimeRange, TimeRange, DataQuery, ExploreStartPageProps, ExploreDataSourceApi } from '@grafana/ui'; import { ExploreItemState, ExploreUrlState, RangeScanner, ExploreId } from 'app/types/explore'; import { StoreState } from 'app/types'; import { LAST_USED_DATASOURCE_KEY, ensureQueries, DEFAULT_RANGE, DEFAULT_UI_STATE } from 'app/core/utils/explore'; @@ -34,7 +34,7 @@ interface ExploreProps { changeSize: typeof changeSize; changeTime: typeof changeTime; datasourceError: string; - datasourceInstance: any; + datasourceInstance: ExploreDataSourceApi; datasourceLoading: boolean | null; datasourceMissing: boolean; exploreId: ExploreId;