diff --git a/CHANGELOG.md b/CHANGELOG.md index daf1306987..66d42f681b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ 1. [#1095](https://github.com/influxdata/chronograf/pull/1095): Add new auth duration CLI option; add client heartbeat 1. [#1168](https://github.com/influxdata/chronograf/issue/1168): Expand support for --basepath on some load balancers 1. [#1207](https://github.com/influxdata/chronograf/pull/1207): Add support for custom OAuth2 providers + 1. [#1212](https://github.com/influxdata/chronograf/issue/1212): Add query templates and loading animation to the RawQueryEditor 1. [#1221](https://github.com/influxdata/chronograf/issue/1221): More sensical Cell and Dashboard defaults ### UI Improvements @@ -46,6 +47,7 @@ 1. [#1187](https://github.com/influxdata/chronograf/pull/1187): Replace Kill Query confirmation modal with ConfirmButtons 1. [#1185](https://github.com/influxdata/chronograf/pull/1185): Alphabetically sort Admin Database Page 1. [#1199](https://github.com/influxdata/chronograf/pull/1199): Move Rename Cell functionality to ContextMenu dropdown + 1. [#1222](https://github.com/influxdata/chronograf/pull/1222): Isolate cell repositioning to just those affected by adding a new cell ## v1.2.0-beta7 [2017-03-28] ### Bug Fixes diff --git a/chronograf.go b/chronograf.go index 33ab74f204..ecc1ea40c0 100644 --- a/chronograf.go +++ b/chronograf.go @@ -380,7 +380,7 @@ type Dashboard struct { // DashboardCell holds visual and query information for a cell type DashboardCell struct { - ID string `json:"-"` + ID string `json:"i"` X int32 `json:"x"` Y int32 `json:"y"` W int32 `json:"w"` diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index ce9c7a3e50..b0bd5a47f9 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -117,21 +117,23 @@ class CellEditorOverlay extends Component { cellName={cellWorkingName} /> - - +
+ + +
diff --git a/ui/src/dashboards/components/Dashboard.js b/ui/src/dashboards/components/Dashboard.js index fe9640616a..0a06ea8330 100644 --- a/ui/src/dashboards/components/Dashboard.js +++ b/ui/src/dashboards/components/Dashboard.js @@ -31,9 +31,8 @@ const Dashboard = ({ } Dashboard.renderDashboard = (dashboard, autoRefresh, timeRange, source, onPositionChange, onEditCell, onRenameCell, onUpdateCell, onDeleteCell, onSummonOverlayTechnologies) => { - const cells = dashboard.cells.map((cell, i) => { - i = `${i}` - const dashboardCell = {...cell, i} + const cells = dashboard.cells.map((cell) => { + const dashboardCell = {...cell} dashboardCell.queries = dashboardCell.queries.map(({label, query, queryConfig, db}) => ({ label, diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index 7266679416..95d7a6c992 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -142,7 +142,7 @@ const DashboardPage = React.createClass({ handleUpdateDashboardCell(newCell) { return () => { - this.props.dashboardActions.editDashboardCell(newCell.x, newCell.y, false) + this.props.dashboardActions.editDashboardCell(this.getActiveDashboard(), newCell.x, newCell.y, false) this.props.dashboardActions.putDashboard(this.getActiveDashboard()) } }, diff --git a/ui/src/data_explorer/components/QueryEditor.js b/ui/src/data_explorer/components/QueryEditor.js index c8c81d7815..23a063b636 100644 --- a/ui/src/data_explorer/components/QueryEditor.js +++ b/ui/src/data_explorer/components/QueryEditor.js @@ -35,13 +35,6 @@ const QueryEditor = React.createClass({ }).isRequired, }, - getInitialState() { - return { - database: null, - measurement: null, - } - }, - handleChooseNamespace(namespace) { this.props.actions.chooseNamespace(this.props.query.id, namespace) }, diff --git a/ui/src/data_explorer/components/RawQueryEditor.js b/ui/src/data_explorer/components/RawQueryEditor.js index 41e9398162..2f960ab3bf 100644 --- a/ui/src/data_explorer/components/RawQueryEditor.js +++ b/ui/src/data_explorer/components/RawQueryEditor.js @@ -1,5 +1,8 @@ import React, {PropTypes} from 'react' import classNames from 'classnames' +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 @@ -45,6 +48,10 @@ const RawQueryEditor = React.createClass({ this.props.onUpdate(this.state.value) }, + handleChooseTemplate(template) { + this.setState({value: template.query}) + }, + render() { const {query: {rawStatus}} = this.props const {value} = this.state @@ -63,6 +70,7 @@ const RawQueryEditor = React.createClass({ spellCheck="false" /> {this.renderStatus(rawStatus)} + ) }, @@ -74,6 +82,14 @@ const RawQueryEditor = React.createClass({ ) } + if (rawStatus.loading) { + return ( +
+ +
+ ) + } + return (
diff --git a/ui/src/data_explorer/components/Table.js b/ui/src/data_explorer/components/Table.js index 220d9be0f7..3888b3cc27 100644 --- a/ui/src/data_explorer/components/Table.js +++ b/ui/src/data_explorer/components/Table.js @@ -82,12 +82,15 @@ const ChronoTable = React.createClass({ return } - this.setState({isLoading: true}) const {onEditRawStatus} = this.props + + onEditRawStatus(query.id, {loading: true}) + this.setState({isLoading: true}) // second param is db, we want to leave this blank try { const {data} = await fetchTimeSeries(query.host, undefined, query.text) this.setState({isLoading: false}) + onEditRawStatus(query.id, {loading: false}) const results = _.get(data, ['results', '0'], false) if (!results) { diff --git a/ui/src/data_explorer/components/Visualization.js b/ui/src/data_explorer/components/Visualization.js index 353b4e9aa8..5dac2526c4 100644 --- a/ui/src/data_explorer/components/Visualization.js +++ b/ui/src/data_explorer/components/Visualization.js @@ -35,7 +35,7 @@ const Visualization = React.createClass({ activeQueryIndex: number, height: string, heightPixels: number, - onEditRawStatus: func.isRequired, + onEditRawStatus: func, }, contextTypes: { @@ -112,7 +112,7 @@ const Visualization = React.createClass({ renderTable(query, heightPixels, onEditRawStatus) { if (!query) { - return
Enter your query below
+ return
Enter your query above
} return diff --git a/ui/src/data_explorer/constants/index.js b/ui/src/data_explorer/constants/index.js index 4b410dccb6..f977c0fc30 100644 --- a/ui/src/data_explorer/constants/index.js +++ b/ui/src/data_explorer/constants/index.js @@ -10,3 +10,23 @@ export const INFLUXQL_FUNCTIONS = [ 'spread', 'stddev', ] + +export const QUERY_TEMPLATES = [ + {text: 'Show Databases', query: 'SHOW DATABASES'}, + {text: 'Create Database', query: 'CREATE DATABASE "db_name"'}, + {text: 'Drop Database', query: 'DROP DATABASE "db_name"'}, + {text: 'Show Measurements', query: 'SHOW MEASUREMENTS ON "db_name"'}, + {text: 'Show Tag Keys', query: 'SHOW TAG KEYS ON "db_name" FROM "measurement_name"'}, + {text: 'Show Tag Values', query: 'SHOW TAG VALUES ON "db_name" FROM "measurement_name" WITH KEY = "tag_key"'}, + {text: 'Show Retention Policies', query: 'SHOW RETENTION POLICIES on "db_name"'}, + {text: 'Create Retention Policy', query: 'CREATE RETENTION POLICY "rp_name" ON "db_name" DURATION 30d REPLICATION 1 DEFAULT'}, + {text: 'Drop Retention Policy', query: 'DROP RETENTION POLICY "rp_name" ON "db_name"'}, + {text: 'Create Continuous Query', query: 'CREATE CONTINUOUS QUERY "cq_name" ON "db_name" BEGIN SELECT min("field") INTO "target_measurement" FROM "current_measurement" GROUP BY time(30m) END'}, + {text: 'Drop Continuous Query', query: 'DROP CONTINUOUS QUERY "cq_name" ON "db_name"'}, + {text: 'Show Users', query: 'SHOW USERS'}, + {text: 'Create User', query: `CREATE USER "username" WITH PASSWORD 'password'`}, + {text: 'Create Admin User', query: `CREATE USER "username" WITH PASSWORD 'password' WITH ALL PRIVILEGES`}, + {text: 'Drop User', query: 'DROP USER "username"'}, + {text: 'Show Stats', query: 'SHOW STATS'}, + {text: 'Show Diagnostics', query: 'SHOW DIAGNOSTICS'}, +] diff --git a/ui/src/data_explorer/containers/DataExplorer.js b/ui/src/data_explorer/containers/DataExplorer.js index 2b694f269c..51e58f8c15 100644 --- a/ui/src/data_explorer/containers/DataExplorer.js +++ b/ui/src/data_explorer/containers/DataExplorer.js @@ -83,22 +83,22 @@ const DataExplorer = React.createClass({ timeRange={timeRange} /> - - diff --git a/ui/src/shared/components/Dygraph.js b/ui/src/shared/components/Dygraph.js index 2d391a76b1..70cfec9347 100644 --- a/ui/src/shared/components/Dygraph.js +++ b/ui/src/shared/components/Dygraph.js @@ -48,6 +48,7 @@ export default React.createClass({ value: string, rangeValue: string, }), + legendOnBottom: bool, }, getDefaultProps() { @@ -55,6 +56,7 @@ export default React.createClass({ containerStyle: {}, isGraphFilled: true, overrideLineColors: null, + legendOnBottom: false, } }, @@ -67,7 +69,7 @@ export default React.createClass({ componentDidMount() { const timeSeries = this.getTimeSeries() // dygraphSeries is a legend label and its corresponding y-axis e.g. {legendLabel1: 'y', legendLabel2: 'y2'}; - const {ranges, dygraphSeries, ruleValues} = this.props + const {ranges, dygraphSeries, ruleValues, legendOnBottom} = this.props const refs = this.refs const graphContainerNode = refs.graphContainer @@ -122,7 +124,12 @@ export default React.createClass({ } legendContainerNode.style.left = `${legendLeft}px` - legendContainerNode.style.top = `${legendTop}px` + if (legendOnBottom) { + legendContainerNode.style.bottom = `4px` + } else { + legendContainerNode.style.top = `${legendTop}px` + } + setMarker(points) }, unhighlightCallback() { diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index 3cd3f9dad6..390ceabde8 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -67,7 +67,7 @@ export default React.createClass({ }, render() { - const {data, ranges, isFetchingInitially, isRefreshing, isGraphFilled, overrideLineColors, title, underlayCallback, queries, showSingleStat, displayOptions, ruleValues} = this.props + const {data, ranges, isFetchingInitially, isRefreshing, isGraphFilled, overrideLineColors, title, underlayCallback, queries, showSingleStat, displayOptions, ruleValues, isInDataExplorer} = this.props const {labels, timeSeries, dygraphSeries} = this._timeSeries // If data for this graph is being fetched for the first time, show a graph-wide spinner. @@ -119,6 +119,7 @@ export default React.createClass({ dygraphSeries={dygraphSeries} ranges={ranges || this.getRanges()} ruleValues={ruleValues} + legendOnBottom={isInDataExplorer} /> {showSingleStat ?
{roundedValue}
: null} diff --git a/ui/src/shared/components/LoadingDots.js b/ui/src/shared/components/LoadingDots.js new file mode 100644 index 0000000000..e0d62b0408 --- /dev/null +++ b/ui/src/shared/components/LoadingDots.js @@ -0,0 +1,19 @@ +import React, {PropTypes} from 'react' + +const LoadingDots = ({className}) => ( +
+
+
+
+
+) + +const { + string, +} = PropTypes + +LoadingDots.propTypes = { + className: string, +} + +export default LoadingDots diff --git a/ui/src/shared/components/ResizeContainer.js b/ui/src/shared/components/ResizeContainer.js index 8b89905f91..e8a6715235 100644 --- a/ui/src/shared/components/ResizeContainer.js +++ b/ui/src/shared/components/ResizeContainer.js @@ -59,7 +59,12 @@ const ResizeContainer = React.createClass({ return } - this.setState({topHeight: `${(newTopPanelPercent)}%`, bottomHeight: `${(newBottomPanelPercent)}%`, topHeightPixels}) + this.setState({ + topHeight: `${(newTopPanelPercent)}%`, + bottomHeight: `${(newBottomPanelPercent)}%`, + topHeightPixels, + bottomHeightPixels, + }) }, renderHandle() { @@ -70,9 +75,13 @@ const ResizeContainer = React.createClass({ }, render() { - const {topHeight, topHeightPixels, bottomHeight} = this.state + const { + topHeight, + topHeightPixels, + bottomHeightPixels, + } = this.state const top = React.cloneElement(this.props.children[0], {height: topHeight, heightPixels: topHeightPixels}) - const bottom = React.cloneElement(this.props.children[1], {height: bottomHeight}) + const bottom = React.cloneElement(this.props.children[1], {height: `${bottomHeightPixels}px`}) return (
{top} @@ -83,17 +92,21 @@ const ResizeContainer = React.createClass({ }, }) -const ResizeBottom = (props) => ( -
- {props.children} -
-) +export const ResizeBottom = ({ + height, + children, +}) => { + const child = React.cloneElement(children, {height}) + return ( +
+ {child} +
+ ) +} ResizeBottom.propTypes = { children: node.isRequired, height: string, } -export {ResizeBottom} - export default ResizeContainer diff --git a/ui/src/style/components/resizer.scss b/ui/src/style/components/resizer.scss index 74fc73ea36..077b1aa85e 100644 --- a/ui/src/style/components/resizer.scss +++ b/ui/src/style/components/resizer.scss @@ -19,7 +19,7 @@ $resizer-color-active: $c-pool; z-index: 2; user-select: none; -webkit-user-select: none; - position: relative; + position: absolute; // Psuedo element for handle &:before { diff --git a/ui/src/style/pages/dashboards.scss b/ui/src/style/pages/dashboards.scss index ae500071e2..46cc66094b 100644 --- a/ui/src/style/pages/dashboards.scss +++ b/ui/src/style/pages/dashboards.scss @@ -57,7 +57,6 @@ $dash-graph-options-arrow: 8px; left: 0; } .dash-graph--container { - z-index: 0; user-select: none !important; -o-user-select: none !important; -moz-user-select: none !important; @@ -89,7 +88,6 @@ $dash-graph-options-arrow: 8px; } } .dash-graph--heading { - z-index: 0; user-select: none !important; -o-user-select: none !important; -moz-user-select: none !important; @@ -370,11 +368,6 @@ $overlay-bg: rgba($c-pool, 0.7); border: 0; border-radius: $radius; @include gradient-h($g3-castle,$overlay-controls-bg); - - /* Hack for making the adjacent query builder have less margin on top */ - & + .query-builder { - margin-top: 2px; - } } .overlay-controls--right { display: flex; @@ -444,3 +437,13 @@ $overlay-bg: rgba($c-pool, 0.7); height: calc(100% - #{$dash-graph-heading}); top: $dash-graph-heading; } +.overlay-technology .query-builder { + flex: 1 0 0; + margin-bottom: 16px; + margin-top: 2px; +} +.overlay-technology .query-builder--tabs, +.overlay-technology .query-builder--tab-contents, +.overlay-technology .qeditor--empty { + margin: 0; +} diff --git a/ui/src/style/pages/data-explorer/font-scale.scss b/ui/src/style/pages/data-explorer/font-scale.scss index 9ebf9a387e..5575dd04a3 100644 --- a/ui/src/style/pages/data-explorer/font-scale.scss +++ b/ui/src/style/pages/data-explorer/font-scale.scss @@ -26,6 +26,9 @@ $breakpoint-c: 2100px; .query-builder--tabs { width: 320px; } + .query-builder--tab-label { + width: (320px - 8px - 16px - 16px); + } .qeditor--list-item, .query-builder--tab-label { font-size: 15px; @@ -89,6 +92,7 @@ $breakpoint-c: 2100px; letter-spacing: 0.3px; } .query-builder--tab-label { + width: (373px - 8px - 16px - 16px); font-size: 16px; } .query-builder--tab { diff --git a/ui/src/style/pages/data-explorer/query-builder.scss b/ui/src/style/pages/data-explorer/query-builder.scss index 7a59d46995..334bab52d2 100644 --- a/ui/src/style/pages/data-explorer/query-builder.scss +++ b/ui/src/style/pages/data-explorer/query-builder.scss @@ -1,7 +1,9 @@ +/* Variables */ +$query-builder-tabs-width: 210px; + + .query-builder { position: relative; - flex: 1 0 0; - margin: 16px 0; width: calc(100% - #{($explorer-page-padding * 2)}); left: $explorer-page-padding; border: 0; @@ -12,11 +14,12 @@ // Tabs .query-builder--tabs { + margin: 16px 0; display: flex; - width: 250px; + width: $query-builder-tabs-width; flex-direction: column; align-items: stretch; - @include gradient-v($g3-castle,$g1-raven); + background-color: $g3-castle; border-radius: $radius 0 0 $radius; } .query-builder--tabs-heading { @@ -132,7 +135,7 @@ font-weight: 600; white-space: nowrap; overflow: hidden; - width: 90%; + width: ($query-builder-tabs-width - 8px - 16px - 16px); text-overflow: ellipsis; @include no-user-select(); } @@ -145,7 +148,8 @@ $query-builder--preview-height: 60px; $query-builder--column-heading-height: 50px; .query-builder--tab-contents { - width: 100%; + flex: 1 0 0; + margin: 16px 0; background-color: $g4-onyx; border-radius: 0 $radius $radius 0; overflow: hidden; @@ -219,6 +223,7 @@ $query-builder--column-heading-height: 50px; background-color: $g4-onyx; } .qeditor--empty { + width: 100%; margin-top: 0; height: calc(100% - #{$query-builder--column-heading-height}); position: absolute; diff --git a/ui/src/style/pages/data-explorer/query-editor.scss b/ui/src/style/pages/data-explorer/query-editor.scss index 0f1e9ef61e..0393608b0a 100644 --- a/ui/src/style/pages/data-explorer/query-editor.scss +++ b/ui/src/style/pages/data-explorer/query-editor.scss @@ -163,7 +163,8 @@ .qeditor--empty { text-align: center; color: $g10-wolf; - width: 100%; + flex: 1 0 0; + margin: 16px 0; padding: 0; display: flex; flex-direction: column; diff --git a/ui/src/style/pages/data-explorer/raw-text.scss b/ui/src/style/pages/data-explorer/raw-text.scss index 2899bc42e2..1c86d949e0 100644 --- a/ui/src/style/pages/data-explorer/raw-text.scss +++ b/ui/src/style/pages/data-explorer/raw-text.scss @@ -26,7 +26,7 @@ } $raw-text-color: $c-pool; -$raw-text-height: 38px; +$raw-text-height: 42px; .raw-text--field { @include custom-scrollbar($g2-kevlar, $raw-text-color); @@ -72,7 +72,7 @@ $raw-text-height: 38px; } .raw-text--status { width: 100%; - height: ($query-builder--preview-height - 2px - $raw-text-height); + height: ($query-builder--preview-height - $raw-text-height); line-height: 12px; font-size: 12px; background-color: $g2-kevlar; @@ -108,4 +108,13 @@ $raw-text-height: 38px; &.raw-text--success { color: $c-rainforest; } -} \ No newline at end of file +} + +.dropdown.query-template { + position: absolute; + top: 8px; + right: 7px; + .dropdown-toggle { + width: 135px; + } +} diff --git a/ui/src/style/pages/data-explorer/visualization.scss b/ui/src/style/pages/data-explorer/visualization.scss index e6c6a58e5d..c7719677cc 100644 --- a/ui/src/style/pages/data-explorer/visualization.scss +++ b/ui/src/style/pages/data-explorer/visualization.scss @@ -4,6 +4,10 @@ width: calc(100% - #{($explorer-page-padding * 2)}); left: $explorer-page-padding; } +/* Special rule for when the graph is in the bottom of resizer */ +.resize-bottom .graph { + height: 100%; +} .graph-heading { position: relative; top: $de-vertical-margin; diff --git a/ui/src/style/unsorted.scss b/ui/src/style/unsorted.scss index 473831de78..e9de6eea6b 100644 --- a/ui/src/style/unsorted.scss +++ b/ui/src/style/unsorted.scss @@ -90,3 +90,31 @@ margin-bottom: 11px; } } + +/* + Loading Dots + ---------------------------------------------- +*/ + +.loading-dots { + position: absolute; + transform: translate(0,0); + transform: translateX(50%); + width: 16px; + height: 18px; + + div { + width: 4px; + height: 4px; + background-color: $g6-smoke; + border-radius: 50%; + position: absolute; + top: 50%; + transform: translate(-50%,-50%); + } + + div:nth-child(1) {left: 0; animation: refreshingSpinnerA 0.8s cubic-bezier(0.645, 0.045, 0.355, 1) infinite;} + div:nth-child(2) {left: 50%; animation: refreshingSpinnerB 0.8s cubic-bezier(0.645, 0.045, 0.355, 1) infinite;} + div:nth-child(3) {left: 100%; animation: refreshingSpinnerC 0.8s cubic-bezier(0.645, 0.045, 0.355, 1) infinite;} +} +