From 546a3a9d983c001240dbd6f76c81575cb0b972fc Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Sun, 13 Jan 2019 23:10:23 +0100 Subject: [PATCH] Connect Explore child components to store --- public/app/features/explore/Explore.tsx | 201 ++---------------- .../app/features/explore/GraphContainer.tsx | 61 ++++++ public/app/features/explore/LogsContainer.tsx | 91 ++++++++ public/app/features/explore/QueryRow.tsx | 163 ++++++++++++++ public/app/features/explore/QueryRows.tsx | 152 +------------ .../app/features/explore/TableContainer.tsx | 49 +++++ public/app/features/explore/state/reducers.ts | 1 + 7 files changed, 394 insertions(+), 324 deletions(-) create mode 100644 public/app/features/explore/GraphContainer.tsx create mode 100644 public/app/features/explore/LogsContainer.tsx create mode 100644 public/app/features/explore/QueryRow.tsx create mode 100644 public/app/features/explore/TableContainer.tsx diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 2e9c71e5517..a70135c00ad 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -6,86 +6,58 @@ import { AutoSizer } from 'react-virtualized'; import { RawTimeRange, TimeRange } from '@grafana/ui'; import { DataSourceSelectItem } from 'app/types/datasources'; -import { - ExploreItemState, - ExploreUrlState, - HistoryItem, - QueryTransaction, - RangeScanner, - ExploreId, -} from 'app/types/explore'; +import { ExploreItemState, ExploreUrlState, RangeScanner, ExploreId } from 'app/types/explore'; import { DataQuery } from 'app/types/series'; import { StoreState } from 'app/types'; import store from 'app/core/store'; import { LAST_USED_DATASOURCE_KEY, ensureQueries, DEFAULT_RANGE } from 'app/core/utils/explore'; import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker'; import { Emitter } from 'app/core/utils/emitter'; -import { LogsModel } from 'app/core/logs_model'; -import TableModel from 'app/core/table_model'; import { - addQueryRow, changeDatasource, - changeQuery, changeSize, changeTime, clickClear, clickCloseSplit, clickExample, - clickGraphButton, - clickLogsButton, clickSplit, - clickTableButton, - highlightLogsExpression, initializeExplore, modifyQueries, - removeQueryRow, runQueries, scanStart, scanStop, } from './state/actions'; -import Panel from './Panel'; -import QueryRows from './QueryRows'; -import Graph from './Graph'; -import Logs from './Logs'; -import Table from './Table'; -import ErrorBoundary from './ErrorBoundary'; import { Alert } from './Error'; +import ErrorBoundary from './ErrorBoundary'; +import GraphContainer from './GraphContainer'; +import LogsContainer from './LogsContainer'; +import QueryRows from './QueryRows'; +import TableContainer from './TableContainer'; import TimePicker, { parseTime } from './TimePicker'; interface ExploreProps { StartPage?: any; - addQueryRow: typeof addQueryRow; changeDatasource: typeof changeDatasource; - changeQuery: typeof changeQuery; changeSize: typeof changeSize; changeTime: typeof changeTime; clickClear: typeof clickClear; clickCloseSplit: typeof clickCloseSplit; clickExample: typeof clickExample; - clickGraphButton: typeof clickGraphButton; - clickLogsButton: typeof clickLogsButton; clickSplit: typeof clickSplit; - clickTableButton: typeof clickTableButton; datasourceError: string; datasourceInstance: any; datasourceLoading: boolean | null; datasourceMissing: boolean; exploreDatasources: DataSourceSelectItem[]; exploreId: ExploreId; - graphResult?: any[]; - highlightLogsExpression: typeof highlightLogsExpression; - history: HistoryItem[]; initialDatasource?: string; initialQueries: DataQuery[]; initializeExplore: typeof initializeExplore; initialized: boolean; - logsHighlighterExpressions?: string[]; - logsResult?: LogsModel; + loading: boolean; modifyQueries: typeof modifyQueries; - queryTransactions: QueryTransaction[]; - removeQueryRow: typeof removeQueryRow; range: RawTimeRange; runQueries: typeof runQueries; scanner?: RangeScanner; @@ -94,14 +66,10 @@ interface ExploreProps { scanStart: typeof scanStart; scanStop: typeof scanStop; split: boolean; - showingGraph: boolean; - showingLogs: boolean; showingStartPage?: boolean; - showingTable: boolean; supportsGraph: boolean | null; supportsLogs: boolean | null; supportsTable: boolean | null; - tableResult?: TableModel; urlState: ExploreUrlState; } @@ -172,24 +140,10 @@ export class Explore extends React.PureComponent { this.el = el; }; - onAddQueryRow = index => { - this.props.addQueryRow(this.props.exploreId, index); - }; - onChangeDatasource = async option => { this.props.changeDatasource(this.props.exploreId, option.value); }; - onChangeQuery = (query: DataQuery, index: number, override?: boolean) => { - const { changeQuery, datasourceInstance, exploreId } = this.props; - - changeQuery(exploreId, query, index, override); - if (query && !override && datasourceInstance.getHighlighterExpression && index === 0) { - // Live preview of log search matches. Only use on first row for now - this.updateLogsHighlights(query); - } - }; - onChangeTime = (range: TimeRange, changedByScanner?: boolean) => { if (this.props.scanning && !changedByScanner) { this.onStopScanning(); @@ -205,14 +159,6 @@ export class Explore extends React.PureComponent { this.props.clickCloseSplit(); }; - onClickGraphButton = () => { - this.props.clickGraphButton(this.props.exploreId); - }; - - onClickLogsButton = () => { - this.props.clickLogsButton(this.props.exploreId); - }; - // Use this in help pages to set page to a single query onClickExample = (query: DataQuery) => { this.props.clickExample(this.props.exploreId, query); @@ -222,10 +168,6 @@ export class Explore extends React.PureComponent { this.props.clickSplit(); }; - onClickTableButton = () => { - this.props.clickTableButton(this.props.exploreId); - }; - onClickLabel = (key: string, value: string) => { this.onModifyQueries({ type: 'ADD_FILTER', key, value }); }; @@ -238,10 +180,6 @@ export class Explore extends React.PureComponent { } }; - onRemoveQueryRow = index => { - this.props.removeQueryRow(this.props.exploreId, index); - }; - onResize = (size: { height: number; width: number }) => { this.props.changeSize(this.props.exploreId, size); }; @@ -265,14 +203,6 @@ export class Explore extends React.PureComponent { this.props.runQueries(this.props.exploreId); }; - updateLogsHighlights = _.debounce((value: DataQuery) => { - const { datasourceInstance } = this.props; - if (datasourceInstance.getHighlighterExpression) { - const expressions = [datasourceInstance.getHighlighterExpression(value)]; - this.props.highlightLogsExpression(this.props.exploreId, expressions); - } - }, 500); - render() { const { StartPage, @@ -282,34 +212,19 @@ export class Explore extends React.PureComponent { datasourceMissing, exploreDatasources, exploreId, - graphResult, - history, + loading, initialQueries, - logsHighlighterExpressions, - logsResult, - queryTransactions, range, - scanning, - scanRange, - showingGraph, - showingLogs, showingStartPage, - showingTable, split, supportsGraph, supportsLogs, supportsTable, - tableResult, } = this.props; - const graphHeight = showingGraph && showingTable ? '200px' : '400px'; const exploreClass = split ? 'explore explore-split' : 'explore'; const selectedDatasource = datasourceInstance ? exploreDatasources.find(d => d.name === datasourceInstance.name) : undefined; - const graphLoading = queryTransactions.some(qt => qt.resultType === 'Graph' && !qt.done); - const tableLoading = queryTransactions.some(qt => qt.resultType === 'Table' && !qt.done); - const logsLoading = queryTransactions.some(qt => qt.resultType === 'Logs' && !qt.done); - const loading = queryTransactions.some(qt => !qt.done); return (
@@ -372,19 +287,7 @@ export class Explore extends React.PureComponent { {datasourceInstance && !datasourceError && (
- + {({ width }) => (
@@ -392,55 +295,16 @@ export class Explore extends React.PureComponent { {showingStartPage && } {!showingStartPage && ( <> - {supportsGraph && ( - - - - )} - {supportsTable && ( - - - - )} + {supportsGraph && } + {supportsTable && } {supportsLogs && ( - - - + )} )} @@ -466,26 +330,17 @@ function mapStateToProps(state: StoreState, { exploreId }) { datasourceLoading, datasourceMissing, exploreDatasources, - graphResult, initialDatasource, initialQueries, initialized, - history, - logsHighlighterExpressions, - logsResult, queryTransactions, range, - scanning, - scanRange, - showingGraph, - showingLogs, showingStartPage, - showingTable, supportsGraph, supportsLogs, supportsTable, - tableResult, } = item; + const loading = queryTransactions.some(qt => !qt.done); return { StartPage, datasourceError, @@ -493,46 +348,30 @@ function mapStateToProps(state: StoreState, { exploreId }) { datasourceLoading, datasourceMissing, exploreDatasources, - graphResult, initialDatasource, initialQueries, initialized, - history, - logsHighlighterExpressions, - logsResult, + loading, queryTransactions, range, - scanning, - scanRange, - showingGraph, - showingLogs, showingStartPage, - showingTable, split, supportsGraph, supportsLogs, supportsTable, - tableResult, }; } const mapDispatchToProps = { - addQueryRow, changeDatasource, - changeQuery, changeSize, changeTime, clickClear, clickCloseSplit, clickExample, - clickGraphButton, - clickLogsButton, clickSplit, - clickTableButton, - highlightLogsExpression, initializeExplore, modifyQueries, - removeQueryRow, runQueries, scanStart, scanStop, diff --git a/public/app/features/explore/GraphContainer.tsx b/public/app/features/explore/GraphContainer.tsx new file mode 100644 index 00000000000..da098f0c92d --- /dev/null +++ b/public/app/features/explore/GraphContainer.tsx @@ -0,0 +1,61 @@ +import React, { PureComponent } from 'react'; +import { hot } from 'react-hot-loader'; +import { connect } from 'react-redux'; +import { RawTimeRange, TimeRange } from '@grafana/ui'; + +import { ExploreId, ExploreItemState } from 'app/types/explore'; +import { StoreState } from 'app/types'; + +import { clickGraphButton } from './state/actions'; +import Graph from './Graph'; +import Panel from './Panel'; + +interface GraphContainerProps { + onChangeTime: (range: TimeRange) => void; + clickGraphButton: typeof clickGraphButton; + exploreId: ExploreId; + graphResult?: any[]; + loading: boolean; + range: RawTimeRange; + showingGraph: boolean; + showingTable: boolean; + split: boolean; +} + +export class GraphContainer extends PureComponent { + onClickGraphButton = () => { + this.props.clickGraphButton(this.props.exploreId); + }; + + render() { + const { exploreId, graphResult, loading, onChangeTime, showingGraph, showingTable, range, split } = this.props; + const graphHeight = showingGraph && showingTable ? '200px' : '400px'; + return ( + + + + ); + } +} + +function mapStateToProps(state: StoreState, { exploreId }) { + const explore = state.explore; + const { split } = explore; + const item: ExploreItemState = explore[exploreId]; + const { graphResult, queryTransactions, range, showingGraph, showingTable } = item; + const loading = queryTransactions.some(qt => qt.resultType === 'Graph' && !qt.done); + return { graphResult, loading, range, showingGraph, showingTable, split }; +} + +const mapDispatchToProps = { + clickGraphButton, +}; + +export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(GraphContainer)); diff --git a/public/app/features/explore/LogsContainer.tsx b/public/app/features/explore/LogsContainer.tsx new file mode 100644 index 00000000000..db2f681a9c5 --- /dev/null +++ b/public/app/features/explore/LogsContainer.tsx @@ -0,0 +1,91 @@ +import React, { PureComponent } from 'react'; +import { hot } from 'react-hot-loader'; +import { connect } from 'react-redux'; +import { RawTimeRange, TimeRange } from '@grafana/ui'; + +import { ExploreId, ExploreItemState } from 'app/types/explore'; +import { LogsModel } from 'app/core/logs_model'; +import { StoreState } from 'app/types'; + +import { clickLogsButton } from './state/actions'; +import Logs from './Logs'; +import Panel from './Panel'; + +interface LogsContainerProps { + clickLogsButton: typeof clickLogsButton; + exploreId: ExploreId; + loading: boolean; + logsHighlighterExpressions?: string[]; + logsResult?: LogsModel; + onChangeTime: (range: TimeRange) => void; + onClickLabel: (key: string, value: string) => void; + onStartScanning: () => void; + onStopScanning: () => void; + range: RawTimeRange; + scanning?: boolean; + scanRange?: RawTimeRange; + showingLogs: boolean; +} + +export class LogsContainer extends PureComponent { + onClickLogsButton = () => { + this.props.clickLogsButton(this.props.exploreId); + }; + + render() { + const { + exploreId, + loading, + logsHighlighterExpressions, + logsResult, + onChangeTime, + onClickLabel, + onStartScanning, + onStopScanning, + range, + showingLogs, + scanning, + scanRange, + } = this.props; + return ( + + + + ); + } +} + +function mapStateToProps(state: StoreState, { exploreId }) { + const explore = state.explore; + const item: ExploreItemState = explore[exploreId]; + const { logsHighlighterExpressions, logsResult, queryTransactions, scanning, scanRange, showingLogs, range } = item; + const loading = queryTransactions.some(qt => qt.resultType === 'Logs' && !qt.done); + return { + loading, + logsHighlighterExpressions, + logsResult, + scanning, + scanRange, + showingLogs, + range, + }; +} + +const mapDispatchToProps = { + clickLogsButton, +}; + +export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(LogsContainer)); diff --git a/public/app/features/explore/QueryRow.tsx b/public/app/features/explore/QueryRow.tsx new file mode 100644 index 00000000000..b5b150b3ba8 --- /dev/null +++ b/public/app/features/explore/QueryRow.tsx @@ -0,0 +1,163 @@ +import React, { PureComponent } from 'react'; +import { hot } from 'react-hot-loader'; +import { connect } from 'react-redux'; +import { RawTimeRange } from '@grafana/ui'; +import _ from 'lodash'; + +import { QueryTransaction, HistoryItem, QueryHint, ExploreItemState, ExploreId } from 'app/types/explore'; +import { Emitter } from 'app/core/utils/emitter'; +import { DataQuery, StoreState } from 'app/types'; + +// import DefaultQueryField from './QueryField'; +import QueryEditor from './QueryEditor'; +import QueryTransactionStatus from './QueryTransactionStatus'; +import { + addQueryRow, + changeQuery, + highlightLogsExpression, + modifyQueries, + removeQueryRow, + runQueries, +} from './state/actions'; + +function getFirstHintFromTransactions(transactions: QueryTransaction[]): QueryHint { + const transaction = transactions.find(qt => qt.hints && qt.hints.length > 0); + if (transaction) { + return transaction.hints[0]; + } + return undefined; +} + +interface QueryRowProps { + addQueryRow: typeof addQueryRow; + changeQuery: typeof changeQuery; + className?: string; + exploreId: ExploreId; + datasourceInstance: any; + highlightLogsExpression: typeof highlightLogsExpression; + history: HistoryItem[]; + index: number; + initialQuery: DataQuery; + modifyQueries: typeof modifyQueries; + queryTransactions: QueryTransaction[]; + exploreEvents: Emitter; + range: RawTimeRange; + removeQueryRow: typeof removeQueryRow; + runQueries: typeof runQueries; +} + +export class QueryRow extends PureComponent { + onExecuteQuery = () => { + const { exploreId } = this.props; + this.props.runQueries(exploreId); + }; + + onChangeQuery = (query: DataQuery, override?: boolean) => { + const { datasourceInstance, exploreId, index } = this.props; + this.props.changeQuery(exploreId, query, index, override); + if (query && !override && datasourceInstance.getHighlighterExpression && index === 0) { + // Live preview of log search matches. Only use on first row for now + this.updateLogsHighlights(query); + } + }; + + onClickAddButton = () => { + const { exploreId, index } = this.props; + this.props.addQueryRow(exploreId, index); + }; + + onClickClearButton = () => { + this.onChangeQuery(null, true); + }; + + onClickHintFix = action => { + const { datasourceInstance, exploreId, index } = this.props; + if (datasourceInstance && datasourceInstance.modifyQuery) { + const modifier = (queries: DataQuery, action: any) => datasourceInstance.modifyQuery(queries, action); + this.props.modifyQueries(exploreId, action, index, modifier); + } + }; + + onClickRemoveButton = () => { + const { exploreId, index } = this.props; + this.props.removeQueryRow(exploreId, index); + }; + + updateLogsHighlights = _.debounce((value: DataQuery) => { + const { datasourceInstance } = this.props; + if (datasourceInstance.getHighlighterExpression) { + const expressions = [datasourceInstance.getHighlighterExpression(value)]; + this.props.highlightLogsExpression(this.props.exploreId, expressions); + } + }, 500); + + render() { + const { datasourceInstance, history, index, initialQuery, 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); + const queryError = transactionWithError ? transactionWithError.error : null; + const QueryField = datasourceInstance.pluginExports.ExploreQueryField; + return ( +
+
+ +
+
+ {QueryField ? ( + + ) : ( + + )} +
+
+ + + +
+
+ ); + } +} + +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 mapDispatchToProps = { + addQueryRow, + changeQuery, + highlightLogsExpression, + modifyQueries, + removeQueryRow, + runQueries, +}; + +export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(QueryRow)); diff --git a/public/app/features/explore/QueryRows.tsx b/public/app/features/explore/QueryRows.tsx index 4101475092b..01bd409f444 100644 --- a/public/app/features/explore/QueryRows.tsx +++ b/public/app/features/explore/QueryRows.tsx @@ -1,159 +1,25 @@ import React, { PureComponent } from 'react'; -import { QueryTransaction, HistoryItem, QueryHint } from 'app/types/explore'; import { Emitter } from 'app/core/utils/emitter'; +import { DataQuery } from 'app/types'; +import { ExploreId } from 'app/types/explore'; -// import DefaultQueryField from './QueryField'; -import QueryEditor from './QueryEditor'; -import QueryTransactionStatus from './QueryTransactionStatus'; -import { DataSource, DataQuery } from 'app/types'; -import { RawTimeRange } from '@grafana/ui'; +import QueryRow from './QueryRow'; -function getFirstHintFromTransactions(transactions: QueryTransaction[]): QueryHint { - const transaction = transactions.find(qt => qt.hints && qt.hints.length > 0); - if (transaction) { - return transaction.hints[0]; - } - return undefined; -} - -interface QueryRowEventHandlers { - onAddQueryRow: (index: number) => void; - onChangeQuery: (value: DataQuery, index: number, override?: boolean) => void; - onClickHintFix: (action: object, index?: number) => void; - onExecuteQuery: () => void; - onRemoveQueryRow: (index: number) => void; -} - -interface QueryRowCommonProps { +interface QueryRowsProps { className?: string; - datasource: DataSource; - history: HistoryItem[]; - transactions: QueryTransaction[]; exploreEvents: Emitter; - range: RawTimeRange; + exploreId: ExploreId; + initialQueries: DataQuery[]; } - -type QueryRowProps = QueryRowCommonProps & - QueryRowEventHandlers & { - index: number; - initialQuery: DataQuery; - }; - -class QueryRow extends PureComponent { - onExecuteQuery = () => { - const { onExecuteQuery } = this.props; - onExecuteQuery(); - }; - - onChangeQuery = (value: DataQuery, override?: boolean) => { - const { index, onChangeQuery } = this.props; - if (onChangeQuery) { - onChangeQuery(value, index, override); - } - }; - - onClickAddButton = () => { - const { index, onAddQueryRow } = this.props; - if (onAddQueryRow) { - onAddQueryRow(index); - } - }; - - onClickClearButton = () => { - this.onChangeQuery(null, true); - }; - - onClickHintFix = action => { - const { index, onClickHintFix } = this.props; - if (onClickHintFix) { - onClickHintFix(action, index); - } - }; - - onClickRemoveButton = () => { - const { index, onRemoveQueryRow } = this.props; - if (onRemoveQueryRow) { - onRemoveQueryRow(index); - } - }; - - onPressEnter = () => { - const { onExecuteQuery } = this.props; - if (onExecuteQuery) { - onExecuteQuery(); - } - }; - - render() { - const { datasource, history, initialQuery, transactions, exploreEvents, range } = this.props; - const transactionWithError = transactions.find(t => t.error !== undefined); - const hint = getFirstHintFromTransactions(transactions); - const queryError = transactionWithError ? transactionWithError.error : null; - const QueryField = datasource.pluginExports.ExploreQueryField; - return ( -
-
- -
-
- {QueryField ? ( - - ) : ( - - )} -
-
- - - -
-
- ); - } -} - -type QueryRowsProps = QueryRowCommonProps & - QueryRowEventHandlers & { - initialQueries: DataQuery[]; - }; - export default class QueryRows extends PureComponent { render() { - const { className = '', initialQueries, transactions, ...handlers } = this.props; + const { className = '', exploreEvents, exploreId, initialQueries } = this.props; return (
{initialQueries.map((query, index) => ( - t.rowIndex === index)} - {...handlers} - /> + // TODO instead of relying on initialQueries, move to react key list in redux + ))}
); diff --git a/public/app/features/explore/TableContainer.tsx b/public/app/features/explore/TableContainer.tsx new file mode 100644 index 00000000000..e510a77c97f --- /dev/null +++ b/public/app/features/explore/TableContainer.tsx @@ -0,0 +1,49 @@ +import React, { PureComponent } from 'react'; +import { hot } from 'react-hot-loader'; +import { connect } from 'react-redux'; + +import { ExploreId, ExploreItemState } from 'app/types/explore'; +import { StoreState } from 'app/types'; + +import { clickTableButton } from './state/actions'; +import Table from './Table'; +import Panel from './Panel'; +import TableModel from 'app/core/table_model'; + +interface TableContainerProps { + clickTableButton: typeof clickTableButton; + exploreId: ExploreId; + loading: boolean; + onClickLabel: (key: string, value: string) => void; + showingTable: boolean; + tableResult?: TableModel; +} + +export class TableContainer extends PureComponent { + onClickTableButton = () => { + this.props.clickTableButton(this.props.exploreId); + }; + + render() { + const { loading, onClickLabel, showingTable, tableResult } = this.props; + return ( + +
+ + ); + } +} + +function mapStateToProps(state: StoreState, { exploreId }) { + const explore = state.explore; + const item: ExploreItemState = explore[exploreId]; + const { queryTransactions, showingTable, tableResult } = item; + const loading = queryTransactions.some(qt => qt.resultType === 'Table' && !qt.done); + return { loading, showingTable, tableResult }; +} + +const mapDispatchToProps = { + clickTableButton, +}; + +export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(TableContainer)); diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index 97a02c33e67..b8273051ffe 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -232,6 +232,7 @@ const itemReducer = (state, action: Action): ExploreItemState => { initialQueries: action.initialQueries, logsHighlighterExpressions: undefined, modifiedQueries: action.initialQueries.slice(), + queryTransactions: [], showingStartPage: action.showingStartPage, supportsGraph: action.supportsGraph, supportsLogs: action.supportsLogs,