Merge remote-tracking branch 'origin/master' into feature/generic-oauth

pull/10616/head
Chris Goller 2017-04-07 15:39:35 -05:00
commit e235292107
23 changed files with 198 additions and 69 deletions

View File

@ -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

View File

@ -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"`

View File

@ -117,21 +117,23 @@ class CellEditorOverlay extends Component {
cellName={cellWorkingName}
/>
<ResizeBottom>
<OverlayControls
selectedGraphType={cellWorkingType}
onSelectGraphType={this.handleSelectGraphType}
onCancel={onCancel}
onSave={this.handleSaveCell}
/>
<QueryBuilder
queries={queriesWorkingDraft}
actions={queryActions}
autoRefresh={autoRefresh}
timeRange={timeRange}
setActiveQueryIndex={this.handleSetActiveQueryIndex}
onDeleteQuery={this.handleDeleteQuery}
activeQueryIndex={activeQueryIndex}
/>
<div>
<OverlayControls
selectedGraphType={cellWorkingType}
onSelectGraphType={this.handleSelectGraphType}
onCancel={onCancel}
onSave={this.handleSaveCell}
/>
<QueryBuilder
queries={queriesWorkingDraft}
actions={queryActions}
autoRefresh={autoRefresh}
timeRange={timeRange}
setActiveQueryIndex={this.handleSetActiveQueryIndex}
onDeleteQuery={this.handleDeleteQuery}
activeQueryIndex={activeQueryIndex}
/>
</div>
</ResizeBottom>
</ResizeContainer>
</div>

View File

@ -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,

View File

@ -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())
}
},

View File

@ -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)
},

View File

@ -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)}
<Dropdown items={QUERY_TEMPLATES} selected={'Query Templates'} onChoose={this.handleChooseTemplate} className="query-template"/>
</div>
)
},
@ -74,6 +82,14 @@ const RawQueryEditor = React.createClass({
)
}
if (rawStatus.loading) {
return (
<div className="raw-text--status">
<LoadingDots />
</div>
)
}
return (
<div className={classNames("raw-text--status", {"raw-text--error": rawStatus.error, "raw-text--success": rawStatus.success, "raw-text--warning": rawStatus.warn})}>
<span className={classNames("icon", {stop: rawStatus.error, checkmark: rawStatus.success, "alert-triangle": rawStatus.warn})}></span>

View File

@ -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) {

View File

@ -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 <div className="generic-empty-state">Enter your query below</div>
return <div className="generic-empty-state">Enter your query above</div>
}
return <Table query={query} height={heightPixels} onEditRawStatus={onEditRawStatus} />

View File

@ -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'},
]

View File

@ -83,22 +83,22 @@ const DataExplorer = React.createClass({
timeRange={timeRange}
/>
<ResizeContainer>
<Visualization
<QueryBuilder
queries={queryConfigs}
actions={queryConfigActions}
autoRefresh={autoRefresh}
timeRange={timeRange}
queryConfigs={queryConfigs}
setActiveQueryIndex={this.handleSetActiveQueryIndex}
onDeleteQuery={this.handleDeleteQuery}
activeQueryIndex={activeQueryIndex}
onEditRawStatus={queryConfigActions.editRawQueryStatus}
/>
<ResizeBottom>
<QueryBuilder
queries={queryConfigs}
actions={queryConfigActions}
<Visualization
autoRefresh={autoRefresh}
timeRange={timeRange}
setActiveQueryIndex={this.handleSetActiveQueryIndex}
onDeleteQuery={this.handleDeleteQuery}
queryConfigs={queryConfigs}
activeQueryIndex={activeQueryIndex}
onEditRawStatus={queryConfigActions.editRawQueryStatus}
/>
</ResizeBottom>
</ResizeContainer>

View File

@ -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() {

View File

@ -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 ? <div className="graph-single-stat single-stat">{roundedValue}</div> : null}
</div>

View File

@ -0,0 +1,19 @@
import React, {PropTypes} from 'react'
const LoadingDots = ({className}) => (
<div className={`loading-dots ${className}`}>
<div></div>
<div></div>
<div></div>
</div>
)
const {
string,
} = PropTypes
LoadingDots.propTypes = {
className: string,
}
export default LoadingDots

View File

@ -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 (
<div className="resize-container page-contents" onMouseLeave={this.handleMouseLeave} onMouseUp={this.handleStopDrag} onMouseMove={this.handleDrag} ref="resizeContainer" >
{top}
@ -83,17 +92,21 @@ const ResizeContainer = React.createClass({
},
})
const ResizeBottom = (props) => (
<div className="resize-bottom" style={{height: props.height}}>
{props.children}
</div>
)
export const ResizeBottom = ({
height,
children,
}) => {
const child = React.cloneElement(children, {height})
return (
<div className="resize-bottom" style={{height}}>
{child}
</div>
)
}
ResizeBottom.propTypes = {
children: node.isRequired,
height: string,
}
export {ResizeBottom}
export default ResizeContainer

View File

@ -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 {

View File

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

View File

@ -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 {

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
}
}
.dropdown.query-template {
position: absolute;
top: 8px;
right: 7px;
.dropdown-toggle {
width: 135px;
}
}

View File

@ -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;

View File

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