diff --git a/CHANGELOG.md b/CHANGELOG.md index cd598f375..f0375eacf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## v1.1.0 [unreleased] +### Features + 1. [#610](https://github.com/influxdata/chronograf/issues/610): Add Ability to edit raw text queries in the Data Explorer + ## v1.1.0-beta2 [2016-12-09] ### Features diff --git a/ui/src/chronograf/actions/view/index.js b/ui/src/chronograf/actions/view/index.js index 7e7dcd139..751fa6bd0 100644 --- a/ui/src/chronograf/actions/view/index.js +++ b/ui/src/chronograf/actions/view/index.js @@ -34,12 +34,13 @@ export function deletePanel(panelId) { }; } -export function addQuery(panelId) { +export function addQuery(panelId, options) { return { type: 'ADD_QUERY', payload: { panelId, queryId: uuid.v4(), + options, }, }; } @@ -118,6 +119,16 @@ export function chooseMeasurement(queryId, measurement) { }; } +export function editRawText(queryId, rawText) { + return { + type: 'EDIT_RAW_TEXT', + payload: { + queryId, + rawText, + }, + }; +} + export function setTimeRange(range) { window.localStorage.setItem('timeRange', JSON.stringify(range)); diff --git a/ui/src/chronograf/components/Explorer.js b/ui/src/chronograf/components/Panel.js similarity index 68% rename from ui/src/chronograf/components/Explorer.js rename to ui/src/chronograf/components/Panel.js index f81e1cddd..94b83fb9c 100644 --- a/ui/src/chronograf/components/Explorer.js +++ b/ui/src/chronograf/components/Panel.js @@ -3,9 +3,10 @@ import classNames from 'classnames'; import QueryEditor from './QueryEditor'; import QueryTabItem from './QueryTabItem'; import RenamePanelModal from './RenamePanelModal'; +import SimpleDropdown from 'src/shared/components/SimpleDropdown'; const {shape, func, bool, arrayOf} = PropTypes; -const Explorer = React.createClass({ +const Panel = React.createClass({ propTypes: { panel: shape({}).isRequired, queries: arrayOf(shape({})).isRequired, @@ -14,7 +15,7 @@ const Explorer = React.createClass({ lower: PropTypes.string, }).isRequired, isExpanded: bool.isRequired, - onToggleExplorer: func.isRequired, + onTogglePanel: func.isRequired, actions: shape({ chooseNamespace: func.isRequired, chooseMeasurement: func.isRequired, @@ -44,12 +45,16 @@ const Explorer = React.createClass({ this.props.actions.addQuery(); }, + handleAddRawQuery() { + this.props.actions.addQuery({rawText: `SELECT "fields" from "db"."rp"."measurement"`}); + }, + handleDeleteQuery(query) { this.props.actions.deleteQuery(query.id); }, - handleSelectExplorer() { - this.props.onToggleExplorer(this.props.panel); + handleSelectPanel() { + this.props.onTogglePanel(this.props.panel); }, handleDeletePanel(e) { @@ -79,16 +84,16 @@ const Explorer = React.createClass({ const {panel, isExpanded} = this.props; return ( -
-
-
+
+
+
{panel.name || "Graph"}
-
-
-
-
+
+ {/*
*/} +
+
{this.renderQueryTabList()} @@ -127,12 +132,13 @@ const Explorer = React.createClass({ }, renderQueryTabList() { - if (!this.props.isExpanded) { + const {isExpanded, queries} = this.props; + if (!isExpanded) { return null; } return ( -
- {this.props.queries.map((q) => { +
+ {queries.map((q) => { const queryTabText = (q.measurement && q.fields.length !== 0) ? `${q.measurement}.${q.fields[0].field}` : 'Query'; return ( ); })} -
- -
+ + {this.renderAddQuery()}
); }, + + onChoose(item) { + switch (item.text) { + case 'Query Builder': + this.handleAddQuery(); + break; + case 'Raw Text': + this.handleAddRawQuery(); + break; + } + }, + + renderAddQuery() { + return ( + + + + ); + }, }); -export default Explorer; +export default Panel; diff --git a/ui/src/chronograf/components/PanelBuilder.js b/ui/src/chronograf/components/PanelBuilder.js index c70db3d01..b6b78e5d8 100644 --- a/ui/src/chronograf/components/PanelBuilder.js +++ b/ui/src/chronograf/components/PanelBuilder.js @@ -1,7 +1,7 @@ import React, {PropTypes} from 'react'; import {connect} from 'react-redux'; import {bindActionCreators} from 'redux'; -import ExplorerList from './ExplorerList'; +import PanelList from './PanelList'; import * as viewActions from '../actions/view'; const {string, func} = PropTypes; @@ -12,6 +12,7 @@ const PanelBuilder = React.createClass({ createPanel: func.isRequired, deleteQuery: func.isRequired, addQuery: func.isRequired, + editRawText: func.isRequired, chooseNamespace: func.isRequired, chooseMeasurement: func.isRequired, toggleField: func.isRequired, @@ -31,15 +32,15 @@ const PanelBuilder = React.createClass({ }, render() { - const {width, actions} = this.props; + const {activePanelID, width, actions, setActivePanel} = this.props; return (
  Create Graph
-
); diff --git a/ui/src/chronograf/components/ExplorerList.js b/ui/src/chronograf/components/PanelList.js similarity index 81% rename from ui/src/chronograf/components/ExplorerList.js rename to ui/src/chronograf/components/PanelList.js index c4e6b27f6..0436b6477 100644 --- a/ui/src/chronograf/components/ExplorerList.js +++ b/ui/src/chronograf/components/PanelList.js @@ -2,10 +2,10 @@ import React, {PropTypes} from 'react'; import {connect} from 'react-redux'; import _ from 'lodash'; -import Explorer from './Explorer'; +import Panel from './Panel'; const {func, string, shape} = PropTypes; -const ExplorerList = React.createClass({ +const PanelList = React.createClass({ propTypes: { timeRange: shape({ upper: string, @@ -18,9 +18,9 @@ const ExplorerList = React.createClass({ activePanelID: string, }, - handleToggleExplorer(panel) { - // If the explorer being toggled is currently active, it means we should - // close everything by setting `activeExplorerIndex` to null. + handleTogglePanel(panel) { + // If the panel being toggled is currently active, it means we should + // close everything by setting `activePanelID` to null. const activePanelID = panel.id === this.props.activePanelID ? null : panel.id; @@ -45,12 +45,12 @@ const ExplorerList = React.createClass({ }); return ( - @@ -69,4 +69,4 @@ function mapStateToProps(state) { }; } -export default connect(mapStateToProps)(ExplorerList); +export default connect(mapStateToProps)(PanelList); diff --git a/ui/src/chronograf/components/QueryEditor.js b/ui/src/chronograf/components/QueryEditor.js index 3d257c7a3..d3bcf4d7e 100644 --- a/ui/src/chronograf/components/QueryEditor.js +++ b/ui/src/chronograf/components/QueryEditor.js @@ -7,6 +7,7 @@ import DatabaseList from './DatabaseList'; import MeasurementList from './MeasurementList'; import FieldList from './FieldList'; import TagList from './TagList'; +import RawQueryEditor from './RawQueryEditor'; const DB_TAB = 'databases'; const MEASUREMENTS_TAB = 'measurments'; @@ -86,37 +87,39 @@ const QueryEditor = React.createClass({ this.props.actions.groupByTag(this.props.query.id, tagKey); }, + handleEditRawText(text) { + this.props.actions.editRawText(this.props.query.id, text); + }, + handleClickTab(tab) { this.setState({activeTab: tab}); }, render() { - const {query, timeRange} = this.props; - - const statement = query.rawText || selectStatement(timeRange, query) || `SELECT "fields" FROM "db"."rp"."measurement"`; - return ( -
-
-
{statement}
-
- {this.renderEditor()} +
+ {this.renderQuery()} + {this.renderLists()}
); }, - renderEditor() { - if (this.props.query.rawText) { + renderQuery() { + const {query, timeRange} = this.props; + const statement = query.rawText || selectStatement(timeRange, query) || `SELECT "fields" FROM "db"."rp"."measurement"`; + + if (!query.rawText) { return ( -
-

- -  Only editable in the Raw Query tab. -

+
+
{statement}
); } + return ; + }, + + renderLists() { const {activeTab} = this.state; return (
diff --git a/ui/src/chronograf/components/QueryTabItem.js b/ui/src/chronograf/components/QueryTabItem.js index ab16e8692..60eda42e0 100644 --- a/ui/src/chronograf/components/QueryTabItem.js +++ b/ui/src/chronograf/components/QueryTabItem.js @@ -23,9 +23,9 @@ const QueryTabItem = React.createClass({ render() { return ( -
- {this.props.query.rawText ? 'Raw Text' : this.props.queryTabText} - +
+ {this.props.query.rawText ? 'Raw Text' : this.props.queryTabText} +
); }, diff --git a/ui/src/chronograf/components/RawQueryEditor.js b/ui/src/chronograf/components/RawQueryEditor.js new file mode 100644 index 000000000..03c347e4c --- /dev/null +++ b/ui/src/chronograf/components/RawQueryEditor.js @@ -0,0 +1,66 @@ +import React, {PropTypes} from 'react'; + +const ENTER = 13; +const ESCAPE = 27; +const RawQueryEditor = React.createClass({ + propTypes: { + query: PropTypes.shape({ + rawText: PropTypes.string.isRequired, + id: PropTypes.string.isRequired, + }).isRequired, + onUpdate: PropTypes.func.isRequired, + }, + + getInitialState() { + return { + value: this.props.query.rawText, + }; + }, + + componentWillReceiveProps(nextProps) { + if (nextProps.query.rawText !== this.props.query.rawText) { + this.setState({value: nextProps.query.rawText}); + } + }, + + handleKeyDown(e) { + if (e.keyCode === ENTER) { + this.handleUpdate(); + this.editor.blur(); + } else if (e.keyCode === ESCAPE) { + this.setState({value: this.props.query.rawText}, () => { + this.editor.blur(); + }); + } + }, + + handleChange() { + this.setState({ + value: this.editor.value, + }); + }, + + handleUpdate() { + this.props.onUpdate(this.state.value); + }, + + render() { + const {value} = this.state; + + return ( +
+