@@ -127,6 +129,7 @@ Dashboard.propTypes = {
onSelectTemplate: func.isRequired,
showTemplateControlBar: bool,
onCancelEditCell: func,
+ onZoom: func,
}
export default Dashboard
diff --git a/ui/src/dashboards/components/DashboardHeader.js b/ui/src/dashboards/components/DashboardHeader.js
index 6977d98d1..a7bd5436a 100644
--- a/ui/src/dashboards/components/DashboardHeader.js
+++ b/ui/src/dashboards/components/DashboardHeader.js
@@ -11,7 +11,8 @@ const DashboardHeader = ({
buttonText,
dashboard,
headerText,
- timeRange,
+ timeRange: {upper, lower},
+ zoomedTimeRange: {zoomedLower, zoomedUpper},
autoRefresh,
isHidden,
handleChooseTimeRange,
@@ -81,7 +82,10 @@ const DashboardHeader = ({
/>
{
+ this.setState({zoomedTimeRange: {zoomedLower, zoomedUpper}})
+ }
+
getActiveDashboard() {
const {params: {dashboardID}, dashboards} = this.props
return dashboards.find(d => d.id === +dashboardID)
}
render() {
+ const {zoomedTimeRange} = this.state
+ const {zoomedLower, zoomedUpper} = zoomedTimeRange
+
const {
source,
timeRange,
@@ -217,8 +225,11 @@ class DashboardPage extends Component {
params: {sourceID, dashboardID},
} = this.props
- const lowerType = lower && lower.includes('Z') ? 'timeStamp' : 'constant'
- const upperType = upper && upper.includes('Z') ? 'timeStamp' : 'constant'
+ const low = zoomedLower ? zoomedLower : lower
+ const up = zoomedUpper ? zoomedUpper : upper
+
+ const lowerType = low && low.includes(':') ? 'timeStamp' : 'constant'
+ const upperType = up && up.includes(':') ? 'timeStamp' : 'constant'
const dashboardTime = {
id: 'dashtime',
@@ -226,7 +237,7 @@ class DashboardPage extends Component {
type: lowerType,
values: [
{
- value: lower,
+ value: low,
type: lowerType,
selected: true,
},
@@ -239,7 +250,7 @@ class DashboardPage extends Component {
type: upperType,
values: [
{
- value: upper || 'now()',
+ value: up || 'now()',
type: upperType,
selected: true,
},
@@ -310,6 +321,7 @@ class DashboardPage extends Component {
sourceID={sourceID}
dashboard={dashboard}
timeRange={timeRange}
+ zoomedTimeRange={zoomedTimeRange}
autoRefresh={autoRefresh}
isHidden={inPresentationMode}
onAddCell={this.handleAddCell}
@@ -337,6 +349,7 @@ class DashboardPage extends Component {
dashboard={dashboard}
timeRange={timeRange}
autoRefresh={autoRefresh}
+ onZoom={this.handleZoomedTimeRange}
onAddCell={this.handleAddCell}
synchronizer={this.synchronizer}
inPresentationMode={inPresentationMode}
diff --git a/ui/src/external/dygraph.js b/ui/src/external/dygraph.js
index 66ce45214..698bef2b4 100644
--- a/ui/src/external/dygraph.js
+++ b/ui/src/external/dygraph.js
@@ -288,22 +288,17 @@ Dygraph.Plugins.Crosshair = (function() {
var width = e.dygraph.width_
var height = e.dygraph.height_
+ var xLabelPixels = 20
+
this.canvas_.width = width
- this.canvas_.height = height
+ this.canvas_.height = height - xLabelPixels
this.canvas_.style.width = width + 'px' // for IE
- this.canvas_.style.height = height + 'px' // for IE
+ this.canvas_.style.height = height - xLabelPixels + 'px' // for IE
var ctx = this.canvas_.getContext('2d')
ctx.clearRect(0, 0, width, height)
-
- const gradient = ctx.createLinearGradient(0, 0, 0, height)
- gradient.addColorStop(0, 'rgba(255, 255, 255, 0.0)')
- gradient.addColorStop(0.11, 'rgba(255, 255, 255, 1.0)')
- gradient.addColorStop(0.89, 'rgba(255, 255, 255, 1.0)')
- gradient.addColorStop(1, 'rgba(255, 255, 255, 0.0)')
-
- ctx.strokeStyle = gradient
- ctx.lineWidth = 1.5
+ ctx.strokeStyle = '#C6CAD3'
+ ctx.lineWidth = 1
// If graphs have different time ranges, it's possible to select a point on
// one graph that doesn't exist in another, resulting in an exception.
diff --git a/ui/src/kapacitor/containers/KapacitorPage.js b/ui/src/kapacitor/containers/KapacitorPage.js
index b5f26c5f8..50cb443cd 100644
--- a/ui/src/kapacitor/containers/KapacitorPage.js
+++ b/ui/src/kapacitor/containers/KapacitorPage.js
@@ -68,11 +68,18 @@ class KapacitorPage extends Component {
handleSubmit(e) {
e.preventDefault()
- const {addFlashMessage, source, params, router} = this.props
+ const {
+ addFlashMessage,
+ source,
+ source: {kapacitors = []},
+ params,
+ router,
+ } = this.props
const {kapacitor} = this.state
- const kapNames = source.kapacitors.map(k => k.name)
- if (kapNames.includes(kapacitor.name)) {
+ const isNameTaken = kapacitors.some(k => k.name === kapacitor.name)
+
+ if (isNameTaken) {
addFlashMessage({
type: 'error',
text: `There is already a Kapacitor configuration named "${kapacitor.name}"`,
diff --git a/ui/src/shared/components/Dygraph.js b/ui/src/shared/components/Dygraph.js
index 627641085..3dc1dfb5c 100644
--- a/ui/src/shared/components/Dygraph.js
+++ b/ui/src/shared/components/Dygraph.js
@@ -3,25 +3,24 @@ import React, {Component, PropTypes} from 'react'
import shallowCompare from 'react-addons-shallow-compare'
import _ from 'lodash'
+import moment from 'moment'
import Dygraphs from 'src/external/dygraph'
-import getRange from 'shared/parsing/getRangeForDygraph'
-
-import {DISPLAY_OPTIONS} from 'src/dashboards/constants'
-import {LINE_COLORS, multiColumnBarPlotter} from 'src/shared/graphs/helpers'
+import getRange, {getStackedRange} from 'shared/parsing/getRangeForDygraph'
import DygraphLegend from 'src/shared/components/DygraphLegend'
+import {DISPLAY_OPTIONS} from 'src/dashboards/constants'
import {buildDefaultYLabel} from 'shared/presenters'
import {numberValueFormatter} from 'src/utils/formatting'
-
-const {LINEAR, LOG, BASE_10} = DISPLAY_OPTIONS
-const labelWidth = 60
-const avgCharPixels = 7
-
-const hasherino = (str, len) =>
- str
- .split('')
- .map(char => char.charCodeAt(0))
- .reduce((hash, code) => hash + code, 0) % len
+import {
+ OPTIONS,
+ LINE_COLORS,
+ LABEL_WIDTH,
+ CHAR_PIXELS,
+ barPlotter,
+ hasherino,
+ highlightSeriesOpts,
+} from 'src/shared/graphs/helpers'
+const {LINEAR, LOG, BASE_10, BASE_2} = DISPLAY_OPTIONS
export default class Dygraph extends Component {
constructor(props) {
@@ -42,157 +41,61 @@ export default class Dygraph extends Component {
}
componentDidMount() {
- const timeSeries = this.getTimeSeries()
- // dygraphSeries is a legend label and its corresponding y-axis e.g. {legendLabel1: 'y', legendLabel2: 'y2'};
const {
axes: {y, y2},
- dygraphSeries,
ruleValues,
- overrideLineColors,
- isGraphFilled,
+ isGraphFilled: fillGraph,
isBarGraph,
options,
} = this.props
+ const timeSeries = this.getTimeSeries()
const graphRef = this.graphRef
- const legendRef = this.legendRef
- const finalLineColors = [...(overrideLineColors || LINE_COLORS)]
- const hashColorDygraphSeries = {}
- const {length} = finalLineColors
-
- for (const seriesName in dygraphSeries) {
- const series = dygraphSeries[seriesName]
- const hashIndex = hasherino(seriesName, length)
- const color = finalLineColors[hashIndex]
- hashColorDygraphSeries[seriesName] = {...series, color}
- }
-
- const axisLabelWidth =
- labelWidth +
- y.prefix.length * avgCharPixels +
- y.suffix.length * avgCharPixels
-
- const defaultOptions = {
- plugins: isBarGraph
- ? []
- : [
- new Dygraphs.Plugins.Crosshair({
- direction: 'vertical',
- }),
- ],
+ let defaultOptions = {
+ fillGraph,
logscale: y.scale === LOG,
- labelsSeparateLines: false,
- labelsKMB: true,
- rightGap: 0,
- highlightSeriesBackgroundAlpha: 1.0,
- highlightSeriesBackgroundColor: 'rgb(41, 41, 51)',
- fillGraph: isGraphFilled,
- axisLineWidth: 2,
- gridLineWidth: 1,
- highlightCircleSize: isBarGraph ? 0 : 3,
- animatedZooms: true,
- hideOverlayOnMouseOut: false,
- colors: finalLineColors,
- series: hashColorDygraphSeries,
+ colors: this.getLineColors(),
+ series: this.hashColorDygraphSeries(),
+ legendFormatter: this.legendFormatter,
+ highlightCallback: this.highlightCallback,
+ unhighlightCallback: this.unhighlightCallback,
+ plugins: [new Dygraphs.Plugins.Crosshair({direction: 'vertical'})],
axes: {
y: {
- valueRange: getRange(timeSeries, y.bounds, ruleValues),
+ valueRange: options.stackedGraph
+ ? getStackedRange(y.bounds)
+ : getRange(timeSeries, y.bounds, ruleValues),
axisLabelFormatter: (yval, __, opts) =>
numberValueFormatter(yval, opts, y.prefix, y.suffix),
- axisLabelWidth,
- labelsKMB: y.base === '10',
- labelsKMG2: y.base === '2',
+ axisLabelWidth: this.getLabelWidth(),
+ labelsKMB: y.base === BASE_10,
+ labelsKMG2: y.base === BASE_2,
},
y2: {
valueRange: getRange(timeSeries, y2.bounds),
},
},
- highlightSeriesOpts: {
- strokeWidth: 2,
- highlightCircleSize: isBarGraph ? 0 : 5,
- },
- legendFormatter: legend => {
- if (!legend.x) {
- return ''
- }
-
- const {state: {legend: prevLegend}} = this
- const highlighted = legend.series.find(s => s.isHighlighted)
- const prevHighlighted = prevLegend.series.find(s => s.isHighlighted)
-
- const yVal = highlighted && highlighted.y
- const prevY = prevHighlighted && prevHighlighted.y
-
- if (legend.x === prevLegend.x && yVal === prevY) {
- return ''
- }
-
- this.setState({legend})
- return ''
- },
- highlightCallback: e => {
- // Move the Legend on hover
- const graphRect = graphRef.getBoundingClientRect()
- const legendRect = legendRef.getBoundingClientRect()
-
- const graphWidth = graphRect.width + 32 // Factoring in padding from parent
- const graphHeight = graphRect.height
- const graphBottom = graphRect.bottom
- const legendWidth = legendRect.width
- const legendHeight = legendRect.height
- const screenHeight = window.innerHeight
- const legendMaxLeft = graphWidth - legendWidth / 2
- const trueGraphX = e.pageX - graphRect.left
-
- let legendLeft = trueGraphX
-
- // Enforcing max & min legend offsets
- if (trueGraphX < legendWidth / 2) {
- legendLeft = legendWidth / 2
- } else if (trueGraphX > legendMaxLeft) {
- legendLeft = legendMaxLeft
- }
-
- // Disallow screen overflow of legend
- const isLegendBottomClipped = graphBottom + legendHeight > screenHeight
-
- const legendTop = isLegendBottomClipped
- ? graphHeight + 8 - legendHeight
- : graphHeight + 8
-
- legendRef.style.left = `${legendLeft}px`
- legendRef.style.top = `${legendTop}px`
-
- this.setState({isHidden: false})
- },
- unhighlightCallback: e => {
- const {top, bottom, left, right} = legendRef.getBoundingClientRect()
-
- const mouseY = e.clientY
- const mouseX = e.clientX
-
- const mouseInLegendY = mouseY <= bottom && mouseY >= top
- const mouseInLegendX = mouseX <= right && mouseX >= left
- const isMouseHoveringLegend = mouseInLegendY && mouseInLegendX
-
- if (!isMouseHoveringLegend) {
- this.setState({isHidden: true})
-
- if (!this.visibility().find(bool => bool === true)) {
- this.setState({filterText: ''})
- }
- }
- },
+ highlightSeriesOpts,
+ zoomCallback: (lower, upper) => this.handleZoom(lower, upper),
}
if (isBarGraph) {
- defaultOptions.plotter = multiColumnBarPlotter
+ defaultOptions = {
+ ...defaultOptions,
+ plotter: barPlotter,
+ plugins: [],
+ highlightSeriesOpts: {
+ ...highlightSeriesOpts,
+ highlightCircleSize: 0,
+ },
+ }
}
this.dygraph = new Dygraphs(graphRef, timeSeries, {
...defaultOptions,
...options,
+ ...OPTIONS,
})
const {w} = this.dygraph.getArea()
@@ -227,15 +130,7 @@ export default class Dygraph extends Component {
}
componentDidUpdate() {
- const {
- labels,
- axes: {y, y2},
- options,
- dygraphSeries,
- ruleValues,
- isBarGraph,
- overrideLineColors,
- } = this.props
+ const {labels, axes: {y, y2}, options, ruleValues, isBarGraph} = this.props
const dygraph = this.dygraph
if (!dygraph) {
@@ -245,48 +140,31 @@ export default class Dygraph extends Component {
}
const timeSeries = this.getTimeSeries()
- const ylabel = this.getLabel('y')
- const finalLineColors = [...(overrideLineColors || LINE_COLORS)]
-
- const hashColorDygraphSeries = {}
- const {length} = finalLineColors
-
- for (const seriesName in dygraphSeries) {
- const series = dygraphSeries[seriesName]
- const hashIndex = hasherino(seriesName, length)
- const color = finalLineColors[hashIndex]
- hashColorDygraphSeries[seriesName] = {...series, color}
- }
-
- const axisLabelWidth =
- labelWidth +
- y.prefix.length * avgCharPixels +
- y.suffix.length * avgCharPixels
const updateOptions = {
+ ...options,
labels,
file: timeSeries,
- ylabel,
logscale: y.scale === LOG,
+ ylabel: this.getLabel('y'),
axes: {
y: {
- valueRange: getRange(timeSeries, y.bounds, ruleValues),
+ valueRange: options.stackedGraph
+ ? getStackedRange(y.bounds)
+ : getRange(timeSeries, y.bounds, ruleValues),
axisLabelFormatter: (yval, __, opts) =>
numberValueFormatter(yval, opts, y.prefix, y.suffix),
- axisLabelWidth,
- labelsKMB: y.base === '10',
- labelsKMG2: y.base === '2',
+ axisLabelWidth: this.getLabelWidth(),
+ labelsKMB: y.base === BASE_10,
+ labelsKMG2: y.base === BASE_2,
},
y2: {
valueRange: getRange(timeSeries, y2.bounds),
},
},
- stepPlot: options.stepPlot,
- stackedGraph: options.stackedGraph,
- underlayCallback: options.underlayCallback,
- colors: finalLineColors,
- series: hashColorDygraphSeries,
- plotter: isBarGraph ? multiColumnBarPlotter : null,
+ colors: this.getLineColors(),
+ series: this.hashColorDygraphSeries(),
+ plotter: isBarGraph ? barPlotter : null,
visibility: this.visibility(),
}
@@ -298,6 +176,31 @@ export default class Dygraph extends Component {
this.props.setResolution(w)
}
+ handleZoom = (lower, upper) => {
+ const {onZoom} = this.props
+
+ if (this.dygraph.isZoomed() === false) {
+ return onZoom(null, null)
+ }
+
+ onZoom(this.formatTimeRange(lower), this.formatTimeRange(upper))
+ }
+
+ hashColorDygraphSeries = () => {
+ const {dygraphSeries} = this.props
+ const colors = this.getLineColors()
+ const hashColorDygraphSeries = {}
+
+ for (const seriesName in dygraphSeries) {
+ const series = dygraphSeries[seriesName]
+ const hashIndex = hasherino(seriesName, colors.length)
+ const color = colors[hashIndex]
+ hashColorDygraphSeries[seriesName] = {...series, color}
+ }
+
+ return hashColorDygraphSeries
+ }
+
sync = () => {
if (!this.state.isSynced) {
this.props.synchronizer(this.dygraph)
@@ -342,6 +245,19 @@ export default class Dygraph extends Component {
}
}
+ getLineColors = () => {
+ return [...(this.props.overrideLineColors || LINE_COLORS)]
+ }
+
+ getLabelWidth = () => {
+ const {axes: {y}} = this.props
+ return (
+ LABEL_WIDTH +
+ y.prefix.length * CHAR_PIXELS +
+ y.suffix.length * CHAR_PIXELS
+ )
+ }
+
visibility = () => {
const timeSeries = this.getTimeSeries()
const {filterText, legend} = this.state
@@ -384,6 +300,103 @@ export default class Dygraph extends Component {
this.dygraph.predraw_()
}
+ formatTimeRange = timeRange => {
+ if (!timeRange) {
+ return ''
+ }
+
+ return moment(timeRange).utc().format()
+ }
+
+ deselectCrosshair = () => {
+ const plugins = this.dygraph.plugins_
+ const crosshair = plugins.find(
+ ({plugin}) => plugin.toString() === 'Crosshair Plugin'
+ )
+
+ if (!crosshair || this.props.isBarGraph) {
+ return
+ }
+
+ crosshair.plugin.deselect()
+ }
+
+ unhighlightCallback = e => {
+ const {top, bottom, left, right} = this.legendRef.getBoundingClientRect()
+
+ const mouseY = e.clientY
+ const mouseX = e.clientX
+
+ const mouseBuffer = 5
+ const mouseInLegendY = mouseY <= bottom && mouseY >= top - mouseBuffer
+ const mouseInLegendX = mouseX <= right && mouseX >= left
+ const isMouseHoveringLegend = mouseInLegendY && mouseInLegendX
+
+ if (!isMouseHoveringLegend) {
+ this.setState({isHidden: true})
+
+ if (!this.visibility().find(bool => bool === true)) {
+ this.setState({filterText: ''})
+ }
+ }
+ }
+
+ highlightCallback = e => {
+ // Move the Legend on hover
+ const graphRect = this.graphRef.getBoundingClientRect()
+ const legendRect = this.legendRef.getBoundingClientRect()
+
+ const graphWidth = graphRect.width + 32 // Factoring in padding from parent
+ const graphHeight = graphRect.height
+ const graphBottom = graphRect.bottom
+ const legendWidth = legendRect.width
+ const legendHeight = legendRect.height
+ const screenHeight = window.innerHeight
+ const legendMaxLeft = graphWidth - legendWidth / 2
+ const trueGraphX = e.pageX - graphRect.left
+
+ let legendLeft = trueGraphX
+
+ // Enforcing max & min legend offsets
+ if (trueGraphX < legendWidth / 2) {
+ legendLeft = legendWidth / 2
+ } else if (trueGraphX > legendMaxLeft) {
+ legendLeft = legendMaxLeft
+ }
+
+ // Disallow screen overflow of legend
+ const isLegendBottomClipped = graphBottom + legendHeight > screenHeight
+
+ const legendTop = isLegendBottomClipped
+ ? graphHeight + 8 - legendHeight
+ : graphHeight + 8
+
+ this.legendRef.style.left = `${legendLeft}px`
+ this.legendRef.style.top = `${legendTop}px`
+
+ this.setState({isHidden: false})
+ }
+
+ legendFormatter = legend => {
+ if (!legend.x) {
+ return ''
+ }
+
+ const {state: {legend: prevLegend}} = this
+ const highlighted = legend.series.find(s => s.isHighlighted)
+ const prevHighlighted = prevLegend.series.find(s => s.isHighlighted)
+
+ const yVal = highlighted && highlighted.y
+ const prevY = prevHighlighted && prevHighlighted.y
+
+ if (legend.x === prevLegend.x && yVal === prevY) {
+ return ''
+ }
+
+ this.setState({legend})
+ return ''
+ }
+
render() {
const {
legend,
@@ -396,7 +409,7 @@ export default class Dygraph extends Component {
} = this.state
return (
-
+
{},
+ onZoom: () => {},
}
Dygraph.propTypes = {
@@ -477,4 +491,5 @@ Dygraph.propTypes = {
synchronizer: func,
setResolution: func,
dygraphRef: func,
+ onZoom: func,
}
diff --git a/ui/src/shared/components/FieldList.js b/ui/src/shared/components/FieldList.js
index 8b5b13b3f..3f72b7edb 100644
--- a/ui/src/shared/components/FieldList.js
+++ b/ui/src/shared/components/FieldList.js
@@ -1,4 +1,4 @@
-import React, {PropTypes} from 'react'
+import React, {PropTypes, Component} from 'react'
import FieldListItem from 'src/data_explorer/components/FieldListItem'
import GroupByTimeDropdown from 'src/data_explorer/components/GroupByTimeDropdown'
@@ -8,42 +8,13 @@ import FancyScrollbar from 'shared/components/FancyScrollbar'
import {showFieldKeys} from 'shared/apis/metaQuery'
import showFieldKeysParser from 'shared/parsing/showFieldKeys'
-const {bool, func, shape, string} = PropTypes
-
-const FieldList = React.createClass({
- propTypes: {
- query: shape({
- database: string,
- retentionPolicy: string,
- measurement: string,
- }).isRequired,
- onToggleField: func.isRequired,
- onGroupByTime: func.isRequired,
- onFill: func.isRequired,
- applyFuncsToField: func.isRequired,
- isKapacitorRule: bool,
- isInDataExplorer: bool,
- },
-
- getDefaultProps() {
- return {
- isKapacitorRule: false,
- }
- },
-
- contextTypes: {
- source: shape({
- links: shape({
- proxy: string.isRequired,
- }).isRequired,
- }).isRequired,
- },
-
- getInitialState() {
- return {
+class FieldList extends Component {
+ constructor(props) {
+ super(props)
+ this.state = {
fields: [],
}
- },
+ }
componentDidMount() {
const {database, measurement} = this.props.query
@@ -52,7 +23,7 @@ const FieldList = React.createClass({
}
this._getFields()
- },
+ }
componentDidUpdate(prevProps) {
const {database, measurement, retentionPolicy} = this.props.query
@@ -74,20 +45,47 @@ const FieldList = React.createClass({
}
this._getFields()
- },
+ }
- handleFill(value) {
- this.props.onFill(value)
- },
-
- handleGroupByTime(groupBy) {
+ handleGroupByTime = groupBy => {
this.props.onGroupByTime(groupBy.menuOption)
- },
+ }
+
+ handleFill = fill => {
+ this.props.onFill(fill)
+ }
+
+ _getFields = () => {
+ const {database, measurement, retentionPolicy} = this.props.query
+ const {source} = this.context
+ const proxySource = source.links.proxy
+
+ showFieldKeys(
+ proxySource,
+ database,
+ measurement,
+ retentionPolicy
+ ).then(resp => {
+ const {errors, fieldSets} = showFieldKeysParser(resp.data)
+ if (errors.length) {
+ console.error('Error parsing fields keys: ', errors)
+ }
+
+ this.setState({
+ fields: fieldSets[measurement].map(f => ({field: f, funcs: []})),
+ })
+ })
+ }
render() {
- const {query, isKapacitorRule, isInDataExplorer} = this.props
- const hasAggregates = query.fields.some(f => f.funcs && f.funcs.length)
- const hasGroupByTime = query.groupBy.time
+ const {
+ query: {fields = [], groupBy, fill},
+ isKapacitorRule,
+ isInDataExplorer,
+ } = this.props
+
+ const hasAggregates = fields.some(f => f.funcs && f.funcs.length)
+ const hasGroupByTime = groupBy.time
return (
@@ -97,27 +95,24 @@ const FieldList = React.createClass({
?
{isKapacitorRule
? null
- : }
+ : }
: null}
{this.renderList()}
)
- },
+ }
renderList() {
- const {database, measurement} = this.props.query
+ const {database, measurement, fields = []} = this.props.query
if (!database || !measurement) {
return (
@@ -132,9 +127,7 @@ const FieldList = React.createClass({
{this.state.fields.map(fieldFunc => {
- const selectedField = this.props.query.fields.find(
- f => f.field === fieldFunc.field
- )
+ const selectedField = fields.find(f => f.field === fieldFunc.field)
return (
)
- },
+ }
+}
- _getFields() {
- const {database, measurement, retentionPolicy} = this.props.query
- const {source} = this.context
- const proxySource = source.links.proxy
+const {bool, func, shape, string} = PropTypes
- showFieldKeys(
- proxySource,
- database,
- measurement,
- retentionPolicy
- ).then(resp => {
- const {errors, fieldSets} = showFieldKeysParser(resp.data)
- if (errors.length) {
- // TODO: do something
- }
+FieldList.defaultProps = {
+ isKapacitorRule: false,
+}
- this.setState({
- fields: fieldSets[measurement].map(f => {
- return {field: f, funcs: []}
- }),
- })
- })
- },
-})
+FieldList.contextTypes = {
+ source: shape({
+ links: shape({
+ proxy: string.isRequired,
+ }).isRequired,
+ }).isRequired,
+}
+
+FieldList.propTypes = {
+ query: shape({
+ database: string,
+ retentionPolicy: string,
+ measurement: string,
+ }).isRequired,
+ onToggleField: func.isRequired,
+ onGroupByTime: func.isRequired,
+ onFill: func.isRequired,
+ applyFuncsToField: func.isRequired,
+ isKapacitorRule: bool,
+ isInDataExplorer: bool,
+}
export default FieldList
diff --git a/ui/src/shared/components/LayoutRenderer.js b/ui/src/shared/components/LayoutRenderer.js
index f43e6c7fb..91a84ffbc 100644
--- a/ui/src/shared/components/LayoutRenderer.js
+++ b/ui/src/shared/components/LayoutRenderer.js
@@ -148,6 +148,7 @@ class LayoutRenderer extends Component {
templates,
synchronizer,
isEditable,
+ onZoom,
} = this.props
return cells.map(cell => {
@@ -176,6 +177,7 @@ class LayoutRenderer extends Component {
queries={this.standardizeQueries(cell, source)}
cellHeight={h}
axes={axes}
+ onZoom={onZoom}
/>}
@@ -303,6 +305,7 @@ LayoutRenderer.propTypes = {
isStatusPage: bool,
isEditable: bool,
onCancelEditCell: func,
+ onZoom: func,
}
export default LayoutRenderer
diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js
index 26a2ab732..1ed333933 100644
--- a/ui/src/shared/components/LineGraph.js
+++ b/ui/src/shared/components/LineGraph.js
@@ -46,6 +46,7 @@ export default React.createClass({
synchronizer: func,
setResolution: func,
cellHeight: number,
+ onZoom: func,
},
getDefaultProps() {
@@ -101,6 +102,7 @@ export default React.createClass({
synchronizer,
timeRange,
cellHeight,
+ onZoom,
} = this.props
const {labels, timeSeries, dygraphSeries} = this._timeSeries
@@ -176,6 +178,7 @@ export default React.createClass({
synchronizer={synchronizer}
timeRange={timeRange}
setResolution={this.props.setResolution}
+ onZoom={onZoom}
/>
{showSingleStat
?
diff --git a/ui/src/shared/components/RefreshingGraph.js b/ui/src/shared/components/RefreshingGraph.js
index e70ff5b5f..bda40a781 100644
--- a/ui/src/shared/components/RefreshingGraph.js
+++ b/ui/src/shared/components/RefreshingGraph.js
@@ -10,6 +10,7 @@ const RefreshingSingleStat = AutoRefresh(SingleStat)
const RefreshingGraph = ({
axes,
type,
+ onZoom,
queries,
templates,
timeRange,
@@ -46,6 +47,7 @@ const RefreshingGraph = ({
synchronizer={synchronizer}
editQueryStatus={editQueryStatus}
axes={axes}
+ onZoom={onZoom}
/>
)
}
@@ -64,6 +66,7 @@ RefreshingGraph.propTypes = {
axes: shape(),
queries: arrayOf(shape()).isRequired,
editQueryStatus: func,
+ onZoom: func,
}
export default RefreshingGraph
diff --git a/ui/src/shared/components/TimeRangeDropdown.js b/ui/src/shared/components/TimeRangeDropdown.js
index becd9f3bb..46223cdb7 100644
--- a/ui/src/shared/components/TimeRangeDropdown.js
+++ b/ui/src/shared/components/TimeRangeDropdown.js
@@ -9,27 +9,21 @@ import CustomTimeRangeOverlay from 'shared/components/CustomTimeRangeOverlay'
import timeRanges from 'hson!shared/data/timeRanges.hson'
import {DROPDOWN_MENU_MAX_HEIGHT} from 'shared/constants/index'
+const emptyTime = {lower: '', upper: ''}
+
class TimeRangeDropdown extends Component {
constructor(props) {
+ super(props)
const {lower, upper} = props.selected
- super(props)
+ const isTimeValid = moment(upper).isValid() && moment(lower).isValid()
+ const customTimeRange = isTimeValid ? {lower, upper} : emptyTime
this.state = {
autobind: false,
isOpen: false,
isCustomTimeRangeOpen: false,
- customTimeRange:
- moment(props.selected.upper).isValid() &&
- moment(props.selected.lower).isValid()
- ? {
- lower,
- upper,
- }
- : {
- lower: '',
- upper: '',
- },
+ customTimeRange,
}
}
diff --git a/ui/src/shared/graphs/helpers.js b/ui/src/shared/graphs/helpers.js
index 33786c8b5..c98911262 100644
--- a/ui/src/shared/graphs/helpers.js
+++ b/ui/src/shared/graphs/helpers.js
@@ -27,7 +27,7 @@ export const darkenColor = colorStr => {
}
// Bar Graph code below is adapted from http://dygraphs.com/tests/plotters.html
-export const multiColumnBarPlotter = e => {
+export const barPlotter = e => {
// We need to handle all the series simultaneously.
if (e.seriesIndex !== 0) {
return
@@ -99,3 +99,28 @@ export const multiColumnBarPlotter = e => {
}
}
}
+
+export const OPTIONS = {
+ rightGap: 0,
+ axisLineWidth: 2,
+ gridLineWidth: 1,
+ animatedZooms: true,
+ labelsSeparateLines: false,
+ hideOverlayOnMouseOut: false,
+ highlightSeriesBackgroundAlpha: 1.0,
+ highlightSeriesBackgroundColor: 'rgb(41, 41, 51)',
+}
+
+export const highlightSeriesOpts = {
+ strokeWidth: 2,
+ highlightCircleSize: 5,
+}
+
+export const hasherino = (str, len) =>
+ str
+ .split('')
+ .map(char => char.charCodeAt(0))
+ .reduce((hash, code) => hash + code, 0) % len
+
+export const LABEL_WIDTH = 60
+export const CHAR_PIXELS = 7
diff --git a/ui/src/shared/parsing/getRangeForDygraph.js b/ui/src/shared/parsing/getRangeForDygraph.js
index 06364a926..eec5dda7c 100644
--- a/ui/src/shared/parsing/getRangeForDygraph.js
+++ b/ui/src/shared/parsing/getRangeForDygraph.js
@@ -79,4 +79,10 @@ const getRange = (
return [min, max]
}
+const coerceToNum = str => (str ? +str : null)
+export const getStackedRange = (bounds = [null, null]) => [
+ coerceToNum(bounds[0]),
+ coerceToNum(bounds[1]),
+]
+
export default getRange
diff --git a/ui/src/side_nav/components/NavItems.js b/ui/src/side_nav/components/NavItems.js
index 0752f29ef..4977b4b35 100644
--- a/ui/src/side_nav/components/NavItems.js
+++ b/ui/src/side_nav/components/NavItems.js
@@ -86,6 +86,7 @@ const NavBlock = React.createClass({
{this.renderSquare()}
)
diff --git a/ui/src/side_nav/containers/SideNav.js b/ui/src/side_nav/containers/SideNav.js
index e782bd404..7b8597470 100644
--- a/ui/src/side_nav/containers/SideNav.js
+++ b/ui/src/side_nav/containers/SideNav.js
@@ -69,10 +69,14 @@ const SideNav = React.createClass({
const dataExplorerLink = `${sourcePrefix}/chronograf/data-explorer`
const isUsingAuth = !!logoutLink
+ const isDefaultPage = location.split('/').includes(DEFAULT_HOME_PAGE)
+
return isHidden
? null
:
-
+
{
- const isSelected = query.fields.find(f => f.field === field)
+ const {fields, groupBy} = query
+
+ if (!fields) {
+ return {
+ ...query,
+ fields: [{field, funcs: ['mean']}],
+ }
+ }
+
+ const isSelected = fields.find(f => f.field === field)
if (isSelected) {
- const nextFields = query.fields.filter(f => f.field !== field)
+ const nextFields = fields.filter(f => f.field !== field)
if (!nextFields.length) {
- const nextGroupBy = {...query.groupBy, time: null}
return {
...query,
fields: nextFields,
- groupBy: nextGroupBy,
+ groupBy: {...groupBy, time: null},
}
}