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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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 b9c58d88dc381dc096a66bc9eadcc6489cf70588 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Mon, 4 Feb 2019 16:48:27 +0100 Subject: [PATCH 12/28] basic layout --- .../AddPanelWidget/AddPanelWidget.tsx | 29 ++++++++++--------- .../AddPanelWidget/_AddPanelWidget.scss | 22 +++++++++++++- public/sass/base/_icons.scss | 2 +- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx index 8c1ab93cec1..7a9767666c0 100644 --- a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx +++ b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx @@ -125,13 +125,20 @@ export class AddPanelWidget extends React.Component { dashboard.removePanel(this.props.panel); }; + renderOptionLink = (icon, text, onClick) => { + return ( + + ); + }; + render() { - let addCopyButton; - - if (this.state.copiedPanelPlugins.length === 1) { - addCopyButton = this.copyButton(this.state.copiedPanelPlugins[0]); - } - return (
@@ -142,13 +149,9 @@ export class AddPanelWidget extends React.Component {
- - {addCopyButton} - + {this.renderOptionLink('queries', 'Add query', this.onCreateNewPanel)} + {this.renderOptionLink('visualization', 'Choose Panel type', this.onCreateNewPanel)} + {this.renderOptionLink('queries', 'Convert to row', this.onCreateNewRow)}
diff --git a/public/app/features/dashboard/components/AddPanelWidget/_AddPanelWidget.scss b/public/app/features/dashboard/components/AddPanelWidget/_AddPanelWidget.scss index 5a1cbee4b44..587daa2703f 100644 --- a/public/app/features/dashboard/components/AddPanelWidget/_AddPanelWidget.scss +++ b/public/app/features/dashboard/components/AddPanelWidget/_AddPanelWidget.scss @@ -26,6 +26,26 @@ } } +.add-panel-widget__link { + display: block; + margin: 0 8px; + width: 130px; + text-align: center; + padding: 8px 0; +} + +.add-panel-widget__icon { + margin-bottom: 8px; + + .gicon { + color: white; + height: 44px; + width: 53px; + position: relative; + left: 5px; + } +} + .add-panel-widget__close { margin-left: auto; background-color: transparent; @@ -39,7 +59,7 @@ justify-content: center; align-items: center; height: 100%; - flex-direction: column; + flex-direction: row; .btn { margin-bottom: 10px; diff --git a/public/sass/base/_icons.scss b/public/sass/base/_icons.scss index a60259ac0f2..a2649b31fcd 100644 --- a/public/sass/base/_icons.scss +++ b/public/sass/base/_icons.scss @@ -212,7 +212,7 @@ padding-right: 5px; } -.panel-editor-tabs { +.panel-editor-tabs, .add-panel-widget__icon { .gicon-advanced-active { background-image: url('../img/icons_#{$theme-name}_theme/icon_advanced_active.svg'); } From f6b46f7a34c822508605ffca9da8c564c26fb209 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Mon, 4 Feb 2019 17:18:46 +0100 Subject: [PATCH 13/28] prepping go to visualization --- .../components/AddPanelWidget/AddPanelWidget.tsx | 9 +++++---- .../features/dashboard/panel_editor/VisualizationTab.tsx | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx index 7a9767666c0..b3d5e6167c4 100644 --- a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx +++ b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx @@ -62,20 +62,21 @@ export class AddPanelWidget extends React.Component { ); } - moveToEdit(panel) { + moveToEdit(panel, tab) { reduxStore.dispatch( updateLocation({ query: { panelId: panel.id, edit: true, fullscreen: true, + tab: tab, }, partial: true, }) ); } - onCreateNewPanel = () => { + onCreateNewPanel = (tab = 'queries') => { const dashboard = this.props.dashboard; const { gridPos } = this.props.panel; @@ -88,7 +89,7 @@ export class AddPanelWidget extends React.Component { dashboard.addPanel(newPanel); dashboard.removePanel(this.props.panel); - this.moveToEdit(newPanel); + this.moveToEdit(newPanel, tab); }; onPasteCopiedPanel = panelPluginInfo => { @@ -150,7 +151,7 @@ export class AddPanelWidget extends React.Component {
{this.renderOptionLink('queries', 'Add query', this.onCreateNewPanel)} - {this.renderOptionLink('visualization', 'Choose Panel type', this.onCreateNewPanel)} + {this.renderOptionLink('visualization', 'Choose Panel type', () => this.onCreateNewPanel('visualization'))} {this.renderOptionLink('queries', 'Convert to row', this.onCreateNewRow)}
diff --git a/public/app/features/dashboard/panel_editor/VisualizationTab.tsx b/public/app/features/dashboard/panel_editor/VisualizationTab.tsx index 35b9b71112a..fdf978acdf9 100644 --- a/public/app/features/dashboard/panel_editor/VisualizationTab.tsx +++ b/public/app/features/dashboard/panel_editor/VisualizationTab.tsx @@ -3,6 +3,7 @@ import React, { PureComponent } from 'react'; // Utils & Services import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader'; +//TODO: See PanelEdit // Components import { EditorTabBody, EditorToolbarView } from './EditorTabBody'; From e4dad78045bc70a17e8274310c67d7b82511660c Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Mon, 4 Feb 2019 21:26:49 +0100 Subject: [PATCH 14/28] added flags to vizpicker from query param --- .../AddPanelWidget/AddPanelWidget.tsx | 48 ++++++++++++++----- .../panel_editor/VisualizationTab.tsx | 4 +- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx index b3d5e6167c4..21c4451d9b9 100644 --- a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx +++ b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx @@ -17,6 +17,17 @@ export interface State { copiedPanelPlugins: any[]; } +type Location = { + query: { + panelId: number; + edit: boolean; + fullscreen: boolean; + tab?: string; + isVizPickerOpen?: boolean; + }; + partial: boolean; +}; + export class AddPanelWidget extends React.Component { constructor(props) { super(props); @@ -62,18 +73,8 @@ export class AddPanelWidget extends React.Component { ); } - moveToEdit(panel, tab) { - reduxStore.dispatch( - updateLocation({ - query: { - panelId: panel.id, - edit: true, - fullscreen: true, - tab: tab, - }, - partial: true, - }) - ); + moveToEdit(location) { + reduxStore.dispatch(updateLocation(location)); } onCreateNewPanel = (tab = 'queries') => { @@ -89,7 +90,28 @@ export class AddPanelWidget extends React.Component { dashboard.addPanel(newPanel); dashboard.removePanel(this.props.panel); - this.moveToEdit(newPanel, tab); + let location: Location = { + query: { + panelId: newPanel.id, + edit: true, + fullscreen: true, + }, + partial: true, + }; + + if (tab === 'visualization') { + location = { + ...location, + query: { + ...location.query, + tab: 'visualization', + isVizPickerOpen: true, + }, + }; + this.moveToEdit(location); + } else { + this.moveToEdit(location); + } }; onPasteCopiedPanel = panelPluginInfo => { diff --git a/public/app/features/dashboard/panel_editor/VisualizationTab.tsx b/public/app/features/dashboard/panel_editor/VisualizationTab.tsx index fdf978acdf9..1ca290d4051 100644 --- a/public/app/features/dashboard/panel_editor/VisualizationTab.tsx +++ b/public/app/features/dashboard/panel_editor/VisualizationTab.tsx @@ -3,7 +3,7 @@ import React, { PureComponent } from 'react'; // Utils & Services import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader'; -//TODO: See PanelEdit +import { store } from 'app/store/store'; // Components import { EditorTabBody, EditorToolbarView } from './EditorTabBody'; @@ -39,7 +39,7 @@ export class VisualizationTab extends PureComponent { super(props); this.state = { - isVizPickerOpen: false, + isVizPickerOpen: store.getState().location.query.isVizPickerOpen === true, searchQuery: '', scrollTop: 0, }; 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 15/28] 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 16/28] 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 275800cca94b79f6aac993e535e8d5f67bb9129a Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 4 Feb 2019 23:19:14 -0800 Subject: [PATCH 17/28] improve the stackdriver logo --- .../stackdriver/img/stackdriver_logo.png | Bin 15726 -> 0 bytes .../stackdriver/img/stackdriver_logo.svg | 1 + .../plugins/datasource/stackdriver/plugin.json | 4 ++-- 3 files changed, 3 insertions(+), 2 deletions(-) delete mode 100644 public/app/plugins/datasource/stackdriver/img/stackdriver_logo.png create mode 100644 public/app/plugins/datasource/stackdriver/img/stackdriver_logo.svg diff --git a/public/app/plugins/datasource/stackdriver/img/stackdriver_logo.png b/public/app/plugins/datasource/stackdriver/img/stackdriver_logo.png deleted file mode 100644 index cd52e773deb8e2fb4fbc27ff188541ce8e649025..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15726 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 UbDsl#Kn+rmQI@WdG!FWI0AbhjWdHyG diff --git a/public/app/plugins/datasource/stackdriver/img/stackdriver_logo.svg b/public/app/plugins/datasource/stackdriver/img/stackdriver_logo.svg new file mode 100644 index 00000000000..93878f20a06 --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/img/stackdriver_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/app/plugins/datasource/stackdriver/plugin.json b/public/app/plugins/datasource/stackdriver/plugin.json index e68100c0e59..1ee3d57e9b1 100644 --- a/public/app/plugins/datasource/stackdriver/plugin.json +++ b/public/app/plugins/datasource/stackdriver/plugin.json @@ -14,8 +14,8 @@ "description": "Google Stackdriver Datasource for Grafana", "version": "1.0.0", "logos": { - "small": "img/stackdriver_logo.png", - "large": "img/stackdriver_logo.png" + "small": "img/stackdriver_logo.svg", + "large": "img/stackdriver_logo.svg" }, "author": { "name": "Grafana Project", 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 18/28] 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; From 139fb65fa926c17cdb68f15c69da95c8eeefd7ee Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 5 Feb 2019 12:36:12 +0100 Subject: [PATCH 19/28] docs: fixes #14940 --- docs/sources/installation/configuration.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/sources/installation/configuration.md b/docs/sources/installation/configuration.md index 46bab83654e..ac3dc6ebfd0 100644 --- a/docs/sources/installation/configuration.md +++ b/docs/sources/installation/configuration.md @@ -393,9 +393,7 @@ Analytics ID here. By default this feature is disabled. ### check_for_updates -Set to false to disable all checks to https://grafana.com for new versions of Grafana and installed plugins. Check is used -in some UI views to notify that a Grafana or plugin update exists. This option does not cause any auto updates, nor -send any sensitive information. +Set to false to disable all checks to https://grafana.com for new versions of installed plugins and to the Grafana GitHub repository to check for a newer version of Grafana. The version information is used in some UI views to notify that a new Grafana update or a plugin update exists. This option does not cause any auto updates, nor send any sensitive information. The check is run every 10 minutes.
From 2802569529197d48e602da0f67bc9f2e1b1e75a1 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Tue, 5 Feb 2019 12:47:42 +0100 Subject: [PATCH 20/28] minor layout change, simple render test --- .../AddPanelWidget/AddPanelWidget.test.tsx | 23 ++++++ .../AddPanelWidget/AddPanelWidget.tsx | 39 +++++---- .../AddPanelWidget/_AddPanelWidget.scss | 27 ++++--- .../AddPanelWidget.test.tsx.snap | 81 +++++++++++++++++++ 4 files changed, 146 insertions(+), 24 deletions(-) create mode 100644 public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.test.tsx create mode 100644 public/app/features/dashboard/components/AddPanelWidget/__snapshots__/AddPanelWidget.test.tsx.snap diff --git a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.test.tsx b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.test.tsx new file mode 100644 index 00000000000..91da066e4cc --- /dev/null +++ b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.test.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { AddPanelWidget, Props } from './AddPanelWidget'; +import { DashboardModel, PanelModel } from '../../state'; + +const setup = (propOverrides?: object) => { + const props: Props = { + dashboard: {} as DashboardModel, + panel: {} as PanelModel, + }; + + Object.assign(props, propOverrides); + + return shallow(); +}; + +describe('Render', () => { + it('should render component', () => { + const wrapper = setup(); + + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx index 21c4451d9b9..e70615bde39 100644 --- a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx +++ b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx @@ -1,8 +1,8 @@ import React from 'react'; import _ from 'lodash'; import config from 'app/core/config'; -import { PanelModel } from '../../state/PanelModel'; -import { DashboardModel } from '../../state/DashboardModel'; +import { PanelModel } from '../../state'; +import { DashboardModel } from '../../state'; import store from 'app/core/store'; import { LS_PANEL_COPY_KEY } from 'app/core/constants'; import { updateLocation } from 'app/core/actions'; @@ -57,6 +57,7 @@ export class AddPanelWidget extends React.Component { copiedPanels.push(pluginCopy); } } + return _.sortBy(copiedPanels, 'sort'); } @@ -65,14 +66,6 @@ export class AddPanelWidget extends React.Component { this.props.dashboard.removePanel(this.props.dashboard.panels[0]); } - copyButton(panel) { - return ( - - ); - } - moveToEdit(location) { reduxStore.dispatch(updateLocation(location)); } @@ -151,7 +144,7 @@ export class AddPanelWidget extends React.Component { renderOptionLink = (icon, text, onClick) => { return (
- +
@@ -162,6 +155,8 @@ export class AddPanelWidget extends React.Component { }; render() { + const { copiedPanelPlugins } = this.state; + return (
@@ -172,9 +167,25 @@ export class AddPanelWidget extends React.Component {
- {this.renderOptionLink('queries', 'Add query', this.onCreateNewPanel)} - {this.renderOptionLink('visualization', 'Choose Panel type', () => this.onCreateNewPanel('visualization'))} - {this.renderOptionLink('queries', 'Convert to row', this.onCreateNewRow)} +
+ {this.renderOptionLink('queries', 'Add query', this.onCreateNewPanel)} + {this.renderOptionLink('visualization', 'Choose Panel type', () => + this.onCreateNewPanel('visualization') + )} +
+
+
+ Convert to row +
+ {copiedPanelPlugins.length === 1 && ( +
this.onPasteCopiedPanel(copiedPanelPlugins[0])} + > + Paste copied panel +
+ )} +
diff --git a/public/app/features/dashboard/components/AddPanelWidget/_AddPanelWidget.scss b/public/app/features/dashboard/components/AddPanelWidget/_AddPanelWidget.scss index 587daa2703f..ab6ff8556d8 100644 --- a/public/app/features/dashboard/components/AddPanelWidget/_AddPanelWidget.scss +++ b/public/app/features/dashboard/components/AddPanelWidget/_AddPanelWidget.scss @@ -27,11 +27,8 @@ } .add-panel-widget__link { - display: block; margin: 0 8px; - width: 130px; - text-align: center; - padding: 8px 0; + width: 150px; } .add-panel-widget__icon { @@ -54,14 +51,24 @@ margin-right: -10px; } +.add-panel-widget__create { + display: inherit; + margin-bottom: 24px; +} + +.add-panel-widget__actions { + display: inherit; +} + +.add-panel-widget__action { + cursor: pointer; + margin: 0 4px; +} + .add-panel-widget__btn-container { + height: 100%; display: flex; justify-content: center; align-items: center; - height: 100%; - flex-direction: row; - - .btn { - margin-bottom: 10px; - } + flex-direction: column; } diff --git a/public/app/features/dashboard/components/AddPanelWidget/__snapshots__/AddPanelWidget.test.tsx.snap b/public/app/features/dashboard/components/AddPanelWidget/__snapshots__/AddPanelWidget.test.tsx.snap new file mode 100644 index 00000000000..585f45210af --- /dev/null +++ b/public/app/features/dashboard/components/AddPanelWidget/__snapshots__/AddPanelWidget.test.tsx.snap @@ -0,0 +1,81 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Render should render component 1`] = ` + +`; From 9ba98b87035e07714bf65c7a11c63a3f1c0c952f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Tue, 5 Feb 2019 13:13:52 +0100 Subject: [PATCH 21/28] Fixes #15223 by handling onPaste event because of bug in Slate --- public/app/features/explore/QueryField.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/public/app/features/explore/QueryField.tsx b/public/app/features/explore/QueryField.tsx index 8ab7e56dc5a..810bca9ef5a 100644 --- a/public/app/features/explore/QueryField.tsx +++ b/public/app/features/explore/QueryField.tsx @@ -468,6 +468,14 @@ export class QueryField extends React.PureComponent { + const pastedValue = event.clipboardData.getData('Text'); + const newValue = change.value.change().insertText(pastedValue); + this.onChange(newValue); + + return true; + }; + render() { const { disabled } = this.props; const wrapperClassName = classnames('slate-query-field__wrapper', { @@ -484,6 +492,7 @@ export class QueryField extends React.PureComponent Date: Tue, 5 Feb 2019 15:25:19 +0100 Subject: [PATCH 22/28] Fixed so that we close angular TimePicker when user clicks outside the dropdown --- public/app/routes/GrafanaCtrl.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/public/app/routes/GrafanaCtrl.ts b/public/app/routes/GrafanaCtrl.ts index 70bdf49e5e4..a860ba87e6b 100644 --- a/public/app/routes/GrafanaCtrl.ts +++ b/public/app/routes/GrafanaCtrl.ts @@ -280,6 +280,24 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop if (popover.length > 0 && target.parents('.graph-legend').length === 0) { popover.hide(); } + + // hide time picker + const timePickerDropDownIsOpen = elem.find('.gf-timepicker-dropdown').length > 0; + const targetIsInTimePickerDropDown = target.parents('.gf-timepicker-dropdown').length > 0; + const targetIsInTimePickerNav = target.parents('.gf-timepicker-nav').length > 0; + const targetIsDatePickerRowBtn = target.parents('td[id^="datepicker-"]').length > 0; + const targetIsDatePickerHeaderBtn = target.parents('button[id^="datepicker-"]').length > 0; + if ( + timePickerDropDownIsOpen && + !targetIsInTimePickerNav && + !targetIsInTimePickerDropDown && + !targetIsDatePickerRowBtn && + !targetIsDatePickerHeaderBtn + ) { + scope.$apply(() => { + scope.appEvent('closeTimepicker'); + }); + } }); }, }; From 0302c7afa7446b28baf02dec1b2c43e5e7f8d3d6 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 5 Feb 2019 15:28:03 +0100 Subject: [PATCH 23/28] stackdriver: add some more typings --- .../stackdriver/components/QueryEditor.tsx | 8 +++--- .../datasource/stackdriver/datasource.ts | 7 ++--- .../datasource/stackdriver/query_ctrl.ts | 4 +-- .../plugins/datasource/stackdriver/types.ts | 26 +++++++++++-------- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx b/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx index 94521041416..c3bd9212b21 100644 --- a/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx +++ b/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx @@ -10,21 +10,21 @@ import { Alignments } from './Alignments'; import { AlignmentPeriods } from './AlignmentPeriods'; import { AliasBy } from './AliasBy'; import { Help } from './Help'; -import { Target, MetricDescriptor } from '../types'; +import { StackdriverQuery, MetricDescriptor } from '../types'; import { getAlignmentPickerData } from '../functions'; import StackdriverDatasource from '../datasource'; import { SelectOptionItem } from '@grafana/ui'; export interface Props { - onQueryChange: (target: Target) => void; + onQueryChange: (target: StackdriverQuery) => void; onExecuteQuery: () => void; - target: Target; + target: StackdriverQuery; events: any; datasource: StackdriverDatasource; templateSrv: TemplateSrv; } -interface State extends Target { +interface State extends StackdriverQuery { alignOptions: SelectOptionItem[]; lastQuery: string; lastQueryError: string; diff --git a/public/app/plugins/datasource/stackdriver/datasource.ts b/public/app/plugins/datasource/stackdriver/datasource.ts index 025955105a7..15c6350c8a0 100644 --- a/public/app/plugins/datasource/stackdriver/datasource.ts +++ b/public/app/plugins/datasource/stackdriver/datasource.ts @@ -2,9 +2,10 @@ import { stackdriverUnitMappings } from './constants'; import appEvents from 'app/core/app_events'; import _ from 'lodash'; import StackdriverMetricFindQuery from './StackdriverMetricFindQuery'; -import { MetricDescriptor } from './types'; +import { StackdriverQuery, MetricDescriptor } from './types'; +import { DataSourceApi, DataQueryOptions } from '@grafana/ui/src/types'; -export default class StackdriverDatasource { +export default class StackdriverDatasource implements DataSourceApi { id: number; url: string; baseUrl: string; @@ -103,7 +104,7 @@ export default class StackdriverDatasource { return unit; } - async query(options) { + async query(options: DataQueryOptions) { const result = []; const data = await this.getTimeSeries(options); if (data.results) { diff --git a/public/app/plugins/datasource/stackdriver/query_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_ctrl.ts index c6a8a4d9782..3a2d0bb970a 100644 --- a/public/app/plugins/datasource/stackdriver/query_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_ctrl.ts @@ -1,7 +1,7 @@ import _ from 'lodash'; import { QueryCtrl } from 'app/plugins/sdk'; -import { Target } from './types'; +import { StackdriverQuery } from './types'; import { TemplateSrv } from 'app/features/templating/template_srv'; export class StackdriverQueryCtrl extends QueryCtrl { @@ -16,7 +16,7 @@ export class StackdriverQueryCtrl extends QueryCtrl { this.onExecuteQuery = this.onExecuteQuery.bind(this); } - onQueryChange(target: Target) { + onQueryChange(target: StackdriverQuery) { Object.assign(this.target, target); } diff --git a/public/app/plugins/datasource/stackdriver/types.ts b/public/app/plugins/datasource/stackdriver/types.ts index 29b12b4289d..83909bbafce 100644 --- a/public/app/plugins/datasource/stackdriver/types.ts +++ b/public/app/plugins/datasource/stackdriver/types.ts @@ -1,3 +1,5 @@ +import { DataQuery } from '@grafana/ui/src/types'; + export enum MetricFindQueryTypes { Services = 'services', MetricTypes = 'metricTypes', @@ -20,20 +22,22 @@ export interface VariableQueryData { services: Array<{ value: string; name: string }>; } -export interface Target { - defaultProject: string; - unit: string; +export interface StackdriverQuery extends DataQuery { + defaultProject?: string; + unit?: string; metricType: string; - service: string; + service?: string; refId: string; crossSeriesReducer: string; - alignmentPeriod: string; - perSeriesAligner: string; - groupBys: string[]; - filters: string[]; - aliasBy: string; - metricKind: string; - valueType: string; + alignmentPeriod?: string; + perSeriesAligner?: string; + groupBys?: string[]; + filters?: string[]; + aliasBy?: string; + metricKind?: string; + valueType?: string; + datasourceId: number; + view: string; } export interface AnnotationTarget { From a344091d82e74a8054fed8279ca271a41278980e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Tue, 5 Feb 2019 15:29:19 +0100 Subject: [PATCH 24/28] Optimized so we only do checks when dropdown is opened --- public/app/routes/GrafanaCtrl.ts | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/public/app/routes/GrafanaCtrl.ts b/public/app/routes/GrafanaCtrl.ts index a860ba87e6b..c6945f26d08 100644 --- a/public/app/routes/GrafanaCtrl.ts +++ b/public/app/routes/GrafanaCtrl.ts @@ -283,17 +283,21 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop // hide time picker const timePickerDropDownIsOpen = elem.find('.gf-timepicker-dropdown').length > 0; - const targetIsInTimePickerDropDown = target.parents('.gf-timepicker-dropdown').length > 0; - const targetIsInTimePickerNav = target.parents('.gf-timepicker-nav').length > 0; - const targetIsDatePickerRowBtn = target.parents('td[id^="datepicker-"]').length > 0; - const targetIsDatePickerHeaderBtn = target.parents('button[id^="datepicker-"]').length > 0; - if ( - timePickerDropDownIsOpen && - !targetIsInTimePickerNav && - !targetIsInTimePickerDropDown && - !targetIsDatePickerRowBtn && - !targetIsDatePickerHeaderBtn - ) { + if (timePickerDropDownIsOpen) { + const targetIsInTimePickerDropDown = target.parents('.gf-timepicker-dropdown').length > 0; + const targetIsInTimePickerNav = target.parents('.gf-timepicker-nav').length > 0; + const targetIsDatePickerRowBtn = target.parents('td[id^="datepicker-"]').length > 0; + const targetIsDatePickerHeaderBtn = target.parents('button[id^="datepicker-"]').length > 0; + + if ( + targetIsInTimePickerNav || + targetIsInTimePickerDropDown || + targetIsDatePickerRowBtn || + targetIsDatePickerHeaderBtn + ) { + return; + } + scope.$apply(() => { scope.appEvent('closeTimepicker'); }); From e42b670f5c29df535d4cb5477077011ab5ec9842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Tue, 5 Feb 2019 15:41:00 +0100 Subject: [PATCH 25/28] Closing timepicker when clicking outside the picker --- public/app/features/explore/ExploreToolbar.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/public/app/features/explore/ExploreToolbar.tsx b/public/app/features/explore/ExploreToolbar.tsx index 35f06d11c81..4d9620e311e 100644 --- a/public/app/features/explore/ExploreToolbar.tsx +++ b/public/app/features/explore/ExploreToolbar.tsx @@ -8,6 +8,7 @@ import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker'; import { StoreState } from 'app/types/store'; import { changeDatasource, clearQueries, splitClose, runQueries, splitOpen } from './state/actions'; import TimePicker from './TimePicker'; +import { ClickOutsideWrapper } from 'app/core/components/ClickOutsideWrapper/ClickOutsideWrapper'; enum IconSide { left = 'left', @@ -79,6 +80,10 @@ export class UnConnectedExploreToolbar extends PureComponent { this.props.runQuery(this.props.exploreId); }; + onCloseTimePicker = () => { + this.props.timepickerRef.current.setState({ isOpen: false }); + }; + render() { const { datasourceMissing, @@ -137,7 +142,9 @@ export class UnConnectedExploreToolbar extends PureComponent {
) : null}
- + + +
- {this.renderOptionLink('queries', 'Add query', this.onCreateNewPanel)} - {this.renderOptionLink('visualization', 'Choose Panel type', () => + {this.renderOptionLink('queries', 'Add Query', this.onCreateNewPanel)} + {this.renderOptionLink('visualization', 'Choose Visualization', () => this.onCreateNewPanel('visualization') )}
-
- Convert to row -
+ {copiedPanelPlugins.length === 1 && ( -
this.onPasteCopiedPanel(copiedPanelPlugins[0])} > Paste copied panel -
+ )}
diff --git a/public/app/features/dashboard/components/AddPanelWidget/_AddPanelWidget.scss b/public/app/features/dashboard/components/AddPanelWidget/_AddPanelWidget.scss index ab6ff8556d8..288b2e7a410 100644 --- a/public/app/features/dashboard/components/AddPanelWidget/_AddPanelWidget.scss +++ b/public/app/features/dashboard/components/AddPanelWidget/_AddPanelWidget.scss @@ -14,6 +14,9 @@ align-items: center; width: 100%; cursor: move; + background: $page-header-bg; + box-shadow: $page-header-shadow; + border-bottom: 1px solid $page-header-border-color; .gicon { font-size: 30px; @@ -26,9 +29,15 @@ } } +.add-panel-widget__title { + font-size: $font-size-md; + font-weight: $font-weight-semi-bold; + margin-right: $spacer*2; +} + .add-panel-widget__link { margin: 0 8px; - width: 150px; + width: 154px; } .add-panel-widget__icon { @@ -54,6 +63,8 @@ .add-panel-widget__create { display: inherit; margin-bottom: 24px; + // this is to have the big button appear centered + margin-top: 55px; } .add-panel-widget__actions { @@ -61,7 +72,6 @@ } .add-panel-widget__action { - cursor: pointer; margin: 0 4px; } diff --git a/public/app/features/dashboard/components/AddPanelWidget/__snapshots__/AddPanelWidget.test.tsx.snap b/public/app/features/dashboard/components/AddPanelWidget/__snapshots__/AddPanelWidget.test.tsx.snap index 585f45210af..00faf48d8df 100644 --- a/public/app/features/dashboard/components/AddPanelWidget/__snapshots__/AddPanelWidget.test.tsx.snap +++ b/public/app/features/dashboard/components/AddPanelWidget/__snapshots__/AddPanelWidget.test.tsx.snap @@ -13,6 +13,11 @@ exports[`Render should render component 1`] = ` + + New Panel +
- Add query + Add Query
@@ -60,7 +65,7 @@ exports[`Render should render component 1`] = ` />
- Choose Panel type + Choose Visualization @@ -68,12 +73,12 @@ exports[`Render should render component 1`] = `
-
Convert to row -
+
diff --git a/public/app/features/dashboard/panel_editor/PanelEditor.tsx b/public/app/features/dashboard/panel_editor/PanelEditor.tsx index d7aafb89e55..bfdc13bc8f2 100644 --- a/public/app/features/dashboard/panel_editor/PanelEditor.tsx +++ b/public/app/features/dashboard/panel_editor/PanelEditor.tsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import classNames from 'classnames'; import { QueriesTab } from './QueriesTab'; -import { VisualizationTab } from './VisualizationTab'; +import VisualizationTab from './VisualizationTab'; import { GeneralTab } from './GeneralTab'; import { AlertTab } from '../../alerting/AlertTab'; @@ -38,7 +38,7 @@ export class PanelEditor extends PureComponent { onChangeTab = (tab: PanelEditorTab) => { store.dispatch( updateLocation({ - query: { tab: tab.id }, + query: { tab: tab.id, openVizPicker: null }, partial: true, }) ); diff --git a/public/app/features/dashboard/panel_editor/VisualizationTab.tsx b/public/app/features/dashboard/panel_editor/VisualizationTab.tsx index 1ca290d4051..94a403c11bf 100644 --- a/public/app/features/dashboard/panel_editor/VisualizationTab.tsx +++ b/public/app/features/dashboard/panel_editor/VisualizationTab.tsx @@ -3,7 +3,9 @@ import React, { PureComponent } from 'react'; // Utils & Services import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader'; -import { store } from 'app/store/store'; +import { connectWithStore } from 'app/core/utils/connectWithReduxStore'; +import { StoreState } from 'app/types'; +import { updateLocation } from 'app/core/actions'; // Components import { EditorTabBody, EditorToolbarView } from './EditorTabBody'; @@ -22,6 +24,8 @@ interface Props { plugin: PanelPlugin; angularPanel?: AngularComponent; onTypeChanged: (newType: PanelPlugin) => void; + updateLocation: typeof updateLocation; + urlOpenVizPicker: boolean; } interface State { @@ -39,7 +43,7 @@ export class VisualizationTab extends PureComponent { super(props); this.state = { - isVizPickerOpen: store.getState().location.query.isVizPickerOpen === true, + isVizPickerOpen: this.props.urlOpenVizPicker, searchQuery: '', scrollTop: 0, }; @@ -150,6 +154,10 @@ export class VisualizationTab extends PureComponent { }; onCloseVizPicker = () => { + if (this.props.urlOpenVizPicker) { + this.props.updateLocation({ query: { openVizPicker: null }, partial: true }); + } + this.setState({ isVizPickerOpen: false }); }; @@ -237,3 +245,13 @@ export class VisualizationTab extends PureComponent { ); } } + +const mapStateToProps = (state: StoreState) => ({ + urlOpenVizPicker: !!state.location.query.openVizPicker +}); + +const mapDispatchToProps = { + updateLocation +}; + +export default connectWithStore(VisualizationTab, mapStateToProps, mapDispatchToProps); diff --git a/public/img/icons_dark_theme/icon_advanced.svg b/public/img/icons_dark_theme/icon_advanced.svg index 5fd18a86dd5..dea3ddff685 100644 --- a/public/img/icons_dark_theme/icon_advanced.svg +++ b/public/img/icons_dark_theme/icon_advanced.svg @@ -4,7 +4,7 @@ diff --git a/public/img/icons_dark_theme/icon_advanced_active.svg b/public/img/icons_dark_theme/icon_advanced_active.svg index 80672a2595b..1227ddc868c 100644 --- a/public/img/icons_dark_theme/icon_advanced_active.svg +++ b/public/img/icons_dark_theme/icon_advanced_active.svg @@ -5,7 +5,7 @@ width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">