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;}
+}
+