From 28ad5f025978c381598993bd02086f6e1c195eb9 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 14 Apr 2017 11:46:24 -0700 Subject: [PATCH 001/163] Add basic TemplateVar UI after minor css refactor Also remove no-longer-used isEdit variable in Dashboard.js --- ui/src/dashboards/components/Dashboard.js | 31 ++++++++++------------- ui/src/style/pages/dashboards.scss | 15 ++++++++--- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/ui/src/dashboards/components/Dashboard.js b/ui/src/dashboards/components/Dashboard.js index b94e2c4543..8e7e091913 100644 --- a/ui/src/dashboards/components/Dashboard.js +++ b/ui/src/dashboards/components/Dashboard.js @@ -5,7 +5,6 @@ import LayoutRenderer from 'shared/components/LayoutRenderer' const Dashboard = ({ dashboard, - isEditMode, inPresentationMode, onAddCell, onPositionChange, @@ -38,22 +37,21 @@ const Dashboard = ({ }) return ( -
+
+ TV CONTROL BAR {cells.length ? -
- -
: + :

This Dashboard has no Graphs

+
{cells.length ? Date: Fri, 14 Apr 2017 14:26:36 -0700 Subject: [PATCH 003/163] Add TempVar Manager Overlay --- ui/src/dashboards/components/Dashboard.js | 14 ++++--- ui/src/dashboards/containers/DashboardPage.js | 40 ++++++++++++++++--- ui/src/style/pages/dashboards.scss | 20 ++++++++++ 3 files changed, 63 insertions(+), 11 deletions(-) diff --git a/ui/src/dashboards/components/Dashboard.js b/ui/src/dashboards/components/Dashboard.js index 5d45ea73fd..d75b3c0e47 100644 --- a/ui/src/dashboards/components/Dashboard.js +++ b/ui/src/dashboards/components/Dashboard.js @@ -4,18 +4,19 @@ import classnames from 'classnames' import LayoutRenderer from 'shared/components/LayoutRenderer' const Dashboard = ({ + source, + timeRange, dashboard, - inPresentationMode, onAddCell, - onPositionChange, onEditCell, + autoRefresh, onRenameCell, onUpdateCell, onDeleteCell, + onPositionChange, + inPresentationMode, + onOpenTemplateManager, onSummonOverlayTechnologies, - source, - autoRefresh, - timeRange, }) => { if (dashboard.id === 0) { return null @@ -40,7 +41,7 @@ const Dashboard = ({
Template Variables - +
{cells.length ? + { + isTemplating ? +
+
+
+
+ Template Variables +
+
+ + + +
+
+
+
+
: + null + } { selectedCell ? : null diff --git a/ui/src/style/pages/dashboards.scss b/ui/src/style/pages/dashboards.scss index bb75b2b26c..81ff46c2db 100644 --- a/ui/src/style/pages/dashboards.scss +++ b/ui/src/style/pages/dashboards.scss @@ -40,6 +40,26 @@ $dash-graph-options-arrow: 8px; transition-property: left, top, border-color, background-color; } +.template-variable-manager { + width: 80%; + margin: auto; + + .template-variable-manager--header { + height: 60px; + background: $g0-obsidian; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 20px; + font-size: 18px; + } + + .template-variable-manager--body { + min-height: 150px; + @include gradient-v($g2-kevlar,$g0-obsidian); + } +} + .dashboard { .tv-control-bar { height: 50px; From 8d271ec32f1e3a00544c209f2765c35cb391f975 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 14 Apr 2017 15:26:52 -0700 Subject: [PATCH 004/163] Move source context declaration to top level --- ui/src/CheckSources.js | 56 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/ui/src/CheckSources.js b/ui/src/CheckSources.js index 214f014e94..98d792ba29 100644 --- a/ui/src/CheckSources.js +++ b/ui/src/CheckSources.js @@ -8,21 +8,57 @@ import {showDatabases} from 'src/shared/apis/metaQuery' // Acts as a 'router middleware'. The main `App` component is responsible for // getting the list of data nodes, but not every page requires them to function. // Routes that do require data nodes can be nested under this component. +const { + arrayOf, + func, + node, + shape, + string, +} = PropTypes const CheckSources = React.createClass({ propTypes: { - addFlashMessage: PropTypes.func, - children: PropTypes.node, - params: PropTypes.shape({ - sourceID: PropTypes.string, + sources: arrayOf(shape({ + links: shape({ + proxy: string.isRequired, + self: string.isRequired, + kapacitors: string.isRequired, + queries: string.isRequired, + permissions: string.isRequired, + users: string.isRequired, + databases: string.isRequired, + }).isRequired, + })), + addFlashMessage: func, + children: node, + params: shape({ + sourceID: string, }).isRequired, - router: PropTypes.shape({ - push: PropTypes.func.isRequired, + router: shape({ + push: func.isRequired, }).isRequired, - location: PropTypes.shape({ - pathname: PropTypes.string.isRequired, + location: shape({ + pathname: string.isRequired, }).isRequired, - sources: PropTypes.array.isRequired, - loadSourcesAction: PropTypes.func.isRequired, + loadSourcesAction: func.isRequired, + }, + + childContextTypes: { + source: shape({ + links: shape({ + proxy: string.isRequired, + self: string.isRequired, + kapacitors: string.isRequired, + queries: string.isRequired, + permissions: string.isRequired, + users: string.isRequired, + databases: string.isRequired, + }).isRequired, + }), + }, + + getChildContext() { + const {sources, params: {sourceID}} = this.props + return {source: sources.find((s) => s.id === sourceID)} }, getInitialState() { From 566652fcf6f25f2103514e946042ef81c9313d4d Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 14 Apr 2017 15:28:05 -0700 Subject: [PATCH 005/163] Refactor DashboardPage to use js class --- ui/src/dashboards/containers/DashboardPage.js | 177 +++++++++--------- 1 file changed, 91 insertions(+), 86 deletions(-) diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index de615ffc85..f3e32d1611 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -1,4 +1,4 @@ -import React, {PropTypes} from 'react' +import React, {PropTypes, Component} from 'react' import {Link} from 'react-router' import {connect} from 'react-redux' import {bindActionCreators} from 'redux' @@ -13,73 +13,32 @@ import * as dashboardActionCreators from 'src/dashboards/actions' import {setAutoRefresh} from 'shared/actions/app' import {presentationButtonDispatcher} from 'shared/dispatchers' -const { - arrayOf, - bool, - func, - number, - shape, - string, -} = PropTypes +class DashboardPage extends Component { + constructor(props) { + super(props) -const DashboardPage = React.createClass({ - propTypes: { - source: shape({ - links: shape({ - proxy: string, - self: string, - }), - }), - params: shape({ - sourceID: string.isRequired, - dashboardID: string.isRequired, - }).isRequired, - location: shape({ - pathname: string.isRequired, - }).isRequired, - dashboardActions: shape({ - putDashboard: func.isRequired, - getDashboardsAsync: func.isRequired, - setTimeRange: func.isRequired, - addDashboardCellAsync: func.isRequired, - editDashboardCell: func.isRequired, - renameDashboardCell: func.isRequired, - }).isRequired, - dashboards: arrayOf(shape({ - id: number.isRequired, - cells: arrayOf(shape({})).isRequired, - })), - handleChooseAutoRefresh: func.isRequired, - autoRefresh: number.isRequired, - timeRange: shape({}).isRequired, - inPresentationMode: bool.isRequired, - handleClickPresentationButton: func, - cellQueryStatus: shape({ - queryID: string, - status: shape(), - }).isRequired, - }, - - childContextTypes: { - source: shape({ - links: shape({ - proxy: string.isRequired, - self: string.isRequired, - }).isRequired, - }).isRequired, - }, - - getChildContext() { - return {source: this.props.source} - }, - - getInitialState() { - return { + this.state = { selectedCell: null, isEditMode: false, isTemplating: false, } - }, + + this.handleAddCell = ::this.handleAddCell + this.handleEditDashboard = ::this.handleEditDashboard + this.handleSaveEditedCell = ::this.handleSaveEditedCell + this.handleDismissOverlay = ::this.handleDismissOverlay + this.handleUpdatePosition = ::this.handleUpdatePosition + this.handleChooseTimeRange = ::this.handleChooseTimeRange + this.handleRenameDashboard = ::this.handleRenameDashboard + this.handleEditDashboardCell = ::this.handleEditDashboardCell + this.handleCancelEditDashboard = ::this.handleCancelEditDashboard + this.handleDeleteDashboardCell = ::this.handleDeleteDashboardCell + this.handleOpenTemplateManager = ::this.handleOpenTemplateManager + this.handleRenameDashboardCell = ::this.handleRenameDashboardCell + this.handleUpdateDashboardCell = ::this.handleUpdateDashboardCell + this.handleCloseTemplateManager = ::this.handleCloseTemplateManager + this.handleSummonOverlayTechnologies = ::this.handleSummonOverlayTechnologies + } componentDidMount() { const { @@ -88,86 +47,86 @@ const DashboardPage = React.createClass({ } = this.props getDashboardsAsync(dashboardID) - }, + } handleOpenTemplateManager() { this.setState({isTemplating: true}) - }, + } handleCloseTemplateManager() { this.setState({isTemplating: false}) - }, + } handleDismissOverlay() { this.setState({selectedCell: null}) - }, + } handleSaveEditedCell(newCell) { this.props.dashboardActions.updateDashboardCell(this.getActiveDashboard(), newCell) .then(this.handleDismissOverlay) - }, + } handleSummonOverlayTechnologies(cell) { this.setState({selectedCell: cell}) - }, + } handleChooseTimeRange({lower}) { this.props.dashboardActions.setTimeRange({lower, upper: null}) - }, + } handleUpdatePosition(cells) { const newDashboard = {...this.getActiveDashboard(), cells} this.props.dashboardActions.updateDashboard(newDashboard) this.props.dashboardActions.putDashboard(newDashboard) - }, + } handleAddCell() { this.props.dashboardActions.addDashboardCellAsync(this.getActiveDashboard()) - }, + } handleEditDashboard() { this.setState({isEditMode: true}) - }, + } handleCancelEditDashboard() { this.setState({isEditMode: false}) - }, + } handleRenameDashboard(name) { this.setState({isEditMode: false}) const newDashboard = {...this.getActiveDashboard(), name} this.props.dashboardActions.updateDashboard(newDashboard) this.props.dashboardActions.putDashboard(newDashboard) - }, + } // Places cell into editing mode. handleEditDashboardCell(x, y, isEditing) { return () => { this.props.dashboardActions.editDashboardCell(this.getActiveDashboard(), x, y, !isEditing) /* eslint-disable no-negated-condition */ } - }, + } handleRenameDashboardCell(x, y) { return (evt) => { this.props.dashboardActions.renameDashboardCell(this.getActiveDashboard(), x, y, evt.target.value) } - }, + } handleUpdateDashboardCell(newCell) { return () => { this.props.dashboardActions.editDashboardCell(this.getActiveDashboard(), newCell.x, newCell.y, false) this.props.dashboardActions.putDashboard(this.getActiveDashboard()) } - }, + } handleDeleteDashboardCell(cell) { this.props.dashboardActions.deleteDashboardCellAsync(cell) - }, + } getActiveDashboard() { const {params: {dashboardID}, dashboards} = this.props return dashboards.find(d => d.id === +dashboardID) - }, + } render() { const { @@ -217,12 +176,12 @@ const DashboardPage = React.createClass({ : null } @@ -283,8 +242,54 @@ const DashboardPage = React.createClass({ }
) - }, -}) + } +} + +const { + arrayOf, + bool, + func, + number, + shape, + string, +} = PropTypes + +DashboardPage.propTypes = { + source: shape({ + links: shape({ + proxy: string, + self: string, + }), + }).isRequired, + params: shape({ + sourceID: string.isRequired, + dashboardID: string.isRequired, + }).isRequired, + location: shape({ + pathname: string.isRequired, + }).isRequired, + dashboardActions: shape({ + putDashboard: func.isRequired, + getDashboardsAsync: func.isRequired, + setTimeRange: func.isRequired, + addDashboardCellAsync: func.isRequired, + editDashboardCell: func.isRequired, + renameDashboardCell: func.isRequired, + }).isRequired, + dashboards: arrayOf(shape({ + id: number.isRequired, + cells: arrayOf(shape({})).isRequired, + })), + handleChooseAutoRefresh: func.isRequired, + autoRefresh: number.isRequired, + timeRange: shape({}).isRequired, + inPresentationMode: bool.isRequired, + handleClickPresentationButton: func, + cellQueryStatus: shape({ + queryID: string, + status: shape(), + }).isRequired, +} const mapStateToProps = (state) => { const { From 9db9137af7d636538148818eea4fc4e9a638be03 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 14 Apr 2017 15:28:39 -0700 Subject: [PATCH 006/163] Add required proxy link prop to CEO --- ui/src/dashboards/components/CellEditorOverlay.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index 879bfb7168..5ac3933ef8 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -195,9 +195,10 @@ CellEditorOverlay.propTypes = { autoRefresh: number.isRequired, source: shape({ links: shape({ + proxy: string.isRequired, queries: string.isRequired, - }), - }), + }).isRequired, + }).isRequired, editQueryStatus: func.isRequired, queryStatus: shape({ queryID: string, From b1a0ffe72ca9afe1e4f1dd93bba23e0bc2e91813 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 14 Apr 2017 15:29:02 -0700 Subject: [PATCH 007/163] Use the power of deconstruction --- ui/src/data_explorer/components/Visualization.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ui/src/data_explorer/components/Visualization.js b/ui/src/data_explorer/components/Visualization.js index 46a7ba1b11..0f8011beed 100644 --- a/ui/src/data_explorer/components/Visualization.js +++ b/ui/src/data_explorer/components/Visualization.js @@ -80,8 +80,7 @@ const Visualization = React.createClass({ editQueryStatus, activeQueryIndex, } = this.props - const {source} = this.context - const proxyLink = source.links.proxy + const {source: {links: proxy}} = this.context const {view} = this.state const statements = queryConfigs.map((query) => { @@ -89,7 +88,7 @@ const Visualization = React.createClass({ return {text, id: query.id} }) const queries = statements.filter((s) => s.text !== null).map((s) => { - return {host: [proxyLink], text: s.text, id: s.id} + return {host: [proxy], text: s.text, id: s.id} }) return ( From aa0053b6def09e128b78c4e3c99044a4e030c5da Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 14 Apr 2017 16:12:09 -0700 Subject: [PATCH 008/163] Make TemplateVariableManager its own SFC --- .../components/TemplateVariableManager.js | 28 +++++++++++++++++++ ui/src/dashboards/containers/DashboardPage.js | 23 ++++++--------- .../shared/components/OverlayTechnologies.js | 13 +++++++++ 3 files changed, 49 insertions(+), 15 deletions(-) create mode 100644 ui/src/dashboards/components/TemplateVariableManager.js create mode 100644 ui/src/shared/components/OverlayTechnologies.js diff --git a/ui/src/dashboards/components/TemplateVariableManager.js b/ui/src/dashboards/components/TemplateVariableManager.js new file mode 100644 index 0000000000..04e2d8ce38 --- /dev/null +++ b/ui/src/dashboards/components/TemplateVariableManager.js @@ -0,0 +1,28 @@ +import React, {PropTypes} from 'react' +import OnClickOutside from 'react-onclickoutside' + +const TemplateVariableManager = ({onClose}) => ( +
+
+
+ Template Variables +
+
+ + + +
+
+
+
+) + +const { + func, +} = PropTypes + +TemplateVariableManager.propTypes = { + onClose: func.isRequired, +} + +export default OnClickOutside(TemplateVariableManager) diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index f3e32d1611..0bf8dc082f 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -3,10 +3,12 @@ import {Link} from 'react-router' import {connect} from 'react-redux' import {bindActionCreators} from 'redux' +import OverlayTechnologies from 'src/shared/components/OverlayTechnologies' import CellEditorOverlay from 'src/dashboards/components/CellEditorOverlay' import DashboardHeader from 'src/dashboards/components/DashboardHeader' import DashboardHeaderEdit from 'src/dashboards/components/DashboardHeaderEdit' import Dashboard from 'src/dashboards/components/Dashboard' +import TemplateVariableManager from 'src/dashboards/components/TemplateVariableManager' import * as dashboardActionCreators from 'src/dashboards/actions' @@ -154,21 +156,12 @@ class DashboardPage extends Component {
{ isTemplating ? -
-
-
-
- Template Variables -
-
- - - -
-
-
-
-
: + + + : null } { diff --git a/ui/src/shared/components/OverlayTechnologies.js b/ui/src/shared/components/OverlayTechnologies.js new file mode 100644 index 0000000000..cd91c7bd63 --- /dev/null +++ b/ui/src/shared/components/OverlayTechnologies.js @@ -0,0 +1,13 @@ +import React, {PropTypes} from 'react' + +const OverlayTechnologies = ({children}) =>
{children}
+ +const { + node, +} = PropTypes + +OverlayTechnologies.propTypes = { + children: node.isRequired, +} + +export default OverlayTechnologies From 6e72e51166bb63ed02727bce07bb5695a756c6be Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 14 Apr 2017 16:15:34 -0700 Subject: [PATCH 009/163] Actually deconstruct proxy --- ui/src/data_explorer/components/Visualization.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/data_explorer/components/Visualization.js b/ui/src/data_explorer/components/Visualization.js index 0f8011beed..d242e121ed 100644 --- a/ui/src/data_explorer/components/Visualization.js +++ b/ui/src/data_explorer/components/Visualization.js @@ -80,7 +80,7 @@ const Visualization = React.createClass({ editQueryStatus, activeQueryIndex, } = this.props - const {source: {links: proxy}} = this.context + const {source: {links: {proxy}}} = this.context const {view} = this.state const statements = queryConfigs.map((query) => { From c499bb01968249a470c2895e17382b16eaa4143e Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 14 Apr 2017 16:31:11 -0700 Subject: [PATCH 010/163] Update cursor style for icon --- ui/src/dashboards/components/TemplateVariableManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/dashboards/components/TemplateVariableManager.js b/ui/src/dashboards/components/TemplateVariableManager.js index 04e2d8ce38..06c6f902f9 100644 --- a/ui/src/dashboards/components/TemplateVariableManager.js +++ b/ui/src/dashboards/components/TemplateVariableManager.js @@ -10,7 +10,7 @@ const TemplateVariableManager = ({onClose}) => (
- +
From bf60c8202a94eb1561adbe66fb548ab2616a06a3 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 14 Apr 2017 16:32:32 -0700 Subject: [PATCH 011/163] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79ea3beafa..2f83f3dc53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Bug Fixes ### Features + 1. [#1292](https://github.com/influxdata/chronograf/pull/1292): Introduce Template Variable Manager + ### UI Improvements 1. [#1259](https://github.com/influxdata/chronograf/pull/1259): Add default display for empty dashboard 1. [#1258](https://github.com/influxdata/chronograf/pull/1258): Display Kapacitor alert endpoint options as radio button group From 237164878491d425885dd53e06c5eb23da025d81 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 14 Apr 2017 16:44:23 -0700 Subject: [PATCH 012/163] Add border radius to bottom of TVM --- ui/src/style/pages/dashboards.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/src/style/pages/dashboards.scss b/ui/src/style/pages/dashboards.scss index 81ff46c2db..9e654e0ab7 100644 --- a/ui/src/style/pages/dashboards.scss +++ b/ui/src/style/pages/dashboards.scss @@ -55,6 +55,7 @@ $dash-graph-options-arrow: 8px; } .template-variable-manager--body { + border-radius: 0 0 $radius $radius; min-height: 150px; @include gradient-v($g2-kevlar,$g0-obsidian); } From 33cc91dc418828a7aa133b2796ef3d4c8c1b0972 Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Mon, 17 Apr 2017 13:21:47 -0600 Subject: [PATCH 013/163] Stubbed out some Template Variable components --- ui/src/dashboards/actions/index.js | 80 +++++--- .../components/TemplateVariableManager.js | 18 +- .../components/TemplateVariableRow.js | 30 +++ .../components/TemplateVariableTable.js | 28 +++ ui/src/dashboards/containers/DashboardPage.js | 182 +++++++++--------- 5 files changed, 223 insertions(+), 115 deletions(-) create mode 100644 ui/src/dashboards/components/TemplateVariableRow.js create mode 100644 ui/src/dashboards/components/TemplateVariableTable.js diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index 849c4d39c1..fd0321694f 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -20,28 +20,28 @@ export const loadDashboards = (dashboards, dashboardID) => ({ }, }) -export const setTimeRange = (timeRange) => ({ +export const setTimeRange = timeRange => ({ type: 'SET_DASHBOARD_TIME_RANGE', payload: { timeRange, }, }) -export const updateDashboard = (dashboard) => ({ +export const updateDashboard = dashboard => ({ type: 'UPDATE_DASHBOARD', payload: { dashboard, }, }) -export const deleteDashboard = (dashboard) => ({ +export const deleteDashboard = dashboard => ({ type: 'DELETE_DASHBOARD', payload: { dashboard, }, }) -export const deleteDashboardFailed = (dashboard) => ({ +export const deleteDashboardFailed = dashboard => ({ type: 'DELETE_DASHBOARD_FAILED', payload: { dashboard, @@ -80,8 +80,8 @@ export const editDashboardCell = (dashboard, x, y, isEditing) => ({ // as a suitable id payload: { dashboard, - x, // x-coord of the cell to be edited - y, // y-coord of the cell to be edited + x, // x-coord of the cell to be edited + y, // y-coord of the cell to be edited isEditing, }, }) @@ -90,13 +90,13 @@ export const renameDashboardCell = (dashboard, x, y, name) => ({ type: 'RENAME_DASHBOARD_CELL', payload: { dashboard, - x, // x-coord of the cell to be renamed - y, // y-coord of the cell to be renamed + x, // x-coord of the cell to be renamed + y, // y-coord of the cell to be renamed name, }, }) -export const deleteDashboardCell = (cell) => ({ +export const deleteDashboardCell = cell => ({ type: 'DELETE_DASHBOARD_CELL', payload: { cell, @@ -111,45 +111,81 @@ export const editCellQueryStatus = (queryID, status) => ({ }, }) +// Stub Template Variables Data + +const templates = [ + { + id: 1, + type: 'query', + label: 'test query', + code: '$HOSTS', + query: { + db: 'db1.rp1', + text: 'SHOW TAGS WHERE HUNTER = "coo"', + }, + values: ['h1', 'h2', 'h3'], + }, + { + id: 2, + type: 'csv', + label: 'test csv', + code: '$INFLX', + values: ['A', 'B', 'C'], + }, +] + // Async Action Creators -export const getDashboardsAsync = (dashboardID) => async (dispatch) => { +export const getDashboardsAsync = dashboardID => async dispatch => { try { const {data: {dashboards}} = await getDashboardsAJAX() - dispatch(loadDashboards(dashboards, dashboardID)) + const stubbedDashboards = dashboards.map(d => ({...d, templates})) + dispatch(loadDashboards(stubbedDashboards, dashboardID)) } catch (error) { console.error(error) throw error } } -export const putDashboard = (dashboard) => (dispatch) => { +export const putDashboard = dashboard => dispatch => { updateDashboardAJAX(dashboard).then(({data}) => { - dispatch(updateDashboard(data)) + dispatch(updateDashboard({...data, templates})) }) } -export const updateDashboardCell = (dashboard, cell) => (dispatch) => { - return updateDashboardCellAJAX(cell) - .then(({data}) => { +export const updateDashboardCell = (dashboard, cell) => dispatch => { + return updateDashboardCellAJAX(cell).then(({data}) => { dispatch(syncDashboardCell(dashboard, data)) }) } -export const deleteDashboardAsync = (dashboard) => async (dispatch) => { +export const deleteDashboardAsync = dashboard => async dispatch => { dispatch(deleteDashboard(dashboard)) try { await deleteDashboardAJAX(dashboard) - dispatch(publishAutoDismissingNotification('success', 'Dashboard deleted successfully.')) + dispatch( + publishAutoDismissingNotification( + 'success', + 'Dashboard deleted successfully.' + ) + ) } catch (error) { dispatch(deleteDashboardFailed(dashboard)) - dispatch(publishNotification('error', `Failed to delete dashboard: ${error.data.message}.`)) + dispatch( + publishNotification( + 'error', + `Failed to delete dashboard: ${error.data.message}.` + ) + ) } } -export const addDashboardCellAsync = (dashboard) => async (dispatch) => { +export const addDashboardCellAsync = dashboard => async dispatch => { try { - const {data} = await addDashboardCellAJAX(dashboard, NEW_DEFAULT_DASHBOARD_CELL) + const {data} = await addDashboardCellAJAX( + dashboard, + NEW_DEFAULT_DASHBOARD_CELL + ) dispatch(addDashboardCell(dashboard, data)) } catch (error) { console.error(error) @@ -157,7 +193,7 @@ export const addDashboardCellAsync = (dashboard) => async (dispatch) => { } } -export const deleteDashboardCellAsync = (cell) => async (dispatch) => { +export const deleteDashboardCellAsync = cell => async dispatch => { try { await deleteDashboardCellAJAX(cell) dispatch(deleteDashboardCell(cell)) diff --git a/ui/src/dashboards/components/TemplateVariableManager.js b/ui/src/dashboards/components/TemplateVariableManager.js index 06c6f902f9..9d26b6a9fb 100644 --- a/ui/src/dashboards/components/TemplateVariableManager.js +++ b/ui/src/dashboards/components/TemplateVariableManager.js @@ -1,7 +1,9 @@ import React, {PropTypes} from 'react' import OnClickOutside from 'react-onclickoutside' +import TemplateVariableTable + from 'src/dashboards/components/TemplateVariableTable' -const TemplateVariableManager = ({onClose}) => ( +const TemplateVariableManager = ({onClose, templates}) => (
@@ -10,16 +12,20 @@ const TemplateVariableManager = ({onClose}) => (
- +
-
+
+ +
) -const { - func, -} = PropTypes +const {func} = PropTypes TemplateVariableManager.propTypes = { onClose: func.isRequired, diff --git a/ui/src/dashboards/components/TemplateVariableRow.js b/ui/src/dashboards/components/TemplateVariableRow.js new file mode 100644 index 0000000000..5085f7a2bb --- /dev/null +++ b/ui/src/dashboards/components/TemplateVariableRow.js @@ -0,0 +1,30 @@ +import React, {PropTypes} from 'react' + +const TemplateVariableRow = ({ + template: {type, label, code, query, values}, +}) => ( +
+
{type}
+
{label}
+
{code}
+
{query && query.text}
+
{values}
+
+) + +const {arrayOf, shape, string} = PropTypes + +TemplateVariableRow.propTypes = { + template: shape({ + type: string.isRequired, + label: string.isRequired, + code: string.isRequired, + query: shape({ + db: string.isRequired, + text: string.isRequired, + }), + values: arrayOf(string.isRequired), + }), +} + +export default TemplateVariableRow diff --git a/ui/src/dashboards/components/TemplateVariableTable.js b/ui/src/dashboards/components/TemplateVariableTable.js new file mode 100644 index 0000000000..873a10397e --- /dev/null +++ b/ui/src/dashboards/components/TemplateVariableTable.js @@ -0,0 +1,28 @@ +import React, {PropTypes} from 'react' + +import TemplateVariableRow from 'src/dashboards/components/TemplateVariableRow' + +const TemplateVariableTable = ({templates}) => ( +
+ {templates.map(t => )} +
+) + +const {arrayOf, shape, string} = PropTypes + +TemplateVariableTable.propTypes = { + templates: arrayOf( + shape({ + type: string.isRequired, + label: string.isRequired, + code: string.isRequired, + query: shape({ + db: string.isRequired, + text: string.isRequired, + }), + values: arrayOf(string.isRequired), + }) + ), +} + +export default TemplateVariableTable diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index 0bf8dc082f..4830267e47 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -8,7 +8,8 @@ import CellEditorOverlay from 'src/dashboards/components/CellEditorOverlay' import DashboardHeader from 'src/dashboards/components/DashboardHeader' import DashboardHeaderEdit from 'src/dashboards/components/DashboardHeaderEdit' import Dashboard from 'src/dashboards/components/Dashboard' -import TemplateVariableManager from 'src/dashboards/components/TemplateVariableManager' +import TemplateVariableManager + from 'src/dashboards/components/TemplateVariableManager' import * as dashboardActionCreators from 'src/dashboards/actions' @@ -39,7 +40,8 @@ class DashboardPage extends Component { this.handleRenameDashboardCell = ::this.handleRenameDashboardCell this.handleUpdateDashboardCell = ::this.handleUpdateDashboardCell this.handleCloseTemplateManager = ::this.handleCloseTemplateManager - this.handleSummonOverlayTechnologies = ::this.handleSummonOverlayTechnologies + this.handleSummonOverlayTechnologies = ::this + .handleSummonOverlayTechnologies } componentDidMount() { @@ -64,8 +66,9 @@ class DashboardPage extends Component { } handleSaveEditedCell(newCell) { - this.props.dashboardActions.updateDashboardCell(this.getActiveDashboard(), newCell) - .then(this.handleDismissOverlay) + this.props.dashboardActions + .updateDashboardCell(this.getActiveDashboard(), newCell) + .then(this.handleDismissOverlay) } handleSummonOverlayTechnologies(cell) { @@ -104,19 +107,34 @@ class DashboardPage extends Component { // Places cell into editing mode. handleEditDashboardCell(x, y, isEditing) { return () => { - this.props.dashboardActions.editDashboardCell(this.getActiveDashboard(), x, y, !isEditing) /* eslint-disable no-negated-condition */ + this.props.dashboardActions.editDashboardCell( + this.getActiveDashboard(), + x, + y, + !isEditing + ) /* eslint-disable no-negated-condition */ } } handleRenameDashboardCell(x, y) { - return (evt) => { - this.props.dashboardActions.renameDashboardCell(this.getActiveDashboard(), x, y, evt.target.value) + return evt => { + this.props.dashboardActions.renameDashboardCell( + this.getActiveDashboard(), + x, + y, + evt.target.value + ) } } handleUpdateDashboardCell(newCell) { return () => { - this.props.dashboardActions.editDashboardCell(this.getActiveDashboard(), newCell.x, newCell.y, false) + this.props.dashboardActions.editDashboardCell( + this.getActiveDashboard(), + newCell.x, + newCell.y, + false + ) this.props.dashboardActions.putDashboard(this.getActiveDashboard()) } } @@ -146,27 +164,21 @@ class DashboardPage extends Component { const dashboard = dashboards.find(d => d.id === +dashboardID) - const { - selectedCell, - isEditMode, - isTemplating, - } = this.state + const {selectedCell, isEditMode, isTemplating} = this.state return (
- { - isTemplating ? - + {isTemplating + ? - : - null - } - { - selectedCell ? - + : null} + {selectedCell + ? : - null - } - { - isEditMode ? - + : null} + {isEditMode + ? : - + : - { - dashboards ? - dashboards.map((d, i) => { - return ( -
  • - - {d.name} - -
  • - ) - }) : - null - } -
    - } - { - dashboard ? - : - null - } + {dashboards + ? dashboards.map((d, i) => { + return ( +
  • + + {d.name} + +
  • + ) + }) + : null} +
    } + {dashboard + ? + : null}
    ) } } -const { - arrayOf, - bool, - func, - number, - shape, - string, -} = PropTypes +const {arrayOf, bool, func, number, shape, string} = PropTypes DashboardPage.propTypes = { source: shape({ @@ -269,10 +270,24 @@ DashboardPage.propTypes = { editDashboardCell: func.isRequired, renameDashboardCell: func.isRequired, }).isRequired, - dashboards: arrayOf(shape({ - id: number.isRequired, - cells: arrayOf(shape({})).isRequired, - })), + dashboards: arrayOf( + shape({ + id: number.isRequired, + cells: arrayOf(shape({})).isRequired, + templates: arrayOf( + shape({ + type: string.isRequired, + label: string.isRequired, + code: string.isRequired, + query: shape({ + db: string.isRequired, + text: string.isRequired, + }), + values: arrayOf(string.isRequired), + }) + ), + }) + ), handleChooseAutoRefresh: func.isRequired, autoRefresh: number.isRequired, timeRange: shape({}).isRequired, @@ -284,17 +299,10 @@ DashboardPage.propTypes = { }).isRequired, } -const mapStateToProps = (state) => { +const mapStateToProps = state => { const { - app: { - ephemeral: {inPresentationMode}, - persisted: {autoRefresh}, - }, - dashboardUI: { - dashboards, - timeRange, - cellQueryStatus, - }, + app: {ephemeral: {inPresentationMode}, persisted: {autoRefresh}}, + dashboardUI: {dashboards, timeRange, cellQueryStatus}, } = state return { @@ -306,7 +314,7 @@ const mapStateToProps = (state) => { } } -const mapDispatchToProps = (dispatch) => ({ +const mapDispatchToProps = dispatch => ({ handleChooseAutoRefresh: bindActionCreators(setAutoRefresh, dispatch), handleClickPresentationButton: presentationButtonDispatcher(dispatch), dashboardActions: bindActionCreators(dashboardActionCreators, dispatch), From 31e38f0cb4722f3038965358956e1c4c2841e29d Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Mon, 17 Apr 2017 14:01:16 -0600 Subject: [PATCH 014/163] Fix linter errors. --- ui/.eslintrc | 4 ++-- .../components/TemplateVariableManager.js | 14 +++++++++++- ui/src/dashboards/containers/DashboardPage.js | 22 +++++++++---------- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/ui/.eslintrc b/ui/.eslintrc index b2b1adb838..395c7d6fca 100644 --- a/ui/.eslintrc +++ b/ui/.eslintrc @@ -212,7 +212,7 @@ 'react/jsx-boolean-value': [2, 'always'], 'react/jsx-curly-spacing': [2, 'never'], 'react/jsx-equals-spacing': [2, 'never'], - 'react/jsx-indent-props': [2, 2], + 'react/jsx-indent-props': [0, 2], 'react/jsx-key': 2, 'react/jsx-no-duplicate-props': 2, 'react/jsx-no-undef': 2, @@ -235,6 +235,6 @@ 'react/require-extension': 0, 'react/self-closing-comp': 0, // TODO: we can re-enable this if some brave soul wants to update the code (mostly spans acting as icons) 'react/sort-comp': 0, // TODO: 2 - 'react/jsx-wrap-multilines': 'error', + 'react/jsx-wrap-multilines': ['error', {'declaration': false, 'assignment': false}], }, } diff --git a/ui/src/dashboards/components/TemplateVariableManager.js b/ui/src/dashboards/components/TemplateVariableManager.js index 9d26b6a9fb..b9b5d64124 100644 --- a/ui/src/dashboards/components/TemplateVariableManager.js +++ b/ui/src/dashboards/components/TemplateVariableManager.js @@ -25,10 +25,22 @@ const TemplateVariableManager = ({onClose, templates}) => (
    ) -const {func} = PropTypes +const {arrayOf, func, shape, string} = PropTypes TemplateVariableManager.propTypes = { onClose: func.isRequired, + templates: arrayOf( + shape({ + type: string.isRequired, + label: string.isRequired, + code: string.isRequired, + query: shape({ + db: string.isRequired, + text: string.isRequired, + }), + values: arrayOf(string.isRequired), + }) + ), } export default OnClickOutside(TemplateVariableManager) diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index 4830267e47..969f301fc1 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -210,18 +210,16 @@ class DashboardPage extends Component { onEditDashboard={this.handleEditDashboard} > {dashboards - ? dashboards.map((d, i) => { - return ( -
  • - - {d.name} - -
  • - ) - }) + ? dashboards.map((d, i) => ( +
  • + + {d.name} + +
  • + )) : null} } {dashboard From 8b605ca0e08a17b04114a8d3f38f03d985f89476 Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Mon, 17 Apr 2017 13:51:04 -0700 Subject: [PATCH 015/163] Make devs nervous --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e652c38441..8e65bbbbfc 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ define CHRONOGIRAFFE ,"" _\_ ," ## | 0 0. ," ## ,-\__ `. - ," / `--._;) + ," / `--._;) - "Do ya feel lucky, punk? Huh, do ya?" ," ## / ," ## / endef From f7d846e13d06f0b48c45c991248dc5b425edf9ed Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Mon, 17 Apr 2017 14:20:14 -0700 Subject: [PATCH 016/163] Delete SimpleDropdown because it's never used and overlaps with the more sophisticated Dropdown --- ui/src/shared/components/SimpleDropdown.js | 59 ---------------------- 1 file changed, 59 deletions(-) delete mode 100644 ui/src/shared/components/SimpleDropdown.js diff --git a/ui/src/shared/components/SimpleDropdown.js b/ui/src/shared/components/SimpleDropdown.js deleted file mode 100644 index 33e75e4ac5..0000000000 --- a/ui/src/shared/components/SimpleDropdown.js +++ /dev/null @@ -1,59 +0,0 @@ -import React, {PropTypes} from 'react' -import classNames from 'classnames' -import OnClickOutside from 'shared/components/OnClickOutside' - -const Dropdown = React.createClass({ - propTypes: { - children: PropTypes.node.isRequired, - items: PropTypes.arrayOf(PropTypes.shape({ - text: PropTypes.string.isRequired, - })).isRequired, - onChoose: PropTypes.func.isRequired, - className: PropTypes.string, - }, - getInitialState() { - return { - isOpen: false, - } - }, - handleClickOutside() { - this.setState({isOpen: false}) - }, - handleSelection(item) { - this.toggleMenu() - this.props.onChoose(item) - }, - toggleMenu(e) { - if (e) { - e.stopPropagation() - } - this.setState({isOpen: !this.state.isOpen}) - }, - render() { - const self = this - const {items, className} = self.props - - return ( -
    -
    - {this.props.children} -
    - {self.state.isOpen ? -
      - {items.map((item, i) => { - return ( -
    • self.handleSelection(item)}> - - {item.text} - -
    • - ) - })} -
    - : null} -
    - ) - }, -}) - -export default OnClickOutside(Dropdown) From 1d383d25665898e5f39efd48e58492b43ede9fde Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Mon, 17 Apr 2017 17:50:49 -0700 Subject: [PATCH 017/163] Start new pattern for defining actionTypes for use with template variables --- ui/src/shared/constants/actionTypes.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 ui/src/shared/constants/actionTypes.js diff --git a/ui/src/shared/constants/actionTypes.js b/ui/src/shared/constants/actionTypes.js new file mode 100644 index 0000000000..8c8f62840e --- /dev/null +++ b/ui/src/shared/constants/actionTypes.js @@ -0,0 +1 @@ +export const TEMPLATE_VARIABLE_SELECTED = 'TEMPLATE_VARIABLE_SELECTED' From 0c778e435faaeeec58016553f76a7ad0f0c267b6 Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Mon, 17 Apr 2017 17:51:26 -0700 Subject: [PATCH 018/163] Persist template variable selection in redux store --- ui/src/dashboards/actions/index.js | 20 ++++++++++++++++++ ui/src/dashboards/components/Dashboard.js | 21 +++++++++++++++++++ ui/src/dashboards/containers/DashboardPage.js | 10 +++++++-- ui/src/dashboards/reducers/ui.js | 21 +++++++++++++++++++ ui/src/style/pages/dashboards.scss | 6 ++++++ 5 files changed, 76 insertions(+), 2 deletions(-) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index fd0321694f..6abadb5aea 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -12,6 +12,10 @@ import {publishAutoDismissingNotification} from 'shared/dispatchers' import {NEW_DEFAULT_DASHBOARD_CELL} from 'src/dashboards/constants' +import { + TEMPLATE_VARIABLE_SELECTED, +} from 'shared/constants/actionTypes' + export const loadDashboards = (dashboards, dashboardID) => ({ type: 'LOAD_DASHBOARDS', payload: { @@ -111,6 +115,15 @@ export const editCellQueryStatus = (queryID, status) => ({ }, }) +export const tvSelected = (dashboardID, tvID, valueText) => ({ + type: TEMPLATE_VARIABLE_SELECTED, + payload: { + dashboardID, + tvID, + valueText, + }, +}) + // Stub Template Variables Data const templates = [ @@ -132,6 +145,13 @@ const templates = [ code: '$INFLX', values: ['A', 'B', 'C'], }, + { + id: 3, + type: 'csv', + label: 'test csv', + code: '$NULL', + values: null, + }, ] // Async Action Creators diff --git a/ui/src/dashboards/components/Dashboard.js b/ui/src/dashboards/components/Dashboard.js index d75b3c0e47..7bd941b3a7 100644 --- a/ui/src/dashboards/components/Dashboard.js +++ b/ui/src/dashboards/components/Dashboard.js @@ -2,6 +2,7 @@ import React, {PropTypes} from 'react' import classnames from 'classnames' import LayoutRenderer from 'shared/components/LayoutRenderer' +import Dropdown from 'shared/components/Dropdown' const Dashboard = ({ source, @@ -17,6 +18,7 @@ const Dashboard = ({ inPresentationMode, onOpenTemplateManager, onSummonOverlayTechnologies, + onSelectTV, }) => { if (dashboard.id === 0) { return null @@ -41,6 +43,24 @@ const Dashboard = ({
    Template Variables + { + dashboard.templates.map(({id, values}) => { + let items + if (values === null) { + items = [{text: 'Loading...'}] + } else { + items = values.map(value => ({text: value})) + } + return ( + onSelectTV(id, item.text)} + /> + ) + }) + }
    {cells.length ? @@ -96,6 +116,7 @@ Dashboard.propTypes = { autoRefresh: number.isRequired, timeRange: shape({}).isRequired, onOpenTemplateManager: func.isRequired, + onSelectTV: func.isRequired, } export default Dashboard diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index 969f301fc1..b1f251fb5f 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -40,8 +40,8 @@ class DashboardPage extends Component { this.handleRenameDashboardCell = ::this.handleRenameDashboardCell this.handleUpdateDashboardCell = ::this.handleUpdateDashboardCell this.handleCloseTemplateManager = ::this.handleCloseTemplateManager - this.handleSummonOverlayTechnologies = ::this - .handleSummonOverlayTechnologies + this.handleSummonOverlayTechnologies = ::this.handleSummonOverlayTechnologies + this.handleSelectTV = ::this.handleSelectTV } componentDidMount() { @@ -143,6 +143,11 @@ class DashboardPage extends Component { this.props.dashboardActions.deleteDashboardCellAsync(cell) } + handleSelectTV(tvID, valueText) { + const {params: {dashboardID}} = this.props + this.props.dashboardActions.tvSelected(+dashboardID, tvID, valueText) + } + getActiveDashboard() { const {params: {dashboardID}, dashboards} = this.props return dashboards.find(d => d.id === +dashboardID) @@ -237,6 +242,7 @@ class DashboardPage extends Component { onUpdateCell={this.handleUpdateDashboardCell} onOpenTemplateManager={this.handleOpenTemplateManager} onSummonOverlayTechnologies={this.handleSummonOverlayTechnologies} + onSelectTV={this.handleSelectTV} /> : null}
    diff --git a/ui/src/dashboards/reducers/ui.js b/ui/src/dashboards/reducers/ui.js index 48a96dba80..935961d7f7 100644 --- a/ui/src/dashboards/reducers/ui.js +++ b/ui/src/dashboards/reducers/ui.js @@ -10,6 +10,10 @@ const initialState = { cellQueryStatus: {queryID: null, status: null}, } +import { + TEMPLATE_VARIABLE_SELECTED, +} from 'shared/constants/actionTypes' + export default function ui(state = initialState, action) { switch (action.type) { case 'LOAD_DASHBOARDS': { @@ -164,6 +168,23 @@ export default function ui(state = initialState, action) { return {...state, cellQueryStatus: {queryID, status}} } + + case TEMPLATE_VARIABLE_SELECTED: { + const {dashboardID, tvID, valueText} = action.payload + const newDashboards = state.dashboards.map((dashboard) => { + if (dashboard.id === dashboardID) { + const newTVs = dashboard.templates.map((tV) => { + if (tV.id === tvID) { + return {...tV, selected: valueText} + } + return tV + }) + return {...dashboard, templates: newTVs} + } + return dashboard + }) + return {...state, dashboards: newDashboards} + } } return state diff --git a/ui/src/style/pages/dashboards.scss b/ui/src/style/pages/dashboards.scss index 9e654e0ab7..70fb9216f8 100644 --- a/ui/src/style/pages/dashboards.scss +++ b/ui/src/style/pages/dashboards.scss @@ -73,6 +73,12 @@ $dash-graph-options-arrow: 8px; margin-bottom: 4px; padding: 10px 15px; @extend .cell-shell; + .dropdown { + flex-grow: 1; + } + .dropdown-toggle { + width: 100%; + } } .react-grid-item { @extend .cell-shell; From 05b9c8fc124c4dae9feeb4dabd49d3d02b2ac5bd Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 18 Apr 2017 09:27:34 -0700 Subject: [PATCH 019/163] WIP Allow selection of value type --- .../dashboards/components/TemplateValues.js | 33 +++++++++ .../components/TemplateVariableRow.js | 72 +++++++++++++++---- .../components/TemplateVariableTable.js | 17 ++++- ui/src/dashboards/constants/index.js | 27 +++++++ ui/src/style/pages/dashboards.scss | 4 ++ 5 files changed, 138 insertions(+), 15 deletions(-) create mode 100644 ui/src/dashboards/components/TemplateValues.js diff --git a/ui/src/dashboards/components/TemplateValues.js b/ui/src/dashboards/components/TemplateValues.js new file mode 100644 index 0000000000..b6dec0d00b --- /dev/null +++ b/ui/src/dashboards/components/TemplateValues.js @@ -0,0 +1,33 @@ +import React, {PropTypes} from 'react' + +const TemplateValues = ({values, selectedType}) => { + switch (selectedType) { + case 'csv': + return
    {values.join(', ')}
    + case 'databases': + return
    SHOW DATABASES
    + case 'measurements': + return
    SHOW MEASUREMENTS on "db"
    + case 'fields': + return
    SHOW FIELD KEYS on "db" from "measurement"
    + case 'tagKeys': + return
    SHOW TAG KEYS ON "db" FROM "measurement"
    + case 'tagValues': + return ( +
    + SHOW TAG VALUES ON "db_name" FROM "measurement_name" WITH KEY = "tag_key" +
    + ) + default: + return
    {values.join(', ')}
    + } +} + +const {arrayOf, string} = PropTypes + +TemplateValues.propTypes = { + selectedType: string.isRequired, + values: arrayOf(string), +} + +export default TemplateValues diff --git a/ui/src/dashboards/components/TemplateVariableRow.js b/ui/src/dashboards/components/TemplateVariableRow.js index 5085f7a2bb..b27bb9595a 100644 --- a/ui/src/dashboards/components/TemplateVariableRow.js +++ b/ui/src/dashboards/components/TemplateVariableRow.js @@ -1,20 +1,62 @@ -import React, {PropTypes} from 'react' +import React, {PropTypes, Component} from 'react' +import Dropdown from 'shared/components/Dropdown' +import TemplateValues from 'src/dashboards/components/TemplateValues' + +import {TEMPLATE_VARIBALE_TYPES} from 'src/dashboards/constants' const TemplateVariableRow = ({ - template: {type, label, code, query, values}, + template: {label, code, values}, + selectedType, + handleSelectType, }) => ( -
    -
    {type}
    -
    {label}
    -
    {code}
    -
    {query && query.text}
    -
    {values}
    -
    + + {label} + {code} + + + + + + + + + + ) -const {arrayOf, shape, string} = PropTypes +class RowWrapper extends Component { + constructor(props) { + super(props) + this.state = { + selectedType: this.props.template.type, + } -TemplateVariableRow.propTypes = { + this.handleSelectType = ::this.handleSelectType + } + + handleSelectType(item) { + this.setState({selectedType: item.type}) + } + + render() { + return ( + + ) + } +} + +const {arrayOf, func, shape, string} = PropTypes + +RowWrapper.propTypes = { template: shape({ type: string.isRequired, label: string.isRequired, @@ -27,4 +69,10 @@ TemplateVariableRow.propTypes = { }), } -export default TemplateVariableRow +TemplateVariableRow.propTypes = { + ...RowWrapper.propTypes, + selectedType: string.isRequired, + handleSelectType: func.isRequired, +} + +export default RowWrapper diff --git a/ui/src/dashboards/components/TemplateVariableTable.js b/ui/src/dashboards/components/TemplateVariableTable.js index 873a10397e..f836c3e2c6 100644 --- a/ui/src/dashboards/components/TemplateVariableTable.js +++ b/ui/src/dashboards/components/TemplateVariableTable.js @@ -3,9 +3,20 @@ import React, {PropTypes} from 'react' import TemplateVariableRow from 'src/dashboards/components/TemplateVariableRow' const TemplateVariableTable = ({templates}) => ( -
    - {templates.map(t => )} -
    + + + + + + + + + + + {templates.map(t => )} + +
    Dropdown LabelShortcodeTypeValues +
    ) const {arrayOf, shape, string} = PropTypes diff --git a/ui/src/dashboards/constants/index.js b/ui/src/dashboards/constants/index.js index 8ede7d3c04..c0ea37a405 100644 --- a/ui/src/dashboards/constants/index.js +++ b/ui/src/dashboards/constants/index.js @@ -26,3 +26,30 @@ export const NEW_DASHBOARD = { name: 'Name This Dashboard', cells: [NEW_DEFAULT_DASHBOARD_CELL], } + +export const TEMPLATE_VARIBALE_TYPES = [ + { + text: 'CSV', + type: 'csv', + }, + { + text: 'Measurements', + type: 'measurements', + }, + { + text: 'Databases', + type: 'databases', + }, + { + text: 'Fields', + type: 'fields', + }, + { + text: 'Tag Keys', + type: 'tagKeys', + }, + { + text: 'Tag Values', + type: 'tagValues', + }, +] diff --git a/ui/src/style/pages/dashboards.scss b/ui/src/style/pages/dashboards.scss index 9e654e0ab7..bb980ac120 100644 --- a/ui/src/style/pages/dashboards.scss +++ b/ui/src/style/pages/dashboards.scss @@ -59,6 +59,10 @@ $dash-graph-options-arrow: 8px; min-height: 150px; @include gradient-v($g2-kevlar,$g0-obsidian); } + + .dropdown-toggle { + width: 150px; + } } .dashboard { From f0fa19c10a29b5232330761f54de26b27f72c663 Mon Sep 17 00:00:00 2001 From: Luke Morris Date: Tue, 18 Apr 2017 11:18:19 -0700 Subject: [PATCH 020/163] Update fixtures to include selected value --- ui/src/dashboards/actions/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index 6abadb5aea..c83098a91e 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -137,6 +137,7 @@ const templates = [ text: 'SHOW TAGS WHERE HUNTER = "coo"', }, values: ['h1', 'h2', 'h3'], + selected: 'h2', }, { id: 2, @@ -144,6 +145,7 @@ const templates = [ label: 'test csv', code: '$INFLX', values: ['A', 'B', 'C'], + selected: 'A', }, { id: 3, @@ -151,6 +153,7 @@ const templates = [ label: 'test csv', code: '$NULL', values: null, + selected: null, }, ] From 9717b40e1b59008483b165e0a33f285a7f1bafae Mon Sep 17 00:00:00 2001 From: Luke Morris Date: Tue, 18 Apr 2017 11:18:32 -0700 Subject: [PATCH 021/163] Display selected value; disallow selection while loading --- ui/src/dashboards/components/Dashboard.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/ui/src/dashboards/components/Dashboard.js b/ui/src/dashboards/components/Dashboard.js index 7bd941b3a7..abf3288efa 100644 --- a/ui/src/dashboards/components/Dashboard.js +++ b/ui/src/dashboards/components/Dashboard.js @@ -44,18 +44,13 @@ const Dashboard = ({
    Template Variables { - dashboard.templates.map(({id, values}) => { - let items - if (values === null) { - items = [{text: 'Loading...'}] - } else { - items = values.map(value => ({text: value})) - } + dashboard.templates.map(({id, values, selected}) => { + const items = values ? values.map(value => ({text: value})) : [] return ( onSelectTV(id, item.text)} /> ) From 2f6fb708f20babf56021301a30b558d8a1c195bd Mon Sep 17 00:00:00 2001 From: Luke Morris Date: Tue, 18 Apr 2017 11:20:18 -0700 Subject: [PATCH 022/163] Basic TVControlBar style polish --- ui/src/dashboards/components/Dashboard.js | 34 +++++++++++++---------- ui/src/style/pages/dashboards.scss | 3 +- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/ui/src/dashboards/components/Dashboard.js b/ui/src/dashboards/components/Dashboard.js index abf3288efa..16f7b2dbd9 100644 --- a/ui/src/dashboards/components/Dashboard.js +++ b/ui/src/dashboards/components/Dashboard.js @@ -42,21 +42,25 @@ const Dashboard = ({ return (
    - Template Variables - { - dashboard.templates.map(({id, values, selected}) => { - const items = values ? values.map(value => ({text: value})) : [] - return ( - onSelectTV(id, item.text)} - /> - ) - }) - } - +
    + Template Variables +
    +
    + { + dashboard.templates.map(({id, values, selected}) => { + const items = values ? values.map(value => ({text: value})) : [] + return ( + onSelectTV(id, item.text)} + /> + ) + }) + } + +
    {cells.length ? Date: Tue, 18 Apr 2017 11:28:30 -0700 Subject: [PATCH 023/163] Upadte CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f83f3dc53..e85736c4f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Bug Fixes ### Features 1. [#1292](https://github.com/influxdata/chronograf/pull/1292): Introduce Template Variable Manager + 1. [#1311](https://github.com/influxdata/chronograf/pull/1311): Display currently selected values in TVControlBar ### UI Improvements 1. [#1259](https://github.com/influxdata/chronograf/pull/1259): Add default display for empty dashboard From 5d77c48f080c9907489c60ec9db03dfb5168152b Mon Sep 17 00:00:00 2001 From: Luke Morris Date: Tue, 18 Apr 2017 11:42:16 -0700 Subject: [PATCH 024/163] Remove unused fetchTimeSeries function --- ui/src/shared/apis/timeSeries.js | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 ui/src/shared/apis/timeSeries.js diff --git a/ui/src/shared/apis/timeSeries.js b/ui/src/shared/apis/timeSeries.js deleted file mode 100644 index 1b81607a51..0000000000 --- a/ui/src/shared/apis/timeSeries.js +++ /dev/null @@ -1,12 +0,0 @@ -import {proxy} from 'utils/queryUrlGenerator' - -const fetchTimeSeries = async (source, database, query) => { - try { - return await proxy({source, query, database}) - } catch (error) { - console.error('error from proxy: ', error) - throw error - } -} - -export default fetchTimeSeries From d7411172cfc9e7498cca707e90472022168b29b8 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 18 Apr 2017 12:08:00 -0700 Subject: [PATCH 025/163] Introduce template query builders --- ui/src/dashboards/actions/index.js | 5 +- .../dashboards/components/DatabaseDropdown.js | 52 ++++++++++++++ .../components/MeasurementDropdown.js | 70 +++++++++++++++++++ .../dashboards/components/TemplateQueries.js | 68 ++++++++++++++++++ .../dashboards/components/TemplateValues.js | 33 --------- .../components/TemplateVariableRow.js | 50 ++++++++++--- .../components/TemplateVariableTable.js | 1 + ui/src/dashboards/constants/index.js | 8 +-- 8 files changed, 240 insertions(+), 47 deletions(-) create mode 100644 ui/src/dashboards/components/DatabaseDropdown.js create mode 100644 ui/src/dashboards/components/MeasurementDropdown.js create mode 100644 ui/src/dashboards/components/TemplateQueries.js delete mode 100644 ui/src/dashboards/components/TemplateValues.js diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index fd0321694f..eaa1e44ef9 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -116,11 +116,12 @@ export const editCellQueryStatus = (queryID, status) => ({ const templates = [ { id: 1, - type: 'query', + type: 'measurement', label: 'test query', code: '$HOSTS', query: { - db: 'db1.rp1', + db: 'db1', + measurement: null, text: 'SHOW TAGS WHERE HUNTER = "coo"', }, values: ['h1', 'h2', 'h3'], diff --git a/ui/src/dashboards/components/DatabaseDropdown.js b/ui/src/dashboards/components/DatabaseDropdown.js new file mode 100644 index 0000000000..2e7ca1ab84 --- /dev/null +++ b/ui/src/dashboards/components/DatabaseDropdown.js @@ -0,0 +1,52 @@ +import React, {PropTypes, Component} from 'react' +import Dropdown from 'shared/components/Dropdown' + +import {showDatabases} from 'shared/apis/metaQuery' +import showDatabasesParser from 'shared/parsing/showDatabases' + +class DatabaseDropdown extends Component { + constructor(props) { + super(props) + this.state = { + databases: [], + } + } + + componentDidMount() { + const {source} = this.context + const proxy = source.links.proxy + showDatabases(proxy).then(resp => { + const {databases} = showDatabasesParser(resp.data) + this.setState({databases}) + }) + } + + render() { + const {databases} = this.state + const {database, onSelectDatabase} = this.props + return ( + ({text: db}))} + selected={database || 'Select Database'} + onChoose={onSelectDatabase} + /> + ) + } +} + +const {func, shape, string} = PropTypes + +DatabaseDropdown.contextTypes = { + source: shape({ + links: shape({ + proxy: string.isRequired, + }).isRequired, + }).isRequired, +} + +DatabaseDropdown.propTypes = { + database: string, + onSelectDatabase: func.isRequired, +} + +export default DatabaseDropdown diff --git a/ui/src/dashboards/components/MeasurementDropdown.js b/ui/src/dashboards/components/MeasurementDropdown.js new file mode 100644 index 0000000000..fa53f2edaa --- /dev/null +++ b/ui/src/dashboards/components/MeasurementDropdown.js @@ -0,0 +1,70 @@ +import React, {PropTypes, Component} from 'react' +import Dropdown from 'shared/components/Dropdown' + +import {showMeasurements} from 'shared/apis/metaQuery' +import showMeasurementsParser from 'shared/parsing/showMeasurements' + +class MeasurementDropdown extends Component { + constructor(props) { + super(props) + this.state = { + measurements: [], + } + + this._getMeasurements = ::this._getMeasurements + } + + componentDidMount() { + this._getMeasurements() + } + + componentDidUpdate(nextProps) { + if (nextProps.database === this.props.database) { + return + } + + this._getMeasurements() + } + + render() { + const {measurements} = this.state + const {measurement, onSelectMeasurement} = this.props + return ( + ({text}))} + selected={measurement || 'Select Measurement'} + onChoose={onSelectMeasurement} + /> + ) + } + + async _getMeasurements() { + const {source: {links: {proxy}}} = this.context + + try { + const {data} = await showMeasurements(proxy, this.props.database) + const {measurementSets} = showMeasurementsParser(data) + this.setState({measurements: measurementSets[0].measurements}) + } catch (error) { + console.error(error) + } + } +} + +const {func, shape, string} = PropTypes + +MeasurementDropdown.contextTypes = { + source: shape({ + links: shape({ + proxy: string.isRequired, + }).isRequired, + }).isRequired, +} + +MeasurementDropdown.propTypes = { + database: string.isRequired, + measurement: string, + onSelectMeasurement: func.isRequired, +} + +export default MeasurementDropdown diff --git a/ui/src/dashboards/components/TemplateQueries.js b/ui/src/dashboards/components/TemplateQueries.js new file mode 100644 index 0000000000..4266b3e718 --- /dev/null +++ b/ui/src/dashboards/components/TemplateQueries.js @@ -0,0 +1,68 @@ +import React, {PropTypes} from 'react' +import DatabaseDropdown from 'src/dashboards/components/DatabaseDropdown' +import MeasurementDropdown from 'src/dashboards/components/MeasurementDropdown' + +const TemplateQueries = ({ + selectedType, + selectedDatabase, + selectedMeasurement, + onSelectDatabase, + onSelectMeasurement, +}) => { + switch (selectedType) { + case 'csv': + return
    n/a
    + case 'databases': + return
    SHOW DATABASES
    + case 'measurements': + return ( +
    + SHOW MEASUREMENTS on + +
    + ) + case 'fields': + return ( +
    + SHOW FIELD KEYS on + + from + {selectedDatabase + ? + : 'Pick a DB'} +
    + ) + case 'tagKeys': + return
    SHOW TAG KEYS ON "db" FROM "measurement"
    + case 'tagValues': + return ( +
    + SHOW TAG VALUES ON "db_name" FROM "measurement_name" WITH KEY = "tag_key" +
    + ) + default: + return
    n/a
    + } +} + +const {func, string} = PropTypes + +TemplateQueries.propTypes = { + selectedType: string.isRequired, + onSelectDatabase: func.isRequired, + onSelectMeasurement: func.isRequired, + selectedMeasurement: string, + selectedDatabase: string, +} + +export default TemplateQueries diff --git a/ui/src/dashboards/components/TemplateValues.js b/ui/src/dashboards/components/TemplateValues.js deleted file mode 100644 index b6dec0d00b..0000000000 --- a/ui/src/dashboards/components/TemplateValues.js +++ /dev/null @@ -1,33 +0,0 @@ -import React, {PropTypes} from 'react' - -const TemplateValues = ({values, selectedType}) => { - switch (selectedType) { - case 'csv': - return
    {values.join(', ')}
    - case 'databases': - return
    SHOW DATABASES
    - case 'measurements': - return
    SHOW MEASUREMENTS on "db"
    - case 'fields': - return
    SHOW FIELD KEYS on "db" from "measurement"
    - case 'tagKeys': - return
    SHOW TAG KEYS ON "db" FROM "measurement"
    - case 'tagValues': - return ( -
    - SHOW TAG VALUES ON "db_name" FROM "measurement_name" WITH KEY = "tag_key" -
    - ) - default: - return
    {values.join(', ')}
    - } -} - -const {arrayOf, string} = PropTypes - -TemplateValues.propTypes = { - selectedType: string.isRequired, - values: arrayOf(string), -} - -export default TemplateValues diff --git a/ui/src/dashboards/components/TemplateVariableRow.js b/ui/src/dashboards/components/TemplateVariableRow.js index b27bb9595a..a80f78d35f 100644 --- a/ui/src/dashboards/components/TemplateVariableRow.js +++ b/ui/src/dashboards/components/TemplateVariableRow.js @@ -1,13 +1,17 @@ import React, {PropTypes, Component} from 'react' import Dropdown from 'shared/components/Dropdown' -import TemplateValues from 'src/dashboards/components/TemplateValues' +import TemplateQueries from 'src/dashboards/components/TemplateQueries' import {TEMPLATE_VARIBALE_TYPES} from 'src/dashboards/constants' const TemplateVariableRow = ({ template: {label, code, values}, selectedType, - handleSelectType, + selectedDatabase, + selectedMeasurement, + onSelectType, + onSelectDatabase, + onSelectMeasurement, }) => ( {label} @@ -15,13 +19,22 @@ const TemplateVariableRow = ({ - + + + + {values.join(' ,')} @@ -32,23 +45,42 @@ const TemplateVariableRow = ({ class RowWrapper extends Component { constructor(props) { super(props) + const {template: {query, type}} = this.props + this.state = { - selectedType: this.props.template.type, + selectedType: type, + selectedDatabase: query && query.db, + selectedMeasurement: query && query.measurement, } this.handleSelectType = ::this.handleSelectType + this.handleSelectDatabase = ::this.handleSelectDatabase + this.handleSelectMeasurement = ::this.handleSelectMeasurement } handleSelectType(item) { this.setState({selectedType: item.type}) } + handleSelectDatabase(item) { + this.setState({selectedDatabase: item.text}) + } + + handleSelectMeasurement(item) { + this.setState({selectedMeasurement: item.text}) + } + render() { + const {selectedType, selectedDatabase, selectedMeasurement} = this.state return ( ) } @@ -72,7 +104,9 @@ RowWrapper.propTypes = { TemplateVariableRow.propTypes = { ...RowWrapper.propTypes, selectedType: string.isRequired, - handleSelectType: func.isRequired, + selectedDatabase: string, + onSelectType: func.isRequired, + onSelectDatabase: func.isRequired, } export default RowWrapper diff --git a/ui/src/dashboards/components/TemplateVariableTable.js b/ui/src/dashboards/components/TemplateVariableTable.js index f836c3e2c6..469e549271 100644 --- a/ui/src/dashboards/components/TemplateVariableTable.js +++ b/ui/src/dashboards/components/TemplateVariableTable.js @@ -9,6 +9,7 @@ const TemplateVariableTable = ({templates}) => ( Dropdown Label Shortcode Type + Queries Values diff --git a/ui/src/dashboards/constants/index.js b/ui/src/dashboards/constants/index.js index c0ea37a405..0092862403 100644 --- a/ui/src/dashboards/constants/index.js +++ b/ui/src/dashboards/constants/index.js @@ -32,14 +32,14 @@ export const TEMPLATE_VARIBALE_TYPES = [ text: 'CSV', type: 'csv', }, - { - text: 'Measurements', - type: 'measurements', - }, { text: 'Databases', type: 'databases', }, + { + text: 'Measurements', + type: 'measurements', + }, { text: 'Fields', type: 'fields', From 579e45d08d2a9aa156b114c57a964c7aa3b86b0d Mon Sep 17 00:00:00 2001 From: Luke Morris Date: Tue, 18 Apr 2017 13:10:29 -0700 Subject: [PATCH 026/163] Factor refreshing graph into its own render function --- ui/src/shared/components/LayoutRenderer.js | 52 +++++++++------------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/ui/src/shared/components/LayoutRenderer.js b/ui/src/shared/components/LayoutRenderer.js index fc6b3cd9fa..030a95f7ce 100644 --- a/ui/src/shared/components/LayoutRenderer.js +++ b/ui/src/shared/components/LayoutRenderer.js @@ -89,11 +89,31 @@ export const LayoutRenderer = React.createClass({ return text }, + renderRefreshingGraph(type, queries, autoRefresh) { + if (type === 'single-stat') { + return + } + + const displayOptions = { + stepPlot: type === 'line-stepplot', + stackedGraph: type === 'line-stacked', + } + + return ( + + ) + }, + generateVisualizations() { const {autoRefresh, timeRange, source, cells, onEditCell, onRenameCell, onUpdateCell, onDeleteCell, onSummonOverlayTechnologies, shouldNotBeEditable} = this.props return cells.map((cell) => { - const qs = cell.queries.map((query) => { + const queries = cell.queries.map((query) => { // TODO: Canned dashboards (and possibly Kubernetes dashboard) use an old query schema, // which does not have enough information for the new `buildInfluxQLQuery` function // to operate on. We will use `buildQueryForOldQuerySchema` until we conform @@ -112,29 +132,6 @@ export const LayoutRenderer = React.createClass({ }) }) - if (cell.type === 'single-stat') { - return ( -
    - - - -
    - ) - } - - const displayOptions = { - stepPlot: cell.type === 'line-stepplot', - stackedGraph: cell.type === 'line-stacked', - } - return (
    - + {this.renderRefreshingGraph(cell.type, queries, autoRefresh)}
    ) From 19779cd2696121143ba5c8ab146d396afa3d631c Mon Sep 17 00:00:00 2001 From: Luke Morris Date: Tue, 18 Apr 2017 13:10:52 -0700 Subject: [PATCH 027/163] Make tv fixtures more realistic --- ui/src/dashboards/actions/index.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index c83098a91e..00d4276c9a 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -131,21 +131,21 @@ const templates = [ id: 1, type: 'query', label: 'test query', - code: '$HOSTS', + code: '$REGION', query: { db: 'db1.rp1', text: 'SHOW TAGS WHERE HUNTER = "coo"', }, - values: ['h1', 'h2', 'h3'], - selected: 'h2', + values: ['us-west', 'us-east', 'us-mount'], + selected: 'us-east', }, { id: 2, type: 'csv', label: 'test csv', - code: '$INFLX', - values: ['A', 'B', 'C'], - selected: 'A', + code: '$TEMPERATURE', + values: ['98.7', '99.1', '101.3'], + selected: '99.1', }, { id: 3, From 1f42f1215245292a3560848f703586bc39abf72f Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Tue, 18 Apr 2017 14:49:45 -0600 Subject: [PATCH 028/163] Ensure initial selection state on database and measurement dropdowns. --- ui/src/dashboards/actions/index.js | 2 +- ui/src/dashboards/components/DatabaseDropdown.js | 8 ++++++-- ui/src/dashboards/components/MeasurementDropdown.js | 10 ++++++++-- ui/src/dashboards/components/TemplateVariableRow.js | 2 +- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index eaa1e44ef9..f7a83da3b0 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -121,7 +121,7 @@ const templates = [ code: '$HOSTS', query: { db: 'db1', - measurement: null, + measurement: 'm1', text: 'SHOW TAGS WHERE HUNTER = "coo"', }, values: ['h1', 'h2', 'h3'], diff --git a/ui/src/dashboards/components/DatabaseDropdown.js b/ui/src/dashboards/components/DatabaseDropdown.js index 2e7ca1ab84..b075bbad7d 100644 --- a/ui/src/dashboards/components/DatabaseDropdown.js +++ b/ui/src/dashboards/components/DatabaseDropdown.js @@ -14,20 +14,24 @@ class DatabaseDropdown extends Component { componentDidMount() { const {source} = this.context + const {database, onSelectDatabase} = this.props const proxy = source.links.proxy showDatabases(proxy).then(resp => { const {databases} = showDatabasesParser(resp.data) this.setState({databases}) + const selected = databases.includes(database) ? database : databases[0] || 'No databases' + onSelectDatabase({text: selected}) }) } render() { const {databases} = this.state const {database, onSelectDatabase} = this.props + return ( ({text: db}))} - selected={database || 'Select Database'} + items={databases.map(text => ({text}))} + selected={database} onChoose={onSelectDatabase} /> ) diff --git a/ui/src/dashboards/components/MeasurementDropdown.js b/ui/src/dashboards/components/MeasurementDropdown.js index fa53f2edaa..ce18ae0973 100644 --- a/ui/src/dashboards/components/MeasurementDropdown.js +++ b/ui/src/dashboards/components/MeasurementDropdown.js @@ -1,6 +1,7 @@ import React, {PropTypes, Component} from 'react' -import Dropdown from 'shared/components/Dropdown' +import _ from 'lodash' +import Dropdown from 'shared/components/Dropdown' import {showMeasurements} from 'shared/apis/metaQuery' import showMeasurementsParser from 'shared/parsing/showMeasurements' @@ -40,11 +41,16 @@ class MeasurementDropdown extends Component { async _getMeasurements() { const {source: {links: {proxy}}} = this.context + const {measurement, database, onSelectMeasurement} = this.props try { - const {data} = await showMeasurements(proxy, this.props.database) + const {data} = await showMeasurements(proxy, database) const {measurementSets} = showMeasurementsParser(data) this.setState({measurements: measurementSets[0].measurements}) + const selected = measurementSets.includes(measurement) + ? measurement + : _.get(measurementSets, ['0', 'measurements', '0'], 'No measurements') + onSelectMeasurement({text: selected}) } catch (error) { console.error(error) } diff --git a/ui/src/dashboards/components/TemplateVariableRow.js b/ui/src/dashboards/components/TemplateVariableRow.js index a80f78d35f..d6cc30efc4 100644 --- a/ui/src/dashboards/components/TemplateVariableRow.js +++ b/ui/src/dashboards/components/TemplateVariableRow.js @@ -34,7 +34,7 @@ const TemplateVariableRow = ({ /> - {values.join(' ,')} + {values.join(', ')} From 66012f40337af3e00e4974e1cb12889c8be2b825 Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Tue, 18 Apr 2017 14:50:49 -0600 Subject: [PATCH 029/163] Fix irregular whitespace linter error. --- ui/src/dashboards/components/DatabaseDropdown.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/src/dashboards/components/DatabaseDropdown.js b/ui/src/dashboards/components/DatabaseDropdown.js index b075bbad7d..3dfed80984 100644 --- a/ui/src/dashboards/components/DatabaseDropdown.js +++ b/ui/src/dashboards/components/DatabaseDropdown.js @@ -19,8 +19,10 @@ class DatabaseDropdown extends Component { showDatabases(proxy).then(resp => { const {databases} = showDatabasesParser(resp.data) this.setState({databases}) - const selected = databases.includes(database) ? database : databases[0] || 'No databases' - onSelectDatabase({text: selected}) + const selected = databases.includes(database) + ? database + : databases[0] || 'No databases' + onSelectDatabase({text: selected}) }) } From 3fb75a4e770536373f029a64e6a5c956791c7f1a Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Tue, 18 Apr 2017 16:47:00 -0600 Subject: [PATCH 030/163] =?UTF-8?q?Add=20tag=20key=20=F0=9F=85=B1=EF=B8=8F?= =?UTF-8?q?ropdown.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboards/components/TagKeyDropdown.js | 74 +++++++++++++++++++ ...lateQueries.js => TemplateQueryBuilder.js} | 43 ++++++++--- .../components/TemplateVariableRow.js | 30 +++++++- 3 files changed, 134 insertions(+), 13 deletions(-) create mode 100644 ui/src/dashboards/components/TagKeyDropdown.js rename ui/src/dashboards/components/{TemplateQueries.js => TemplateQueryBuilder.js} (55%) diff --git a/ui/src/dashboards/components/TagKeyDropdown.js b/ui/src/dashboards/components/TagKeyDropdown.js new file mode 100644 index 0000000000..cab6ce9001 --- /dev/null +++ b/ui/src/dashboards/components/TagKeyDropdown.js @@ -0,0 +1,74 @@ +import React, {PropTypes, Component} from 'react' + +import Dropdown from 'shared/components/Dropdown' +import {showTagKeys} from 'shared/apis/metaQuery' +import showTagKeysParser from 'shared/parsing/showTagKeys' + +class TagKeyDropdown extends Component { + constructor(props) { + super(props) + this.state = { + tagKeys: [], + } + + this._getTags = ::this._getTags + } + + componentDidMount() { + this._getTags() + } + + componentDidUpdate(nextProps) { + if ( + nextProps.database === this.props.database && + nextProps.measurement === this.props.measurement + ) { + return + } + + this._getTags() + } + + render() { + const {tagKeys} = this.state + const {tagKey, onSelectTagKey} = this.props + return ( + ({text}))} + selected={tagKey || 'Select Tag Key'} + onChoose={onSelectTagKey} + /> + ) + } + + async _getTags() { + const {database, measurement, tagKey, onSelectTagKey} = this.props + const {source: {links: {proxy}}} = this.context + + const {data} = await showTagKeys({source: proxy, database, measurement}) + const {tagKeys} = showTagKeysParser(data) + + this.setState({tagKeys}) + const selected = tagKeys.includes(tagKey) ? tagKey : tagKeys[0] || 'No tags' + onSelectTagKey({text: selected}) + } +} + +const {func, shape, string} = PropTypes + +TagKeyDropdown.contextTypes = { + source: shape({ + links: shape({ + proxy: string.isRequired, + }).isRequired, + }).isRequired, +} + +TagKeyDropdown.propTypes = { + database: string.isRequired, + measurement: string.isRequired, + tagKey: string, + onSelectTagKey: func.isRequired, +} + +export default TagKeyDropdown diff --git a/ui/src/dashboards/components/TemplateQueries.js b/ui/src/dashboards/components/TemplateQueryBuilder.js similarity index 55% rename from ui/src/dashboards/components/TemplateQueries.js rename to ui/src/dashboards/components/TemplateQueryBuilder.js index 4266b3e718..2aa238c584 100644 --- a/ui/src/dashboards/components/TemplateQueries.js +++ b/ui/src/dashboards/components/TemplateQueryBuilder.js @@ -1,13 +1,16 @@ import React, {PropTypes} from 'react' import DatabaseDropdown from 'src/dashboards/components/DatabaseDropdown' import MeasurementDropdown from 'src/dashboards/components/MeasurementDropdown' +import TagKeyDropdown from 'src/dashboards/components/TagKeyDropdown' -const TemplateQueries = ({ +const TemplateQueryBuilder = ({ selectedType, selectedDatabase, selectedMeasurement, + selectedTagKey, onSelectDatabase, onSelectMeasurement, + onSelectTagKey, }) => { switch (selectedType) { case 'csv': @@ -17,7 +20,7 @@ const TemplateQueries = ({ case 'measurements': return (
    - SHOW MEASUREMENTS on + SHOW MEASUREMENTS ON ) case 'fields': + case 'tagKeys': return (
    - SHOW FIELD KEYS on + SHOW {selectedType === 'fields' ? 'FIELD' : 'TAG'} KEYS ON - from + FROM {selectedDatabase ? ) - case 'tagKeys': - return
    SHOW TAG KEYS ON "db" FROM "measurement"
    case 'tagValues': return (
    - SHOW TAG VALUES ON "db_name" FROM "measurement_name" WITH KEY = "tag_key" + SHOW TAG VALUES ON + + FROM + {selectedDatabase + ? + : 'Pick a DB'} + WITH KEY = + {selectedMeasurement + ? + : 'Pick a Tag Key'}
    ) default: @@ -57,12 +80,14 @@ const TemplateQueries = ({ const {func, string} = PropTypes -TemplateQueries.propTypes = { +TemplateQueryBuilder.propTypes = { selectedType: string.isRequired, onSelectDatabase: func.isRequired, onSelectMeasurement: func.isRequired, + onSelectTagKey: func.isRequired, selectedMeasurement: string, selectedDatabase: string, + selectedTagKey: string, } -export default TemplateQueries +export default TemplateQueryBuilder diff --git a/ui/src/dashboards/components/TemplateVariableRow.js b/ui/src/dashboards/components/TemplateVariableRow.js index d6cc30efc4..395e1198be 100644 --- a/ui/src/dashboards/components/TemplateVariableRow.js +++ b/ui/src/dashboards/components/TemplateVariableRow.js @@ -1,6 +1,7 @@ import React, {PropTypes, Component} from 'react' import Dropdown from 'shared/components/Dropdown' -import TemplateQueries from 'src/dashboards/components/TemplateQueries' +import TemplateQueryBuilder + from 'src/dashboards/components/TemplateQueryBuilder' import {TEMPLATE_VARIBALE_TYPES} from 'src/dashboards/constants' @@ -12,6 +13,8 @@ const TemplateVariableRow = ({ onSelectType, onSelectDatabase, onSelectMeasurement, + selectedTagKey, + onSelectTagKey, }) => ( {label} @@ -25,12 +28,14 @@ const TemplateVariableRow = ({ /> - @@ -51,11 +56,13 @@ class RowWrapper extends Component { selectedType: type, selectedDatabase: query && query.db, selectedMeasurement: query && query.measurement, + selectedTagKey: query && query.tagKey, } this.handleSelectType = ::this.handleSelectType this.handleSelectDatabase = ::this.handleSelectDatabase this.handleSelectMeasurement = ::this.handleSelectMeasurement + this.handleSelectTagKey = ::this.handleSelectTagKey } handleSelectType(item) { @@ -70,17 +77,28 @@ class RowWrapper extends Component { this.setState({selectedMeasurement: item.text}) } + handleSelectTagKey(item) { + this.setState({selectedTagKey: item.text}) + } + render() { - const {selectedType, selectedDatabase, selectedMeasurement} = this.state + const { + selectedType, + selectedDatabase, + selectedMeasurement, + selectedTagKey, + } = this.state return ( ) } @@ -94,8 +112,10 @@ RowWrapper.propTypes = { label: string.isRequired, code: string.isRequired, query: shape({ - db: string.isRequired, + db: string, text: string.isRequired, + measurement: string, + tagKey: string, }), values: arrayOf(string.isRequired), }), @@ -105,8 +125,10 @@ TemplateVariableRow.propTypes = { ...RowWrapper.propTypes, selectedType: string.isRequired, selectedDatabase: string, + selectedTagKey: string, onSelectType: func.isRequired, onSelectDatabase: func.isRequired, + onSelectTagKey: func.isRequired, } export default RowWrapper From f7c414fed307fa6ec77d7b10143d85e42c627529 Mon Sep 17 00:00:00 2001 From: Luke Morris Date: Tue, 18 Apr 2017 16:09:55 -0700 Subject: [PATCH 031/163] Peel templates off of dashboard --- ui/src/dashboards/components/Dashboard.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/src/dashboards/components/Dashboard.js b/ui/src/dashboards/components/Dashboard.js index 16f7b2dbd9..5d107c7c77 100644 --- a/ui/src/dashboards/components/Dashboard.js +++ b/ui/src/dashboards/components/Dashboard.js @@ -24,6 +24,8 @@ const Dashboard = ({ return null } + const {templates} = dashboard + const cells = dashboard.cells.map((cell) => { const dashboardCell = {...cell} dashboardCell.queries = dashboardCell.queries.map(({label, query, queryConfig, db}) => @@ -47,7 +49,7 @@ const Dashboard = ({
    { - dashboard.templates.map(({id, values, selected}) => { + templates.map(({id, values, selected}) => { const items = values ? values.map(value => ({text: value})) : [] return ( Date: Tue, 18 Apr 2017 16:10:43 -0700 Subject: [PATCH 032/163] =?UTF-8?q?Pass=20templates=20through=20to=20proxy?= =?UTF-8?q?=20as=20=E2=80=98params=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/src/dashboards/components/Dashboard.js | 1 + ui/src/shared/actions/timeSeries.js | 4 +- ui/src/shared/components/AutoRefresh.js | 49 +++++++++++++--------- ui/src/shared/components/LayoutRenderer.js | 21 ++++++++-- ui/src/utils/queryUrlGenerator.js | 3 +- 5 files changed, 52 insertions(+), 26 deletions(-) diff --git a/ui/src/dashboards/components/Dashboard.js b/ui/src/dashboards/components/Dashboard.js index 5d107c7c77..bf611bc94b 100644 --- a/ui/src/dashboards/components/Dashboard.js +++ b/ui/src/dashboards/components/Dashboard.js @@ -67,6 +67,7 @@ const Dashboard = ({ {cells.length ? { console.error(error) } -export const fetchTimeSeriesAsync = async ({source, db, rp, query}, editQueryStatus = noop) => { +export const fetchTimeSeriesAsync = async ({source, db, rp, query, params}, editQueryStatus = noop) => { handleLoading(query, editQueryStatus) try { - const {data} = await proxy({source, db, rp, query: query.text}) + const {data} = await proxy({source, db, rp, query: query.text, params}) return handleSuccess(data, query, editQueryStatus) } catch (error) { handleError(error, query, editQueryStatus) diff --git a/ui/src/shared/components/AutoRefresh.js b/ui/src/shared/components/AutoRefresh.js index 54032e3bcc..ca3c9c1dc3 100644 --- a/ui/src/shared/components/AutoRefresh.js +++ b/ui/src/shared/components/AutoRefresh.js @@ -17,18 +17,24 @@ const AutoRefresh = (ComposedComponent) => { propTypes: { children: element, autoRefresh: number.isRequired, + params: arrayOf(shape({ + code: string.isRequired, + values: arrayOf(string), + })), queries: arrayOf(shape({ host: oneOfType([string, arrayOf(string)]), text: string, }).isRequired).isRequired, editQueryStatus: func, }, + getInitialState() { return { lastQuerySuccessful: false, timeSeries: [], } }, + componentDidMount() { const {queries, autoRefresh} = this.props this.executeQueries(queries) @@ -36,6 +42,7 @@ const AutoRefresh = (ComposedComponent) => { this.intervalID = setInterval(() => this.executeQueries(queries), autoRefresh) } }, + componentWillReceiveProps(nextProps) { const shouldRefetch = this.queryDifference(this.props.queries, nextProps.queries).length @@ -51,41 +58,45 @@ const AutoRefresh = (ComposedComponent) => { } } }, + queryDifference(left, right) { const leftStrs = left.map((q) => `${q.host}${q.text}`) const rightStrs = right.map((q) => `${q.host}${q.text}`) return _.difference(_.union(leftStrs, rightStrs), _.intersection(leftStrs, rightStrs)) }, + async executeQueries(queries) { + const {params, editQueryStatus} = this.props + if (!queries.length) { - this.setState({ - timeSeries: [], - }) + this.setState({timeSeries: []}) return } this.setState({isFetching: true}) - let count = 0 - const newSeries = [] - for (const query of queries) { + + const timeSeriesPromises = queries.map((query) => { const {host, database, rp} = query - const response = await fetchTimeSeriesAsync({source: host, db: database, rp, query}, this.props.editQueryStatus) - newSeries.push({response}) - count += 1 - if (count === queries.length) { - const querySuccessful = !this._noResultsForQuery(newSeries) - this.setState({ - lastQuerySuccessful: querySuccessful, - isFetching: false, - timeSeries: newSeries, - }) - } - } + return fetchTimeSeriesAsync({source: host, db: database, rp, query, params}, editQueryStatus) + }) + + Promise.all(timeSeriesPromises).then(timeSeries => { + const lastSeries = timeSeries[timeSeries.length - 1] + const lastQuerySuccessful = !this._noResultsForQuery(lastSeries) + + this.setState({ + timeSeries, + lastQuerySuccessful, + isFetching: false, + }) + }) }, + componentWillUnmount() { clearInterval(this.intervalID) this.intervalID = false }, + render() { const {timeSeries} = this.state @@ -139,7 +150,7 @@ const AutoRefresh = (ComposedComponent) => { } return data.every((datum) => { - return datum.response.results.every((result) => { + return datum.results.every((result) => { return Object.keys(result).length === 0 }) }) diff --git a/ui/src/shared/components/LayoutRenderer.js b/ui/src/shared/components/LayoutRenderer.js index 030a95f7ce..3ef2705afe 100644 --- a/ui/src/shared/components/LayoutRenderer.js +++ b/ui/src/shared/components/LayoutRenderer.js @@ -46,6 +46,10 @@ export const LayoutRenderer = React.createClass({ type: string.isRequired, }).isRequired ), + templates: arrayOf(shape({ + code: string.isRequired, + selected: string, + })).isRequired, host: string, source: string, onPositionChange: func, @@ -89,9 +93,17 @@ export const LayoutRenderer = React.createClass({ return text }, - renderRefreshingGraph(type, queries, autoRefresh) { + renderRefreshingGraph(type, queries) { + const {autoRefresh, templates} = this.props + if (type === 'single-stat') { - return + return ( + + ) } const displayOptions = { @@ -102,6 +114,7 @@ export const LayoutRenderer = React.createClass({ return ( { const queries = cell.queries.map((query) => { @@ -143,7 +156,7 @@ export const LayoutRenderer = React.createClass({ shouldNotBeEditable={shouldNotBeEditable} cell={cell} > - {this.renderRefreshingGraph(cell.type, queries, autoRefresh)} + {this.renderRefreshingGraph(cell.type, queries)}
    ) diff --git a/ui/src/utils/queryUrlGenerator.js b/ui/src/utils/queryUrlGenerator.js index fba968da74..d839f03091 100644 --- a/ui/src/utils/queryUrlGenerator.js +++ b/ui/src/utils/queryUrlGenerator.js @@ -1,11 +1,12 @@ import AJAX from 'utils/ajax' -export const proxy = async ({source, query, db, rp}) => { +export const proxy = async ({source, query, db, rp, params}) => { try { return await AJAX({ method: 'POST', url: source, data: { + params, query, db, rp, From f17cf6bb0966fd41e5077a0acb207e7a99f2e876 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Wed, 19 Apr 2017 11:03:53 -0500 Subject: [PATCH 033/163] Add InfluxQL template rendering --- influx/templates.go | 53 +++++++++++++++ influx/templates_test.go | 139 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 influx/templates.go create mode 100644 influx/templates_test.go diff --git a/influx/templates.go b/influx/templates.go new file mode 100644 index 0000000000..24f5338baa --- /dev/null +++ b/influx/templates.go @@ -0,0 +1,53 @@ +package influx + +import "strings" + +// TempValue is a value use to replace a template in an InfluxQL query +type TempValue struct { + Value string `json:"value"` + Type string `json:"type"` +} + +// TempVar is a named variable within an InfluxQL query to be replaced with Values +type TempVar struct { + Var string `json:"tempVar"` + Values []TempValue `json:"values"` +} + +// String converts the template variable into a correct InfluxQL string based +// on its type +func (t TempVar) String() string { + if len(t.Values) == 0 { + return "" + } + switch t.Values[0].Type { + case "tagKey", "fieldKey": + return `"` + t.Values[0].Value + `"` + case "tagValue": + return `'` + t.Values[0].Value + `'` + case "csv": + return t.Values[0].Value + default: + return "" + } +} + +// TempVars are template variables to replace within an InfluxQL query +type TempVars struct { + Vars []TempVar `json:"tempVars"` +} + +// TemplateReplace replaces templates with values within the query string +func TemplateReplace(query string, templates TempVars) string { + replacements := []string{} + for _, v := range templates.Vars { + newVal := v.String() + if newVal != "" { + replacements = append(replacements, v.Var, newVal) + } + } + + replacer := strings.NewReplacer(replacements...) + replaced := replacer.Replace(query) + return replaced +} diff --git a/influx/templates_test.go b/influx/templates_test.go new file mode 100644 index 0000000000..ba0ba26407 --- /dev/null +++ b/influx/templates_test.go @@ -0,0 +1,139 @@ +package influx + +import ( + "testing" +) + +func TestTemplateReplace(t *testing.T) { + tests := []struct { + name string + query string + vars TempVars + want string + }{ + { + name: "select with parameters", + query: "$METHOD field1, $field FROM $measurement WHERE temperature > $temperature", + vars: TempVars{ + Vars: []TempVar{ + { + Var: "$temperature", + Values: []TempValue{ + { + Type: "csv", + Value: "10", + }, + }, + }, + { + Var: "$field", + Values: []TempValue{ + { + Type: "fieldKey", + Value: "field2", + }, + }, + }, + { + Var: "$METHOD", + Values: []TempValue{ + { + Type: "csv", + Value: "SELECT", + }, + }, + }, + { + Var: "$measurement", + Values: []TempValue{ + { + Type: "csv", + Value: `"cpu"`, + }, + }, + }, + }, + }, + want: `SELECT field1, "field2" FROM "cpu" WHERE temperature > 10`, + }, + { + name: "select with parameters and aggregates", + query: `SELECT mean($field) FROM "cpu" WHERE $tag = $value GROUP BY $tag`, + vars: TempVars{ + Vars: []TempVar{ + { + Var: "$value", + Values: []TempValue{ + { + Type: "tagValue", + Value: "howdy.com", + }, + }, + }, + { + Var: "$tag", + Values: []TempValue{ + { + Type: "tagKey", + Value: "host", + }, + }, + }, + { + Var: "$field", + Values: []TempValue{ + { + Type: "fieldKey", + Value: "field", + }, + }, + }, + }, + }, + want: `SELECT mean("field") FROM "cpu" WHERE "host" = 'howdy.com' GROUP BY "host"`, + }, + { + name: "Non-existant parameters", + query: `SELECT $field FROM "cpu"`, + want: `SELECT $field FROM "cpu"`, + }, + { + name: "var without a value", + query: `SELECT $field FROM "cpu"`, + vars: TempVars{ + Vars: []TempVar{ + { + Var: "$field", + }, + }, + }, + want: `SELECT $field FROM "cpu"`, + }, + { + name: "var with unknown type", + query: `SELECT $field FROM "cpu"`, + vars: TempVars{ + Vars: []TempVar{ + { + Var: "$field", + Values: []TempValue{ + { + Type: "who knows?", + Value: "field", + }, + }, + }, + }, + }, + want: `SELECT $field FROM "cpu"`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := TemplateReplace(tt.query, tt.vars) + if got != tt.want { + t.Errorf("TestParse %s =\n%s\nwant\n%s", tt.name, got, tt.want) + } + }) + } +} From c4465863da0c72ee1854404d175ddb0a92f1b001 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Wed, 19 Apr 2017 11:08:14 -0500 Subject: [PATCH 034/163] Update template variable naming to Template rather than Temp --- influx/templates.go | 22 +++++++++++----------- influx/templates_test.go | 34 +++++++++++++++++----------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/influx/templates.go b/influx/templates.go index 24f5338baa..3cdded2159 100644 --- a/influx/templates.go +++ b/influx/templates.go @@ -2,21 +2,21 @@ package influx import "strings" -// TempValue is a value use to replace a template in an InfluxQL query -type TempValue struct { +// TemplateValue is a value use to replace a template in an InfluxQL query +type TemplateValue struct { Value string `json:"value"` Type string `json:"type"` } -// TempVar is a named variable within an InfluxQL query to be replaced with Values -type TempVar struct { - Var string `json:"tempVar"` - Values []TempValue `json:"values"` +// TemplateVar is a named variable within an InfluxQL query to be replaced with Values +type TemplateVar struct { + Var string `json:"tempVar"` + Values []TemplateValue `json:"values"` } // String converts the template variable into a correct InfluxQL string based // on its type -func (t TempVar) String() string { +func (t TemplateVar) String() string { if len(t.Values) == 0 { return "" } @@ -32,13 +32,13 @@ func (t TempVar) String() string { } } -// TempVars are template variables to replace within an InfluxQL query -type TempVars struct { - Vars []TempVar `json:"tempVars"` +// TemplateVars are template variables to replace within an InfluxQL query +type TemplateVars struct { + Vars []TemplateVar `json:"tempVars"` } // TemplateReplace replaces templates with values within the query string -func TemplateReplace(query string, templates TempVars) string { +func TemplateReplace(query string, templates TemplateVars) string { replacements := []string{} for _, v := range templates.Vars { newVal := v.String() diff --git a/influx/templates_test.go b/influx/templates_test.go index ba0ba26407..794b8522ee 100644 --- a/influx/templates_test.go +++ b/influx/templates_test.go @@ -8,17 +8,17 @@ func TestTemplateReplace(t *testing.T) { tests := []struct { name string query string - vars TempVars + vars TemplateVars want string }{ { name: "select with parameters", query: "$METHOD field1, $field FROM $measurement WHERE temperature > $temperature", - vars: TempVars{ - Vars: []TempVar{ + vars: TemplateVars{ + Vars: []TemplateVar{ { Var: "$temperature", - Values: []TempValue{ + Values: []TemplateValue{ { Type: "csv", Value: "10", @@ -27,7 +27,7 @@ func TestTemplateReplace(t *testing.T) { }, { Var: "$field", - Values: []TempValue{ + Values: []TemplateValue{ { Type: "fieldKey", Value: "field2", @@ -36,7 +36,7 @@ func TestTemplateReplace(t *testing.T) { }, { Var: "$METHOD", - Values: []TempValue{ + Values: []TemplateValue{ { Type: "csv", Value: "SELECT", @@ -45,7 +45,7 @@ func TestTemplateReplace(t *testing.T) { }, { Var: "$measurement", - Values: []TempValue{ + Values: []TemplateValue{ { Type: "csv", Value: `"cpu"`, @@ -59,11 +59,11 @@ func TestTemplateReplace(t *testing.T) { { name: "select with parameters and aggregates", query: `SELECT mean($field) FROM "cpu" WHERE $tag = $value GROUP BY $tag`, - vars: TempVars{ - Vars: []TempVar{ + vars: TemplateVars{ + Vars: []TemplateVar{ { Var: "$value", - Values: []TempValue{ + Values: []TemplateValue{ { Type: "tagValue", Value: "howdy.com", @@ -72,7 +72,7 @@ func TestTemplateReplace(t *testing.T) { }, { Var: "$tag", - Values: []TempValue{ + Values: []TemplateValue{ { Type: "tagKey", Value: "host", @@ -81,7 +81,7 @@ func TestTemplateReplace(t *testing.T) { }, { Var: "$field", - Values: []TempValue{ + Values: []TemplateValue{ { Type: "fieldKey", Value: "field", @@ -100,8 +100,8 @@ func TestTemplateReplace(t *testing.T) { { name: "var without a value", query: `SELECT $field FROM "cpu"`, - vars: TempVars{ - Vars: []TempVar{ + vars: TemplateVars{ + Vars: []TemplateVar{ { Var: "$field", }, @@ -112,11 +112,11 @@ func TestTemplateReplace(t *testing.T) { { name: "var with unknown type", query: `SELECT $field FROM "cpu"`, - vars: TempVars{ - Vars: []TempVar{ + vars: TemplateVars{ + Vars: []TemplateVar{ { Var: "$field", - Values: []TempValue{ + Values: []TemplateValue{ { Type: "who knows?", Value: "field", From 4ba46c8c0de07a4764c05470d609b13d2cb2e68c Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Wed, 19 Apr 2017 11:18:23 -0500 Subject: [PATCH 035/163] Move template variables into chronograf.go --- chronograf.go | 45 +++++++++++--- influx/templates.go | 43 ++----------- influx/templates_test.go | 130 +++++++++++++++++++-------------------- 3 files changed, 106 insertions(+), 112 deletions(-) diff --git a/chronograf.go b/chronograf.go index 337bffc49e..a7b0faa0a9 100644 --- a/chronograf.go +++ b/chronograf.go @@ -123,15 +123,46 @@ type Range struct { Lower int64 `json:"lower"` // Lower is the lower bound } +// TemplateValue is a value use to replace a template in an InfluxQL query +type TemplateValue struct { + Value string `json:"value"` + Type string `json:"type"` +} + +// TemplateVar is a named variable within an InfluxQL query to be replaced with Values +type TemplateVar struct { + Var string `json:"tempVar"` + Values []TemplateValue `json:"values"` +} + +// String converts the template variable into a correct InfluxQL string based +// on its type +func (t TemplateVar) String() string { + if len(t.Values) == 0 { + return "" + } + switch t.Values[0].Type { + case "tagKey", "fieldKey": + return `"` + t.Values[0].Value + `"` + case "tagValue": + return `'` + t.Values[0].Value + `'` + case "csv": + return t.Values[0].Value + default: + return "" + } +} + // Query retrieves a Response from a TimeSeries. type Query struct { - Command string `json:"query"` // Command is the query itself - DB string `json:"db,omitempty"` // DB is optional and if empty will not be used. - RP string `json:"rp,omitempty"` // RP is a retention policy and optional; if empty will not be used. - Wheres []string `json:"wheres,omitempty"` // Wheres restricts the query to certain attributes - GroupBys []string `json:"groupbys,omitempty"` // GroupBys collate the query by these tags - Label string `json:"label,omitempty"` // Label is the Y-Axis label for the data - Range *Range `json:"range,omitempty"` // Range is the default Y-Axis range for the data + Command string `json:"query"` // Command is the query itself + DB string `json:"db,omitempty"` // DB is optional and if empty will not be used. + RP string `json:"rp,omitempty"` // RP is a retention policy and optional; if empty will not be used. + TemplateVars []TemplateVar `json:"tempVars"` // TemplateVars are template variables to replace within an InfluxQL query + Wheres []string `json:"wheres,omitempty"` // Wheres restricts the query to certain attributes + GroupBys []string `json:"groupbys,omitempty"` // GroupBys collate the query by these tags + Label string `json:"label,omitempty"` // Label is the Y-Axis label for the data + Range *Range `json:"range,omitempty"` // Range is the default Y-Axis range for the data } // DashboardQuery includes state for the query builder. This is a transition diff --git a/influx/templates.go b/influx/templates.go index 3cdded2159..7017a38456 100644 --- a/influx/templates.go +++ b/influx/templates.go @@ -1,46 +1,15 @@ package influx -import "strings" +import ( + "strings" -// TemplateValue is a value use to replace a template in an InfluxQL query -type TemplateValue struct { - Value string `json:"value"` - Type string `json:"type"` -} - -// TemplateVar is a named variable within an InfluxQL query to be replaced with Values -type TemplateVar struct { - Var string `json:"tempVar"` - Values []TemplateValue `json:"values"` -} - -// String converts the template variable into a correct InfluxQL string based -// on its type -func (t TemplateVar) String() string { - if len(t.Values) == 0 { - return "" - } - switch t.Values[0].Type { - case "tagKey", "fieldKey": - return `"` + t.Values[0].Value + `"` - case "tagValue": - return `'` + t.Values[0].Value + `'` - case "csv": - return t.Values[0].Value - default: - return "" - } -} - -// TemplateVars are template variables to replace within an InfluxQL query -type TemplateVars struct { - Vars []TemplateVar `json:"tempVars"` -} + "github.com/influxdata/chronograf" +) // TemplateReplace replaces templates with values within the query string -func TemplateReplace(query string, templates TemplateVars) string { +func TemplateReplace(query string, templates []chronograf.TemplateVar) string { replacements := []string{} - for _, v := range templates.Vars { + for _, v := range templates { newVal := v.String() if newVal != "" { replacements = append(replacements, v.Var, newVal) diff --git a/influx/templates_test.go b/influx/templates_test.go index 794b8522ee..b66c8dc2f0 100644 --- a/influx/templates_test.go +++ b/influx/templates_test.go @@ -2,54 +2,54 @@ package influx import ( "testing" + + "github.com/influxdata/chronograf" ) func TestTemplateReplace(t *testing.T) { tests := []struct { name string query string - vars TemplateVars + vars []chronograf.TemplateVar want string }{ { name: "select with parameters", query: "$METHOD field1, $field FROM $measurement WHERE temperature > $temperature", - vars: TemplateVars{ - Vars: []TemplateVar{ - { - Var: "$temperature", - Values: []TemplateValue{ - { - Type: "csv", - Value: "10", - }, + vars: []chronograf.TemplateVar{ + { + Var: "$temperature", + Values: []chronograf.TemplateValue{ + { + Type: "csv", + Value: "10", }, }, - { - Var: "$field", - Values: []TemplateValue{ - { - Type: "fieldKey", - Value: "field2", - }, + }, + { + Var: "$field", + Values: []chronograf.TemplateValue{ + { + Type: "fieldKey", + Value: "field2", }, }, - { - Var: "$METHOD", - Values: []TemplateValue{ - { - Type: "csv", - Value: "SELECT", - }, + }, + { + Var: "$METHOD", + Values: []chronograf.TemplateValue{ + { + Type: "csv", + Value: "SELECT", }, }, - { - Var: "$measurement", - Values: []TemplateValue{ - { - Type: "csv", - Value: `"cpu"`, - }, + }, + { + Var: "$measurement", + Values: []chronograf.TemplateValue{ + { + Type: "csv", + Value: `"cpu"`, }, }, }, @@ -59,33 +59,31 @@ func TestTemplateReplace(t *testing.T) { { name: "select with parameters and aggregates", query: `SELECT mean($field) FROM "cpu" WHERE $tag = $value GROUP BY $tag`, - vars: TemplateVars{ - Vars: []TemplateVar{ - { - Var: "$value", - Values: []TemplateValue{ - { - Type: "tagValue", - Value: "howdy.com", - }, + vars: []chronograf.TemplateVar{ + { + Var: "$value", + Values: []chronograf.TemplateValue{ + { + Type: "tagValue", + Value: "howdy.com", }, }, - { - Var: "$tag", - Values: []TemplateValue{ - { - Type: "tagKey", - Value: "host", - }, + }, + { + Var: "$tag", + Values: []chronograf.TemplateValue{ + { + Type: "tagKey", + Value: "host", }, }, - { - Var: "$field", - Values: []TemplateValue{ - { - Type: "fieldKey", - Value: "field", - }, + }, + { + Var: "$field", + Values: []chronograf.TemplateValue{ + { + Type: "fieldKey", + Value: "field", }, }, }, @@ -100,11 +98,9 @@ func TestTemplateReplace(t *testing.T) { { name: "var without a value", query: `SELECT $field FROM "cpu"`, - vars: TemplateVars{ - Vars: []TemplateVar{ - { - Var: "$field", - }, + vars: []chronograf.TemplateVar{ + { + Var: "$field", }, }, want: `SELECT $field FROM "cpu"`, @@ -112,15 +108,13 @@ func TestTemplateReplace(t *testing.T) { { name: "var with unknown type", query: `SELECT $field FROM "cpu"`, - vars: TemplateVars{ - Vars: []TemplateVar{ - { - Var: "$field", - Values: []TemplateValue{ - { - Type: "who knows?", - Value: "field", - }, + vars: []chronograf.TemplateVar{ + { + Var: "$field", + Values: []chronograf.TemplateValue{ + { + Type: "who knows?", + Value: "field", }, }, }, From 673a0c07c03c9af6737a80fdd1f95a0efa2a0b8b Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Wed, 19 Apr 2017 12:09:22 -0500 Subject: [PATCH 036/163] Add swagger documentation for template variables --- server/swagger.json | 59 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/server/swagger.json b/server/swagger.json index 13047913b1..1380fb484b 100644 --- a/server/swagger.json +++ b/server/swagger.json @@ -3049,10 +3049,20 @@ "Proxy": { "type": "object", "example": { - "query": "select * from cpu where time > now() - 10m", + "query": "select $myfield from cpu where time > now() - 10m", "db": "telegraf", "rp": "autogen", - "format": "raw" + "tempVars": [ + { + "tempVar": "$myfield", + "values": [ + { + "type": "fieldKey", + "value": "usage_user" + } + ] + } + ] }, "required": [ "query" @@ -3067,12 +3077,49 @@ "rp": { "type": "string" }, - "format": { + "tempVars": { + "type": "array", + "description": "Template variables to replace within an InfluxQL query", + "items": { + "$ref": "#/definitions/TemplateVariable" + } + } + } + }, + "TemplateVariable": { + "type": "object", + "description": "Named variable within an InfluxQL query to be replaced with values", + "properties": { + "tempVar": { + "type": "string", + "description": "String to replace within an InfluxQL statement" + }, + "values": { + "type": "array", + "description": "Values used to replace tempVar.", + "items": { + "$ref": "#/definitions/TemplateValue" + } + } + } + }, + "TemplateValue": { + "type": "object", + "description": "Value use to replace a template in an InfluxQL query. The type governs the output format", + "properties": { + "value": { + "type": "string", + "description": "Specific value that will be encoded based on type" + }, + "type": { "type": "string", "enum": [ - "raw" + "csv", + "tagKey", + "tagValue", + "fieldKey" ], - "default": "raw" + "description": "The type will change the format of the output value. tagKey/fieldKey are double quoted; tagValue are single quoted; csv are not quoted." } } }, @@ -3980,4 +4027,4 @@ } } } -} +} \ No newline at end of file From ca6e541a3851ea3aa5885cd299c939c7a617f45f Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 19 Apr 2017 10:06:49 -0700 Subject: [PATCH 037/163] Create table classes Since a table can now be a bunch of
    's. This will allow us to include forms inside of tables without breaking HTML. http://stackoverflow.com/questions/4035966/create-a-html-table-where-each-tr-is-a-form --- .../components/TemplateVariableRow.js | 24 ++++++------ .../components/TemplateVariableTable.js | 28 ++++++------- ui/src/style/components/tables.scss | 39 +++++++++++++++++++ 3 files changed, 65 insertions(+), 26 deletions(-) diff --git a/ui/src/dashboards/components/TemplateVariableRow.js b/ui/src/dashboards/components/TemplateVariableRow.js index 395e1198be..36a5d291cd 100644 --- a/ui/src/dashboards/components/TemplateVariableRow.js +++ b/ui/src/dashboards/components/TemplateVariableRow.js @@ -16,18 +16,18 @@ const TemplateVariableRow = ({ selectedTagKey, onSelectTagKey, }) => ( - - {label} - {code} - +
    +
    {label}
    +
    {code}
    +
    - - +
    +
    - - +
    +
    {values.join(', ')} - - +
    +
    - - +
    +
    ) class RowWrapper extends Component { diff --git a/ui/src/dashboards/components/TemplateVariableTable.js b/ui/src/dashboards/components/TemplateVariableTable.js index 469e549271..d79a3d63fa 100644 --- a/ui/src/dashboards/components/TemplateVariableTable.js +++ b/ui/src/dashboards/components/TemplateVariableTable.js @@ -3,21 +3,21 @@ import React, {PropTypes} from 'react' import TemplateVariableRow from 'src/dashboards/components/TemplateVariableRow' const TemplateVariableTable = ({templates}) => ( - - - - - - - - - - - +
    +
    +
    +
    Dropdown Label
    +
    Shortcode
    +
    Type
    +
    Queries
    +
    Values
    +
    +
    +
    +
    {templates.map(t => )} -
    -
    Dropdown LabelShortcodeTypeQueriesValues -
    +
    +
    ) const {arrayOf, shape, string} = PropTypes diff --git a/ui/src/style/components/tables.scss b/ui/src/style/components/tables.scss index 2d99e41abe..538025bd3f 100644 --- a/ui/src/style/components/tables.scss +++ b/ui/src/style/components/tables.scss @@ -3,6 +3,45 @@ ---------------------------------------------- */ +// table-custom class allows us to make a table from divs so we can use +// forms inside a table +.table-custom { + display: table !important; + border-collapse: separate; + border-spacing: 2px; + width: 100%; + padding: 10px; + .thead { + display: table-header-group; + color: white; + color: $g17-whisper !important; + border-width: 1px; + } + .th { + display: table-cell; + font-weight: 700; + color: $g14-chromium !important; + border: 0 !important; + padding: 6px 8px !important; + } + .tbody { + display: table-row-group; + } + .tr { + display: table-row; + } + .td { + display: table-cell; + font-weight: 500; + color: $g14-chromium !important; + border: 0 !important; + padding: 6px 8px !important; + } + .tr.editing .td INPUT { + background-color: $g5-pepper; + color: $g19-ghost !important; + } +} table { thead th { From 0902f23f96b5a99c8aa460d98d0c8dd8ef8d7507 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Wed, 19 Apr 2017 12:10:18 -0500 Subject: [PATCH 038/163] Add template variable substitution to influx queries --- influx/influx.go | 30 +++++++++++------------------- influx/influx_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/influx/influx.go b/influx/influx.go index f01234af5b..c39eb844a0 100644 --- a/influx/influx.go +++ b/influx/influx.go @@ -68,17 +68,20 @@ func (c *Client) query(u *url.URL, q chronograf.Query) (chronograf.Response, err return nil, err } req.Header.Set("Content-Type", "application/json") - - c.Logger. + command := q.Command + if len(q.TemplateVars) > 0 { + command = TemplateReplace(q.Command, q.TemplateVars) + } + logs := c.Logger. WithField("component", "proxy"). WithField("host", req.Host). - WithField("command", q.Command). + WithField("command", command). WithField("db", q.DB). - WithField("rp", q.RP). - Debug("query") + WithField("rp", q.RP) + logs.Debug("query") params := req.URL.Query() - params.Set("q", q.Command) + params.Set("q", command) params.Set("db", q.DB) params.Set("rp", q.RP) params.Set("epoch", "ms") @@ -111,13 +114,7 @@ func (c *Client) query(u *url.URL, q chronograf.Query) (chronograf.Response, err // If we got a valid decode error, send that back if decErr != nil { - c.Logger. - WithField("component", "proxy"). - WithField("host", req.Host). - WithField("command", q.Command). - WithField("db", q.DB). - WithField("rp", q.RP). - WithField("influx_status", resp.StatusCode). + logs.WithField("influx_status", resp.StatusCode). Error("Error parsing results from influxdb: err:", decErr) return nil, decErr } @@ -125,12 +122,7 @@ func (c *Client) query(u *url.URL, q chronograf.Query) (chronograf.Response, err // If we don't have an error in our json response, and didn't get statusOK // then send back an error if resp.StatusCode != http.StatusOK && response.Err != "" { - c.Logger. - WithField("component", "proxy"). - WithField("host", req.Host). - WithField("command", q.Command). - WithField("db", q.DB). - WithField("rp", q.RP). + logs. WithField("influx_status", resp.StatusCode). Error("Received non-200 response from influxdb") diff --git a/influx/influx_test.go b/influx/influx_test.go index 6fa4a859fa..535fd97447 100644 --- a/influx/influx_test.go +++ b/influx/influx_test.go @@ -82,6 +82,7 @@ func Test_Influx_HTTPS_Failure(t *testing.T) { func Test_Influx_HTTPS_InsecureSkipVerify(t *testing.T) { t.Parallel() called := false + q := "" ts := httptest.NewTLSServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { rw.WriteHeader(http.StatusOK) rw.Write([]byte(`{}`)) @@ -89,6 +90,8 @@ func Test_Influx_HTTPS_InsecureSkipVerify(t *testing.T) { if path := r.URL.Path; path != "/query" { t.Error("Expected the path to contain `/query` but was", path) } + values := r.URL.Query() + q = values.Get("q") })) defer ts.Close() @@ -118,6 +121,34 @@ func Test_Influx_HTTPS_InsecureSkipVerify(t *testing.T) { if called == false { t.Error("Expected http request to Influx but there was none") } + called = false + q = "" + query = chronograf.Query{ + Command: "select $field from cpu", + TemplateVars: []chronograf.TemplateVar{ + { + Var: "$field", + Values: []chronograf.TemplateValue{ + { + Value: "usage_user", + Type: "fieldKey", + }, + }, + }, + }, + } + _, err = series.Query(ctx, query) + if err != nil { + t.Fatal("Expected no error but was", err) + } + + if called == false { + t.Error("Expected http request to Influx but there was none") + } + + if q != `select "usage_user" from cpu` { + t.Errorf("Unexpected query: %s", q) + } } func Test_Influx_CancelsInFlightRequests(t *testing.T) { From 4a0ee1a0204d77d85d0b66f3431f6b0c06365d32 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 19 Apr 2017 13:08:03 -0700 Subject: [PATCH 039/163] Introduce table inputs to the tempVar builder --- .../components/TemplateVariableRow.js | 78 +++++++++++++++++-- ui/src/style/components/tables.scss | 2 +- 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/ui/src/dashboards/components/TemplateVariableRow.js b/ui/src/dashboards/components/TemplateVariableRow.js index 36a5d291cd..10f1cd5c40 100644 --- a/ui/src/dashboards/components/TemplateVariableRow.js +++ b/ui/src/dashboards/components/TemplateVariableRow.js @@ -1,4 +1,5 @@ import React, {PropTypes, Component} from 'react' +import OnClickOutside from 'react-onclickoutside' import Dropdown from 'shared/components/Dropdown' import TemplateQueryBuilder from 'src/dashboards/components/TemplateQueryBuilder' @@ -7,6 +8,7 @@ import {TEMPLATE_VARIBALE_TYPES} from 'src/dashboards/constants' const TemplateVariableRow = ({ template: {label, code, values}, + isEditing, selectedType, selectedDatabase, selectedMeasurement, @@ -15,10 +17,24 @@ const TemplateVariableRow = ({ onSelectMeasurement, selectedTagKey, onSelectTagKey, + onStartEdit, + autoFocusTarget, }) => ( -
    -
    {label}
    -
    {code}
    +
    + +
    -
    + ) +const TableInput = ({ + name, + defaultValue, + isEditing, + onStartEdit, + autoFocusTarget, +}) => { + return isEditing + ?
    + +
    + :
    onStartEdit(name)}>{defaultValue}
    +} + class RowWrapper extends Component { constructor(props) { super(props) const {template: {query, type}} = this.props this.state = { + isEditing: false, selectedType: type, selectedDatabase: query && query.db, selectedMeasurement: query && query.measurement, selectedTagKey: query && query.tagKey, + autoFocusTarget: null, } this.handleSelectType = ::this.handleSelectType this.handleSelectDatabase = ::this.handleSelectDatabase this.handleSelectMeasurement = ::this.handleSelectMeasurement this.handleSelectTagKey = ::this.handleSelectTagKey + this.handleStartEdit = ::this.handleStartEdit + this.handleEndEdit = ::this.handleEndEdit + } + + handleClickOutside() { + this.setState({isEditing: false}) + } + + handleEndEdit() { + this.setState({isEditing: false}) + } + + handleStartEdit(name) { + this.setState({isEditing: true, autoFocusTarget: name}) } handleSelectType(item) { @@ -83,14 +135,18 @@ class RowWrapper extends Component { render() { const { + isEditing, selectedType, selectedDatabase, selectedMeasurement, selectedTagKey, + autoFocusTarget, } = this.state + return ( ) } } -const {arrayOf, func, shape, string} = PropTypes +const {arrayOf, bool, func, shape, string} = PropTypes RowWrapper.propTypes = { template: shape({ @@ -131,4 +189,12 @@ TemplateVariableRow.propTypes = { onSelectTagKey: func.isRequired, } -export default RowWrapper +TableInput.propTypes = { + defaultValue: string, + isEditing: bool.isRequired, + onStartEdit: func.isRequired, + name: string.isRequired, + autoFocusTarget: string, +} + +export default OnClickOutside(RowWrapper) diff --git a/ui/src/style/components/tables.scss b/ui/src/style/components/tables.scss index 538025bd3f..196e276299 100644 --- a/ui/src/style/components/tables.scss +++ b/ui/src/style/components/tables.scss @@ -37,7 +37,7 @@ border: 0 !important; padding: 6px 8px !important; } - .tr.editing .td INPUT { + .tr .td .input { background-color: $g5-pepper; color: $g19-ghost !important; } From c617de0afd066ad13c4b5bd49fa35260f83feb83 Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Wed, 19 Apr 2017 14:30:12 -0700 Subject: [PATCH 040/163] Rename 'params' to 'tempVars' --- ui/src/shared/components/AutoRefresh.js | 6 +++--- ui/src/shared/components/LayoutRenderer.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ui/src/shared/components/AutoRefresh.js b/ui/src/shared/components/AutoRefresh.js index ca3c9c1dc3..e903a83874 100644 --- a/ui/src/shared/components/AutoRefresh.js +++ b/ui/src/shared/components/AutoRefresh.js @@ -17,7 +17,7 @@ const AutoRefresh = (ComposedComponent) => { propTypes: { children: element, autoRefresh: number.isRequired, - params: arrayOf(shape({ + tempVars: arrayOf(shape({ code: string.isRequired, values: arrayOf(string), })), @@ -66,7 +66,7 @@ const AutoRefresh = (ComposedComponent) => { }, async executeQueries(queries) { - const {params, editQueryStatus} = this.props + const {tempVars, editQueryStatus} = this.props if (!queries.length) { this.setState({timeSeries: []}) @@ -77,7 +77,7 @@ const AutoRefresh = (ComposedComponent) => { const timeSeriesPromises = queries.map((query) => { const {host, database, rp} = query - return fetchTimeSeriesAsync({source: host, db: database, rp, query, params}, editQueryStatus) + return fetchTimeSeriesAsync({source: host, db: database, rp, query, tempVars}, editQueryStatus) }) Promise.all(timeSeriesPromises).then(timeSeries => { diff --git a/ui/src/shared/components/LayoutRenderer.js b/ui/src/shared/components/LayoutRenderer.js index 3ef2705afe..8265e46de9 100644 --- a/ui/src/shared/components/LayoutRenderer.js +++ b/ui/src/shared/components/LayoutRenderer.js @@ -100,7 +100,7 @@ export const LayoutRenderer = React.createClass({ return ( ) @@ -114,7 +114,7 @@ export const LayoutRenderer = React.createClass({ return ( Date: Wed, 19 Apr 2017 14:46:26 -0700 Subject: [PATCH 041/163] Add editTemplate test and action / update tests --- ui/spec/dashboards/reducers/uiSpec.js | 43 +++++++++++++++-- ui/src/dashboards/actions/index.js | 9 ++++ ui/src/dashboards/reducers/ui.js | 68 ++++++++++++++++++++------- 3 files changed, 97 insertions(+), 23 deletions(-) diff --git a/ui/spec/dashboards/reducers/uiSpec.js b/ui/spec/dashboards/reducers/uiSpec.js index 2a10a3bfc3..edf941775d 100644 --- a/ui/spec/dashboards/reducers/uiSpec.js +++ b/ui/spec/dashboards/reducers/uiSpec.js @@ -10,11 +10,12 @@ import { editDashboardCell, renameDashboardCell, syncDashboardCell, + editTemplate, } from 'src/dashboards/actions' let state -const d1 = {id: 1, cells: [], name: "d1"} -const d2 = {id: 2, cells: [], name: "d2"} +const d1 = {id: 1, cells: [], name: 'd1', templates: []} +const d2 = {id: 2, cells: [], name: 'd2', templates: []} const dashboards = [d1, d2] const c1 = { x: 0, @@ -23,9 +24,20 @@ const c1 = { h: 4, id: 1, isEditing: false, - name: "Gigawatts", + name: 'Gigawatts', } const cells = [c1] +const tempVar = { + id: 1, + type: 'measurement', + label: 'test query', + code: '$HOSTS', + query: { + db: 'db1', + text: 'SHOW TAGS WHERE HUNTER = "coo"', + }, + values: ['h1', 'h2', 'h3'], +} describe('DataExplorer.Reducers.UI', () => { it('can load the dashboards', () => { @@ -106,7 +118,28 @@ describe('DataExplorer.Reducers.UI', () => { dashboards: [dash], } - const actual = reducer(state, renameDashboardCell(dash, 0, 0, "Plutonium Consumption Rate (ug/sec)")) - expect(actual.dashboards[0].cells[0].name).to.equal("Plutonium Consumption Rate (ug/sec)") + const actual = reducer( + state, + renameDashboardCell(dash, 0, 0, 'Plutonium Consumption Rate (ug/sec)') + ) + expect(actual.dashboards[0].cells[0].name).to.equal( + 'Plutonium Consumption Rate (ug/sec)' + ) + }) + + it('can edit a template', () => { + const dash = {...d1, templates: [tempVar]} + state = { + dashboards: [dash], + } + const updates = { + code: '$NEWCODE', + label: 'new label', + type: 'tagKey', + } + const expected = {...tempVar, ...updates} + + const actual = reducer(state, editTemplate(dash.id, tempVar.id, updates)) + expect(actual.dashboards[0].templates[0]).to.deep.equal(expected) }) }) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index f7a83da3b0..815f8a2f2b 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -111,6 +111,15 @@ export const editCellQueryStatus = (queryID, status) => ({ }, }) +export const editTemplate = (dashboardID, templateID, updates) => ({ + type: 'EDIT_TEMPLATE', + payload: { + dashboardID, + templateID, + updates, + }, +}) + // Stub Template Variables Data const templates = [ diff --git a/ui/src/dashboards/reducers/ui.js b/ui/src/dashboards/reducers/ui.js index 48a96dba80..c36385837a 100644 --- a/ui/src/dashboards/reducers/ui.js +++ b/ui/src/dashboards/reducers/ui.js @@ -31,7 +31,9 @@ export default function ui(state = initialState, action) { const {dashboard} = action.payload const newState = { dashboard, - dashboards: state.dashboards.map((d) => d.id === dashboard.id ? dashboard : d), + dashboards: state.dashboards.map( + d => (d.id === dashboard.id ? dashboard : d) + ), } return {...state, ...newState} @@ -40,7 +42,7 @@ export default function ui(state = initialState, action) { case 'DELETE_DASHBOARD': { const {dashboard} = action.payload const newState = { - dashboards: state.dashboards.filter((d) => d.id !== dashboard.id), + dashboards: state.dashboards.filter(d => d.id !== dashboard.id), } return {...state, ...newState} @@ -49,10 +51,7 @@ export default function ui(state = initialState, action) { case 'DELETE_DASHBOARD_FAILED': { const {dashboard} = action.payload const newState = { - dashboards: [ - _.cloneDeep(dashboard), - ...state.dashboards, - ], + dashboards: [_.cloneDeep(dashboard), ...state.dashboards], } return {...state, ...newState} } @@ -66,7 +65,9 @@ export default function ui(state = initialState, action) { } const newState = { - dashboards: state.dashboards.map((d) => d.id === dashboard.id ? newDashboard : d), + dashboards: state.dashboards.map( + d => (d.id === dashboard.id ? newDashboard : d) + ), } return {...state, ...newState} @@ -78,7 +79,9 @@ export default function ui(state = initialState, action) { const newCells = [cell, ...dashboard.cells] const newDashboard = {...dashboard, cells: newCells} - const newDashboards = dashboards.map((d) => d.id === dashboard.id ? newDashboard : d) + const newDashboards = dashboards.map( + d => (d.id === dashboard.id ? newDashboard : d) + ) const newState = {dashboards: newDashboards} return {...state, ...newState} @@ -87,7 +90,7 @@ export default function ui(state = initialState, action) { case 'EDIT_DASHBOARD_CELL': { const {x, y, isEditing, dashboard} = action.payload - const cell = dashboard.cells.find((c) => c.x === x && c.y === y) + const cell = dashboard.cells.find(c => c.x === x && c.y === y) const newCell = { ...cell, @@ -96,11 +99,13 @@ export default function ui(state = initialState, action) { const newDashboard = { ...dashboard, - cells: dashboard.cells.map((c) => c.x === x && c.y === y ? newCell : c), + cells: dashboard.cells.map(c => (c.x === x && c.y === y ? newCell : c)), } const newState = { - dashboards: state.dashboards.map((d) => d.id === dashboard.id ? newDashboard : d), + dashboards: state.dashboards.map( + d => (d.id === dashboard.id ? newDashboard : d) + ), } return {...state, ...newState} @@ -110,13 +115,17 @@ export default function ui(state = initialState, action) { const {cell} = action.payload const {dashboard} = state - const newCells = dashboard.cells.filter((c) => !(c.x === cell.x && c.y === cell.y)) + const newCells = dashboard.cells.filter( + c => !(c.x === cell.x && c.y === cell.y) + ) const newDashboard = { ...dashboard, cells: newCells, } const newState = { - dashboards: state.dashboards.map((d) => d.id === dashboard.id ? newDashboard : d), + dashboards: state.dashboards.map( + d => (d.id === dashboard.id ? newDashboard : d) + ), } return {...state, ...newState} @@ -127,11 +136,15 @@ export default function ui(state = initialState, action) { const newDashboard = { ...dashboard, - cells: dashboard.cells.map((c) => c.x === cell.x && c.y === cell.y ? cell : c), + cells: dashboard.cells.map( + c => (c.x === cell.x && c.y === cell.y ? cell : c) + ), } const newState = { - dashboards: state.dashboards.map((d) => d.id === dashboard.id ? newDashboard : d), + dashboards: state.dashboards.map( + d => (d.id === dashboard.id ? newDashboard : d) + ), } return {...state, ...newState} @@ -140,7 +153,7 @@ export default function ui(state = initialState, action) { case 'RENAME_DASHBOARD_CELL': { const {x, y, name, dashboard} = action.payload - const cell = dashboard.cells.find((c) => c.x === x && c.y === y) + const cell = dashboard.cells.find(c => c.x === x && c.y === y) const newCell = { ...cell, @@ -149,11 +162,13 @@ export default function ui(state = initialState, action) { const newDashboard = { ...dashboard, - cells: dashboard.cells.map((c) => c.x === x && c.y === y ? newCell : c), + cells: dashboard.cells.map(c => (c.x === x && c.y === y ? newCell : c)), } const newState = { - dashboards: state.dashboards.map((d) => d.id === dashboard.id ? newDashboard : d), + dashboards: state.dashboards.map( + d => (d.id === dashboard.id ? newDashboard : d) + ), } return {...state, ...newState} @@ -164,6 +179,23 @@ export default function ui(state = initialState, action) { return {...state, cellQueryStatus: {queryID, status}} } + + case 'EDIT_TEMPLATE': { + const {dashboardID, templateID, updates} = action.payload + + const dashboards = state.dashboards.map( + d => + (d.id === dashboardID + ? { + ...d, + templates: d.templates.map( + t => (t.id === templateID ? {...t, ...updates} : t) + ), + } + : d) + ) + return {...state, dashboards} + } } return state From 784a36a75ca2d6cd1d923a14912c64f361a15a69 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 19 Apr 2017 14:47:07 -0700 Subject: [PATCH 042/163] Add onSubmit to template variable form --- .../components/TemplateVariableRow.js | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/ui/src/dashboards/components/TemplateVariableRow.js b/ui/src/dashboards/components/TemplateVariableRow.js index 10f1cd5c40..8a58bd0f2c 100644 --- a/ui/src/dashboards/components/TemplateVariableRow.js +++ b/ui/src/dashboards/components/TemplateVariableRow.js @@ -19,8 +19,9 @@ const TemplateVariableRow = ({ onSelectTagKey, onStartEdit, autoFocusTarget, + onSubmit, }) => ( -
    + {values.join(', ')}
    -
    - +
    + {isEditing + ?
    + + +
    + : }
    ) @@ -73,6 +85,7 @@ const TableInput = ({ return isEditing ?
    ) } From 60308938fcbe45f92825575aa90539cb02a1af37 Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Wed, 19 Apr 2017 15:10:07 -0700 Subject: [PATCH 043/163] Update tempVars with current schema and successfully send to proxy --- ui/src/dashboards/actions/index.js | 38 +++++++++---------- ui/src/dashboards/components/Dashboard.js | 17 ++++++--- ui/src/dashboards/containers/DashboardPage.js | 21 ++++++---- ui/src/dashboards/reducers/ui.js | 8 ++-- ui/src/shared/actions/timeSeries.js | 4 +- ui/src/shared/components/AutoRefresh.js | 17 ++++++++- ui/src/shared/components/LayoutRenderer.js | 16 +++++--- ui/src/utils/queryUrlGenerator.js | 4 +- 8 files changed, 77 insertions(+), 48 deletions(-) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index 00d4276c9a..ce856d7031 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -115,45 +115,45 @@ export const editCellQueryStatus = (queryID, status) => ({ }, }) -export const tvSelected = (dashboardID, tvID, valueText) => ({ +export const tvSelected = (dashboardID, tvID, values) => ({ type: TEMPLATE_VARIABLE_SELECTED, payload: { dashboardID, tvID, - valueText, + values, }, }) // Stub Template Variables Data -const templates = [ +const tempVars = [ { id: 1, type: 'query', label: 'test query', - code: '$REGION', + tempVar: '$REGION', query: { db: 'db1.rp1', text: 'SHOW TAGS WHERE HUNTER = "coo"', }, - values: ['us-west', 'us-east', 'us-mount'], - selected: 'us-east', + values: [ + {value: 'us-west', type: 'tagKey'}, + {value: 'us-east', type: 'tagKey'}, + {value: 'us-mount', type: 'tagKey'}, + ], + selectedValues: [{value: 'us-east', type: 'tagKey'}], }, { id: 2, type: 'csv', label: 'test csv', - code: '$TEMPERATURE', - values: ['98.7', '99.1', '101.3'], - selected: '99.1', - }, - { - id: 3, - type: 'csv', - label: 'test csv', - code: '$NULL', - values: null, - selected: null, + tempVar: '$TEMPERATURE', + values: [ + {value: '98.7', type: 'measurement'}, + {value: '99.1', type: 'measurement'}, + {value: '101.3', type: 'measurement'}, + ], + selectedValues: [{value: '99.1', type: 'measurement'}], }, ] @@ -162,7 +162,7 @@ const templates = [ export const getDashboardsAsync = dashboardID => async dispatch => { try { const {data: {dashboards}} = await getDashboardsAJAX() - const stubbedDashboards = dashboards.map(d => ({...d, templates})) + const stubbedDashboards = dashboards.map(d => ({...d, tempVars})) dispatch(loadDashboards(stubbedDashboards, dashboardID)) } catch (error) { console.error(error) @@ -172,7 +172,7 @@ export const getDashboardsAsync = dashboardID => async dispatch => { export const putDashboard = dashboard => dispatch => { updateDashboardAJAX(dashboard).then(({data}) => { - dispatch(updateDashboard({...data, templates})) + dispatch(updateDashboard({...data, tempVars})) }) } diff --git a/ui/src/dashboards/components/Dashboard.js b/ui/src/dashboards/components/Dashboard.js index bf611bc94b..8b4454bb2c 100644 --- a/ui/src/dashboards/components/Dashboard.js +++ b/ui/src/dashboards/components/Dashboard.js @@ -24,7 +24,7 @@ const Dashboard = ({ return null } - const {templates} = dashboard + const {tempVars} = dashboard const cells = dashboard.cells.map((cell) => { const dashboardCell = {...cell} @@ -49,14 +49,17 @@ const Dashboard = ({
    { - templates.map(({id, values, selected}) => { - const items = values ? values.map(value => ({text: value})) : [] + tempVars.map(({id, values, selectedValues}) => { + const items = values ? values.map((value) => Object.assign(value, {text: value.value})) : [] + // TODO: change Dropdown to a MultiSelectDropdown, [0].value to + // the full array, and [item] to all selectedValues when we update + // this component to support multiple values return ( onSelectTV(id, item.text)} + selected={selectedValues[0].value || "Loading..."} + onChoose={(item) => onSelectTV(id, [item])} /> ) }) @@ -67,7 +70,7 @@ const Dashboard = ({ {cells.length ? : null} @@ -278,17 +278,24 @@ DashboardPage.propTypes = { shape({ id: number.isRequired, cells: arrayOf(shape({})).isRequired, - templates: arrayOf( + tempVars: arrayOf( shape({ type: string.isRequired, label: string.isRequired, - code: string.isRequired, + tempVar: string.isRequired, query: shape({ db: string.isRequired, text: string.isRequired, }), - values: arrayOf(string.isRequired), - }) + values: arrayOf(shape({ + type: string.isRequired, + value: string.isRequired, + })).isRequired, + selectedValues: arrayOf(shape({ + type: string.isRequired, + value: string.isRequired, + })).isRequired, + }), ), }) ), diff --git a/ui/src/dashboards/reducers/ui.js b/ui/src/dashboards/reducers/ui.js index 935961d7f7..c2bc24d0db 100644 --- a/ui/src/dashboards/reducers/ui.js +++ b/ui/src/dashboards/reducers/ui.js @@ -170,16 +170,16 @@ export default function ui(state = initialState, action) { } case TEMPLATE_VARIABLE_SELECTED: { - const {dashboardID, tvID, valueText} = action.payload + const {dashboardID, tvID, values} = action.payload const newDashboards = state.dashboards.map((dashboard) => { if (dashboard.id === dashboardID) { - const newTVs = dashboard.templates.map((tV) => { + const newTVs = dashboard.tempVars.map((tV) => { if (tV.id === tvID) { - return {...tV, selected: valueText} + return {...tV, selectedValues: values} } return tV }) - return {...dashboard, templates: newTVs} + return {...dashboard, tempVars: newTVs} } return dashboard }) diff --git a/ui/src/shared/actions/timeSeries.js b/ui/src/shared/actions/timeSeries.js index d5078a1662..d49fb9b51b 100644 --- a/ui/src/shared/actions/timeSeries.js +++ b/ui/src/shared/actions/timeSeries.js @@ -35,10 +35,10 @@ export const handleError = (error, query, editQueryStatus) => { console.error(error) } -export const fetchTimeSeriesAsync = async ({source, db, rp, query, params}, editQueryStatus = noop) => { +export const fetchTimeSeriesAsync = async ({source, db, rp, query, tempVars}, editQueryStatus = noop) => { handleLoading(query, editQueryStatus) try { - const {data} = await proxy({source, db, rp, query: query.text, params}) + const {data} = await proxy({source, db, rp, query: query.text, tempVars}) return handleSuccess(data, query, editQueryStatus) } catch (error) { handleError(error, query, editQueryStatus) diff --git a/ui/src/shared/components/AutoRefresh.js b/ui/src/shared/components/AutoRefresh.js index e903a83874..06a2f19bc8 100644 --- a/ui/src/shared/components/AutoRefresh.js +++ b/ui/src/shared/components/AutoRefresh.js @@ -18,8 +18,21 @@ const AutoRefresh = (ComposedComponent) => { children: element, autoRefresh: number.isRequired, tempVars: arrayOf(shape({ - code: string.isRequired, - values: arrayOf(string), + type: string.isRequired, + label: string.isRequired, + tempVar: string.isRequired, + query: shape({ + db: string.isRequired, + text: string.isRequired, + }), + values: arrayOf(shape({ + type: string.isRequired, + value: string.isRequired, + })).isRequired, + selectedValues: arrayOf(shape({ + type: string.isRequired, + value: string.isRequired, + })).isRequired, })), queries: arrayOf(shape({ host: oneOfType([string, arrayOf(string)]), diff --git a/ui/src/shared/components/LayoutRenderer.js b/ui/src/shared/components/LayoutRenderer.js index 8265e46de9..f8f37f4b72 100644 --- a/ui/src/shared/components/LayoutRenderer.js +++ b/ui/src/shared/components/LayoutRenderer.js @@ -46,9 +46,13 @@ export const LayoutRenderer = React.createClass({ type: string.isRequired, }).isRequired ), - templates: arrayOf(shape({ - code: string.isRequired, - selected: string, + tempVars: arrayOf(shape({ + id: number.isRequired, + type: string.isRequired, + label: string.isRequired, + tempVar: string.isRequired, + values: arrayOf(shape()).isRequired, + selectedValues: arrayOf(shape()).isRequired, })).isRequired, host: string, source: string, @@ -94,13 +98,13 @@ export const LayoutRenderer = React.createClass({ }, renderRefreshingGraph(type, queries) { - const {autoRefresh, templates} = this.props + const {autoRefresh, tempVars} = this.props if (type === 'single-stat') { return ( ) @@ -114,7 +118,7 @@ export const LayoutRenderer = React.createClass({ return ( { +export const proxy = async ({source, query, db, rp, tempVars}) => { try { return await AJAX({ method: 'POST', url: source, data: { - params, + tempVars, query, db, rp, From 04cc465d282fcc024567317f3577fcbd7493203e Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Wed, 19 Apr 2017 16:19:49 -0700 Subject: [PATCH 044/163] Fix timeSeries data shape to restore full expected functionality of AutoRefreshingLineGraphs on Dashboard --- ui/src/shared/components/AutoRefresh.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/src/shared/components/AutoRefresh.js b/ui/src/shared/components/AutoRefresh.js index 06a2f19bc8..50e9a0da3f 100644 --- a/ui/src/shared/components/AutoRefresh.js +++ b/ui/src/shared/components/AutoRefresh.js @@ -94,11 +94,11 @@ const AutoRefresh = (ComposedComponent) => { }) Promise.all(timeSeriesPromises).then(timeSeries => { - const lastSeries = timeSeries[timeSeries.length - 1] - const lastQuerySuccessful = !this._noResultsForQuery(lastSeries) + const newSeries = timeSeries.map((response) => ({response})) + const lastQuerySuccessful = !this._noResultsForQuery(newSeries) this.setState({ - timeSeries, + timeSeries: newSeries, lastQuerySuccessful, isFetching: false, }) @@ -163,7 +163,7 @@ const AutoRefresh = (ComposedComponent) => { } return data.every((datum) => { - return datum.results.every((result) => { + return datum.response.results.every((result) => { return Object.keys(result).length === 0 }) }) From e1daefa6d7b038f025a8f5fe8c50bd3dcf57493c Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Wed, 19 Apr 2017 16:25:04 -0700 Subject: [PATCH 045/163] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e85736c4f3..9206d6bb25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features 1. [#1292](https://github.com/influxdata/chronograf/pull/1292): Introduce Template Variable Manager 1. [#1311](https://github.com/influxdata/chronograf/pull/1311): Display currently selected values in TVControlBar + 1. [#1315](https://github.com/influxdata/chronograf/pull/1315): Send selected TV values to proxy ### UI Improvements 1. [#1259](https://github.com/influxdata/chronograf/pull/1259): Add default display for empty dashboard From c141709451195a64ab6443d69e4d351e23a7412e Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 20 Apr 2017 09:24:57 -0500 Subject: [PATCH 046/163] Move dashboard cells into cells.go --- server/cells.go | 228 +++++++++++++++++++++++++++++++++++++++++++ server/dashboards.go | 219 ----------------------------------------- 2 files changed, 228 insertions(+), 219 deletions(-) create mode 100644 server/cells.go diff --git a/server/cells.go b/server/cells.go new file mode 100644 index 0000000000..99ce5c07cf --- /dev/null +++ b/server/cells.go @@ -0,0 +1,228 @@ +package server + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/bouk/httprouter" + "github.com/influxdata/chronograf" + "github.com/influxdata/chronograf/uuid" +) + +// ValidDashboardCellRequest verifies that the dashboard cells have a query +func ValidDashboardCellRequest(c *chronograf.DashboardCell) error { + CorrectWidthHeight(c) + return nil +} + +// CorrectWidthHeight changes the cell to have at least the +// minimum width and height +func CorrectWidthHeight(c *chronograf.DashboardCell) { + if c.W < 1 { + c.W = DefaultWidth + } + if c.H < 1 { + c.H = DefaultHeight + } +} + +// AddQueryConfig updates a cell by converting InfluxQL into queryconfigs +// If influxql cannot be represented by a full query config, then, the +// query config's raw text is set to the command. +func AddQueryConfig(c *chronograf.DashboardCell) { + for i, q := range c.Queries { + qc := ToQueryConfig(q.Command) + q.QueryConfig = qc + c.Queries[i] = q + } +} + +// DashboardCells returns all cells from a dashboard within the store +func (s *Service) DashboardCells(w http.ResponseWriter, r *http.Request) { + id, err := paramID("id", r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) + return + } + + ctx := r.Context() + e, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id)) + if err != nil { + notFound(w, id, s.Logger) + return + } + + boards := newDashboardResponse(e) + cells := boards.Cells + encodeJSON(w, http.StatusOK, cells, s.Logger) +} + +// NewDashboardCell adds a cell to an existing dashboard +func (s *Service) NewDashboardCell(w http.ResponseWriter, r *http.Request) { + id, err := paramID("id", r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) + return + } + + ctx := r.Context() + dash, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id)) + if err != nil { + notFound(w, id, s.Logger) + return + } + var cell chronograf.DashboardCell + if err := json.NewDecoder(r.Body).Decode(&cell); err != nil { + invalidJSON(w, s.Logger) + return + } + + if err := ValidDashboardCellRequest(&cell); err != nil { + invalidData(w, err, s.Logger) + return + } + + ids := uuid.V4{} + cid, err := ids.Generate() + if err != nil { + msg := fmt.Sprintf("Error creating cell ID of dashboard %d: %v", id, err) + Error(w, http.StatusInternalServerError, msg, s.Logger) + return + } + cell.ID = cid + + dash.Cells = append(dash.Cells, cell) + if err := s.DashboardsStore.Update(ctx, dash); err != nil { + msg := fmt.Sprintf("Error adding cell %s to dashboard %d: %v", cid, id, err) + Error(w, http.StatusInternalServerError, msg, s.Logger) + return + } + + boards := newDashboardResponse(dash) + for _, cell := range boards.Cells { + if cell.ID == cid { + encodeJSON(w, http.StatusOK, cell, s.Logger) + return + } + } +} + +// DashboardCellID adds a cell to an existing dashboard +func (s *Service) DashboardCellID(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + id, err := paramID("id", r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) + return + } + + dash, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id)) + if err != nil { + notFound(w, id, s.Logger) + return + } + + boards := newDashboardResponse(dash) + cid := httprouter.GetParamFromContext(ctx, "cid") + for _, cell := range boards.Cells { + if cell.ID == cid { + encodeJSON(w, http.StatusOK, cell, s.Logger) + return + } + } + notFound(w, id, s.Logger) +} + +// RemoveDashboardCell adds a cell to an existing dashboard +func (s *Service) RemoveDashboardCell(w http.ResponseWriter, r *http.Request) { + id, err := paramID("id", r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) + return + } + + ctx := r.Context() + dash, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id)) + if err != nil { + notFound(w, id, s.Logger) + return + } + + cid := httprouter.GetParamFromContext(ctx, "cid") + cellid := -1 + for i, cell := range dash.Cells { + if cell.ID == cid { + cellid = i + break + } + } + if cellid == -1 { + notFound(w, id, s.Logger) + return + } + + dash.Cells = append(dash.Cells[:cellid], dash.Cells[cellid+1:]...) + if err := s.DashboardsStore.Update(ctx, dash); err != nil { + msg := fmt.Sprintf("Error removing cell %s from dashboard %d: %v", cid, id, err) + Error(w, http.StatusInternalServerError, msg, s.Logger) + return + } + w.WriteHeader(http.StatusNoContent) +} + +// ReplaceDashboardCell adds a cell to an existing dashboard +func (s *Service) ReplaceDashboardCell(w http.ResponseWriter, r *http.Request) { + id, err := paramID("id", r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) + return + } + + ctx := r.Context() + dash, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id)) + if err != nil { + notFound(w, id, s.Logger) + return + } + + cid := httprouter.GetParamFromContext(ctx, "cid") + cellid := -1 + for i, cell := range dash.Cells { + if cell.ID == cid { + cellid = i + break + } + } + if cellid == -1 { + notFound(w, id, s.Logger) + return + } + + var cell chronograf.DashboardCell + if err := json.NewDecoder(r.Body).Decode(&cell); err != nil { + invalidJSON(w, s.Logger) + return + } + + if err := ValidDashboardCellRequest(&cell); err != nil { + invalidData(w, err, s.Logger) + return + } + cell.ID = cid + + dash.Cells[cellid] = cell + if err := s.DashboardsStore.Update(ctx, dash); err != nil { + msg := fmt.Sprintf("Error updating cell %s in dashboard %d: %v", cid, id, err) + Error(w, http.StatusInternalServerError, msg, s.Logger) + return + } + + boards := newDashboardResponse(dash) + for _, cell := range boards.Cells { + if cell.ID == cid { + encodeJSON(w, http.StatusOK, cell, s.Logger) + return + } + } +} diff --git a/server/dashboards.go b/server/dashboards.go index 03d40c16f7..33e065b60f 100644 --- a/server/dashboards.go +++ b/server/dashboards.go @@ -5,9 +5,7 @@ import ( "fmt" "net/http" - "github.com/bouk/httprouter" "github.com/influxdata/chronograf" - "github.com/influxdata/chronograf/uuid" ) const ( @@ -250,12 +248,6 @@ func ValidDashboardRequest(d *chronograf.Dashboard) error { return nil } -// ValidDashboardCellRequest verifies that the dashboard cells have a query -func ValidDashboardCellRequest(c *chronograf.DashboardCell) error { - CorrectWidthHeight(c) - return nil -} - // DashboardDefaults updates the dashboard with the default values // if none are specified func DashboardDefaults(d *chronograf.Dashboard) { @@ -265,17 +257,6 @@ func DashboardDefaults(d *chronograf.Dashboard) { } } -// CorrectWidthHeight changes the cell to have at least the -// minimum width and height -func CorrectWidthHeight(c *chronograf.DashboardCell) { - if c.W < 1 { - c.W = DefaultWidth - } - if c.H < 1 { - c.H = DefaultHeight - } -} - // AddQueryConfigs updates all the celsl in the dashboard to have query config // objects corresponding to their influxql queries. func AddQueryConfigs(d *chronograf.Dashboard) { @@ -284,203 +265,3 @@ func AddQueryConfigs(d *chronograf.Dashboard) { d.Cells[i] = c } } - -// AddQueryConfig updates a cell by converting InfluxQL into queryconfigs -// If influxql cannot be represented by a full query config, then, the -// query config's raw text is set to the command. -func AddQueryConfig(c *chronograf.DashboardCell) { - for i, q := range c.Queries { - qc := ToQueryConfig(q.Command) - q.QueryConfig = qc - c.Queries[i] = q - } -} - -// DashboardCells returns all cells from a dashboard within the store -func (s *Service) DashboardCells(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - e, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id)) - if err != nil { - notFound(w, id, s.Logger) - return - } - - boards := newDashboardResponse(e) - cells := boards.Cells - encodeJSON(w, http.StatusOK, cells, s.Logger) -} - -// NewDashboardCell adds a cell to an existing dashboard -func (s *Service) NewDashboardCell(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - dash, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id)) - if err != nil { - notFound(w, id, s.Logger) - return - } - var cell chronograf.DashboardCell - if err := json.NewDecoder(r.Body).Decode(&cell); err != nil { - invalidJSON(w, s.Logger) - return - } - - if err := ValidDashboardCellRequest(&cell); err != nil { - invalidData(w, err, s.Logger) - return - } - - ids := uuid.V4{} - cid, err := ids.Generate() - if err != nil { - msg := fmt.Sprintf("Error creating cell ID of dashboard %d: %v", id, err) - Error(w, http.StatusInternalServerError, msg, s.Logger) - return - } - cell.ID = cid - - dash.Cells = append(dash.Cells, cell) - if err := s.DashboardsStore.Update(ctx, dash); err != nil { - msg := fmt.Sprintf("Error adding cell %s to dashboard %d: %v", cid, id, err) - Error(w, http.StatusInternalServerError, msg, s.Logger) - return - } - - boards := newDashboardResponse(dash) - for _, cell := range boards.Cells { - if cell.ID == cid { - encodeJSON(w, http.StatusOK, cell, s.Logger) - return - } - } -} - -// DashboardCellID adds a cell to an existing dashboard -func (s *Service) DashboardCellID(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - dash, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id)) - if err != nil { - notFound(w, id, s.Logger) - return - } - - boards := newDashboardResponse(dash) - cid := httprouter.GetParamFromContext(ctx, "cid") - for _, cell := range boards.Cells { - if cell.ID == cid { - encodeJSON(w, http.StatusOK, cell, s.Logger) - return - } - } - notFound(w, id, s.Logger) -} - -// RemoveDashboardCell adds a cell to an existing dashboard -func (s *Service) RemoveDashboardCell(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - dash, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id)) - if err != nil { - notFound(w, id, s.Logger) - return - } - - cid := httprouter.GetParamFromContext(ctx, "cid") - cellid := -1 - for i, cell := range dash.Cells { - if cell.ID == cid { - cellid = i - break - } - } - if cellid == -1 { - notFound(w, id, s.Logger) - return - } - - dash.Cells = append(dash.Cells[:cellid], dash.Cells[cellid+1:]...) - if err := s.DashboardsStore.Update(ctx, dash); err != nil { - msg := fmt.Sprintf("Error removing cell %s from dashboard %d: %v", cid, id, err) - Error(w, http.StatusInternalServerError, msg, s.Logger) - return - } - w.WriteHeader(http.StatusNoContent) -} - -// ReplaceDashboardCell adds a cell to an existing dashboard -func (s *Service) ReplaceDashboardCell(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - dash, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id)) - if err != nil { - notFound(w, id, s.Logger) - return - } - - cid := httprouter.GetParamFromContext(ctx, "cid") - cellid := -1 - for i, cell := range dash.Cells { - if cell.ID == cid { - cellid = i - break - } - } - if cellid == -1 { - notFound(w, id, s.Logger) - return - } - - var cell chronograf.DashboardCell - if err := json.NewDecoder(r.Body).Decode(&cell); err != nil { - invalidJSON(w, s.Logger) - return - } - - if err := ValidDashboardCellRequest(&cell); err != nil { - invalidData(w, err, s.Logger) - return - } - cell.ID = cid - - dash.Cells[cellid] = cell - if err := s.DashboardsStore.Update(ctx, dash); err != nil { - msg := fmt.Sprintf("Error updating cell %s in dashboard %d: %v", cid, id, err) - Error(w, http.StatusInternalServerError, msg, s.Logger) - return - } - - boards := newDashboardResponse(dash) - for _, cell := range boards.Cells { - if cell.ID == cid { - encodeJSON(w, http.StatusOK, cell, s.Logger) - return - } - } -} From d4f45305653e22cc55824efccc7c4b32564a6ac7 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 20 Apr 2017 10:33:47 -0500 Subject: [PATCH 047/163] Add template structure definitions to chronograf --- chronograf.go | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/chronograf.go b/chronograf.go index a7b0faa0a9..094ba2da8e 100644 --- a/chronograf.go +++ b/chronograf.go @@ -125,14 +125,15 @@ type Range struct { // TemplateValue is a value use to replace a template in an InfluxQL query type TemplateValue struct { - Value string `json:"value"` - Type string `json:"type"` + Value string `json:"value"` // Value is the specific value used to replace a template in an InfluxQL query + Type string `json:"type"` // Type can be tagKey, tagValue, fieldKey, csv + Selected bool `json:"selected"` // Selected states that this variable has been picked to use for replacement } // TemplateVar is a named variable within an InfluxQL query to be replaced with Values type TemplateVar struct { - Var string `json:"tempVar"` - Values []TemplateValue `json:"values"` + Var string `json:"tempVar"` // Var is the string to replace within InfluxQL + Values []TemplateValue `json:"values"` // Values are the replacement values within InfluxQL } // String converts the template variable into a correct InfluxQL string based @@ -153,12 +154,24 @@ func (t TemplateVar) String() string { } } +// TemplateID is the unique ID used to identify a template +type TemplateID string + +// Template represents a series of choices to replace TemplateVars within InfluxQL +type Template struct { + TemplateVar + ID TemplateID `json:"id"` // ID is the unique ID associated with this template + Type string `json:"type"` // Type can be fieldKeys, tagKeys, tagValues, CSV, constant, query + Label string `json:"label"` // Label is a user-facing description of the Template + Query *TemplateQuery `json:"query,omitempty"` // Query is used to generate the choices for a template +} + // Query retrieves a Response from a TimeSeries. type Query struct { Command string `json:"query"` // Command is the query itself DB string `json:"db,omitempty"` // DB is optional and if empty will not be used. RP string `json:"rp,omitempty"` // RP is a retention policy and optional; if empty will not be used. - TemplateVars []TemplateVar `json:"tempVars"` // TemplateVars are template variables to replace within an InfluxQL query + TemplateVars []TemplateVar `json:"tempVars,omitempty"` // TemplateVars are template variables to replace within an InfluxQL query Wheres []string `json:"wheres,omitempty"` // Wheres restricts the query to certain attributes GroupBys []string `json:"groupbys,omitempty"` // GroupBys collate the query by these tags Label string `json:"label,omitempty"` // Label is the Y-Axis label for the data @@ -174,6 +187,13 @@ type DashboardQuery struct { QueryConfig QueryConfig `json:"queryConfig,omitempty"` // QueryConfig represents the query state that is understood by the data explorer } +// TemplateQuery is used to retrieve choices for template replacement +type TemplateQuery struct { + Command string `json:"query"` // Command is the query itself + DB string `json:"db,omitempty"` // DB is optional and if empty will not be used. + RP string `json:"rp,omitempty"` // RP is a retention policy and optional; if empty will not be used. +} + // Response is the result of a query against a TimeSeries type Response interface { MarshalJSON() ([]byte, error) @@ -404,9 +424,10 @@ type DashboardID int // Dashboard represents all visual and query data for a dashboard type Dashboard struct { - ID DashboardID `json:"id"` - Cells []DashboardCell `json:"cells"` - Name string `json:"name"` + ID DashboardID `json:"id"` + Cells []DashboardCell `json:"cells"` + Templates []Template `json:"templates"` + Name string `json:"name"` } // DashboardCell holds visual and query information for a cell From 2018f7300a61692fb4b132780ddb767f5fad5715 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 20 Apr 2017 11:09:56 -0500 Subject: [PATCH 048/163] Add initial template CRUD operations --- server/cells.go | 39 +++++- server/dashboards.go | 58 +++------ server/dashboards_test.go | 12 +- server/mux.go | 7 ++ server/templates.go | 241 ++++++++++++++++++++++++++++++++++++++ server/templates_test.go | 71 +++++++++++ 6 files changed, 383 insertions(+), 45 deletions(-) create mode 100644 server/templates.go create mode 100644 server/templates_test.go diff --git a/server/cells.go b/server/cells.go index 99ce5c07cf..b6a79290b6 100644 --- a/server/cells.go +++ b/server/cells.go @@ -10,6 +10,39 @@ import ( "github.com/influxdata/chronograf/uuid" ) +const ( + // DefaultWidth is used if not specified + DefaultWidth = 4 + // DefaultHeight is used if not specified + DefaultHeight = 4 +) + +type dashboardCellLinks struct { + Self string `json:"self"` // Self link mapping to this resource +} + +type dashboardCellResponse struct { + chronograf.DashboardCell + Links dashboardCellLinks `json:"links"` +} + +func newCellResponses(dID chronograf.DashboardID, dcells []chronograf.DashboardCell) []dashboardCellResponse { + base := "/chronograf/v1/dashboards" + cells := make([]dashboardCellResponse, len(dcells)) + for i, cell := range dcells { + if len(cell.Queries) == 0 { + cell.Queries = make([]chronograf.DashboardQuery, 0) + } + cells[i] = dashboardCellResponse{ + DashboardCell: cell, + Links: dashboardCellLinks{ + Self: fmt.Sprintf("%s/%d/cells/%s", base, dID, cell.ID), + }, + } + } + return cells +} + // ValidDashboardCellRequest verifies that the dashboard cells have a query func ValidDashboardCellRequest(c *chronograf.DashboardCell) error { CorrectWidthHeight(c) @@ -108,7 +141,7 @@ func (s *Service) NewDashboardCell(w http.ResponseWriter, r *http.Request) { } } -// DashboardCellID adds a cell to an existing dashboard +// DashboardCellID gets a specific cell from an existing dashboard func (s *Service) DashboardCellID(w http.ResponseWriter, r *http.Request) { ctx := r.Context() id, err := paramID("id", r) @@ -134,7 +167,7 @@ func (s *Service) DashboardCellID(w http.ResponseWriter, r *http.Request) { notFound(w, id, s.Logger) } -// RemoveDashboardCell adds a cell to an existing dashboard +// RemoveDashboardCell removes a specific cell from an existing dashboard func (s *Service) RemoveDashboardCell(w http.ResponseWriter, r *http.Request) { id, err := paramID("id", r) if err != nil { @@ -171,7 +204,7 @@ func (s *Service) RemoveDashboardCell(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) } -// ReplaceDashboardCell adds a cell to an existing dashboard +// ReplaceDashboardCell replaces a cell entirely within an existing dashboard func (s *Service) ReplaceDashboardCell(w http.ResponseWriter, r *http.Request) { id, err := paramID("id", r) if err != nil { diff --git a/server/dashboards.go b/server/dashboards.go index 33e065b60f..6448d91eff 100644 --- a/server/dashboards.go +++ b/server/dashboards.go @@ -8,32 +8,18 @@ import ( "github.com/influxdata/chronograf" ) -const ( - // DefaultWidth is used if not specified - DefaultWidth = 4 - // DefaultHeight is used if not specified - DefaultHeight = 4 -) - type dashboardLinks struct { - Self string `json:"self"` // Self link mapping to this resource - Cells string `json:"cells"` // Cells link to the cells endpoint -} - -type dashboardCellLinks struct { - Self string `json:"self"` // Self link mapping to this resource -} - -type dashboardCellResponse struct { - chronograf.DashboardCell - Links dashboardCellLinks `json:"links"` + Self string `json:"self"` // Self link mapping to this resource + Cells string `json:"cells"` // Cells link to the cells endpoint + Templates string `json:"templates"` // Templates link to the templates endpoint } type dashboardResponse struct { - ID chronograf.DashboardID `json:"id"` - Cells []dashboardCellResponse `json:"cells"` - Name string `json:"name"` - Links dashboardLinks `json:"links"` + ID chronograf.DashboardID `json:"id"` + Cells []dashboardCellResponse `json:"cells"` + Templates []templateResponse `json:"templates"` + Name string `json:"name"` + Links dashboardLinks `json:"links"` } type getDashboardsResponse struct { @@ -44,25 +30,18 @@ func newDashboardResponse(d chronograf.Dashboard) *dashboardResponse { base := "/chronograf/v1/dashboards" DashboardDefaults(&d) AddQueryConfigs(&d) - cells := make([]dashboardCellResponse, len(d.Cells)) - for i, cell := range d.Cells { - if len(cell.Queries) == 0 { - cell.Queries = make([]chronograf.DashboardQuery, 0) - } - cells[i] = dashboardCellResponse{ - DashboardCell: cell, - Links: dashboardCellLinks{ - Self: fmt.Sprintf("%s/%d/cells/%s", base, d.ID, cell.ID), - }, - } - } + cells := newCellResponses(d.ID, d.Cells) + templates := newTemplateResponses(d.ID, d.Templates) + return &dashboardResponse{ - ID: d.ID, - Name: d.Name, - Cells: cells, + ID: d.ID, + Name: d.Name, + Cells: cells, + Templates: templates, Links: dashboardLinks{ - Self: fmt.Sprintf("%s/%d", base, d.ID), - Cells: fmt.Sprintf("%s/%d/cells", base, d.ID), + Self: fmt.Sprintf("%s/%d", base, d.ID), + Cells: fmt.Sprintf("%s/%d/cells", base, d.ID), + Templates: fmt.Sprintf("%s/%d/templates", base, d.ID), }, } } @@ -83,7 +62,6 @@ func (s *Service) Dashboards(w http.ResponseWriter, r *http.Request) { for _, dashboard := range dashboards { res.Dashboards = append(res.Dashboards, newDashboardResponse(dashboard)) } - encodeJSON(w, http.StatusOK, res, s.Logger) } diff --git a/server/dashboards_test.go b/server/dashboards_test.go index 2c7c1c490b..672d0fa5be 100644 --- a/server/dashboards_test.go +++ b/server/dashboards_test.go @@ -1,6 +1,8 @@ package server import ( + "encoding/json" + "log" "reflect" "testing" @@ -233,6 +235,7 @@ func Test_newDashboardResponse(t *testing.T) { }, }, want: &dashboardResponse{ + Templates: []templateResponse{}, Cells: []dashboardCellResponse{ dashboardCellResponse{ Links: dashboardCellLinks{ @@ -289,8 +292,9 @@ func Test_newDashboardResponse(t *testing.T) { }, }, Links: dashboardLinks{ - Self: "/chronograf/v1/dashboards/0", - Cells: "/chronograf/v1/dashboards/0/cells", + Self: "/chronograf/v1/dashboards/0", + Cells: "/chronograf/v1/dashboards/0/cells", + Templates: "/chronograf/v1/dashboards/0/templates", }, }, }, @@ -298,6 +302,10 @@ func Test_newDashboardResponse(t *testing.T) { for _, tt := range tests { if got := newDashboardResponse(tt.d); !reflect.DeepEqual(got, tt.want) { t.Errorf("%q. newDashboardResponse() = \n%+v\n\n, want\n\n%+v", tt.name, got, tt.want) + g, _ := json.MarshalIndent(got, "", " ") + w, _ := json.MarshalIndent(tt.want, "", " ") + log.Printf(string(g)) + log.Printf(string(w)) } } } diff --git a/server/mux.go b/server/mux.go index 9e05756c11..0024cf221d 100644 --- a/server/mux.go +++ b/server/mux.go @@ -155,6 +155,13 @@ func NewMux(opts MuxOpts, service Service) http.Handler { router.GET("/chronograf/v1/dashboards/:id/cells/:cid", service.DashboardCellID) router.DELETE("/chronograf/v1/dashboards/:id/cells/:cid", service.RemoveDashboardCell) router.PUT("/chronograf/v1/dashboards/:id/cells/:cid", service.ReplaceDashboardCell) + // Dashboard Templates + router.GET("/chronograf/v1/dashboards/:id/templates", service.Templates) + router.POST("/chronograf/v1/dashboards/:id/templates", service.NewTemplate) + + router.GET("/chronograf/v1/dashboards/:id/templates/:tid", service.TemplateID) + router.DELETE("/chronograf/v1/dashboards/:id/templates/:tid", service.RemoveTemplate) + router.PUT("/chronograf/v1/dashboards/:id/templates/:tid", service.ReplaceTemplate) // Databases router.GET("/chronograf/v1/sources/:id/dbs", service.GetDatabases) diff --git a/server/templates.go b/server/templates.go new file mode 100644 index 0000000000..f13cfbb879 --- /dev/null +++ b/server/templates.go @@ -0,0 +1,241 @@ +package server + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/bouk/httprouter" + "github.com/influxdata/chronograf" + "github.com/influxdata/chronograf/uuid" +) + +// ValidTemplateRequest checks if the request sent to the server is the correct format. +func ValidTemplateRequest(template *chronograf.Template) error { + switch template.Type { + default: + return fmt.Errorf("Unknown template type %s", template.Type) + case "query", "constant", "csv", "fieldKeys", "tagKeys", "tagValues": + } + + for _, v := range template.Values { + switch v.Type { + default: + return fmt.Errorf("Unknown template variable type %s", v.Type) + case "csv", "fieldKey", "tagKey", "tagValue": + } + } + + if template.Type == "query" && template.Query == nil { + return fmt.Errorf("No query set for template of type 'query'") + } + return nil +} + +type templateLinks struct { + Self string `json:"self"` // Self link mapping to this resource +} + +type templateResponse struct { + chronograf.Template + Links templateLinks `json:"links"` +} + +func newTemplateResponses(dID chronograf.DashboardID, tmps []chronograf.Template) []templateResponse { + res := make([]templateResponse, len(tmps)) + for i, t := range tmps { + res[i] = newTemplateResponse(dID, t) + } + return res +} + +func newTemplateResponse(dID chronograf.DashboardID, tmp chronograf.Template) templateResponse { + base := "/chronograf/v1/dashboards" + return templateResponse{ + Template: tmp, + Links: templateLinks{ + Self: fmt.Sprintf("%s/%d/templates/%s", base, dID, tmp.ID), + }, + } +} + +// Templates returns all templates from a dashboard within the store +func (s *Service) Templates(w http.ResponseWriter, r *http.Request) { + id, err := paramID("id", r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) + return + } + + ctx := r.Context() + e, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id)) + if err != nil { + notFound(w, id, s.Logger) + return + } + + boards := newDashboardResponse(e) + templates := boards.Templates + encodeJSON(w, http.StatusOK, templates, s.Logger) +} + +// NewTemplate adds a template to an existing dashboard +func (s *Service) NewTemplate(w http.ResponseWriter, r *http.Request) { + id, err := paramID("id", r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) + return + } + + ctx := r.Context() + dash, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id)) + if err != nil { + notFound(w, id, s.Logger) + return + } + + var template chronograf.Template + if err := json.NewDecoder(r.Body).Decode(&template); err != nil { + invalidJSON(w, s.Logger) + return + } + + if err := ValidTemplateRequest(&template); err != nil { + invalidData(w, err, s.Logger) + return + } + + ids := uuid.V4{} + tid, err := ids.Generate() + if err != nil { + msg := fmt.Sprintf("Error creating template ID for dashboard %d: %v", id, err) + Error(w, http.StatusInternalServerError, msg, s.Logger) + return + } + template.ID = chronograf.TemplateID(tid) + + dash.Templates = append(dash.Templates, template) + if err := s.DashboardsStore.Update(ctx, dash); err != nil { + msg := fmt.Sprintf("Error adding template %s to dashboard %d: %v", tid, id, err) + Error(w, http.StatusInternalServerError, msg, s.Logger) + return + } + + res := newTemplateResponse(dash.ID, template) + encodeJSON(w, http.StatusOK, res, s.Logger) +} + +// TemplateID retrieves a specific template from a dashboard +func (s *Service) TemplateID(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + id, err := paramID("id", r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) + return + } + + dash, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id)) + if err != nil { + notFound(w, id, s.Logger) + return + } + + tid := httprouter.GetParamFromContext(ctx, "tid") + for _, t := range dash.Templates { + if t.ID == chronograf.TemplateID(tid) { + res := newTemplateResponse(chronograf.DashboardID(id), t) + encodeJSON(w, http.StatusOK, res, s.Logger) + } + } + + notFound(w, id, s.Logger) +} + +// RemoveTemplate removes a specific template from an existing dashboard +func (s *Service) RemoveTemplate(w http.ResponseWriter, r *http.Request) { + id, err := paramID("id", r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) + return + } + + ctx := r.Context() + dash, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id)) + if err != nil { + notFound(w, id, s.Logger) + return + } + + tid := httprouter.GetParamFromContext(ctx, "tid") + pos := -1 + for i, t := range dash.Templates { + if t.ID == chronograf.TemplateID(tid) { + pos = i + break + } + } + if pos == -1 { + notFound(w, id, s.Logger) + return + } + + dash.Templates = append(dash.Templates[:pos], dash.Templates[pos+1:]...) + if err := s.DashboardsStore.Update(ctx, dash); err != nil { + msg := fmt.Sprintf("Error removing template %s from dashboard %d: %v", tid, id, err) + Error(w, http.StatusInternalServerError, msg, s.Logger) + return + } + + w.WriteHeader(http.StatusNoContent) +} + +// ReplaceTemplate replaces a template entirely within an existing dashboard +func (s *Service) ReplaceTemplate(w http.ResponseWriter, r *http.Request) { + id, err := paramID("id", r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) + return + } + + ctx := r.Context() + dash, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id)) + if err != nil { + notFound(w, id, s.Logger) + return + } + + tid := httprouter.GetParamFromContext(ctx, "tid") + pos := -1 + for i, t := range dash.Templates { + if t.ID == chronograf.TemplateID(tid) { + pos = i + break + } + } + if pos == -1 { + notFound(w, id, s.Logger) + return + } + + var template chronograf.Template + if err := json.NewDecoder(r.Body).Decode(&template); err != nil { + invalidJSON(w, s.Logger) + return + } + + if err := ValidTemplateRequest(&template); err != nil { + invalidData(w, err, s.Logger) + return + } + template.ID = chronograf.TemplateID(tid) + + dash.Templates[pos] = template + if err := s.DashboardsStore.Update(ctx, dash); err != nil { + msg := fmt.Sprintf("Error updating template %s in dashboard %d: %v", tid, id, err) + Error(w, http.StatusInternalServerError, msg, s.Logger) + return + } + + res := newTemplateResponse(chronograf.DashboardID(id), template) + encodeJSON(w, http.StatusOK, res, s.Logger) +} diff --git a/server/templates_test.go b/server/templates_test.go new file mode 100644 index 0000000000..8a9bec46f6 --- /dev/null +++ b/server/templates_test.go @@ -0,0 +1,71 @@ +package server + +import ( + "testing" + + "github.com/influxdata/chronograf" +) + +func TestValidTemplateRequest(t *testing.T) { + tests := []struct { + name string + template *chronograf.Template + wantErr bool + }{ + { + name: "Valid Template", + template: &chronograf.Template{ + Type: "fieldKeys", + TemplateVar: chronograf.TemplateVar{ + Values: []chronograf.TemplateValue{ + { + Type: "fieldKey", + }, + }, + }, + }, + }, + { + name: "Invalid Template Type", + wantErr: true, + template: &chronograf.Template{ + Type: "Unknown Type", + TemplateVar: chronograf.TemplateVar{ + Values: []chronograf.TemplateValue{ + { + Type: "fieldKey", + }, + }, + }, + }, + }, + { + name: "Invalid Template Variable Type", + wantErr: true, + template: &chronograf.Template{ + Type: "csv", + TemplateVar: chronograf.TemplateVar{ + Values: []chronograf.TemplateValue{ + { + Type: "unknown value", + }, + }, + }, + }, + }, + { + name: "No query set", + wantErr: true, + template: &chronograf.Template{ + Type: "query", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := ValidTemplateRequest(tt.template); (err != nil) != tt.wantErr { + t.Errorf("ValidTemplateRequest() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} From 4be9a1b27a9e4f6df5bf2a726c4509e1ecbb2314 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 20 Apr 2017 11:46:59 -0500 Subject: [PATCH 049/163] Fix linter errors to allow builds to pass --- ui/src/dashboards/components/TemplateVariableRow.js | 4 ++-- ui/src/dashboards/reducers/ui.js | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ui/src/dashboards/components/TemplateVariableRow.js b/ui/src/dashboards/components/TemplateVariableRow.js index 8a58bd0f2c..e53d262e1d 100644 --- a/ui/src/dashboards/components/TemplateVariableRow.js +++ b/ui/src/dashboards/components/TemplateVariableRow.js @@ -120,8 +120,8 @@ class RowWrapper extends Component { handleSubmit(e) { e.preventDefault() - const code = e.target.code.value - const label = e.target.label.value + // const code = e.target.code.value + // const label = e.target.label.value // updateTempVarsAsync({code, label}) } diff --git a/ui/src/dashboards/reducers/ui.js b/ui/src/dashboards/reducers/ui.js index c36385837a..312a94e643 100644 --- a/ui/src/dashboards/reducers/ui.js +++ b/ui/src/dashboards/reducers/ui.js @@ -187,11 +187,11 @@ export default function ui(state = initialState, action) { d => (d.id === dashboardID ? { - ...d, - templates: d.templates.map( - t => (t.id === templateID ? {...t, ...updates} : t) - ), - } + ...d, + templates: d.templates.map( + t => (t.id === templateID ? {...t, ...updates} : t) + ), + } : d) ) return {...state, dashboards} From adb67657c33a98004b38f2611e549f295b9bbacb Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 20 Apr 2017 11:47:31 -0500 Subject: [PATCH 050/163] Add more validation checks to dashboard updates --- server/dashboards.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/server/dashboards.go b/server/dashboards.go index 6448d91eff..ee9bb8a602 100644 --- a/server/dashboards.go +++ b/server/dashboards.go @@ -219,9 +219,16 @@ func (s *Service) UpdateDashboard(w http.ResponseWriter, r *http.Request) { // ValidDashboardRequest verifies that the dashboard cells have a query func ValidDashboardRequest(d *chronograf.Dashboard) error { for i, c := range d.Cells { - CorrectWidthHeight(&c) + if err := ValidDashboardCellRequest(&c); err != nil { + return err + } d.Cells[i] = c } + for _, t := range d.Templates { + if err := ValidTemplateRequest(&t); err != nil { + return err + } + } DashboardDefaults(d) return nil } From e7dbdb7204359fe3edf2fbc1cc3cabaefbc7ca24 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 20 Apr 2017 11:51:04 -0500 Subject: [PATCH 051/163] Update CHANGELOG to mention templates api --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53f583ab0d..4c96a91aee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ 1. [#1292](https://github.com/influxdata/chronograf/pull/1292): Introduce Template Variable Manager 1. [#1232](https://github.com/influxdata/chronograf/pull/1232): Fuse the query builder and raw query editor 1. [#1286](https://github.com/influxdata/chronograf/pull/1286): Add refreshing JWTs for authentication + 1. [#1316](https://github.com/influxdata/chronograf/pull/1316): Add templates API scoped within a dashboard ### UI Improvements From 5f54d9282ccfc87cbd21972582c951dbb7c7ee30 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 20 Apr 2017 12:22:55 -0500 Subject: [PATCH 052/163] Update templates API response --- server/templates.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/server/templates.go b/server/templates.go index f13cfbb879..7453dc38d8 100644 --- a/server/templates.go +++ b/server/templates.go @@ -49,6 +49,10 @@ func newTemplateResponses(dID chronograf.DashboardID, tmps []chronograf.Template return res } +type templatesResponses struct { + Templates []templateResponse `json:"templates"` +} + func newTemplateResponse(dID chronograf.DashboardID, tmp chronograf.Template) templateResponse { base := "/chronograf/v1/dashboards" return templateResponse{ @@ -68,15 +72,16 @@ func (s *Service) Templates(w http.ResponseWriter, r *http.Request) { } ctx := r.Context() - e, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id)) + d, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id)) if err != nil { notFound(w, id, s.Logger) return } - boards := newDashboardResponse(e) - templates := boards.Templates - encodeJSON(w, http.StatusOK, templates, s.Logger) + res := templatesResponses{ + Templates: newTemplateResponses(chronograf.DashboardID(id), d.Templates), + } + encodeJSON(w, http.StatusOK, res, s.Logger) } // NewTemplate adds a template to an existing dashboard From 8dcf168ea3135c874eadeef63b6edd1fbe59af0b Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 20 Apr 2017 14:30:17 -0500 Subject: [PATCH 053/163] Add fields to store pieces of meta queries --- chronograf.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/chronograf.go b/chronograf.go index 094ba2da8e..4cb3dc31ad 100644 --- a/chronograf.go +++ b/chronograf.go @@ -126,7 +126,7 @@ type Range struct { // TemplateValue is a value use to replace a template in an InfluxQL query type TemplateValue struct { Value string `json:"value"` // Value is the specific value used to replace a template in an InfluxQL query - Type string `json:"type"` // Type can be tagKey, tagValue, fieldKey, csv + Type string `json:"type"` // Type can be tagKey, tagValue, fieldKey, csv, measurement, database, constant Selected bool `json:"selected"` // Selected states that this variable has been picked to use for replacement } @@ -143,11 +143,11 @@ func (t TemplateVar) String() string { return "" } switch t.Values[0].Type { - case "tagKey", "fieldKey": + case "tagKey", "fieldKey", "measurement", "database": return `"` + t.Values[0].Value + `"` case "tagValue": return `'` + t.Values[0].Value + `'` - case "csv": + case "csv", "constant": return t.Values[0].Value default: return "" @@ -161,7 +161,7 @@ type TemplateID string type Template struct { TemplateVar ID TemplateID `json:"id"` // ID is the unique ID associated with this template - Type string `json:"type"` // Type can be fieldKeys, tagKeys, tagValues, CSV, constant, query + Type string `json:"type"` // Type can be fieldKeys, tagKeys, tagValues, CSV, constant, query, measurements, databases Label string `json:"label"` // Label is a user-facing description of the Template Query *TemplateQuery `json:"query,omitempty"` // Query is used to generate the choices for a template } @@ -189,9 +189,12 @@ type DashboardQuery struct { // TemplateQuery is used to retrieve choices for template replacement type TemplateQuery struct { - Command string `json:"query"` // Command is the query itself - DB string `json:"db,omitempty"` // DB is optional and if empty will not be used. - RP string `json:"rp,omitempty"` // RP is a retention policy and optional; if empty will not be used. + Command string `json:"query"` // Command is the query itself + DB string `json:"db,omitempty"` // DB is optional and if empty will not be used. + RP string `json:"rp,omitempty"` // RP is a retention policy and optional; if empty will not be used. + Measurement string `json:"measurement"` // Measurement is the optinally selected measurement for the query + TagKey string `json:"tagKey"` // TagKey is the optionally selected tag key for the query + FieldKey string `json:"fieldKey"` // FieldKey is the optionally selected field key for the query } // Response is the result of a query against a TimeSeries From fc40e344c86732fa3744b03808160b43ce1b551d Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 20 Apr 2017 14:31:29 -0500 Subject: [PATCH 054/163] Add template variables to bolt storage --- bolt/internal/internal.go | 74 +++++++++++++- bolt/internal/internal.pb.go | 184 +++++++++++++++++++++++++---------- bolt/internal/internal.proto | 25 +++++ 3 files changed, 226 insertions(+), 57 deletions(-) diff --git a/bolt/internal/internal.go b/bolt/internal/internal.go index 0980622322..aff1501077 100644 --- a/bolt/internal/internal.go +++ b/bolt/internal/internal.go @@ -188,11 +188,41 @@ func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) { Type: c.Type, } } + templates := make([]*Template, len(d.Templates)) + for i, t := range d.Templates { + vals := make([]*TemplateValue, len(t.Values)) + for j, v := range t.Values { + vals[j] = &TemplateValue{ + Selected: v.Selected, + Type: v.Type, + Value: v.Value, + } + } + template := &Template{ + ID: string(t.ID), + TempVar: t.Var, + Values: vals, + Type: t.Type, + Label: t.Label, + } + if t.Query != nil { + template.Query = &TemplateQuery{ + Command: t.Query.Command, + Db: t.Query.DB, + Rp: t.Query.RP, + Measurement: t.Query.Measurement, + TagKey: t.Query.TagKey, + FieldKey: t.Query.FieldKey, + } + } + templates[i] = template + } return proto.Marshal(&Dashboard{ - ID: int64(d.ID), - Cells: cells, - Name: d.Name, + ID: int64(d.ID), + Cells: cells, + Templates: templates, + Name: d.Name, }) } @@ -230,8 +260,44 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error { Type: c.Type, } } + + templates := make([]chronograf.Template, len(pb.Templates)) + for i, t := range pb.Templates { + vals := make([]chronograf.TemplateValue, len(t.Values)) + for j, v := range t.Values { + vals[j] = chronograf.TemplateValue{ + Selected: v.Selected, + Type: v.Type, + Value: v.Value, + } + } + + template := chronograf.Template{ + ID: chronograf.TemplateID(t.ID), + TemplateVar: chronograf.TemplateVar{ + Var: t.TempVar, + Values: vals, + }, + Type: t.Type, + Label: t.Label, + } + + if t.Query != nil { + template.Query = &chronograf.TemplateQuery{ + Command: t.Query.Command, + DB: t.Query.Db, + RP: t.Query.Rp, + Measurement: t.Query.Measurement, + TagKey: t.Query.TagKey, + FieldKey: t.Query.FieldKey, + } + } + templates[i] = template + } + d.ID = chronograf.DashboardID(pb.ID) d.Cells = cells + d.Templates = templates d.Name = pb.Name return nil } @@ -298,7 +364,7 @@ func UnmarshalUser(data []byte, u *chronograf.User) error { return nil } -// UnmarshalUser decodes a user from binary protobuf data. +// UnmarshalUserPB decodes a user from binary protobuf data. // We are ignoring the password for now. func UnmarshalUserPB(data []byte, u *User) error { if err := proto.Unmarshal(data, u); err != nil { diff --git a/bolt/internal/internal.pb.go b/bolt/internal/internal.pb.go index 30cdaec76c..c4b3952a5e 100644 --- a/bolt/internal/internal.pb.go +++ b/bolt/internal/internal.pb.go @@ -12,6 +12,9 @@ It has these top-level messages: Source Dashboard DashboardCell + Template + TemplateValue + TemplateQuery Server Layout Cell @@ -56,9 +59,10 @@ func (*Source) ProtoMessage() {} func (*Source) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{0} } type Dashboard struct { - ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` - Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` - Cells []*DashboardCell `protobuf:"bytes,3,rep,name=cells" json:"cells,omitempty"` + ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` + Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` + Cells []*DashboardCell `protobuf:"bytes,3,rep,name=cells" json:"cells,omitempty"` + Templates []*Template `protobuf:"bytes,4,rep,name=templates" json:"templates,omitempty"` } func (m *Dashboard) Reset() { *m = Dashboard{} } @@ -73,6 +77,13 @@ func (m *Dashboard) GetCells() []*DashboardCell { return nil } +func (m *Dashboard) GetTemplates() []*Template { + if m != nil { + return m.Templates + } + return nil +} + type DashboardCell struct { X int32 `protobuf:"varint,1,opt,name=x,proto3" json:"x,omitempty"` Y int32 `protobuf:"varint,2,opt,name=y,proto3" json:"y,omitempty"` @@ -96,6 +107,59 @@ func (m *DashboardCell) GetQueries() []*Query { return nil } +type Template struct { + ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` + TempVar string `protobuf:"bytes,2,opt,name=temp_var,json=tempVar,proto3" json:"temp_var,omitempty"` + Values []*TemplateValue `protobuf:"bytes,3,rep,name=values" json:"values,omitempty"` + Type string `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"` + Label string `protobuf:"bytes,5,opt,name=label,proto3" json:"label,omitempty"` + Query *TemplateQuery `protobuf:"bytes,6,opt,name=query" json:"query,omitempty"` +} + +func (m *Template) Reset() { *m = Template{} } +func (m *Template) String() string { return proto.CompactTextString(m) } +func (*Template) ProtoMessage() {} +func (*Template) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{3} } + +func (m *Template) GetValues() []*TemplateValue { + if m != nil { + return m.Values + } + return nil +} + +func (m *Template) GetQuery() *TemplateQuery { + if m != nil { + return m.Query + } + return nil +} + +type TemplateValue struct { + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + Selected bool `protobuf:"varint,3,opt,name=selected,proto3" json:"selected,omitempty"` +} + +func (m *TemplateValue) Reset() { *m = TemplateValue{} } +func (m *TemplateValue) String() string { return proto.CompactTextString(m) } +func (*TemplateValue) ProtoMessage() {} +func (*TemplateValue) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{4} } + +type TemplateQuery struct { + Command string `protobuf:"bytes,1,opt,name=command,proto3" json:"command,omitempty"` + Db string `protobuf:"bytes,2,opt,name=db,proto3" json:"db,omitempty"` + Rp string `protobuf:"bytes,3,opt,name=rp,proto3" json:"rp,omitempty"` + Measurement string `protobuf:"bytes,4,opt,name=measurement,proto3" json:"measurement,omitempty"` + TagKey string `protobuf:"bytes,5,opt,name=tag_key,json=tagKey,proto3" json:"tag_key,omitempty"` + FieldKey string `protobuf:"bytes,6,opt,name=field_key,json=fieldKey,proto3" json:"field_key,omitempty"` +} + +func (m *TemplateQuery) Reset() { *m = TemplateQuery{} } +func (m *TemplateQuery) String() string { return proto.CompactTextString(m) } +func (*TemplateQuery) ProtoMessage() {} +func (*TemplateQuery) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{5} } + type Server struct { ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` @@ -108,7 +172,7 @@ type Server struct { func (m *Server) Reset() { *m = Server{} } func (m *Server) String() string { return proto.CompactTextString(m) } func (*Server) ProtoMessage() {} -func (*Server) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{3} } +func (*Server) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{6} } type Layout struct { ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` @@ -121,7 +185,7 @@ type Layout struct { func (m *Layout) Reset() { *m = Layout{} } func (m *Layout) String() string { return proto.CompactTextString(m) } func (*Layout) ProtoMessage() {} -func (*Layout) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{4} } +func (*Layout) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{7} } func (m *Layout) GetCells() []*Cell { if m != nil { @@ -146,7 +210,7 @@ type Cell struct { func (m *Cell) Reset() { *m = Cell{} } func (m *Cell) String() string { return proto.CompactTextString(m) } func (*Cell) ProtoMessage() {} -func (*Cell) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{5} } +func (*Cell) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{8} } func (m *Cell) GetQueries() []*Query { if m != nil { @@ -168,7 +232,7 @@ type Query struct { func (m *Query) Reset() { *m = Query{} } func (m *Query) String() string { return proto.CompactTextString(m) } func (*Query) ProtoMessage() {} -func (*Query) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{6} } +func (*Query) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{9} } func (m *Query) GetRange() *Range { if m != nil { @@ -185,7 +249,7 @@ type Range struct { func (m *Range) Reset() { *m = Range{} } func (m *Range) String() string { return proto.CompactTextString(m) } func (*Range) ProtoMessage() {} -func (*Range) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{7} } +func (*Range) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{10} } type AlertRule struct { ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` @@ -197,7 +261,7 @@ type AlertRule struct { func (m *AlertRule) Reset() { *m = AlertRule{} } func (m *AlertRule) String() string { return proto.CompactTextString(m) } func (*AlertRule) ProtoMessage() {} -func (*AlertRule) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{8} } +func (*AlertRule) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{11} } type User struct { ID uint64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` @@ -207,12 +271,15 @@ type User struct { func (m *User) Reset() { *m = User{} } func (m *User) String() string { return proto.CompactTextString(m) } func (*User) ProtoMessage() {} -func (*User) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{9} } +func (*User) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{12} } func init() { proto.RegisterType((*Source)(nil), "internal.Source") proto.RegisterType((*Dashboard)(nil), "internal.Dashboard") proto.RegisterType((*DashboardCell)(nil), "internal.DashboardCell") + proto.RegisterType((*Template)(nil), "internal.Template") + proto.RegisterType((*TemplateValue)(nil), "internal.TemplateValue") + proto.RegisterType((*TemplateQuery)(nil), "internal.TemplateQuery") proto.RegisterType((*Server)(nil), "internal.Server") proto.RegisterType((*Layout)(nil), "internal.Layout") proto.RegisterType((*Cell)(nil), "internal.Cell") @@ -225,47 +292,58 @@ func init() { func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) } var fileDescriptorInternal = []byte{ - // 660 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x54, 0xdd, 0x6e, 0xd3, 0x4a, - 0x10, 0xd6, 0xc6, 0x76, 0x7e, 0xa6, 0x3d, 0x3d, 0x47, 0xab, 0x23, 0x58, 0x71, 0x15, 0x59, 0x20, - 0x05, 0x24, 0x7a, 0x41, 0x9f, 0xa0, 0xad, 0x25, 0x14, 0x68, 0x4b, 0xd9, 0xb4, 0x70, 0x05, 0xd2, - 0x36, 0x9d, 0x34, 0x16, 0x8e, 0x6d, 0xd6, 0x36, 0xa9, 0x5f, 0x01, 0xf1, 0x0c, 0x3c, 0x00, 0x97, - 0xbc, 0x0a, 0x2f, 0x84, 0x66, 0x77, 0xed, 0xb8, 0xa2, 0xa0, 0x5e, 0x71, 0x37, 0xdf, 0xcc, 0x66, - 0x7e, 0xbe, 0xef, 0x73, 0x60, 0x27, 0x4e, 0x4b, 0xd4, 0xa9, 0x4a, 0x76, 0x73, 0x9d, 0x95, 0x19, - 0x1f, 0x36, 0x38, 0xfc, 0xdc, 0x83, 0xfe, 0x2c, 0xab, 0xf4, 0x1c, 0xf9, 0x0e, 0xf4, 0xa6, 0x91, - 0x60, 0x63, 0x36, 0xf1, 0x64, 0x6f, 0x1a, 0x71, 0x0e, 0xfe, 0x89, 0x5a, 0xa1, 0xe8, 0x8d, 0xd9, - 0x64, 0x24, 0x4d, 0x4c, 0xb9, 0xb3, 0x3a, 0x47, 0xe1, 0xd9, 0x1c, 0xc5, 0xfc, 0x01, 0x0c, 0xcf, - 0x0b, 0xea, 0xb6, 0x42, 0xe1, 0x9b, 0x7c, 0x8b, 0xa9, 0x76, 0xaa, 0x8a, 0x62, 0x9d, 0xe9, 0x4b, - 0x11, 0xd8, 0x5a, 0x83, 0xf9, 0x7f, 0xe0, 0x9d, 0xcb, 0x23, 0xd1, 0x37, 0x69, 0x0a, 0xb9, 0x80, - 0x41, 0x84, 0x0b, 0x55, 0x25, 0xa5, 0x18, 0x8c, 0xd9, 0x64, 0x28, 0x1b, 0x48, 0x7d, 0xce, 0x30, - 0xc1, 0x2b, 0xad, 0x16, 0x62, 0x68, 0xfb, 0x34, 0x98, 0xef, 0x02, 0x9f, 0xa6, 0x05, 0xce, 0x2b, - 0x8d, 0xb3, 0x0f, 0x71, 0xfe, 0x06, 0x75, 0xbc, 0xa8, 0xc5, 0xc8, 0x34, 0xb8, 0xa5, 0x42, 0x53, - 0x8e, 0xb1, 0x54, 0x34, 0x1b, 0x4c, 0xab, 0x06, 0x86, 0xef, 0x61, 0x14, 0xa9, 0x62, 0x79, 0x91, - 0x29, 0x7d, 0x79, 0x27, 0x3a, 0x9e, 0x42, 0x30, 0xc7, 0x24, 0x29, 0x84, 0x37, 0xf6, 0x26, 0x5b, - 0xcf, 0xee, 0xef, 0xb6, 0x3c, 0xb7, 0x7d, 0x0e, 0x31, 0x49, 0xa4, 0x7d, 0x15, 0x7e, 0x63, 0xf0, - 0xcf, 0x8d, 0x02, 0xdf, 0x06, 0x76, 0x6d, 0x66, 0x04, 0x92, 0x5d, 0x13, 0xaa, 0x4d, 0xff, 0x40, - 0xb2, 0x9a, 0xd0, 0xda, 0x10, 0x1d, 0x48, 0xb6, 0x26, 0xb4, 0x34, 0xf4, 0x06, 0x92, 0x2d, 0xf9, - 0x63, 0x18, 0x7c, 0xac, 0x50, 0xc7, 0x58, 0x88, 0xc0, 0x8c, 0xfe, 0x77, 0x33, 0xfa, 0x75, 0x85, - 0xba, 0x96, 0x4d, 0x9d, 0xf6, 0x36, 0xd2, 0x58, 0x9e, 0x4d, 0x4c, 0xb9, 0x92, 0x64, 0x1c, 0xd8, - 0x1c, 0xc5, 0xee, 0x5e, 0x4b, 0x6e, 0x6f, 0x1a, 0x85, 0x5f, 0x18, 0xf4, 0x67, 0xa8, 0x3f, 0xa1, - 0xbe, 0x13, 0x15, 0x5d, 0x17, 0x78, 0x7f, 0x70, 0x81, 0x7f, 0xbb, 0x0b, 0x82, 0x8d, 0x0b, 0xfe, - 0x87, 0x60, 0xa6, 0xe7, 0xd3, 0xc8, 0x6c, 0xec, 0x49, 0x0b, 0xc2, 0xaf, 0x0c, 0xfa, 0x47, 0xaa, - 0xce, 0xaa, 0xb2, 0xb3, 0x8e, 0xd9, 0x94, 0x8f, 0x61, 0x6b, 0x3f, 0xcf, 0x93, 0x78, 0xae, 0xca, - 0x38, 0x4b, 0xdd, 0x56, 0xdd, 0x14, 0xbd, 0x38, 0x46, 0x55, 0x54, 0x1a, 0x57, 0x98, 0x96, 0x6e, - 0xbf, 0x6e, 0x8a, 0x3f, 0x84, 0xe0, 0xd0, 0x28, 0xe9, 0x1b, 0x3a, 0x77, 0x36, 0x74, 0x5a, 0x01, - 0x4d, 0x91, 0x0e, 0xd9, 0xaf, 0xca, 0x6c, 0x91, 0x64, 0x6b, 0xb3, 0xf1, 0x50, 0xb6, 0x38, 0xfc, - 0xc1, 0xc0, 0xff, 0x5b, 0x9a, 0x6e, 0x03, 0x8b, 0x9d, 0xa0, 0x2c, 0x6e, 0x15, 0x1e, 0x74, 0x14, - 0x16, 0x30, 0xa8, 0xb5, 0x4a, 0xaf, 0xb0, 0x10, 0xc3, 0xb1, 0x37, 0xf1, 0x64, 0x03, 0x4d, 0x25, - 0x51, 0x17, 0x98, 0x14, 0x62, 0x34, 0xf6, 0xc8, 0xfe, 0x0e, 0xb6, 0xae, 0x80, 0x8d, 0x2b, 0xc2, - 0xef, 0x0c, 0x02, 0x33, 0x9c, 0x7e, 0x77, 0x98, 0xad, 0x56, 0x2a, 0xbd, 0x74, 0xd4, 0x37, 0x90, - 0xf4, 0x88, 0x0e, 0x1c, 0xed, 0xbd, 0xe8, 0x80, 0xb0, 0x3c, 0x75, 0x24, 0xf7, 0xe4, 0x29, 0xb1, - 0xf6, 0x5c, 0x67, 0x55, 0x7e, 0x50, 0x5b, 0x7a, 0x47, 0xb2, 0xc5, 0xfc, 0x1e, 0xf4, 0xdf, 0x2e, - 0x51, 0xbb, 0x9b, 0x47, 0xd2, 0x21, 0x32, 0xc1, 0x11, 0x6d, 0xe5, 0xae, 0xb4, 0x80, 0x3f, 0x82, - 0x40, 0xd2, 0x15, 0xe6, 0xd4, 0x1b, 0x04, 0x99, 0xb4, 0xb4, 0xd5, 0x70, 0xcf, 0x3d, 0xa3, 0x2e, - 0xe7, 0x79, 0x8e, 0xda, 0x79, 0xd7, 0x02, 0xd3, 0x3b, 0x5b, 0xa3, 0x36, 0x2b, 0x7b, 0xd2, 0x82, - 0xf0, 0x1d, 0x8c, 0xf6, 0x13, 0xd4, 0xa5, 0xac, 0x12, 0xfc, 0xc5, 0x62, 0x1c, 0xfc, 0x17, 0xb3, - 0x57, 0x27, 0x8d, 0xe3, 0x29, 0xde, 0xf8, 0xd4, 0xeb, 0xf8, 0x94, 0x0e, 0x7a, 0xa9, 0x72, 0x35, - 0x8d, 0x8c, 0xb0, 0x9e, 0x74, 0x28, 0x7c, 0x02, 0x3e, 0x7d, 0x0f, 0x9d, 0xce, 0xfe, 0xef, 0xbe, - 0xa5, 0x8b, 0xbe, 0xf9, 0x97, 0xde, 0xfb, 0x19, 0x00, 0x00, 0xff, 0xff, 0x93, 0x68, 0x0f, 0xcf, - 0xb7, 0x05, 0x00, 0x00, + // 848 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x55, 0xdd, 0x6e, 0xe3, 0x44, + 0x14, 0xd6, 0xc4, 0x76, 0x62, 0x9f, 0xdd, 0x2d, 0x68, 0xb4, 0x62, 0x0d, 0xdc, 0x44, 0x16, 0x48, + 0x01, 0x89, 0x82, 0xd8, 0x27, 0x68, 0x6b, 0x09, 0x85, 0x76, 0x97, 0x32, 0x69, 0xcb, 0x15, 0x5a, + 0x4d, 0x92, 0x93, 0xd6, 0xda, 0x49, 0x6c, 0xc6, 0x76, 0xb3, 0x7e, 0x05, 0xc4, 0x05, 0x4f, 0x80, + 0xc4, 0x2d, 0x97, 0xbc, 0x00, 0x0f, 0xc1, 0x0b, 0xa1, 0x33, 0x33, 0xfe, 0x89, 0xb6, 0xa0, 0xbd, + 0xe2, 0x6e, 0xbe, 0x73, 0x26, 0xdf, 0x9c, 0x9f, 0xef, 0x73, 0xe0, 0x28, 0xdb, 0x55, 0xa8, 0x77, + 0x52, 0x1d, 0x17, 0x3a, 0xaf, 0x72, 0x1e, 0xb6, 0x38, 0xf9, 0x79, 0x04, 0xe3, 0x45, 0x5e, 0xeb, + 0x15, 0xf2, 0x23, 0x18, 0xcd, 0xd3, 0x98, 0x4d, 0xd9, 0xcc, 0x13, 0xa3, 0x79, 0xca, 0x39, 0xf8, + 0x2f, 0xe5, 0x16, 0xe3, 0xd1, 0x94, 0xcd, 0x22, 0x61, 0xce, 0x14, 0xbb, 0x6a, 0x0a, 0x8c, 0x3d, + 0x1b, 0xa3, 0x33, 0xff, 0x08, 0xc2, 0xeb, 0x92, 0xd8, 0xb6, 0x18, 0xfb, 0x26, 0xde, 0x61, 0xca, + 0x5d, 0xca, 0xb2, 0xdc, 0xe7, 0x7a, 0x1d, 0x07, 0x36, 0xd7, 0x62, 0xfe, 0x3e, 0x78, 0xd7, 0xe2, + 0x22, 0x1e, 0x9b, 0x30, 0x1d, 0x79, 0x0c, 0x93, 0x14, 0x37, 0xb2, 0x56, 0x55, 0x3c, 0x99, 0xb2, + 0x59, 0x28, 0x5a, 0x48, 0x3c, 0x57, 0xa8, 0xf0, 0x56, 0xcb, 0x4d, 0x1c, 0x5a, 0x9e, 0x16, 0xf3, + 0x63, 0xe0, 0xf3, 0x5d, 0x89, 0xab, 0x5a, 0xe3, 0xe2, 0x75, 0x56, 0xdc, 0xa0, 0xce, 0x36, 0x4d, + 0x1c, 0x19, 0x82, 0x07, 0x32, 0xf4, 0xca, 0x0b, 0xac, 0x24, 0xbd, 0x0d, 0x86, 0xaa, 0x85, 0xc9, + 0xaf, 0x0c, 0xa2, 0x54, 0x96, 0x77, 0xcb, 0x5c, 0xea, 0xf5, 0x3b, 0xcd, 0xe3, 0x0b, 0x08, 0x56, + 0xa8, 0x54, 0x19, 0x7b, 0x53, 0x6f, 0xf6, 0xe8, 0xeb, 0x67, 0xc7, 0xdd, 0xa0, 0x3b, 0x9e, 0x33, + 0x54, 0x4a, 0xd8, 0x5b, 0xfc, 0x2b, 0x88, 0x2a, 0xdc, 0x16, 0x4a, 0x56, 0x58, 0xc6, 0xbe, 0xf9, + 0x09, 0xef, 0x7f, 0x72, 0xe5, 0x52, 0xa2, 0xbf, 0x94, 0xfc, 0xc1, 0xe0, 0xc9, 0x01, 0x15, 0x7f, + 0x0c, 0xec, 0x8d, 0xa9, 0x2a, 0x10, 0xec, 0x0d, 0xa1, 0xc6, 0x54, 0x14, 0x08, 0xd6, 0x10, 0xda, + 0x9b, 0xdd, 0x04, 0x82, 0xed, 0x09, 0xdd, 0x99, 0x8d, 0x04, 0x82, 0xdd, 0xf1, 0xcf, 0x60, 0xf2, + 0x53, 0x8d, 0x3a, 0xc3, 0x32, 0x0e, 0xcc, 0xcb, 0xef, 0xf5, 0x2f, 0x7f, 0x5f, 0xa3, 0x6e, 0x44, + 0x9b, 0xa7, 0x4e, 0xcd, 0x36, 0xed, 0x6a, 0xcc, 0x99, 0x62, 0x15, 0x6d, 0x7e, 0x62, 0x63, 0x74, + 0x76, 0x13, 0xb2, 0xfb, 0x18, 0xcd, 0xd3, 0xe4, 0x2f, 0x46, 0x6b, 0xb2, 0xa5, 0x0f, 0xc6, 0x67, + 0x92, 0xfc, 0x43, 0x08, 0xa9, 0xad, 0x57, 0xf7, 0x52, 0xbb, 0x11, 0x4e, 0x08, 0xdf, 0x48, 0xcd, + 0xbf, 0x84, 0xf1, 0xbd, 0x54, 0x35, 0x3e, 0x30, 0xc6, 0x96, 0xee, 0x86, 0xf2, 0xc2, 0x5d, 0xeb, + 0x8a, 0xf1, 0x07, 0xc5, 0x3c, 0x85, 0x40, 0xc9, 0x25, 0x2a, 0xa7, 0x33, 0x0b, 0x68, 0x41, 0xd4, + 0x55, 0x63, 0x7a, 0x79, 0x90, 0xd9, 0xf6, 0x6e, 0x6f, 0x25, 0xd7, 0xf0, 0xe4, 0xe0, 0xc5, 0xee, + 0x25, 0x76, 0xf8, 0x92, 0xa9, 0xc3, 0xb5, 0x61, 0x01, 0x49, 0xb4, 0x44, 0x85, 0xab, 0x0a, 0xd7, + 0x66, 0x05, 0xa1, 0xe8, 0x70, 0xf2, 0x3b, 0xeb, 0x79, 0xcd, 0x7b, 0x24, 0xc2, 0x55, 0xbe, 0xdd, + 0xca, 0xdd, 0xda, 0x51, 0xb7, 0x90, 0xe6, 0xb6, 0x5e, 0x3a, 0xea, 0xd1, 0x7a, 0x49, 0x58, 0x17, + 0xce, 0x70, 0x23, 0x5d, 0xf0, 0x29, 0x3c, 0xda, 0xa2, 0x2c, 0x6b, 0x8d, 0x5b, 0xdc, 0x55, 0x6e, + 0x04, 0xc3, 0x10, 0x7f, 0x06, 0x93, 0x4a, 0xde, 0xbe, 0x7a, 0x8d, 0x8d, 0x9b, 0xc5, 0xb8, 0x92, + 0xb7, 0xe7, 0xd8, 0xf0, 0x8f, 0x21, 0xda, 0x64, 0xa8, 0xd6, 0x26, 0x65, 0x97, 0x1b, 0x9a, 0xc0, + 0x39, 0x36, 0xc9, 0x2f, 0x0c, 0xc6, 0x0b, 0xd4, 0xf7, 0xa8, 0xdf, 0x49, 0xf9, 0x43, 0xd7, 0x7b, + 0xff, 0xe1, 0x7a, 0xff, 0x61, 0xd7, 0x07, 0xbd, 0xeb, 0x9f, 0x42, 0xb0, 0xd0, 0xab, 0x79, 0x6a, + 0x2a, 0xf2, 0x84, 0x05, 0xc9, 0x6f, 0x0c, 0xc6, 0x17, 0xb2, 0xc9, 0xeb, 0xea, 0x2d, 0x25, 0x4d, + 0xe1, 0xd1, 0x49, 0x51, 0xa8, 0x6c, 0x25, 0xab, 0x2c, 0xdf, 0xb9, 0xaa, 0x86, 0x21, 0xba, 0xf1, + 0x62, 0x30, 0x23, 0x5b, 0xdf, 0x30, 0xc4, 0x3f, 0x81, 0xe0, 0xcc, 0x18, 0xd7, 0xba, 0xf0, 0xa8, + 0xd7, 0x85, 0xf5, 0xab, 0x49, 0x52, 0x23, 0x27, 0x75, 0x95, 0x6f, 0x54, 0xbe, 0x37, 0x15, 0x87, + 0xa2, 0xc3, 0xc9, 0xdf, 0x0c, 0xfc, 0xff, 0xcb, 0x90, 0x8f, 0x81, 0x65, 0x6e, 0x61, 0x2c, 0xeb, + 0xec, 0x39, 0x19, 0xd8, 0x33, 0x86, 0x49, 0xa3, 0xe5, 0xee, 0x16, 0xcb, 0x38, 0x9c, 0x7a, 0x33, + 0x4f, 0xb4, 0xd0, 0x64, 0x8c, 0x17, 0xca, 0x38, 0x9a, 0x7a, 0xa4, 0x34, 0x07, 0x3b, 0x6d, 0x43, + 0xaf, 0xed, 0xe4, 0x4f, 0x06, 0x41, 0xa7, 0xd0, 0xb3, 0x43, 0x85, 0x9e, 0xf5, 0x0a, 0x4d, 0x4f, + 0x5b, 0x85, 0xa6, 0xa7, 0x84, 0xc5, 0x65, 0xab, 0x50, 0x71, 0x49, 0x53, 0xfb, 0x46, 0xe7, 0x75, + 0x71, 0xda, 0xd8, 0xf1, 0x46, 0xa2, 0xc3, 0xfc, 0x03, 0x18, 0xff, 0x70, 0x87, 0xda, 0xf5, 0x1c, + 0x09, 0x87, 0x48, 0x04, 0x17, 0xc6, 0xbd, 0xb6, 0x4b, 0x0b, 0xf8, 0xa7, 0x10, 0x08, 0xea, 0xc2, + 0xb4, 0x7a, 0x30, 0x20, 0x13, 0x16, 0x36, 0x9b, 0x3c, 0x77, 0xd7, 0x88, 0xe5, 0xba, 0x28, 0x50, + 0x3b, 0xed, 0x5a, 0x60, 0xb8, 0xf3, 0x3d, 0xda, 0xcf, 0x8e, 0x27, 0x2c, 0x48, 0x7e, 0x84, 0xe8, + 0x44, 0xa1, 0xae, 0x44, 0xad, 0xde, 0xfe, 0x58, 0x71, 0xf0, 0xbf, 0x5d, 0x7c, 0xf7, 0xb2, 0x55, + 0x3c, 0x9d, 0x7b, 0x9d, 0x7a, 0x03, 0x9d, 0x52, 0x43, 0xe7, 0xb2, 0x90, 0xf3, 0xd4, 0x2c, 0xd6, + 0x13, 0x0e, 0x25, 0x9f, 0x83, 0x4f, 0x7e, 0x18, 0x30, 0xfb, 0xff, 0xe6, 0xa5, 0xe5, 0xd8, 0xfc, + 0x2b, 0x3f, 0xff, 0x27, 0x00, 0x00, 0xff, 0xff, 0xdd, 0x04, 0x69, 0xb6, 0xa7, 0x07, 0x00, 0x00, } diff --git a/bolt/internal/internal.proto b/bolt/internal/internal.proto index d67d3ba55e..14d468c2ad 100644 --- a/bolt/internal/internal.proto +++ b/bolt/internal/internal.proto @@ -18,6 +18,7 @@ message Dashboard { int64 ID = 1; // ID is the unique ID of the dashboard string Name = 2; // Name is the user-defined name of the dashboard repeated DashboardCell cells = 3; // a representation of all visual data required for rendering the dashboard + repeated Template templates = 4; // Templates replace template variables within InfluxQL } message DashboardCell { @@ -31,6 +32,30 @@ message DashboardCell { string ID = 8; // id is the unique id of the dashboard. MIGRATED FIELD added in 1.2.0-beta6 } +message Template { + string ID = 1; // ID is the unique ID associated with this template + string temp_var = 2; + repeated TemplateValue values = 3; + string type = 4; // Type can be fieldKeys, tagKeys, tagValues, CSV, constant, query, measurements, databases + string label = 5; // Label is a user-facing description of the Template + TemplateQuery query = 6; // Query is used to generate the choices for a template +} + +message TemplateValue { + string type = 1; // Type can be tagKey, tagValue, fieldKey, csv, measurement, database, constant + string value = 2; // Value is the specific value used to replace a template in an InfluxQL query + bool selected = 3; // Selected states that this variable has been picked to use for replacement +} + +message TemplateQuery { + string command = 1; // Command is the query itself + string db = 2; // DB the database for the query (optional) + string rp = 3; // RP is a retention policy and optional; + string measurement = 4; // Measurement is the optinally selected measurement for the query + string tag_key = 5; // TagKey is the optionally selected tag key for the query + string field_key = 6; // FieldKey is the optionally selected field key for the query +} + message Server { int64 ID = 1; // ID is the unique ID of the server string Name = 2; // Name is the user-defined name for the server From d45a1ae7f465925db90eee3d365cb93af5f2bc19 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 20 Apr 2017 14:32:02 -0500 Subject: [PATCH 055/163] Add measurements and databases to template variables --- server/templates.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/templates.go b/server/templates.go index 7453dc38d8..16500e97a0 100644 --- a/server/templates.go +++ b/server/templates.go @@ -15,20 +15,21 @@ func ValidTemplateRequest(template *chronograf.Template) error { switch template.Type { default: return fmt.Errorf("Unknown template type %s", template.Type) - case "query", "constant", "csv", "fieldKeys", "tagKeys", "tagValues": + case "query", "constant", "csv", "fieldKeys", "tagKeys", "tagValues", "measurements", "databases": } for _, v := range template.Values { switch v.Type { default: return fmt.Errorf("Unknown template variable type %s", v.Type) - case "csv", "fieldKey", "tagKey", "tagValue": + case "csv", "fieldKey", "tagKey", "tagValue", "measurement", "database", "constant": } } if template.Type == "query" && template.Query == nil { return fmt.Errorf("No query set for template of type 'query'") } + return nil } @@ -150,6 +151,7 @@ func (s *Service) TemplateID(w http.ResponseWriter, r *http.Request) { if t.ID == chronograf.TemplateID(tid) { res := newTemplateResponse(chronograf.DashboardID(id), t) encodeJSON(w, http.StatusOK, res, s.Logger) + return } } From 5c64a24760e5f0b2497a8ac18b89d5462ebd4f2c Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 20 Apr 2017 14:35:59 -0500 Subject: [PATCH 056/163] Fix whitespace in proto --- bolt/internal/internal.proto | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bolt/internal/internal.proto b/bolt/internal/internal.proto index 14d468c2ad..a927fe6319 100644 --- a/bolt/internal/internal.proto +++ b/bolt/internal/internal.proto @@ -18,7 +18,7 @@ message Dashboard { int64 ID = 1; // ID is the unique ID of the dashboard string Name = 2; // Name is the user-defined name of the dashboard repeated DashboardCell cells = 3; // a representation of all visual data required for rendering the dashboard - repeated Template templates = 4; // Templates replace template variables within InfluxQL + repeated Template templates = 4; // Templates replace template variables within InfluxQL } message DashboardCell { @@ -52,8 +52,8 @@ message TemplateQuery { string db = 2; // DB the database for the query (optional) string rp = 3; // RP is a retention policy and optional; string measurement = 4; // Measurement is the optinally selected measurement for the query - string tag_key = 5; // TagKey is the optionally selected tag key for the query - string field_key = 6; // FieldKey is the optionally selected field key for the query + string tag_key = 5; // TagKey is the optionally selected tag key for the query + string field_key = 6; // FieldKey is the optionally selected field key for the query } message Server { From 02c81fbc82a487343bc3a4e74e2020acb2f29b7b Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Thu, 20 Apr 2017 14:01:09 -0700 Subject: [PATCH 057/163] Updated tempVars schema and refactor how selected values are reduced --- ui/src/dashboards/actions/index.js | 13 ++++++------ ui/src/dashboards/components/Dashboard.js | 20 +++++++++++++------ ui/src/dashboards/containers/DashboardPage.js | 5 +---- ui/src/dashboards/reducers/ui.js | 20 ++++++++++++++----- ui/src/shared/components/AutoRefresh.js | 6 ++---- ui/src/shared/components/LayoutRenderer.js | 3 +-- 6 files changed, 39 insertions(+), 28 deletions(-) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index ce856d7031..5a926f6684 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -128,32 +128,31 @@ export const tvSelected = (dashboardID, tvID, values) => ({ const tempVars = [ { - id: 1, + id: '1', type: 'query', label: 'test query', tempVar: '$REGION', query: { - db: 'db1.rp1', + db: 'db1', + rp: 'rp1', text: 'SHOW TAGS WHERE HUNTER = "coo"', }, values: [ {value: 'us-west', type: 'tagKey'}, - {value: 'us-east', type: 'tagKey'}, + {value: 'us-east', type: 'tagKey', selected: true}, {value: 'us-mount', type: 'tagKey'}, ], - selectedValues: [{value: 'us-east', type: 'tagKey'}], }, { - id: 2, + id: '2', type: 'csv', label: 'test csv', tempVar: '$TEMPERATURE', values: [ {value: '98.7', type: 'measurement'}, {value: '99.1', type: 'measurement'}, - {value: '101.3', type: 'measurement'}, + {value: '101.3', type: 'measurement', selected: true}, ], - selectedValues: [{value: '99.1', type: 'measurement'}], }, ] diff --git a/ui/src/dashboards/components/Dashboard.js b/ui/src/dashboards/components/Dashboard.js index 8b4454bb2c..a0f3638912 100644 --- a/ui/src/dashboards/components/Dashboard.js +++ b/ui/src/dashboards/components/Dashboard.js @@ -1,6 +1,8 @@ import React, {PropTypes} from 'react' import classnames from 'classnames' +import omit from 'lodash/omit' + import LayoutRenderer from 'shared/components/LayoutRenderer' import Dropdown from 'shared/components/Dropdown' @@ -49,17 +51,23 @@ const Dashboard = ({
    { - tempVars.map(({id, values, selectedValues}) => { - const items = values ? values.map((value) => Object.assign(value, {text: value.value})) : [] - // TODO: change Dropdown to a MultiSelectDropdown, [0].value to - // the full array, and [item] to all selectedValues when we update + tempVars.map(({id, values}) => { + let selected + const items = values ? values.map((value) => { + if (value.selected) { + selected = value.value + } + return Object.assign(value, {text: value.value}) + }) : [] + // TODO: change Dropdown to a MultiSelectDropdown, `selected` to + // the full array, and [item] to all `selected` values when we update // this component to support multiple values return ( onSelectTV(id, [item])} + selected={selected || "Loading..."} + onChoose={(item) => onSelectTV(id, [item].map((x) => omit(x, 'text')))} /> ) }) diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index b781118f25..5868d2e6ea 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -290,10 +290,7 @@ DashboardPage.propTypes = { values: arrayOf(shape({ type: string.isRequired, value: string.isRequired, - })).isRequired, - selectedValues: arrayOf(shape({ - type: string.isRequired, - value: string.isRequired, + selected: bool, })).isRequired, }), ), diff --git a/ui/src/dashboards/reducers/ui.js b/ui/src/dashboards/reducers/ui.js index c2bc24d0db..1133474a25 100644 --- a/ui/src/dashboards/reducers/ui.js +++ b/ui/src/dashboards/reducers/ui.js @@ -170,14 +170,24 @@ export default function ui(state = initialState, action) { } case TEMPLATE_VARIABLE_SELECTED: { - const {dashboardID, tvID, values} = action.payload + const {dashboardID, tvID, values: updatedSelectedValues} = action.payload const newDashboards = state.dashboards.map((dashboard) => { if (dashboard.id === dashboardID) { - const newTVs = dashboard.tempVars.map((tV) => { - if (tV.id === tvID) { - return {...tV, selectedValues: values} + const newTVs = dashboard.tempVars.map((staleTV) => { + if (staleTV.id === tvID) { + const newValues = staleTV.values.map((staleValue) => { + let selected = false + for (let i = 0; i < updatedSelectedValues.length; i++) { + if (updatedSelectedValues[i].value === staleValue.value) { + selected = true + break + } + } + return {...staleValue, selected} + }) + return {...staleTV, values: newValues} } - return tV + return staleTV }) return {...dashboard, tempVars: newTVs} } diff --git a/ui/src/shared/components/AutoRefresh.js b/ui/src/shared/components/AutoRefresh.js index 50e9a0da3f..b690cfbdeb 100644 --- a/ui/src/shared/components/AutoRefresh.js +++ b/ui/src/shared/components/AutoRefresh.js @@ -4,6 +4,7 @@ import {fetchTimeSeriesAsync} from 'shared/actions/timeSeries' const { arrayOf, + bool, element, func, number, @@ -28,10 +29,7 @@ const AutoRefresh = (ComposedComponent) => { values: arrayOf(shape({ type: string.isRequired, value: string.isRequired, - })).isRequired, - selectedValues: arrayOf(shape({ - type: string.isRequired, - value: string.isRequired, + selected: bool, })).isRequired, })), queries: arrayOf(shape({ diff --git a/ui/src/shared/components/LayoutRenderer.js b/ui/src/shared/components/LayoutRenderer.js index f8f37f4b72..f668b04be2 100644 --- a/ui/src/shared/components/LayoutRenderer.js +++ b/ui/src/shared/components/LayoutRenderer.js @@ -47,12 +47,11 @@ export const LayoutRenderer = React.createClass({ }).isRequired ), tempVars: arrayOf(shape({ - id: number.isRequired, + id: string.isRequired, type: string.isRequired, label: string.isRequired, tempVar: string.isRequired, values: arrayOf(shape()).isRequired, - selectedValues: arrayOf(shape()).isRequired, })).isRequired, host: string, source: string, From ff176164c1dc80ecc608f8e004b26a0912f900e9 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 20 Apr 2017 16:31:00 -0500 Subject: [PATCH 058/163] Remove extra debug info in the dashboard testing --- server/dashboards_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/server/dashboards_test.go b/server/dashboards_test.go index 672d0fa5be..a37c3851a9 100644 --- a/server/dashboards_test.go +++ b/server/dashboards_test.go @@ -302,10 +302,6 @@ func Test_newDashboardResponse(t *testing.T) { for _, tt := range tests { if got := newDashboardResponse(tt.d); !reflect.DeepEqual(got, tt.want) { t.Errorf("%q. newDashboardResponse() = \n%+v\n\n, want\n\n%+v", tt.name, got, tt.want) - g, _ := json.MarshalIndent(got, "", " ") - w, _ := json.MarshalIndent(tt.want, "", " ") - log.Printf(string(g)) - log.Printf(string(w)) } } } From 620158631d742908f37014e3fa95ca844dcda262 Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Thu, 20 Apr 2017 16:19:49 -0700 Subject: [PATCH 059/163] Update prop key to match what API expects --- ui/src/dashboards/actions/index.js | 2 +- ui/src/dashboards/containers/DashboardPage.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index 0773473edd..e3eba59f32 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -145,7 +145,7 @@ const tempVars = [ db: 'db1', rp: 'rp1', measurement: 'm1', - text: 'SHOW TAGS WHERE HUNTER = "coo"', + query: 'SHOW TAGS WHERE HUNTER = "coo"', }, values: [ {value: 'us-west', type: 'tagKey', selected: false}, diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index 5868d2e6ea..bc4bf10724 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -285,7 +285,7 @@ DashboardPage.propTypes = { tempVar: string.isRequired, query: shape({ db: string.isRequired, - text: string.isRequired, + query: string.isRequired, }), values: arrayOf(shape({ type: string.isRequired, From c1e878647493ebeced5ffdccfff3e63e1b84d816 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 20 Apr 2017 18:23:59 -0500 Subject: [PATCH 060/163] Update template query json key to be influxql --- chronograf.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chronograf.go b/chronograf.go index 4cb3dc31ad..e3f5416514 100644 --- a/chronograf.go +++ b/chronograf.go @@ -189,7 +189,7 @@ type DashboardQuery struct { // TemplateQuery is used to retrieve choices for template replacement type TemplateQuery struct { - Command string `json:"query"` // Command is the query itself + Command string `json:"influxql"` // Command is the query itself DB string `json:"db,omitempty"` // DB is optional and if empty will not be used. RP string `json:"rp,omitempty"` // RP is a retention policy and optional; if empty will not be used. Measurement string `json:"measurement"` // Measurement is the optinally selected measurement for the query From 891824eab49be275520a6d9b83f8452c43a7d81f Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Thu, 20 Apr 2017 16:34:30 -0700 Subject: [PATCH 061/163] Update var name to match what API expects --- ui/src/dashboards/actions/index.js | 6 +++--- ui/src/dashboards/components/Dashboard.js | 8 ++++---- ui/src/dashboards/containers/DashboardPage.js | 4 ++-- ui/src/dashboards/reducers/ui.js | 6 +++--- ui/src/shared/actions/timeSeries.js | 4 ++-- ui/src/shared/components/AutoRefresh.js | 6 +++--- ui/src/shared/components/LayoutRenderer.js | 8 ++++---- ui/src/utils/queryUrlGenerator.js | 4 ++-- 8 files changed, 23 insertions(+), 23 deletions(-) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index e3eba59f32..8a17a6bc14 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -135,7 +135,7 @@ export const editTemplate = (dashboardID, templateID, updates) => ({ // Stub Template Variables Data -const tempVars = [ +const templates = [ { id: '1', type: 'query', @@ -171,7 +171,7 @@ const tempVars = [ export const getDashboardsAsync = dashboardID => async dispatch => { try { const {data: {dashboards}} = await getDashboardsAJAX() - const stubbedDashboards = dashboards.map(d => ({...d, tempVars})) + const stubbedDashboards = dashboards.map(d => ({...d, templates})) dispatch(loadDashboards(stubbedDashboards, dashboardID)) } catch (error) { console.error(error) @@ -181,7 +181,7 @@ export const getDashboardsAsync = dashboardID => async dispatch => { export const putDashboard = dashboard => dispatch => { updateDashboardAJAX(dashboard).then(({data}) => { - dispatch(updateDashboard({...data, tempVars})) + dispatch(updateDashboard({...data, templates})) }) } diff --git a/ui/src/dashboards/components/Dashboard.js b/ui/src/dashboards/components/Dashboard.js index a0f3638912..a8794225cd 100644 --- a/ui/src/dashboards/components/Dashboard.js +++ b/ui/src/dashboards/components/Dashboard.js @@ -26,7 +26,7 @@ const Dashboard = ({ return null } - const {tempVars} = dashboard + const {templates} = dashboard const cells = dashboard.cells.map((cell) => { const dashboardCell = {...cell} @@ -51,7 +51,7 @@ const Dashboard = ({
    { - tempVars.map(({id, values}) => { + templates.map(({id, values}) => { let selected const items = values ? values.map((value) => { if (value.selected) { @@ -78,7 +78,7 @@ const Dashboard = ({ {cells.length ? : null} @@ -278,7 +278,7 @@ DashboardPage.propTypes = { shape({ id: number.isRequired, cells: arrayOf(shape({})).isRequired, - tempVars: arrayOf( + templates: arrayOf( shape({ type: string.isRequired, label: string.isRequired, diff --git a/ui/src/dashboards/reducers/ui.js b/ui/src/dashboards/reducers/ui.js index 4568b97d77..7a9e3b99cb 100644 --- a/ui/src/dashboards/reducers/ui.js +++ b/ui/src/dashboards/reducers/ui.js @@ -188,7 +188,7 @@ export default function ui(state = initialState, action) { const {dashboardID, tvID, values: updatedSelectedValues} = action.payload const newDashboards = state.dashboards.map((dashboard) => { if (dashboard.id === dashboardID) { - const newTVs = dashboard.tempVars.map((staleTV) => { + const newTVs = dashboard.templates.map((staleTV) => { if (staleTV.id === tvID) { const newValues = staleTV.values.map((staleValue) => { let selected = false @@ -204,13 +204,13 @@ export default function ui(state = initialState, action) { } return staleTV }) - return {...dashboard, tempVars: newTVs} + return {...dashboard, templates: newTVs} } return dashboard }) return {...state, dashboards: newDashboards} } - + case 'EDIT_TEMPLATE': { const {dashboardID, templateID, updates} = action.payload diff --git a/ui/src/shared/actions/timeSeries.js b/ui/src/shared/actions/timeSeries.js index d49fb9b51b..c6d110e57f 100644 --- a/ui/src/shared/actions/timeSeries.js +++ b/ui/src/shared/actions/timeSeries.js @@ -35,10 +35,10 @@ export const handleError = (error, query, editQueryStatus) => { console.error(error) } -export const fetchTimeSeriesAsync = async ({source, db, rp, query, tempVars}, editQueryStatus = noop) => { +export const fetchTimeSeriesAsync = async ({source, db, rp, query, templates}, editQueryStatus = noop) => { handleLoading(query, editQueryStatus) try { - const {data} = await proxy({source, db, rp, query: query.text, tempVars}) + const {data} = await proxy({source, db, rp, query: query.text, templates}) return handleSuccess(data, query, editQueryStatus) } catch (error) { handleError(error, query, editQueryStatus) diff --git a/ui/src/shared/components/AutoRefresh.js b/ui/src/shared/components/AutoRefresh.js index b690cfbdeb..1f00c5f2f9 100644 --- a/ui/src/shared/components/AutoRefresh.js +++ b/ui/src/shared/components/AutoRefresh.js @@ -18,7 +18,7 @@ const AutoRefresh = (ComposedComponent) => { propTypes: { children: element, autoRefresh: number.isRequired, - tempVars: arrayOf(shape({ + templates: arrayOf(shape({ type: string.isRequired, label: string.isRequired, tempVar: string.isRequired, @@ -77,7 +77,7 @@ const AutoRefresh = (ComposedComponent) => { }, async executeQueries(queries) { - const {tempVars, editQueryStatus} = this.props + const {templates, editQueryStatus} = this.props if (!queries.length) { this.setState({timeSeries: []}) @@ -88,7 +88,7 @@ const AutoRefresh = (ComposedComponent) => { const timeSeriesPromises = queries.map((query) => { const {host, database, rp} = query - return fetchTimeSeriesAsync({source: host, db: database, rp, query, tempVars}, editQueryStatus) + return fetchTimeSeriesAsync({source: host, db: database, rp, query, templates}, editQueryStatus) }) Promise.all(timeSeriesPromises).then(timeSeries => { diff --git a/ui/src/shared/components/LayoutRenderer.js b/ui/src/shared/components/LayoutRenderer.js index f668b04be2..5216c981b8 100644 --- a/ui/src/shared/components/LayoutRenderer.js +++ b/ui/src/shared/components/LayoutRenderer.js @@ -46,7 +46,7 @@ export const LayoutRenderer = React.createClass({ type: string.isRequired, }).isRequired ), - tempVars: arrayOf(shape({ + templates: arrayOf(shape({ id: string.isRequired, type: string.isRequired, label: string.isRequired, @@ -97,13 +97,13 @@ export const LayoutRenderer = React.createClass({ }, renderRefreshingGraph(type, queries) { - const {autoRefresh, tempVars} = this.props + const {autoRefresh, templates} = this.props if (type === 'single-stat') { return ( ) @@ -117,7 +117,7 @@ export const LayoutRenderer = React.createClass({ return ( { +export const proxy = async ({source, query, db, rp, templates}) => { try { return await AJAX({ method: 'POST', url: source, data: { - tempVars, + templates, query, db, rp, From c968c70a270729e08e496e67491b74c30789019a Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Thu, 20 Apr 2017 16:36:10 -0700 Subject: [PATCH 062/163] Make devs happy again --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8e65bbbbfc..8190a2c93d 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ define CHRONOGIRAFFE ,"" _\_ ," ## | 0 0. ," ## ,-\__ `. - ," / `--._;) - "Do ya feel lucky, punk? Huh, do ya?" + ," / `--._;) - "HAI, I'm Chronogiraffe. Let's be friends!" ," ## / ," ## / endef From 5a1f49eaa25edbba3b652ff4a0dc1860e942a038 Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Thu, 20 Apr 2017 16:36:26 -0700 Subject: [PATCH 063/163] Update key name to match what API expects --- ui/src/dashboards/actions/index.js | 4 ++-- ui/src/dashboards/containers/DashboardPage.js | 2 +- ui/src/shared/components/AutoRefresh.js | 2 +- ui/src/shared/components/LayoutRenderer.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index 8a17a6bc14..5cb82b9dcc 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -140,7 +140,7 @@ const templates = [ id: '1', type: 'query', label: 'test query', - tempVar: '$REGION', + code: '$REGION', query: { db: 'db1', rp: 'rp1', @@ -157,7 +157,7 @@ const templates = [ id: '2', type: 'csv', label: 'test csv', - tempVar: '$TEMPERATURE', + code: '$TEMPERATURE', values: [ {value: '98.7', type: 'measurement', selected: false}, {value: '99.1', type: 'measurement', selected: false}, diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index 66f986c044..f97f9635e4 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -282,7 +282,7 @@ DashboardPage.propTypes = { shape({ type: string.isRequired, label: string.isRequired, - tempVar: string.isRequired, + code: string.isRequired, query: shape({ db: string.isRequired, query: string.isRequired, diff --git a/ui/src/shared/components/AutoRefresh.js b/ui/src/shared/components/AutoRefresh.js index 1f00c5f2f9..34350ee67e 100644 --- a/ui/src/shared/components/AutoRefresh.js +++ b/ui/src/shared/components/AutoRefresh.js @@ -21,7 +21,7 @@ const AutoRefresh = (ComposedComponent) => { templates: arrayOf(shape({ type: string.isRequired, label: string.isRequired, - tempVar: string.isRequired, + code: string.isRequired, query: shape({ db: string.isRequired, text: string.isRequired, diff --git a/ui/src/shared/components/LayoutRenderer.js b/ui/src/shared/components/LayoutRenderer.js index 5216c981b8..ab3a3a63ba 100644 --- a/ui/src/shared/components/LayoutRenderer.js +++ b/ui/src/shared/components/LayoutRenderer.js @@ -50,7 +50,7 @@ export const LayoutRenderer = React.createClass({ id: string.isRequired, type: string.isRequired, label: string.isRequired, - tempVar: string.isRequired, + code: string.isRequired, values: arrayOf(shape()).isRequired, })).isRequired, host: string, From 21ba809a6c3d2e142a0dc2302503e6f105b98f5d Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Thu, 20 Apr 2017 16:42:27 -0700 Subject: [PATCH 064/163] Update tV to 'template' for consistency --- ui/src/dashboards/actions/index.js | 4 ++-- ui/src/dashboards/components/Dashboard.js | 8 ++++---- ui/src/dashboards/containers/DashboardPage.js | 8 ++++---- ui/src/dashboards/reducers/ui.js | 14 +++++++------- ui/src/style/pages/dashboards.scss | 2 +- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index 5cb82b9dcc..62c039f11d 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -115,11 +115,11 @@ export const editCellQueryStatus = (queryID, status) => ({ }, }) -export const tvSelected = (dashboardID, tvID, values) => ({ +export const templateSelected = (dashboardID, templateID, values) => ({ type: TEMPLATE_VARIABLE_SELECTED, payload: { dashboardID, - tvID, + templateID, values, }, }) diff --git a/ui/src/dashboards/components/Dashboard.js b/ui/src/dashboards/components/Dashboard.js index a8794225cd..71400d1f4b 100644 --- a/ui/src/dashboards/components/Dashboard.js +++ b/ui/src/dashboards/components/Dashboard.js @@ -20,7 +20,7 @@ const Dashboard = ({ inPresentationMode, onOpenTemplateManager, onSummonOverlayTechnologies, - onSelectTV, + onSelectTemplate, }) => { if (dashboard.id === 0) { return null @@ -45,7 +45,7 @@ const Dashboard = ({ return (
    -
    +
    Template Variables
    @@ -67,7 +67,7 @@ const Dashboard = ({ key={id} items={items} selected={selected || "Loading..."} - onChoose={(item) => onSelectTV(id, [item].map((x) => omit(x, 'text')))} + onChoose={(item) => onSelectTemplate(id, [item].map((x) => omit(x, 'text')))} /> ) }) @@ -130,7 +130,7 @@ Dashboard.propTypes = { autoRefresh: number.isRequired, timeRange: shape({}).isRequired, onOpenTemplateManager: func.isRequired, - onSelectTV: func.isRequired, + onSelectTemplate: func.isRequired, templates: arrayOf(shape()), } diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index f97f9635e4..b62f547696 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -41,7 +41,7 @@ class DashboardPage extends Component { this.handleUpdateDashboardCell = ::this.handleUpdateDashboardCell this.handleCloseTemplateManager = ::this.handleCloseTemplateManager this.handleSummonOverlayTechnologies = ::this.handleSummonOverlayTechnologies - this.handleSelectTV = ::this.handleSelectTV + this.handleSelectTemplate = ::this.handleSelectTemplate } componentDidMount() { @@ -143,9 +143,9 @@ class DashboardPage extends Component { this.props.dashboardActions.deleteDashboardCellAsync(cell) } - handleSelectTV(tvID, values) { + handleSelectTemplate(templateID, values) { const {params: {dashboardID}} = this.props - this.props.dashboardActions.tvSelected(+dashboardID, tvID, values) + this.props.dashboardActions.templateSelected(+dashboardID, templateID, values) } getActiveDashboard() { @@ -242,7 +242,7 @@ class DashboardPage extends Component { onUpdateCell={this.handleUpdateDashboardCell} onOpenTemplateManager={this.handleOpenTemplateManager} onSummonOverlayTechnologies={this.handleSummonOverlayTechnologies} - onSelectTV={this.handleSelectTV} + onSelectTemplate={this.handleSelectTemplate} /> : null}
    diff --git a/ui/src/dashboards/reducers/ui.js b/ui/src/dashboards/reducers/ui.js index 7a9e3b99cb..4c5f0f71da 100644 --- a/ui/src/dashboards/reducers/ui.js +++ b/ui/src/dashboards/reducers/ui.js @@ -185,12 +185,12 @@ export default function ui(state = initialState, action) { } case TEMPLATE_VARIABLE_SELECTED: { - const {dashboardID, tvID, values: updatedSelectedValues} = action.payload + const {dashboardID, templateID, values: updatedSelectedValues} = action.payload const newDashboards = state.dashboards.map((dashboard) => { if (dashboard.id === dashboardID) { - const newTVs = dashboard.templates.map((staleTV) => { - if (staleTV.id === tvID) { - const newValues = staleTV.values.map((staleValue) => { + const newTemplates = dashboard.templates.map((staleTemplate) => { + if (staleTemplate.id === templateID) { + const newValues = staleTemplate.values.map((staleValue) => { let selected = false for (let i = 0; i < updatedSelectedValues.length; i++) { if (updatedSelectedValues[i].value === staleValue.value) { @@ -200,11 +200,11 @@ export default function ui(state = initialState, action) { } return {...staleValue, selected} }) - return {...staleTV, values: newValues} + return {...staleTemplate, values: newValues} } - return staleTV + return staleTemplate }) - return {...dashboard, templates: newTVs} + return {...dashboard, templates: newTemplates} } return dashboard }) diff --git a/ui/src/style/pages/dashboards.scss b/ui/src/style/pages/dashboards.scss index f09753091b..3c1a729cb1 100644 --- a/ui/src/style/pages/dashboards.scss +++ b/ui/src/style/pages/dashboards.scss @@ -66,7 +66,7 @@ $dash-graph-options-arrow: 8px; } .dashboard { - .tv-control-bar { + .template-control-bar { height: 50px; font-size: 18px; font-weight: 400; From e940fa22df15dcb59f44fff0e8e7b56bac1d528a Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Thu, 20 Apr 2017 16:51:49 -0700 Subject: [PATCH 065/163] Update 'query' key to 'influxql' --- ui/src/dashboards/actions/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index 62c039f11d..92b9af8403 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -145,7 +145,7 @@ const templates = [ db: 'db1', rp: 'rp1', measurement: 'm1', - query: 'SHOW TAGS WHERE HUNTER = "coo"', + influxql: 'SHOW TAGS WHERE HUNTER = "coo"', }, values: [ {value: 'us-west', type: 'tagKey', selected: false}, From b8da81171fbc099b2195de5ab1179b5e4ffeb53f Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Thu, 20 Apr 2017 17:31:43 -0700 Subject: [PATCH 066/163] Update var names and propTypes to match spec --- ui/src/dashboards/actions/index.js | 6 +++--- ui/src/dashboards/containers/DashboardPage.js | 3 ++- ui/src/shared/components/AutoRefresh.js | 5 +++-- ui/src/shared/components/LayoutRenderer.js | 8 +------- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index 92b9af8403..32e1ee513a 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -140,12 +140,12 @@ const templates = [ id: '1', type: 'query', label: 'test query', - code: '$REGION', + tempVar: '$REGION', query: { db: 'db1', rp: 'rp1', measurement: 'm1', - influxql: 'SHOW TAGS WHERE HUNTER = "coo"', + influxql: 'SHOW TAGS WHERE CHRONOGIRAFFE = "friend"', }, values: [ {value: 'us-west', type: 'tagKey', selected: false}, @@ -157,7 +157,7 @@ const templates = [ id: '2', type: 'csv', label: 'test csv', - code: '$TEMPERATURE', + tempVar: '$TEMPERATURE', values: [ {value: '98.7', type: 'measurement', selected: false}, {value: '99.1', type: 'measurement', selected: false}, diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index b62f547696..4eac822ea1 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -285,7 +285,8 @@ DashboardPage.propTypes = { code: string.isRequired, query: shape({ db: string.isRequired, - query: string.isRequired, + rp: string, + influxql: string.isRequired, }), values: arrayOf(shape({ type: string.isRequired, diff --git a/ui/src/shared/components/AutoRefresh.js b/ui/src/shared/components/AutoRefresh.js index 34350ee67e..0ab8ac86d0 100644 --- a/ui/src/shared/components/AutoRefresh.js +++ b/ui/src/shared/components/AutoRefresh.js @@ -21,10 +21,11 @@ const AutoRefresh = (ComposedComponent) => { templates: arrayOf(shape({ type: string.isRequired, label: string.isRequired, - code: string.isRequired, + tempVar: string.isRequired, query: shape({ db: string.isRequired, - text: string.isRequired, + rp: string, + influxql: string.isRequired, }), values: arrayOf(shape({ type: string.isRequired, diff --git a/ui/src/shared/components/LayoutRenderer.js b/ui/src/shared/components/LayoutRenderer.js index ab3a3a63ba..c3e1f3499b 100644 --- a/ui/src/shared/components/LayoutRenderer.js +++ b/ui/src/shared/components/LayoutRenderer.js @@ -46,13 +46,7 @@ export const LayoutRenderer = React.createClass({ type: string.isRequired, }).isRequired ), - templates: arrayOf(shape({ - id: string.isRequired, - type: string.isRequired, - label: string.isRequired, - code: string.isRequired, - values: arrayOf(shape()).isRequired, - })).isRequired, + templates: arrayOf(shape()).isRequired, host: string, source: string, onPositionChange: func, From b890f22ca6834e1080357d86b3cf7135a5962319 Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Thu, 20 Apr 2017 18:11:55 -0700 Subject: [PATCH 067/163] Add test for selectTemplate, update spec fixtures --- ui/spec/dashboards/reducers/uiSpec.js | 50 ++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/ui/spec/dashboards/reducers/uiSpec.js b/ui/spec/dashboards/reducers/uiSpec.js index edf941775d..8abdc39f16 100644 --- a/ui/spec/dashboards/reducers/uiSpec.js +++ b/ui/spec/dashboards/reducers/uiSpec.js @@ -11,10 +11,45 @@ import { renameDashboardCell, syncDashboardCell, editTemplate, + templateSelected, } from 'src/dashboards/actions' let state -const d1 = {id: 1, cells: [], name: 'd1', templates: []} +const d1 = { + id: 1, + cells: [], + name: 'd1', + templates: [ + { + id: '1', + type: 'query', + label: 'test query', + tempVar: '$REGION', + query: { + db: 'db1', + rp: 'rp1', + measurement: 'm1', + influxql: 'SHOW TAGS WHERE CHRONOGIRAFFE = "friend"', + }, + values: [ + {value: 'us-west', type: 'tagKey', selected: false}, + {value: 'us-east', type: 'tagKey', selected: true}, + {value: 'us-mount', type: 'tagKey', selected: false}, + ], + }, + { + id: '2', + type: 'csv', + label: 'test csv', + tempVar: '$TEMPERATURE', + values: [ + {value: '98.7', type: 'measurement', selected: false}, + {value: '99.1', type: 'measurement', selected: false}, + {value: '101.3', type: 'measurement', selected: true}, + ], + }, + ], +} const d2 = {id: 2, cells: [], name: 'd2', templates: []} const dashboards = [d1, d2] const c1 = { @@ -142,4 +177,17 @@ describe('DataExplorer.Reducers.UI', () => { const actual = reducer(state, editTemplate(dash.id, tempVar.id, updates)) expect(actual.dashboards[0].templates[0]).to.deep.equal(expected) }) + + it('can select a different template variable', () => { + const dash = _.cloneDeep(d1) + state = { + dashboards: [dash] + } + const value = dash.templates[0].values[2].value + const actual = reducer({dashboards}, templateSelected(dash.id, dash.templates[0].id, [{value}])) + + expect(actual.dashboards[0].templates[0].values[0].selected).to.equal(false) + expect(actual.dashboards[0].templates[0].values[1].selected).to.equal(false) + expect(actual.dashboards[0].templates[0].values[2].selected).to.equal(true) + }) }) From 622bcb1571956f2ede38956da59f43357192db6d Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Fri, 21 Apr 2017 14:20:18 -0600 Subject: [PATCH 068/163] Add template proptypes to Dashboard. --- ui/src/dashboards/components/Dashboard.js | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/ui/src/dashboards/components/Dashboard.js b/ui/src/dashboards/components/Dashboard.js index 71400d1f4b..c47ee88b69 100644 --- a/ui/src/dashboards/components/Dashboard.js +++ b/ui/src/dashboards/components/Dashboard.js @@ -53,12 +53,12 @@ const Dashboard = ({ { templates.map(({id, values}) => { let selected - const items = values ? values.map((value) => { + const items = values.map((value) => { if (value.selected) { selected = value.value } - return Object.assign(value, {text: value.value}) - }) : [] + return {...value, text: value.value} + }) // TODO: change Dropdown to a MultiSelectDropdown, `selected` to // the full array, and [item] to all `selected` values when we update // this component to support multiple values @@ -131,7 +131,21 @@ Dashboard.propTypes = { timeRange: shape({}).isRequired, onOpenTemplateManager: func.isRequired, onSelectTemplate: func.isRequired, - templates: arrayOf(shape()), + templates: arrayOf(shape({ + type: string.isRequired, + label: string.isRequired, + tempVar: string.isRequired, + query: shape({ + db: string.isRequired, + rp: string, + influxql: string.isRequired, + }), + values: arrayOf(shape({ + type: string.isRequired, + value: string.isRequired, + selected: bool, + })).isRequired, + })), } export default Dashboard From 7241c8f94fe7a23ea09d030ddb6b6ef92ea8fe7a Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Fri, 21 Apr 2017 14:48:03 -0600 Subject: [PATCH 069/163] Merge fixes. Shape-of-data fixes. --- ui/src/dashboards/actions/index.js | 6 +- ui/src/dashboards/components/Dashboard.js | 161 +++++++++--------- .../components/TemplateVariableManager.js | 14 +- .../components/TemplateVariableRow.js | 28 +-- .../components/TemplateVariableTable.js | 14 +- ui/src/dashboards/constants/index.js | 2 +- ui/src/dashboards/containers/DashboardPage.js | 25 ++- 7 files changed, 139 insertions(+), 111 deletions(-) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index 32e1ee513a..fa63c23d95 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -12,9 +12,7 @@ import {publishAutoDismissingNotification} from 'shared/dispatchers' import {NEW_DEFAULT_DASHBOARD_CELL} from 'src/dashboards/constants' -import { - TEMPLATE_VARIABLE_SELECTED, -} from 'shared/constants/actionTypes' +import {TEMPLATE_VARIABLE_SELECTED} from 'shared/constants/actionTypes' export const loadDashboards = (dashboards, dashboardID) => ({ type: 'LOAD_DASHBOARDS', @@ -138,7 +136,7 @@ export const editTemplate = (dashboardID, templateID, updates) => ({ const templates = [ { id: '1', - type: 'query', + type: 'tagKeys', label: 'test query', tempVar: '$REGION', query: { diff --git a/ui/src/dashboards/components/Dashboard.js b/ui/src/dashboards/components/Dashboard.js index c47ee88b69..50d396bb13 100644 --- a/ui/src/dashboards/components/Dashboard.js +++ b/ui/src/dashboards/components/Dashboard.js @@ -28,89 +28,90 @@ const Dashboard = ({ const {templates} = dashboard - const cells = dashboard.cells.map((cell) => { + const cells = dashboard.cells.map(cell => { const dashboardCell = {...cell} - dashboardCell.queries = dashboardCell.queries.map(({label, query, queryConfig, db}) => - ({ - label, - query, - queryConfig, - db, - database: db, - text: query, - }) - ) + dashboardCell.queries = dashboardCell.queries.map(({ + label, + query, + queryConfig, + db, + }) => ({ + label, + query, + queryConfig, + db, + database: db, + text: query, + })) return dashboardCell }) return ( -
    +
    Template Variables
    - { - templates.map(({id, values}) => { - let selected - const items = values.map((value) => { - if (value.selected) { - selected = value.value - } - return {...value, text: value.value} - }) - // TODO: change Dropdown to a MultiSelectDropdown, `selected` to - // the full array, and [item] to all `selected` values when we update - // this component to support multiple values - return ( - onSelectTemplate(id, [item].map((x) => omit(x, 'text')))} - /> - ) + {templates.map(({id, values}) => { + let selected + const items = values.map(value => { + if (value.selected) { + selected = value.value + } + return {...value, text: value.value} }) - } - -
    -
    - {cells.length ? - : -
    -

    This Dashboard has no Graphs

    + // TODO: change Dropdown to a MultiSelectDropdown, `selected` to + // the full array, and [item] to all `selected` values when we update + // this component to support multiple values + return ( + + onSelectTemplate(id, [item].map(x => omit(x, 'text')))} + /> + ) + })}
    - } +
    + {cells.length + ? + :
    +

    This Dashboard has no Graphs

    + +
    }
    ) } -const { - arrayOf, - bool, - func, - shape, - string, - number, -} = PropTypes +const {arrayOf, bool, func, shape, string, number} = PropTypes Dashboard.propTypes = { dashboard: shape({}).isRequired, @@ -131,21 +132,25 @@ Dashboard.propTypes = { timeRange: shape({}).isRequired, onOpenTemplateManager: func.isRequired, onSelectTemplate: func.isRequired, - templates: arrayOf(shape({ - type: string.isRequired, - label: string.isRequired, - tempVar: string.isRequired, - query: shape({ - db: string.isRequired, - rp: string, - influxql: string.isRequired, - }), - values: arrayOf(shape({ + templates: arrayOf( + shape({ type: string.isRequired, - value: string.isRequired, - selected: bool, - })).isRequired, - })), + label: string.isRequired, + tempVar: string.isRequired, + query: shape({ + db: string.isRequired, + rp: string, + influxql: string.isRequired, + }), + values: arrayOf( + shape({ + type: string.isRequired, + value: string.isRequired, + selected: bool, + }) + ).isRequired, + }) + ), } export default Dashboard diff --git a/ui/src/dashboards/components/TemplateVariableManager.js b/ui/src/dashboards/components/TemplateVariableManager.js index b9b5d64124..a76c6a4675 100644 --- a/ui/src/dashboards/components/TemplateVariableManager.js +++ b/ui/src/dashboards/components/TemplateVariableManager.js @@ -25,7 +25,7 @@ const TemplateVariableManager = ({onClose, templates}) => (
    ) -const {arrayOf, func, shape, string} = PropTypes +const {arrayOf, bool, func, shape, string} = PropTypes TemplateVariableManager.propTypes = { onClose: func.isRequired, @@ -33,12 +33,18 @@ TemplateVariableManager.propTypes = { shape({ type: string.isRequired, label: string.isRequired, - code: string.isRequired, + tempVar: string.isRequired, query: shape({ db: string.isRequired, - text: string.isRequired, + influxql: string.isRequired, }), - values: arrayOf(string.isRequired), + values: arrayOf( + shape({ + value: string.isRequired, + type: string.isRequired, + selected: bool.isRequired, + }) + ).isRequired, }) ), } diff --git a/ui/src/dashboards/components/TemplateVariableRow.js b/ui/src/dashboards/components/TemplateVariableRow.js index e53d262e1d..fd0722d7e3 100644 --- a/ui/src/dashboards/components/TemplateVariableRow.js +++ b/ui/src/dashboards/components/TemplateVariableRow.js @@ -4,10 +4,10 @@ import Dropdown from 'shared/components/Dropdown' import TemplateQueryBuilder from 'src/dashboards/components/TemplateQueryBuilder' -import {TEMPLATE_VARIBALE_TYPES} from 'src/dashboards/constants' +import {TEMPLATE_VARIABLE_TYPES} from 'src/dashboards/constants' const TemplateVariableRow = ({ - template: {label, code, values}, + template: {label, tempVar, values}, isEditing, selectedType, selectedDatabase, @@ -30,15 +30,15 @@ const TemplateVariableRow = ({ autoFocusTarget={autoFocusTarget} />
    - {values.join(', ')} + {values.map(({value}) => value).join(', ')}
    {isEditing @@ -120,10 +120,10 @@ class RowWrapper extends Component { handleSubmit(e) { e.preventDefault() - // const code = e.target.code.value + // const tempVar = e.target.tempVar.value // const label = e.target.label.value - // updateTempVarsAsync({code, label}) + // updateTempVarsAsync({tempVar, label}) } handleClickOutside() { @@ -190,14 +190,20 @@ RowWrapper.propTypes = { template: shape({ type: string.isRequired, label: string.isRequired, - code: string.isRequired, + tempVar: string.isRequired, query: shape({ db: string, - text: string.isRequired, + influxql: string.isRequired, measurement: string, tagKey: string, }), - values: arrayOf(string.isRequired), + values: arrayOf( + shape({ + value: string.isRequired, + type: string.isRequired, + selected: bool.isRequired, + }) + ).isRequired, }), } diff --git a/ui/src/dashboards/components/TemplateVariableTable.js b/ui/src/dashboards/components/TemplateVariableTable.js index d79a3d63fa..c875203f23 100644 --- a/ui/src/dashboards/components/TemplateVariableTable.js +++ b/ui/src/dashboards/components/TemplateVariableTable.js @@ -20,19 +20,25 @@ const TemplateVariableTable = ({templates}) => (
    ) -const {arrayOf, shape, string} = PropTypes +const {arrayOf, bool, shape, string} = PropTypes TemplateVariableTable.propTypes = { templates: arrayOf( shape({ type: string.isRequired, label: string.isRequired, - code: string.isRequired, + tempVar: string.isRequired, query: shape({ db: string.isRequired, - text: string.isRequired, + influxql: string.isRequired, }), - values: arrayOf(string.isRequired), + values: arrayOf( + shape({ + value: string.isRequired, + type: string.isRequired, + selected: bool.isRequired, + }) + ).isRequired, }) ), } diff --git a/ui/src/dashboards/constants/index.js b/ui/src/dashboards/constants/index.js index 0092862403..d506344221 100644 --- a/ui/src/dashboards/constants/index.js +++ b/ui/src/dashboards/constants/index.js @@ -27,7 +27,7 @@ export const NEW_DASHBOARD = { cells: [NEW_DEFAULT_DASHBOARD_CELL], } -export const TEMPLATE_VARIBALE_TYPES = [ +export const TEMPLATE_VARIABLE_TYPES = [ { text: 'CSV', type: 'csv', diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index 4eac822ea1..0fa6597c0f 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -40,7 +40,8 @@ class DashboardPage extends Component { this.handleRenameDashboardCell = ::this.handleRenameDashboardCell this.handleUpdateDashboardCell = ::this.handleUpdateDashboardCell this.handleCloseTemplateManager = ::this.handleCloseTemplateManager - this.handleSummonOverlayTechnologies = ::this.handleSummonOverlayTechnologies + this.handleSummonOverlayTechnologies = ::this + .handleSummonOverlayTechnologies this.handleSelectTemplate = ::this.handleSelectTemplate } @@ -145,7 +146,11 @@ class DashboardPage extends Component { handleSelectTemplate(templateID, values) { const {params: {dashboardID}} = this.props - this.props.dashboardActions.templateSelected(+dashboardID, templateID, values) + this.props.dashboardActions.templateSelected( + +dashboardID, + templateID, + values + ) } getActiveDashboard() { @@ -282,18 +287,20 @@ DashboardPage.propTypes = { shape({ type: string.isRequired, label: string.isRequired, - code: string.isRequired, + tempVar: string.isRequired, query: shape({ db: string.isRequired, rp: string, influxql: string.isRequired, }), - values: arrayOf(shape({ - type: string.isRequired, - value: string.isRequired, - selected: bool, - })).isRequired, - }), + values: arrayOf( + shape({ + type: string.isRequired, + value: string.isRequired, + selected: bool, + }) + ).isRequired, + }) ), }) ), From b7592834d4d117d2183011390d8105bcb49e1d8b Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Fri, 21 Apr 2017 14:55:39 -0600 Subject: [PATCH 070/163] Fix cancel button on Template screen. Show selected variable text. --- .../dashboards/components/TemplateVariableRow.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ui/src/dashboards/components/TemplateVariableRow.js b/ui/src/dashboards/components/TemplateVariableRow.js index fd0722d7e3..7c99c531c0 100644 --- a/ui/src/dashboards/components/TemplateVariableRow.js +++ b/ui/src/dashboards/components/TemplateVariableRow.js @@ -18,6 +18,7 @@ const TemplateVariableRow = ({ selectedTagKey, onSelectTagKey, onStartEdit, + onEndEdit, autoFocusTarget, onSubmit, }) => ( @@ -40,7 +41,9 @@ const TemplateVariableRow = ({ t.type === selectedType).text + } className={'template-variable--dropdown'} />
    @@ -64,7 +67,11 @@ const TemplateVariableRow = ({ -
    @@ -177,6 +184,7 @@ class RowWrapper extends Component { onSelectMeasurement={this.handleSelectMeasurement} onSelectTagKey={this.handleSelectTagKey} onStartEdit={this.handleStartEdit} + onEndEdit={this.handleEndEdit} autoFocusTarget={autoFocusTarget} onSubmit={this.handleSubmit} /> @@ -215,6 +223,8 @@ TemplateVariableRow.propTypes = { onSelectType: func.isRequired, onSelectDatabase: func.isRequired, onSelectTagKey: func.isRequired, + onStartEdit: func.isRequired, + onEndEdit: func.isRequired, } TableInput.propTypes = { From 9e56b9776c847073c38327563f40bf01d8a89591 Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Fri, 21 Apr 2017 19:52:01 -0600 Subject: [PATCH 071/163] Fix TVM UX flow issue with a React lifecycle antipattern; make TVM editing mode consistent --- chronograf.go | 1 - ui/spec/dashboards/reducers/uiSpec.js | 16 ++-- ui/src/dashboards/actions/index.js | 6 +- .../dashboards/components/DatabaseDropdown.js | 11 ++- .../components/MeasurementDropdown.js | 4 +- .../dashboards/components/TagKeyDropdown.js | 4 +- .../components/TemplateQueryBuilder.js | 14 ++- .../components/TemplateVariableManager.js | 3 +- .../components/TemplateVariableRow.js | 89 ++++++++++++++----- ui/src/dashboards/constants/index.js | 33 ++++++- ui/src/dashboards/containers/DashboardPage.js | 12 ++- ui/src/shared/components/Dropdown.js | 74 ++++++++------- 12 files changed, 192 insertions(+), 75 deletions(-) diff --git a/chronograf.go b/chronograf.go index e3f5416514..57f7812ce9 100644 --- a/chronograf.go +++ b/chronograf.go @@ -194,7 +194,6 @@ type TemplateQuery struct { RP string `json:"rp,omitempty"` // RP is a retention policy and optional; if empty will not be used. Measurement string `json:"measurement"` // Measurement is the optinally selected measurement for the query TagKey string `json:"tagKey"` // TagKey is the optionally selected tag key for the query - FieldKey string `json:"fieldKey"` // FieldKey is the optionally selected field key for the query } // Response is the result of a query against a TimeSeries diff --git a/ui/spec/dashboards/reducers/uiSpec.js b/ui/spec/dashboards/reducers/uiSpec.js index 8abdc39f16..40e96c5526 100644 --- a/ui/spec/dashboards/reducers/uiSpec.js +++ b/ui/spec/dashboards/reducers/uiSpec.js @@ -10,8 +10,8 @@ import { editDashboardCell, renameDashboardCell, syncDashboardCell, - editTemplate, - templateSelected, + templateVariableEdited, + templateVariableSelected, } from 'src/dashboards/actions' let state @@ -174,17 +174,23 @@ describe('DataExplorer.Reducers.UI', () => { } const expected = {...tempVar, ...updates} - const actual = reducer(state, editTemplate(dash.id, tempVar.id, updates)) + const actual = reducer( + state, + templateVariableEdited(dash.id, tempVar.id, updates) + ) expect(actual.dashboards[0].templates[0]).to.deep.equal(expected) }) it('can select a different template variable', () => { const dash = _.cloneDeep(d1) state = { - dashboards: [dash] + dashboards: [dash], } const value = dash.templates[0].values[2].value - const actual = reducer({dashboards}, templateSelected(dash.id, dash.templates[0].id, [{value}])) + const actual = reducer( + {dashboards}, + templateVariableSelected(dash.id, dash.templates[0].id, [{value}]) + ) expect(actual.dashboards[0].templates[0].values[0].selected).to.equal(false) expect(actual.dashboards[0].templates[0].values[1].selected).to.equal(false) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index fa63c23d95..f847bb6582 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -113,7 +113,7 @@ export const editCellQueryStatus = (queryID, status) => ({ }, }) -export const templateSelected = (dashboardID, templateID, values) => ({ +export const templateVariableSelected = (dashboardID, templateID, values) => ({ type: TEMPLATE_VARIABLE_SELECTED, payload: { dashboardID, @@ -122,8 +122,8 @@ export const templateSelected = (dashboardID, templateID, values) => ({ }, }) -export const editTemplate = (dashboardID, templateID, updates) => ({ - type: 'EDIT_TEMPLATE', +export const templateVariableEdited = (dashboardID, templateID, updates) => ({ + type: 'TEMPLATE_VARIABLE_EDITED', payload: { dashboardID, templateID, diff --git a/ui/src/dashboards/components/DatabaseDropdown.js b/ui/src/dashboards/components/DatabaseDropdown.js index 3dfed80984..abc319c2c3 100644 --- a/ui/src/dashboards/components/DatabaseDropdown.js +++ b/ui/src/dashboards/components/DatabaseDropdown.js @@ -28,13 +28,19 @@ class DatabaseDropdown extends Component { render() { const {databases} = this.state - const {database, onSelectDatabase} = this.props + const {database, onSelectDatabase, onStartEdit} = this.props + + // :( + if (!database) { + this.componentDidMount() + } return ( ({text}))} - selected={database} + selected={database || 'Loading...'} onChoose={onSelectDatabase} + onClick={() => onStartEdit(null)} /> ) } @@ -53,6 +59,7 @@ DatabaseDropdown.contextTypes = { DatabaseDropdown.propTypes = { database: string, onSelectDatabase: func.isRequired, + onStartEdit: func.isRequired, } export default DatabaseDropdown diff --git a/ui/src/dashboards/components/MeasurementDropdown.js b/ui/src/dashboards/components/MeasurementDropdown.js index ce18ae0973..d111d34e25 100644 --- a/ui/src/dashboards/components/MeasurementDropdown.js +++ b/ui/src/dashboards/components/MeasurementDropdown.js @@ -29,12 +29,13 @@ class MeasurementDropdown extends Component { render() { const {measurements} = this.state - const {measurement, onSelectMeasurement} = this.props + const {measurement, onSelectMeasurement, onStartEdit} = this.props return ( ({text}))} selected={measurement || 'Select Measurement'} onChoose={onSelectMeasurement} + onClick={() => onStartEdit(null)} /> ) } @@ -71,6 +72,7 @@ MeasurementDropdown.propTypes = { database: string.isRequired, measurement: string, onSelectMeasurement: func.isRequired, + onStartEdit: func.isRequired, } export default MeasurementDropdown diff --git a/ui/src/dashboards/components/TagKeyDropdown.js b/ui/src/dashboards/components/TagKeyDropdown.js index cab6ce9001..2838806686 100644 --- a/ui/src/dashboards/components/TagKeyDropdown.js +++ b/ui/src/dashboards/components/TagKeyDropdown.js @@ -31,12 +31,13 @@ class TagKeyDropdown extends Component { render() { const {tagKeys} = this.state - const {tagKey, onSelectTagKey} = this.props + const {tagKey, onSelectTagKey, onStartEdit} = this.props return ( ({text}))} selected={tagKey || 'Select Tag Key'} onChoose={onSelectTagKey} + onClick={() => onStartEdit(null)} /> ) } @@ -69,6 +70,7 @@ TagKeyDropdown.propTypes = { measurement: string.isRequired, tagKey: string, onSelectTagKey: func.isRequired, + onStartEdit: func.isRequired, } export default TagKeyDropdown diff --git a/ui/src/dashboards/components/TemplateQueryBuilder.js b/ui/src/dashboards/components/TemplateQueryBuilder.js index 2aa238c584..af85b066c3 100644 --- a/ui/src/dashboards/components/TemplateQueryBuilder.js +++ b/ui/src/dashboards/components/TemplateQueryBuilder.js @@ -11,6 +11,7 @@ const TemplateQueryBuilder = ({ onSelectDatabase, onSelectMeasurement, onSelectTagKey, + onStartEdit, }) => { switch (selectedType) { case 'csv': @@ -24,17 +25,19 @@ const TemplateQueryBuilder = ({
    ) - case 'fields': + case 'fieldKeys': case 'tagKeys': return (
    - SHOW {selectedType === 'fields' ? 'FIELD' : 'TAG'} KEYS ON + SHOW {selectedType === 'fieldKeys' ? 'FIELD' : 'TAG'} KEYS ON FROM {selectedDatabase @@ -42,8 +45,9 @@ const TemplateQueryBuilder = ({ database={selectedDatabase} measurement={selectedMeasurement} onSelectMeasurement={onSelectMeasurement} + onStartEdit={onStartEdit} /> - : 'Pick a DB'} + :
    No database selected
    }
    ) case 'tagValues': @@ -53,6 +57,7 @@ const TemplateQueryBuilder = ({ FROM {selectedDatabase @@ -60,6 +65,7 @@ const TemplateQueryBuilder = ({ database={selectedDatabase} measurement={selectedMeasurement} onSelectMeasurement={onSelectMeasurement} + onStartEdit={onStartEdit} /> : 'Pick a DB'} WITH KEY = @@ -69,6 +75,7 @@ const TemplateQueryBuilder = ({ measurement={selectedMeasurement} tagKey={selectedTagKey} onSelectTagKey={onSelectTagKey} + onStartEdit={onStartEdit} /> : 'Pick a Tag Key'}
    @@ -85,6 +92,7 @@ TemplateQueryBuilder.propTypes = { onSelectDatabase: func.isRequired, onSelectMeasurement: func.isRequired, onSelectTagKey: func.isRequired, + onStartEdit: func.isRequired, selectedMeasurement: string, selectedDatabase: string, selectedTagKey: string, diff --git a/ui/src/dashboards/components/TemplateVariableManager.js b/ui/src/dashboards/components/TemplateVariableManager.js index a76c6a4675..43c93ec450 100644 --- a/ui/src/dashboards/components/TemplateVariableManager.js +++ b/ui/src/dashboards/components/TemplateVariableManager.js @@ -10,8 +10,7 @@ const TemplateVariableManager = ({onClose, templates}) => ( Template Variables
    - - + ( -
    +
    t.type === selectedType).text - } + onClick={() => onStartEdit(null)} + selected={TEMPLATE_TYPES.find(t => t.type === selectedType).text} className={'template-variable--dropdown'} />
    @@ -56,6 +66,7 @@ const TemplateVariableRow = ({ selectedMeasurement={selectedMeasurement} selectedTagKey={selectedTagKey} onSelectTagKey={onSelectTagKey} + onStartEdit={onStartEdit} />
    @@ -70,7 +81,7 @@ const TemplateVariableRow = ({ @@ -122,31 +133,65 @@ class RowWrapper extends Component { this.handleSelectMeasurement = ::this.handleSelectMeasurement this.handleSelectTagKey = ::this.handleSelectTagKey this.handleStartEdit = ::this.handleStartEdit - this.handleEndEdit = ::this.handleEndEdit + this.handleCancelEdit = ::this.handleCancelEdit } - handleSubmit(e) { - e.preventDefault() - // const tempVar = e.target.tempVar.value - // const label = e.target.label.value + handleSubmit( + { + selectedDatabase: database, + selectedMeasurement: measurement, + selectedTagKey: tagKey, + selectedType: type, + }, + id + ) { + return e => { + e.preventDefault() - // updateTempVarsAsync({tempVar, label}) + const label = e.target.label.value + const tempVar = e.target.tempVar.value + + console.log({ + id, + type, + label, + tempVar, + query: { + db: database, + measurement, + tagKey, + }, + }) + // updateTempVarsAsync({tempVar, label}) + } } handleClickOutside() { this.setState({isEditing: false}) } - handleEndEdit() { - this.setState({isEditing: false}) - } - handleStartEdit(name) { this.setState({isEditing: true, autoFocusTarget: name}) } + handleCancelEdit() { + const {template: {type, query: {db, measurement, tagKey}}} = this.props + this.setState({ + selectedType: type, + selectedDatabase: db, + selectedMeasurement: measurement, + selectedKey: tagKey, + isEditing: false, + }) + } + handleSelectType(item) { - this.setState({selectedType: item.type}) + this.setState({ + selectedType: item.type, + selectedDatabase: null, + selectedMeasurement: null, + selectedKey: null, + }) } handleSelectDatabase(item) { @@ -184,7 +229,7 @@ class RowWrapper extends Component { onSelectMeasurement={this.handleSelectMeasurement} onSelectTagKey={this.handleSelectTagKey} onStartEdit={this.handleStartEdit} - onEndEdit={this.handleEndEdit} + onCancelEdit={this.handleCancelEdit} autoFocusTarget={autoFocusTarget} onSubmit={this.handleSubmit} /> @@ -224,7 +269,7 @@ TemplateVariableRow.propTypes = { onSelectDatabase: func.isRequired, onSelectTagKey: func.isRequired, onStartEdit: func.isRequired, - onEndEdit: func.isRequired, + onCancelEdit: func.isRequired, } TableInput.propTypes = { diff --git a/ui/src/dashboards/constants/index.js b/ui/src/dashboards/constants/index.js index d506344221..9661d2b2b6 100644 --- a/ui/src/dashboards/constants/index.js +++ b/ui/src/dashboards/constants/index.js @@ -27,7 +27,7 @@ export const NEW_DASHBOARD = { cells: [NEW_DEFAULT_DASHBOARD_CELL], } -export const TEMPLATE_VARIABLE_TYPES = [ +export const TEMPLATE_TYPES = [ { text: 'CSV', type: 'csv', @@ -41,8 +41,8 @@ export const TEMPLATE_VARIABLE_TYPES = [ type: 'measurements', }, { - text: 'Fields', - type: 'fields', + text: 'Field Keys', + type: 'fieldKeys', }, { text: 'Tag Keys', @@ -53,3 +53,30 @@ export const TEMPLATE_VARIABLE_TYPES = [ type: 'tagValues', }, ] + +export const TEMPLATE_VARIABLE_TYPES = [ + { + text: 'CSV', + type: 'csv', + }, + { + text: 'Database', + type: 'database', + }, + { + text: 'Measurement', + type: 'measurement', + }, + { + text: 'Field Key', + type: 'fieldKey', + }, + { + text: 'Tag Key', + type: 'tagKey', + }, + { + text: 'Tag Value', + type: 'tagValue', + }, +] diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index 0fa6597c0f..194dec0e2b 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -146,13 +146,22 @@ class DashboardPage extends Component { handleSelectTemplate(templateID, values) { const {params: {dashboardID}} = this.props - this.props.dashboardActions.templateSelected( + this.props.dashboardActions.templateVariableSelected( +dashboardID, templateID, values ) } + handleEditTemplate(templateVariableID, updates) { + const {params: {dashboardID}} = this.props + this.props.dashboardActions.templateVariableEdited( + +dashboardID, + templateVariableID, + updates + ) + } + getActiveDashboard() { const {params: {dashboardID}, dashboards} = this.props return dashboards.find(d => d.id === +dashboardID) @@ -248,6 +257,7 @@ class DashboardPage extends Component { onOpenTemplateManager={this.handleOpenTemplateManager} onSummonOverlayTechnologies={this.handleSummonOverlayTechnologies} onSelectTemplate={this.handleSelectTemplate} + onEditTemplate={this.handleEditTemplate} /> : null}
    diff --git a/ui/src/shared/components/Dropdown.js b/ui/src/shared/components/Dropdown.js index 8d18c6a55f..047ae41c28 100644 --- a/ui/src/shared/components/Dropdown.js +++ b/ui/src/shared/components/Dropdown.js @@ -2,19 +2,17 @@ import React, {PropTypes} from 'react' import classnames from 'classnames' import OnClickOutside from 'shared/components/OnClickOutside' -const { - arrayOf, - shape, - string, - func, -} = PropTypes +const {arrayOf, shape, string, func} = PropTypes const Dropdown = React.createClass({ propTypes: { - items: arrayOf(shape({ - text: string.isRequired, - })).isRequired, + items: arrayOf( + shape({ + text: string.isRequired, + }) + ).isRequired, onChoose: func.isRequired, + onClick: func, selected: string.isRequired, iconName: string, className: string, @@ -40,6 +38,9 @@ const Dropdown = React.createClass({ if (e) { e.stopPropagation() } + if (this.props.onClick) { + this.props.onClick(e) + } this.setState({isOpen: !this.state.isOpen}) }, handleAction(e, action, item) { @@ -53,31 +54,42 @@ const Dropdown = React.createClass({ return (
    - {iconName ? : null} + {iconName + ? + : null} {selected}
    - {self.state.isOpen ? - + {self.state.isOpen + ? : null}
    ) From a993686130f8f22b2eacc0b1e88f39f255303174 Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Fri, 21 Apr 2017 19:06:24 -0700 Subject: [PATCH 072/163] Revert FieldKey struct schema changes to chronograf that break build --- chronograf.go | 1 + 1 file changed, 1 insertion(+) diff --git a/chronograf.go b/chronograf.go index 57f7812ce9..e3f5416514 100644 --- a/chronograf.go +++ b/chronograf.go @@ -194,6 +194,7 @@ type TemplateQuery struct { RP string `json:"rp,omitempty"` // RP is a retention policy and optional; if empty will not be used. Measurement string `json:"measurement"` // Measurement is the optinally selected measurement for the query TagKey string `json:"tagKey"` // TagKey is the optionally selected tag key for the query + FieldKey string `json:"fieldKey"` // FieldKey is the optionally selected field key for the query } // Response is the result of a query against a TimeSeries From c4844ec865cdc111329442bb236ab45417d1cc3d Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Mon, 24 Apr 2017 12:34:39 -0600 Subject: [PATCH 073/163] onSubmit on Template Variable Manager is now providing original template, including link data, to handler. --- .../components/TemplateVariableManager.js | 12 +++++- .../components/TemplateVariableRow.js | 43 +++++++++---------- .../components/TemplateVariableTable.js | 13 ++++-- ui/src/dashboards/containers/DashboardPage.js | 18 ++++---- 4 files changed, 51 insertions(+), 35 deletions(-) diff --git a/ui/src/dashboards/components/TemplateVariableManager.js b/ui/src/dashboards/components/TemplateVariableManager.js index 43c93ec450..7adc2e43c9 100644 --- a/ui/src/dashboards/components/TemplateVariableManager.js +++ b/ui/src/dashboards/components/TemplateVariableManager.js @@ -3,7 +3,11 @@ import OnClickOutside from 'react-onclickoutside' import TemplateVariableTable from 'src/dashboards/components/TemplateVariableTable' -const TemplateVariableManager = ({onClose, templates}) => ( +const TemplateVariableManager = ({ + onClose, + onEditTemplateVariable, + templates, +}) => (
    @@ -19,7 +23,10 @@ const TemplateVariableManager = ({onClose, templates}) => (
    - +
    ) @@ -28,6 +35,7 @@ const {arrayOf, bool, func, shape, string} = PropTypes TemplateVariableManager.propTypes = { onClose: func.isRequired, + onEditTemplateVariable: func.isRequired, templates: arrayOf( shape({ type: string.isRequired, diff --git a/ui/src/dashboards/components/TemplateVariableRow.js b/ui/src/dashboards/components/TemplateVariableRow.js index af796211e0..3acbb96db0 100644 --- a/ui/src/dashboards/components/TemplateVariableRow.js +++ b/ui/src/dashboards/components/TemplateVariableRow.js @@ -7,7 +7,7 @@ import TemplateQueryBuilder import {TEMPLATE_TYPES} from 'src/dashboards/constants' const TemplateVariableRow = ({ - template: {id, label, tempVar, values}, + template: {label, tempVar, values}, isEditing, selectedType, selectedDatabase, @@ -24,15 +24,12 @@ const TemplateVariableRow = ({ }) => ( { e.preventDefault() const label = e.target.label.value const tempVar = e.target.tempVar.value - console.log({ - id, + const {template, onEditTemplateVariable} = this.props + + onEditTemplateVariable(template, { type, label, tempVar, @@ -162,7 +158,6 @@ class RowWrapper extends Component { tagKey, }, }) - // updateTempVarsAsync({tempVar, label}) } } @@ -257,7 +252,11 @@ RowWrapper.propTypes = { selected: bool.isRequired, }) ).isRequired, + links: shape({ + self: string.isRequired, + }).isRequired, }), + onEditTemplateVariable: func.isRequired, } TemplateVariableRow.propTypes = { diff --git a/ui/src/dashboards/components/TemplateVariableTable.js b/ui/src/dashboards/components/TemplateVariableTable.js index c875203f23..527d64cb0a 100644 --- a/ui/src/dashboards/components/TemplateVariableTable.js +++ b/ui/src/dashboards/components/TemplateVariableTable.js @@ -2,7 +2,7 @@ import React, {PropTypes} from 'react' import TemplateVariableRow from 'src/dashboards/components/TemplateVariableRow' -const TemplateVariableTable = ({templates}) => ( +const TemplateVariableTable = ({templates, onEditTemplateVariable}) => (
    @@ -15,12 +15,18 @@ const TemplateVariableTable = ({templates}) => (
    - {templates.map(t => )} + {templates.map(t => ( + + ))}
    ) -const {arrayOf, bool, shape, string} = PropTypes +const {arrayOf, bool, func, shape, string} = PropTypes TemplateVariableTable.propTypes = { templates: arrayOf( @@ -41,6 +47,7 @@ TemplateVariableTable.propTypes = { ).isRequired, }) ), + onEditTemplateVariable: func.isRequired, } export default TemplateVariableTable diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index 194dec0e2b..c2f6aebf8b 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -43,6 +43,7 @@ class DashboardPage extends Component { this.handleSummonOverlayTechnologies = ::this .handleSummonOverlayTechnologies this.handleSelectTemplate = ::this.handleSelectTemplate + this.handleEditTemplateVariable = ::this.handleEditTemplateVariable } componentDidMount() { @@ -153,13 +154,14 @@ class DashboardPage extends Component { ) } - handleEditTemplate(templateVariableID, updates) { - const {params: {dashboardID}} = this.props - this.props.dashboardActions.templateVariableEdited( - +dashboardID, - templateVariableID, - updates - ) + handleEditTemplateVariable(staleTemplateVariable, newTemplateVariable) { + // const {params: {dashboardID}} = this.props + console.log(staleTemplateVariable, newTemplateVariable) + // this.props.dashboardActions.templateVariableEdited( + // +dashboardID, + // templateVariableID, + // updates + // ) } getActiveDashboard() { @@ -191,6 +193,7 @@ class DashboardPage extends Component { ? @@ -257,7 +260,6 @@ class DashboardPage extends Component { onOpenTemplateManager={this.handleOpenTemplateManager} onSummonOverlayTechnologies={this.handleSummonOverlayTechnologies} onSelectTemplate={this.handleSelectTemplate} - onEditTemplate={this.handleEditTemplate} /> : null}
    From 51cde6c2709792cdcb600df8124f691acb3b56d2 Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Mon, 24 Apr 2017 13:49:50 -0600 Subject: [PATCH 074/163] PUT to Template Variable API works. --- ui/spec/dashboards/reducers/uiSpec.js | 21 ++++++------ ui/src/dashboards/actions/index.js | 33 ++++++++++++++++--- ui/src/dashboards/apis/index.js | 22 +++++++++++-- ui/src/dashboards/containers/DashboardPage.js | 14 ++++---- ui/src/dashboards/reducers/ui.js | 30 ++++++++--------- 5 files changed, 80 insertions(+), 40 deletions(-) diff --git a/ui/spec/dashboards/reducers/uiSpec.js b/ui/spec/dashboards/reducers/uiSpec.js index 40e96c5526..f4212632b5 100644 --- a/ui/spec/dashboards/reducers/uiSpec.js +++ b/ui/spec/dashboards/reducers/uiSpec.js @@ -10,7 +10,7 @@ import { editDashboardCell, renameDashboardCell, syncDashboardCell, - templateVariableEdited, + editTemplateVariableSuccess, templateVariableSelected, } from 'src/dashboards/actions' @@ -63,10 +63,11 @@ const c1 = { } const cells = [c1] const tempVar = { - id: 1, + ...d1.templates[0], + id: '1', type: 'measurement', label: 'test query', - code: '$HOSTS', + tempVar: '$HOSTS', query: { db: 'db1', text: 'SHOW TAGS WHERE HUNTER = "coo"', @@ -167,17 +168,17 @@ describe('DataExplorer.Reducers.UI', () => { state = { dashboards: [dash], } - const updates = { - code: '$NEWCODE', + const edited = { + ...d1.templates[0], + id: '1', + tempVar: '$NEWCODE', label: 'new label', type: 'tagKey', } - const expected = {...tempVar, ...updates} + const expected = {...tempVar, ...edited} + + const actual = reducer(state, editTemplateVariableSuccess(dash.id, edited)) - const actual = reducer( - state, - templateVariableEdited(dash.id, tempVar.id, updates) - ) expect(actual.dashboards[0].templates[0]).to.deep.equal(expected) }) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index f847bb6582..cc6aa5d4f1 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -5,10 +5,12 @@ import { updateDashboardCell as updateDashboardCellAJAX, addDashboardCell as addDashboardCellAJAX, deleteDashboardCell as deleteDashboardCellAJAX, + editTemplateVariable as editTemplateVariableAJAX, } from 'src/dashboards/apis' import {publishNotification} from 'shared/actions/notifications' import {publishAutoDismissingNotification} from 'shared/dispatchers' +// import {errorThrown} from 'shared/actions/errors' import {NEW_DEFAULT_DASHBOARD_CELL} from 'src/dashboards/constants' @@ -122,12 +124,11 @@ export const templateVariableSelected = (dashboardID, templateID, values) => ({ }, }) -export const templateVariableEdited = (dashboardID, templateID, updates) => ({ - type: 'TEMPLATE_VARIABLE_EDITED', +export const editTemplateVariableSuccess = (dashboardID, data) => ({ + type: 'EDIT_TEMPLATE_VARIABLE_SUCCESS', payload: { dashboardID, - templateID, - updates, + data, }, }) @@ -150,6 +151,9 @@ const templates = [ {value: 'us-east', type: 'tagKey', selected: true}, {value: 'us-mount', type: 'tagKey', selected: false}, ], + links: { + self: '/chronograf/v1/dashboards/2/templates/1', + }, }, { id: '2', @@ -161,6 +165,9 @@ const templates = [ {value: '99.1', type: 'measurement', selected: false}, {value: '101.3', type: 'measurement', selected: true}, ], + links: { + self: '/chronograf/v1/dashboards/2/templates/2', + }, }, ] @@ -232,3 +239,21 @@ export const deleteDashboardCellAsync = cell => async dispatch => { throw error } } + +export const editTemplateVariableAsync = ( + dashboardID, + staleTemplateVariable, + editedTemplateVariable +) => async dispatch => { + // dispatch(editTemplateVariableRequested()) + try { + const {data} = await editTemplateVariableAJAX( + staleTemplateVariable, + editedTemplateVariable + ) + dispatch(editTemplateVariableSuccess(+dashboardID, data)) + } catch (error) { + // dispatch(errorThrown(error)) + // dispatch(editTemplateVariableFailed()) + } +} diff --git a/ui/src/dashboards/apis/index.js b/ui/src/dashboards/apis/index.js index baded96042..99a335c460 100644 --- a/ui/src/dashboards/apis/index.js +++ b/ui/src/dashboards/apis/index.js @@ -23,7 +23,7 @@ export function updateDashboardCell(cell) { }) } -export const createDashboard = async (dashboard) => { +export const createDashboard = async dashboard => { try { return await AJAX({ method: 'POST', @@ -36,7 +36,7 @@ export const createDashboard = async (dashboard) => { } } -export const deleteDashboard = async (dashboard) => { +export const deleteDashboard = async dashboard => { try { return await AJAX({ method: 'DELETE', @@ -61,7 +61,7 @@ export const addDashboardCell = async (dashboard, cell) => { } } -export const deleteDashboardCell = async (cell) => { +export const deleteDashboardCell = async cell => { try { return await AJAX({ method: 'DELETE', @@ -72,3 +72,19 @@ export const deleteDashboardCell = async (cell) => { throw error } } + +export const editTemplateVariable = async ( + staleTemplateVariable, + editedTemplateVariable +) => { + try { + return await AJAX({ + method: 'PUT', + url: staleTemplateVariable.links.self, + data: editedTemplateVariable, + }) + } catch (error) { + console.error(error) + throw error + } +} diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index c2f6aebf8b..3bd29eaecc 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -154,14 +154,12 @@ class DashboardPage extends Component { ) } - handleEditTemplateVariable(staleTemplateVariable, newTemplateVariable) { - // const {params: {dashboardID}} = this.props - console.log(staleTemplateVariable, newTemplateVariable) - // this.props.dashboardActions.templateVariableEdited( - // +dashboardID, - // templateVariableID, - // updates - // ) + handleEditTemplateVariable(staleTemplateVariable, editedTemplateVariable) { + this.props.dashboardActions.editTemplateVariableAsync( + this.props.params.dashboardID, + staleTemplateVariable, + editedTemplateVariable + ) } getActiveDashboard() { diff --git a/ui/src/dashboards/reducers/ui.js b/ui/src/dashboards/reducers/ui.js index 5c72626b49..90dd954e55 100644 --- a/ui/src/dashboards/reducers/ui.js +++ b/ui/src/dashboards/reducers/ui.js @@ -10,9 +10,7 @@ const initialState = { cellQueryStatus: {queryID: null, status: null}, } -import { - TEMPLATE_VARIABLE_SELECTED, -} from 'shared/constants/actionTypes' +import {TEMPLATE_VARIABLE_SELECTED} from 'shared/constants/actionTypes' export default function ui(state = initialState, action) { switch (action.type) { @@ -185,12 +183,16 @@ export default function ui(state = initialState, action) { } case TEMPLATE_VARIABLE_SELECTED: { - const {dashboardID, templateID, values: updatedSelectedValues} = action.payload - const newDashboards = state.dashboards.map((dashboard) => { + const { + dashboardID, + templateID, + values: updatedSelectedValues, + } = action.payload + const newDashboards = state.dashboards.map(dashboard => { if (dashboard.id === dashboardID) { - const newTemplates = dashboard.templates.map((staleTemplate) => { + const newTemplates = dashboard.templates.map(staleTemplate => { if (staleTemplate.id === templateID) { - const newValues = staleTemplate.values.map((staleValue) => { + const newValues = staleTemplate.values.map(staleValue => { let selected = false for (let i = 0; i < updatedSelectedValues.length; i++) { if (updatedSelectedValues[i].value === staleValue.value) { @@ -211,18 +213,16 @@ export default function ui(state = initialState, action) { return {...state, dashboards: newDashboards} } - case 'EDIT_TEMPLATE': { - const {dashboardID, templateID, updates} = action.payload - + case 'EDIT_TEMPLATE_VARIABLE_SUCCESS': { + const {dashboardID, data} = action.payload + debugger const dashboards = state.dashboards.map( d => (d.id === dashboardID ? { - ...d, - templates: d.templates.map( - t => (t.id === templateID ? {...t, ...updates} : t) - ), - } + ...d, + templates: d.templates.map(t => (t.id === data.id ? data : t)), + } : d) ) return {...state, dashboards} From a19321bff9fdb43dbea23295d1e176e626934e3c Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Mon, 24 Apr 2017 17:12:26 -0700 Subject: [PATCH 075/163] 'Submit' in TVM now retrieves query values; remove template variable fixture data --- ui/src/dashboards/actions/index.js | 81 +++++++++---------- ui/src/dashboards/apis/index.js | 17 ++++ .../components/TemplateVariableManager.js | 13 ++- .../components/TemplateVariableRow.js | 30 +++++-- .../components/TemplateVariableTable.js | 6 +- ui/src/dashboards/constants/index.js | 33 ++------ ui/src/dashboards/containers/DashboardPage.js | 38 +++++++-- ui/src/dashboards/reducers/ui.js | 2 - ...BigLeagueTemplateVariableQueryGenerator.js | 56 +++++++++++++ ui/src/shared/parsing/index.js | 24 ++++++ ui/src/shared/parsing/showMeasurements.js | 3 +- ui/src/utils/queryUrlGenerator.js | 4 +- 12 files changed, 213 insertions(+), 94 deletions(-) create mode 100644 ui/src/dashboards/utils/onlyTheBigliestBigLeagueTemplateVariableQueryGenerator.js create mode 100644 ui/src/shared/parsing/index.js diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index cc6aa5d4f1..c2b308a67c 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -6,12 +6,15 @@ import { addDashboardCell as addDashboardCellAJAX, deleteDashboardCell as deleteDashboardCellAJAX, editTemplateVariable as editTemplateVariableAJAX, + runTemplateVariableQuery as runTemplateVariableQueryAJAX, } from 'src/dashboards/apis' import {publishNotification} from 'shared/actions/notifications' import {publishAutoDismissingNotification} from 'shared/dispatchers' // import {errorThrown} from 'shared/actions/errors' +import parsers from 'shared/parsing' + import {NEW_DEFAULT_DASHBOARD_CELL} from 'src/dashboards/constants' import {TEMPLATE_VARIABLE_SELECTED} from 'shared/constants/actionTypes' @@ -132,52 +135,20 @@ export const editTemplateVariableSuccess = (dashboardID, data) => ({ }, }) -// Stub Template Variables Data - -const templates = [ - { - id: '1', - type: 'tagKeys', - label: 'test query', - tempVar: '$REGION', - query: { - db: 'db1', - rp: 'rp1', - measurement: 'm1', - influxql: 'SHOW TAGS WHERE CHRONOGIRAFFE = "friend"', - }, - values: [ - {value: 'us-west', type: 'tagKey', selected: false}, - {value: 'us-east', type: 'tagKey', selected: true}, - {value: 'us-mount', type: 'tagKey', selected: false}, - ], - links: { - self: '/chronograf/v1/dashboards/2/templates/1', - }, +export const runTemplateVariableQuerySuccess = (templateVariable, values) => ({ + type: 'RUN_TEMPLATE_VARIABLE_QUERY_SUCCESS', + payload: { + templateVariable, + values, }, - { - id: '2', - type: 'csv', - label: 'test csv', - tempVar: '$TEMPERATURE', - values: [ - {value: '98.7', type: 'measurement', selected: false}, - {value: '99.1', type: 'measurement', selected: false}, - {value: '101.3', type: 'measurement', selected: true}, - ], - links: { - self: '/chronograf/v1/dashboards/2/templates/2', - }, - }, -] +}) // Async Action Creators -export const getDashboardsAsync = dashboardID => async dispatch => { +export const getDashboardsAsync = () => async dispatch => { try { const {data: {dashboards}} = await getDashboardsAJAX() - const stubbedDashboards = dashboards.map(d => ({...d, templates})) - dispatch(loadDashboards(stubbedDashboards, dashboardID)) + dispatch(loadDashboards(dashboards)) } catch (error) { console.error(error) throw error @@ -186,7 +157,7 @@ export const getDashboardsAsync = dashboardID => async dispatch => { export const putDashboard = dashboard => dispatch => { updateDashboardAJAX(dashboard).then(({data}) => { - dispatch(updateDashboard({...data, templates})) + dispatch(updateDashboard(data)) }) } @@ -253,7 +224,35 @@ export const editTemplateVariableAsync = ( ) dispatch(editTemplateVariableSuccess(+dashboardID, data)) } catch (error) { + console.error(error) // dispatch(errorThrown(error)) // dispatch(editTemplateVariableFailed()) } } + +export const runTemplateVariableQueryAsync = ( + templateVariable, + {source, query, database, rp, tempVars, type, measurement, tagKey} +) => async dispatch => { + // dispatch(runTemplateVariableQueryRequested()) + try { + const {data} = await runTemplateVariableQueryAJAX({ + source, + query, + db: database, + rp, + tempVars, + }) + const parsedData = parsers[type](data, tagKey || measurement) // tagKey covers tagKey and fieldKey + if (parsedData.errors.length) { + throw parsedData.errors + } + dispatch( + runTemplateVariableQuerySuccess(templateVariable, parsedData[type]) + ) + } catch (error) { + console.error(error) + // dispatch(errorThrown(error)) + // dispatch(runTemplateVariableQueryFailed()) + } +} diff --git a/ui/src/dashboards/apis/index.js b/ui/src/dashboards/apis/index.js index 99a335c460..cf3452e024 100644 --- a/ui/src/dashboards/apis/index.js +++ b/ui/src/dashboards/apis/index.js @@ -1,4 +1,5 @@ import AJAX from 'utils/ajax' +import {proxy} from 'utils/queryUrlGenerator' export function getDashboards() { return AJAX({ @@ -88,3 +89,19 @@ export const editTemplateVariable = async ( throw error } } + +export const runTemplateVariableQuery = async ({ + source, + query, + db, + // rp, TODO + tempVars, +}) => { + try { + // TODO: add rp as argument to proxy + return await proxy({source: source.links.proxy, query, db, tempVars}) + } catch (error) { + console.error(error) + throw error + } +} diff --git a/ui/src/dashboards/components/TemplateVariableManager.js b/ui/src/dashboards/components/TemplateVariableManager.js index 7adc2e43c9..900451efd1 100644 --- a/ui/src/dashboards/components/TemplateVariableManager.js +++ b/ui/src/dashboards/components/TemplateVariableManager.js @@ -5,7 +5,8 @@ import TemplateVariableTable const TemplateVariableManager = ({ onClose, - onEditTemplateVariable, + onEditTemplateVariables, + onRunTemplateVariableQuery, templates, }) => (
    @@ -15,6 +16,11 @@ const TemplateVariableManager = ({
    +
    @@ -35,7 +41,8 @@ const {arrayOf, bool, func, shape, string} = PropTypes TemplateVariableManager.propTypes = { onClose: func.isRequired, - onEditTemplateVariable: func.isRequired, + onEditTemplateVariables: func.isRequired, + onRunTemplateVariableQuery: func.isRequired, templates: arrayOf( shape({ type: string.isRequired, diff --git a/ui/src/dashboards/components/TemplateVariableRow.js b/ui/src/dashboards/components/TemplateVariableRow.js index 3acbb96db0..d40215f72e 100644 --- a/ui/src/dashboards/components/TemplateVariableRow.js +++ b/ui/src/dashboards/components/TemplateVariableRow.js @@ -5,6 +5,8 @@ import TemplateQueryBuilder from 'src/dashboards/components/TemplateQueryBuilder' import {TEMPLATE_TYPES} from 'src/dashboards/constants' +import q + from 'src/dashboards/utils/onlyTheBigliestBigLeagueTemplateVariableQueryGenerator' const TemplateVariableRow = ({ template: {label, tempVar, values}, @@ -125,7 +127,7 @@ class RowWrapper extends Component { autoFocusTarget: null, } - this.handleSubmit = ::this.handleSubmit + this.handleRunQuery = ::this.handleRunQuery this.handleSelectType = ::this.handleSelectType this.handleSelectDatabase = ::this.handleSelectDatabase this.handleSelectMeasurement = ::this.handleSelectMeasurement @@ -134,7 +136,7 @@ class RowWrapper extends Component { this.handleCancelEdit = ::this.handleCancelEdit } - handleSubmit({ + handleRunQuery({ selectedDatabase: database, selectedMeasurement: measurement, selectedTagKey: tagKey, @@ -146,18 +148,30 @@ class RowWrapper extends Component { const label = e.target.label.value const tempVar = e.target.tempVar.value - const {template, onEditTemplateVariable} = this.props - - onEditTemplateVariable(template, { + const {template, onRunTemplateVariableQuery} = this.props + const {query, tempVars} = q({ type, label, tempVar, query: { - db: database, + database, + // rp, TODO measurement, tagKey, }, }) + + onRunTemplateVariableQuery(template, { + query, + database, + // rp: TODO + tempVars, + type, + measurement, + tagKey, + }) + + // TODO: save values to state in TVM, using template } } @@ -226,7 +240,7 @@ class RowWrapper extends Component { onStartEdit={this.handleStartEdit} onCancelEdit={this.handleCancelEdit} autoFocusTarget={autoFocusTarget} - onSubmit={this.handleSubmit} + onSubmit={this.handleRunQuery} /> ) } @@ -256,7 +270,7 @@ RowWrapper.propTypes = { self: string.isRequired, }).isRequired, }), - onEditTemplateVariable: func.isRequired, + onRunTemplateVariableQuery: func.isRequired, } TemplateVariableRow.propTypes = { diff --git a/ui/src/dashboards/components/TemplateVariableTable.js b/ui/src/dashboards/components/TemplateVariableTable.js index 527d64cb0a..711f6384b0 100644 --- a/ui/src/dashboards/components/TemplateVariableTable.js +++ b/ui/src/dashboards/components/TemplateVariableTable.js @@ -2,7 +2,7 @@ import React, {PropTypes} from 'react' import TemplateVariableRow from 'src/dashboards/components/TemplateVariableRow' -const TemplateVariableTable = ({templates, onEditTemplateVariable}) => ( +const TemplateVariableTable = ({templates, onRunTemplateVariableQuery}) => (
    @@ -19,7 +19,7 @@ const TemplateVariableTable = ({templates, onEditTemplateVariable}) => ( ))}
    @@ -47,7 +47,7 @@ TemplateVariableTable.propTypes = { ).isRequired, }) ), - onEditTemplateVariable: func.isRequired, + onRunTemplateVariableQuery: func.isRequired, } export default TemplateVariableTable diff --git a/ui/src/dashboards/constants/index.js b/ui/src/dashboards/constants/index.js index 9661d2b2b6..fc1556308e 100644 --- a/ui/src/dashboards/constants/index.js +++ b/ui/src/dashboards/constants/index.js @@ -54,29 +54,10 @@ export const TEMPLATE_TYPES = [ }, ] -export const TEMPLATE_VARIABLE_TYPES = [ - { - text: 'CSV', - type: 'csv', - }, - { - text: 'Database', - type: 'database', - }, - { - text: 'Measurement', - type: 'measurement', - }, - { - text: 'Field Key', - type: 'fieldKey', - }, - { - text: 'Tag Key', - type: 'tagKey', - }, - { - text: 'Tag Value', - type: 'tagValue', - }, -] +export const TEMPLATE_VARIABLE_QUERIES = { + databases: 'SHOW DATABASES', + measurements: 'SHOW MEASUREMENTS ON $database', + fieldKeys: 'SHOW FIELD KEYS ON $database FROM $measurement', + tagKeys: 'SHOW TAG KEYS ON $database FROM $measurement', + tagValues: 'SHOW TAG VALUES ON $database FROM $measurement WITH KEY=$tagKey', +} diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index 3bd29eaecc..fc8b949c5b 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -42,8 +42,9 @@ class DashboardPage extends Component { this.handleCloseTemplateManager = ::this.handleCloseTemplateManager this.handleSummonOverlayTechnologies = ::this .handleSummonOverlayTechnologies + this.handleRunTemplateVariableQuery = ::this.handleRunTemplateVariableQuery this.handleSelectTemplate = ::this.handleSelectTemplate - this.handleEditTemplateVariable = ::this.handleEditTemplateVariable + this.handleEditTemplateVariables = ::this.handleEditTemplateVariables } componentDidMount() { @@ -154,14 +155,36 @@ class DashboardPage extends Component { ) } - handleEditTemplateVariable(staleTemplateVariable, editedTemplateVariable) { - this.props.dashboardActions.editTemplateVariableAsync( - this.props.params.dashboardID, - staleTemplateVariable, - editedTemplateVariable + handleRunTemplateVariableQuery( + templateVariable, + {query, db, tempVars, type, tagKey, measurement} + ) { + const {source} = this.props + this.props.dashboardActions.runTemplateVariableQueryAsync( + templateVariable, + { + source, + query, + db, + // rp, TODO + tempVars, + type, + tagKey, + measurement, + } ) } + // TODO: make this work over array of template variables onSave in TVM + handleEditTemplateVariables(staleTemplateVariable, editedTemplateVariable) { + // // this.props.dashboardActions.editTemplateVariableAsync( + // // this.props.params.dashboardID, + // // staleTemplateVariable, + // // editedTemplateVariable + // // ) + // console.log('hello') + } + getActiveDashboard() { const {params: {dashboardID}, dashboards} = this.props return dashboards.find(d => d.id === +dashboardID) @@ -191,7 +214,8 @@ class DashboardPage extends Component { ? diff --git a/ui/src/dashboards/reducers/ui.js b/ui/src/dashboards/reducers/ui.js index 90dd954e55..801bd7aeae 100644 --- a/ui/src/dashboards/reducers/ui.js +++ b/ui/src/dashboards/reducers/ui.js @@ -32,7 +32,6 @@ export default function ui(state = initialState, action) { case 'UPDATE_DASHBOARD': { const {dashboard} = action.payload const newState = { - dashboard, dashboards: state.dashboards.map( d => (d.id === dashboard.id ? dashboard : d) ), @@ -215,7 +214,6 @@ export default function ui(state = initialState, action) { case 'EDIT_TEMPLATE_VARIABLE_SUCCESS': { const {dashboardID, data} = action.payload - debugger const dashboards = state.dashboards.map( d => (d.id === dashboardID diff --git a/ui/src/dashboards/utils/onlyTheBigliestBigLeagueTemplateVariableQueryGenerator.js b/ui/src/dashboards/utils/onlyTheBigliestBigLeagueTemplateVariableQueryGenerator.js new file mode 100644 index 0000000000..8cb1e090b6 --- /dev/null +++ b/ui/src/dashboards/utils/onlyTheBigliestBigLeagueTemplateVariableQueryGenerator.js @@ -0,0 +1,56 @@ +import {TEMPLATE_VARIABLE_QUERIES} from 'src/dashboards/constants' + +const q = ({ + type, + query: { + database, + // rp, TODO + measurement, + tagKey, + }, +}) => { + const tempVars = [] + + if (database) { + tempVars.push({ + tempVar: '$database', + values: [ + { + type: 'database', + value: database, + }, + ], + }) + } + if (measurement) { + tempVars.push({ + tempVar: '$measurement', + values: [ + { + type: 'measurement', + value: measurement, + }, + ], + }) + } + if (tagKey) { + tempVars.push({ + tempVar: '$tagKey', + values: [ + { + type: 'tagKey', + value: tagKey, + }, + ], + }) + } + + const query = TEMPLATE_VARIABLE_QUERIES[type] + + return { + query, + tempVars, + } +} + +export default q diff --git a/ui/src/shared/parsing/index.js b/ui/src/shared/parsing/index.js new file mode 100644 index 0000000000..7ac11b03bf --- /dev/null +++ b/ui/src/shared/parsing/index.js @@ -0,0 +1,24 @@ +import databases from 'shared/parsing/showDatabases' +import measurements from 'shared/parsing/showMeasurements' +import fieldKeys from 'shared/parsing/showFieldKeys' +import tagKeys from 'shared/parsing/showTagKeys' +import tagValues from 'shared/parsing/showTagValues' + +const parsers = { + databases, + measurements: data => { + const {errors, measurementSets} = measurements(data) + return {errors, measurements: measurementSets[0].measurements} + }, + fieldKeys: (data, key) => { + const {errors, fieldSets} = fieldKeys(data) + return {errors, fieldKeys: fieldSets[key]} + }, + tagKeys, + tagValues: (data, key) => { + const {errors, tags} = tagValues(data) + return {errors, tagValues: tags[key]} + }, +} + +export default parsers diff --git a/ui/src/shared/parsing/showMeasurements.js b/ui/src/shared/parsing/showMeasurements.js index c3141ac312..ab291da5b8 100644 --- a/ui/src/shared/parsing/showMeasurements.js +++ b/ui/src/shared/parsing/showMeasurements.js @@ -21,7 +21,7 @@ export default function parseShowMeasurements(response) { const series = result.series[0] const measurementNameIndex = series.columns.indexOf('name') - const measurements = series.values.map((value) => value[measurementNameIndex]) + const measurements = series.values.map(value => value[measurementNameIndex]) measurementSets.push({ index, @@ -34,4 +34,3 @@ export default function parseShowMeasurements(response) { measurementSets, } } - diff --git a/ui/src/utils/queryUrlGenerator.js b/ui/src/utils/queryUrlGenerator.js index 03c8aef23b..28c4b93eab 100644 --- a/ui/src/utils/queryUrlGenerator.js +++ b/ui/src/utils/queryUrlGenerator.js @@ -1,12 +1,12 @@ import AJAX from 'utils/ajax' -export const proxy = async ({source, query, db, rp, templates}) => { +export const proxy = async ({source, query, db, rp, tempVars}) => { try { return await AJAX({ method: 'POST', url: source, data: { - templates, + tempVars, query, db, rp, From 129512985f0f939a8e713ecf50ad602691a99796 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 20 Apr 2017 11:41:50 -0700 Subject: [PATCH 076/163] Be the change --- .../components/RawQueryEditor.js | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/ui/src/data_explorer/components/RawQueryEditor.js b/ui/src/data_explorer/components/RawQueryEditor.js index bb52d4b607..5ced519d26 100644 --- a/ui/src/data_explorer/components/RawQueryEditor.js +++ b/ui/src/data_explorer/components/RawQueryEditor.js @@ -4,13 +4,7 @@ import Dropdown from 'src/shared/components/Dropdown' import LoadingDots from 'src/shared/components/LoadingDots' import {QUERY_TEMPLATES} from 'src/data_explorer/constants' -const ENTER = 13 -const ESCAPE = 27 -const { - func, - shape, - string, -} = PropTypes +const {func, shape, string} = PropTypes const RawQueryEditor = React.createClass({ propTypes: { query: string.isRequired, @@ -31,10 +25,10 @@ const RawQueryEditor = React.createClass({ }, handleKeyDown(e) { - if (e.keyCode === ENTER) { + if (e.key === 'Enter') { e.preventDefault() this.handleUpdate() - } else if (e.keyCode === ESCAPE) { + } else if (e.key === 'Escape') { this.setState({value: this.state.value}, () => { this.editor.blur() }) @@ -66,23 +60,26 @@ const RawQueryEditor = React.createClass({ onChange={this.handleChange} onKeyDown={this.handleKeyDown} onBlur={this.handleUpdate} - ref={(editor) => this.editor = editor} + ref={editor => this.editor = editor} value={value} placeholder="Enter a query or select database, measurement, and field below and have us build one for you..." autoComplete="off" spellCheck="false" /> {this.renderStatus(status)} - +
    ) }, renderStatus(status) { if (!status) { - return ( -
    - ) + return
    } if (status.loading) { @@ -94,8 +91,20 @@ const RawQueryEditor = React.createClass({ } return ( -
    - +
    + {status.error || status.warn || status.success}
    ) From 5cf3ddc6526db5f521fcf401d4dccd3253990339 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 20 Apr 2017 11:42:13 -0700 Subject: [PATCH 077/163] Remove prettier indentation conflicts --- ui/.eslintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/.eslintrc b/ui/.eslintrc index 395c7d6fca..6a7e6b9602 100644 --- a/ui/.eslintrc +++ b/ui/.eslintrc @@ -149,7 +149,7 @@ 'eol-last': 0, // TODO: revisit 'id-length': 0, 'id-match': 0, - 'indent': [2, 2, {SwitchCase: 1}], + 'indent': [0, 2, {SwitchCase: 1}], 'key-spacing': [2, {beforeColon: false, afterColon: true}], 'linebreak-style': [2, 'unix'], 'lines-around-comment': 0, From 1eb564aaff191f48fca68ae7579a4933425c0e85 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 20 Apr 2017 12:12:02 -0700 Subject: [PATCH 078/163] Be the change, again --- .../components/RawQueryEditor.js | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/ui/src/data_explorer/components/RawQueryEditor.js b/ui/src/data_explorer/components/RawQueryEditor.js index 5ced519d26..8772f78a46 100644 --- a/ui/src/data_explorer/components/RawQueryEditor.js +++ b/ui/src/data_explorer/components/RawQueryEditor.js @@ -1,28 +1,28 @@ -import React, {PropTypes} from 'react' +import React, {PropTypes, Component} from 'react' import classNames from 'classnames' import Dropdown from 'src/shared/components/Dropdown' import LoadingDots from 'src/shared/components/LoadingDots' +import TemplateDrawer from 'src/shared/components/TemplateDrawer' import {QUERY_TEMPLATES} from 'src/data_explorer/constants' -const {func, shape, string} = PropTypes -const RawQueryEditor = React.createClass({ - propTypes: { - query: string.isRequired, - onUpdate: func.isRequired, - config: shape().isRequired, - }, - - getInitialState() { - return { +class RawQueryEditor extends Component { + constructor(props) { + super(props) + this.state = { value: this.props.query, } - }, + + this.handleKeyDown = ::this.handleKeyDown + this.handleChange = ::this.handleChange + this.handleUpdate = ::this.handleUpdate + this.handleChooseTemplate = ::this.handleChooseTemplate + } componentWillReceiveProps(nextProps) { if (this.props.query !== nextProps.query) { this.setState({value: nextProps.query}) } - }, + } handleKeyDown(e) { if (e.key === 'Enter') { @@ -33,21 +33,21 @@ const RawQueryEditor = React.createClass({ this.editor.blur() }) } - }, + } handleChange() { this.setState({ value: this.editor.value, }) - }, + } handleUpdate() { this.props.onUpdate(this.state.value) - }, + } handleChooseTemplate(template) { this.setState({value: template.query}) - }, + } render() { const {config: {status}} = this.props @@ -60,7 +60,7 @@ const RawQueryEditor = React.createClass({ onChange={this.handleChange} onKeyDown={this.handleKeyDown} onBlur={this.handleUpdate} - ref={editor => this.editor = editor} + ref={editor => (this.editor = editor)} value={value} placeholder="Enter a query or select database, measurement, and field below and have us build one for you..." autoComplete="off" @@ -75,7 +75,7 @@ const RawQueryEditor = React.createClass({ />
    ) - }, + } renderStatus(status) { if (!status) { @@ -108,7 +108,15 @@ const RawQueryEditor = React.createClass({ {status.error || status.warn || status.success}
    ) - }, -}) + } +} + +const {func, shape, string} = PropTypes + +RawQueryEditor.propTypes = { + query: string.isRequired, + onUpdate: func.isRequired, + config: shape().isRequired, +} export default RawQueryEditor From fe84abdda9cea6a004283b2b38fe64cc62e4c9ec Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 20 Apr 2017 14:27:23 -0700 Subject: [PATCH 079/163] Be the change --- .../components/CellEditorOverlay.js | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index 5ac3933ef8..1ca4c9098d 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -3,7 +3,9 @@ import React, {Component, PropTypes} from 'react' import _ from 'lodash' import uuid from 'node-uuid' -import ResizeContainer, {ResizeBottom} from 'src/shared/components/ResizeContainer' +import ResizeContainer, { + ResizeBottom, +} from 'src/shared/components/ResizeContainer' import QueryBuilder from 'src/data_explorer/components/QueryBuilder' import Visualization from 'src/data_explorer/components/Visualization' import OverlayControls from 'src/dashboards/components/OverlayControls' @@ -30,7 +32,9 @@ class CellEditorOverlay extends Component { const {cell: {name, type, queries}} = props - const queriesWorkingDraft = _.cloneDeep(queries.map(({queryConfig}) => ({...queryConfig, id: uuid.v4()}))) + const queriesWorkingDraft = _.cloneDeep( + queries.map(({queryConfig}) => ({...queryConfig, id: uuid.v4()})) + ) this.state = { cellWorkingName: name, @@ -45,7 +49,9 @@ class CellEditorOverlay extends Component { const nextStatus = nextProps.queryStatus if (nextStatus.status && nextStatus.queryID) { if (nextStatus.queryID !== queryID || nextStatus.status !== status) { - const nextQueries = this.state.queriesWorkingDraft.map((q) => q.id === queryID ? ({...q, status: nextStatus.status}) : q) + const nextQueries = this.state.queriesWorkingDraft.map( + q => (q.id === queryID ? {...q, status: nextStatus.status} : q) + ) this.setState({queriesWorkingDraft: nextQueries}) } } @@ -54,11 +60,13 @@ class CellEditorOverlay extends Component { queryStateReducer(queryModifier) { return (queryID, payload) => { const {queriesWorkingDraft} = this.state - const query = queriesWorkingDraft.find((q) => q.id === queryID) + const query = queriesWorkingDraft.find(q => q.id === queryID) const nextQuery = queryModifier(query, payload) - const nextQueries = queriesWorkingDraft.map((q) => q.id === query.id ? nextQuery : q) + const nextQueries = queriesWorkingDraft.map( + q => (q.id === query.id ? nextQuery : q) + ) this.setState({queriesWorkingDraft: nextQueries}) } } @@ -70,7 +78,9 @@ class CellEditorOverlay extends Component { } handleDeleteQuery(index) { - const nextQueries = this.state.queriesWorkingDraft.filter((__, i) => i !== index) + const nextQueries = this.state.queriesWorkingDraft.filter( + (__, i) => i !== index + ) this.setState({queriesWorkingDraft: nextQueries}) } @@ -81,11 +91,9 @@ class CellEditorOverlay extends Component { const newCell = _.cloneDeep(cell) newCell.name = cellWorkingName newCell.type = cellWorkingType - newCell.queries = queriesWorkingDraft.map((q) => { + newCell.queries = queriesWorkingDraft.map(q => { const query = q.rawText || buildInfluxQLQuery(timeRange, q) - const label = q.rawText ? - "" : - `${q.measurement}.${q.fields[0].field}` + const label = q.rawText ? '' : `${q.measurement}.${q.fields[0].field}` return { queryConfig: q, @@ -110,7 +118,9 @@ class CellEditorOverlay extends Component { try { const {data} = await getQueryConfig(url, [{query: text, id}]) const config = data.queries.find(q => q.id === id) - const nextQueries = this.state.queriesWorkingDraft.map((q) => q.id === id ? config.queryConfig : q) + const nextQueries = this.state.queriesWorkingDraft.map( + q => (q.id === id ? config.queryConfig : q) + ) this.setState({queriesWorkingDraft: nextQueries}) } catch (error) { console.error(error) @@ -136,7 +146,7 @@ class CellEditorOverlay extends Component { const queryActions = { addQuery: this.handleAddQuery, editRawTextAsync: this.handleEditRawText, - ..._.mapValues(queryModifiers, (qm) => this.queryStateReducer(qm)), + ..._.mapValues(queryModifiers, qm => this.queryStateReducer(qm)), } return ( @@ -152,7 +162,9 @@ class CellEditorOverlay extends Component { editQueryStatus={editQueryStatus} /> -
    +
    Date: Thu, 20 Apr 2017 14:28:49 -0700 Subject: [PATCH 080/163] Show tempVars when user types $ --- .../components/CellEditorOverlay.js | 14 +++++----- ui/src/dashboards/containers/DashboardPage.js | 7 ++--- .../data_explorer/components/QueryBuilder.js | 27 +++++++++++-------- .../data_explorer/components/QueryEditor.js | 15 ++++++++--- .../components/RawQueryEditor.js | 15 ++++++++--- ui/src/shared/components/TemplateDrawer.js | 19 +++++++++++++ 6 files changed, 71 insertions(+), 26 deletions(-) create mode 100644 ui/src/shared/components/TemplateDrawer.js diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index 1ca4c9098d..76ad52956c 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -131,6 +131,7 @@ class CellEditorOverlay extends Component { const { source, onCancel, + templates, timeRange, autoRefresh, editQueryStatus, @@ -173,6 +174,7 @@ class CellEditorOverlay extends Component { />
    This Graph has no Queries
    -
    -
    Add a Query
    +
    +
    + Add a Query +
    ) } @@ -93,6 +97,7 @@ const QueryBuilder = React.createClass({
    - + {this.renderLists()}
    ) }, - renderLists() { const {query} = this.props diff --git a/ui/src/data_explorer/components/RawQueryEditor.js b/ui/src/data_explorer/components/RawQueryEditor.js index 8772f78a46..a28f5d8f88 100644 --- a/ui/src/data_explorer/components/RawQueryEditor.js +++ b/ui/src/data_explorer/components/RawQueryEditor.js @@ -10,6 +10,7 @@ class RawQueryEditor extends Component { super(props) this.state = { value: this.props.query, + isTemplating: false, } this.handleKeyDown = ::this.handleKeyDown @@ -32,6 +33,8 @@ class RawQueryEditor extends Component { this.setState({value: this.state.value}, () => { this.editor.blur() }) + } else if (e.key === '$') { + this.setState({isTemplating: true}) } } @@ -50,11 +53,12 @@ class RawQueryEditor extends Component { } render() { - const {config: {status}} = this.props - const {value} = this.state + const {config: {status}, templates} = this.props + const {value, isTemplating} = this.state return (
    + {isTemplating ? : null}