diff --git a/ui/spec/utils/timeSeriesToDygraphSpec.js b/ui/spec/utils/timeSeriesToDygraphSpec.js index a89bfd226..0b3b6c72a 100644 --- a/ui/spec/utils/timeSeriesToDygraphSpec.js +++ b/ui/spec/utils/timeSeriesToDygraphSpec.js @@ -1,4 +1,7 @@ import timeSeriesToDygraph from 'src/utils/timeSeriesToDygraph'; +import {STROKE_WIDTH} from 'src/shared/constants'; + +const {light: strokeWidth} = STROKE_WIDTH; describe('timeSeriesToDygraph', () => { it('parses a raw InfluxDB response into a dygraph friendly data format', () => { @@ -46,9 +49,11 @@ describe('timeSeriesToDygraph', () => { dygraphSeries: { 'm1.f1': { axis: 'y', + strokeWidth, }, 'm1.f2': { axis: 'y', + strokeWidth, }, }, }; @@ -156,12 +161,15 @@ describe('timeSeriesToDygraph', () => { dygraphSeries: { 'm1.f1': { axis: 'y', + strokeWidth, }, 'm1.f2': { axis: 'y', + strokeWidth, }, 'm3.f3': { axis: 'y2', + strokeWidth, }, }, }; diff --git a/ui/src/chronograf/components/Panel.js b/ui/src/chronograf/components/Panel.js index 94b83fb9c..c680e3e99 100644 --- a/ui/src/chronograf/components/Panel.js +++ b/ui/src/chronograf/components/Panel.js @@ -5,40 +5,38 @@ import QueryTabItem from './QueryTabItem'; import RenamePanelModal from './RenamePanelModal'; import SimpleDropdown from 'src/shared/components/SimpleDropdown'; -const {shape, func, bool, arrayOf} = PropTypes; const Panel = React.createClass({ propTypes: { - panel: shape({}).isRequired, - queries: arrayOf(shape({})).isRequired, + panel: PropTypes.shape({ + id: PropTypes.string.isRequired, + }).isRequired, + queries: PropTypes.arrayOf(PropTypes.shape({})).isRequired, timeRange: PropTypes.shape({ upper: PropTypes.string, lower: PropTypes.string, }).isRequired, - isExpanded: bool.isRequired, - onTogglePanel: func.isRequired, - actions: shape({ - chooseNamespace: func.isRequired, - chooseMeasurement: func.isRequired, - chooseTag: func.isRequired, - groupByTag: func.isRequired, - addQuery: func.isRequired, - deleteQuery: func.isRequired, - toggleField: func.isRequired, - groupByTime: func.isRequired, - toggleTagAcceptance: func.isRequired, - applyFuncsToField: func.isRequired, - deletePanel: func.isRequired, + isExpanded: PropTypes.bool.isRequired, + onTogglePanel: PropTypes.func.isRequired, + actions: PropTypes.shape({ + chooseNamespace: PropTypes.func.isRequired, + chooseMeasurement: PropTypes.func.isRequired, + chooseTag: PropTypes.func.isRequired, + groupByTag: PropTypes.func.isRequired, + addQuery: PropTypes.func.isRequired, + deleteQuery: PropTypes.func.isRequired, + toggleField: PropTypes.func.isRequired, + groupByTime: PropTypes.func.isRequired, + toggleTagAcceptance: PropTypes.func.isRequired, + applyFuncsToField: PropTypes.func.isRequired, + deletePanel: PropTypes.func.isRequired, + renamePanel: PropTypes.func.isRequired, }).isRequired, - }, - - getInitialState() { - return { - activeQueryId: null, - }; + setActiveQuery: PropTypes.func.isRequired, + activeQueryID: PropTypes.string, }, handleSetActiveQuery(query) { - this.setState({activeQueryId: query.id}); + this.props.setActiveQuery(query.id); }, handleAddQuery() { @@ -63,8 +61,8 @@ const Panel = React.createClass({ }, getActiveQuery() { - const {queries} = this.props; - const activeQuery = queries.find((query) => query.id === this.state.activeQueryId); + const {queries, activeQueryID} = this.props; + const activeQuery = queries.find((query) => query.id === activeQueryID); const defaultQuery = queries[0]; return activeQuery || defaultQuery; diff --git a/ui/src/chronograf/components/PanelBuilder.js b/ui/src/chronograf/components/PanelBuilder.js index b6b78e5d8..b827971f5 100644 --- a/ui/src/chronograf/components/PanelBuilder.js +++ b/ui/src/chronograf/components/PanelBuilder.js @@ -24,7 +24,9 @@ const PanelBuilder = React.createClass({ deletePanel: func.isRequired, }).isRequired, setActivePanel: func.isRequired, + setActiveQuery: func.isRequired, activePanelID: string, + activeQueryID: string, }, handleCreateExploer() { @@ -32,7 +34,7 @@ const PanelBuilder = React.createClass({ }, render() { - const {activePanelID, width, actions, setActivePanel} = this.props; + const {width, actions, setActivePanel, setActiveQuery, activePanelID, activeQueryID} = this.props; return (
@@ -40,7 +42,9 @@ const PanelBuilder = React.createClass({
); diff --git a/ui/src/chronograf/components/PanelList.js b/ui/src/chronograf/components/PanelList.js index 0436b6477..411f31286 100644 --- a/ui/src/chronograf/components/PanelList.js +++ b/ui/src/chronograf/components/PanelList.js @@ -15,7 +15,9 @@ const PanelList = React.createClass({ queryConfigs: PropTypes.shape({}), actions: shape({}).isRequired, setActivePanel: func.isRequired, + setActiveQuery: func.isRequired, activePanelID: string, + activeQueryID: string, }, handleTogglePanel(panel) { @@ -25,12 +27,12 @@ const PanelList = React.createClass({ null : panel.id; this.props.setActivePanel(activePanelID); + // Reset the activeQueryID when toggling Exporations + this.props.setActiveQuery(null); }, render() { - const {actions, panels, timeRange, queryConfigs} = this.props; - - const activePanelID = this.props.activePanelID; + const {actions, panels, timeRange, queryConfigs, setActiveQuery, activeQueryID, activePanelID} = this.props; return (
@@ -51,8 +53,10 @@ const PanelList = React.createClass({ queries={queries} timeRange={timeRange} onTogglePanel={this.handleTogglePanel} + setActiveQuery={setActiveQuery} isExpanded={panelID === activePanelID} actions={allActions} + activeQueryID={activeQueryID} /> ); })} diff --git a/ui/src/chronograf/components/Visualization.js b/ui/src/chronograf/components/Visualization.js index 2a58796a6..63a11c772 100644 --- a/ui/src/chronograf/components/Visualization.js +++ b/ui/src/chronograf/components/Visualization.js @@ -6,22 +6,22 @@ import LineGraph from 'shared/components/LineGraph'; import MultiTable from './MultiTable'; const RefreshingLineGraph = AutoRefresh(LineGraph); -const {bool, shape, string, arrayOf} = PropTypes; const Visualization = React.createClass({ propTypes: { - timeRange: shape({ - upper: string, - lower: string, + timeRange: PropTypes.shape({ + upper: PropTypes.string, + lower: PropTypes.string, }).isRequired, - queryConfigs: arrayOf(shape({})).isRequired, - isActive: bool.isRequired, - name: string, + queryConfigs: PropTypes.arrayOf(PropTypes.shape({})).isRequired, + isActive: PropTypes.bool.isRequired, + name: PropTypes.string, + activeQueryIndex: PropTypes.number, }, contextTypes: { - source: shape({ - links: shape({ - proxy: string.isRequired, + source: PropTypes.shape({ + links: PropTypes.shape({ + proxy: PropTypes.string.isRequired, }).isRequired, }).isRequired, }, @@ -45,7 +45,7 @@ const Visualization = React.createClass({ }, render() { - const {queryConfigs, timeRange, isActive, name} = this.props; + const {queryConfigs, timeRange, isActive, name, activeQueryIndex} = this.props; const {source} = this.context; const proxyLink = source.links.proxy; @@ -77,6 +77,7 @@ const Visualization = React.createClass({ ) : }
diff --git a/ui/src/chronograf/components/Visualizations.js b/ui/src/chronograf/components/Visualizations.js index 4b53cedd8..aa577b0d6 100644 --- a/ui/src/chronograf/components/Visualizations.js +++ b/ui/src/chronograf/components/Visualizations.js @@ -14,6 +14,7 @@ const Visualizations = React.createClass({ queryConfigs: shape({}).isRequired, width: string, activePanelID: string, + activeQueryID: string, }, render() { @@ -22,7 +23,9 @@ const Visualizations = React.createClass({ const visualizations = Object.keys(panels).map((panelID) => { const panel = panels[panelID]; const queries = panel.queryIds.map((id) => queryConfigs[id]); - return ; + const isActive = panelID === activePanelID; + + return ; }); return ( @@ -31,6 +34,21 @@ const Visualizations = React.createClass({ ); }, + + getActiveQueryIndex(panelID) { + const {activeQueryID, activePanelID, panels} = this.props; + const isPanelActive = panelID === activePanelID; + + if (!isPanelActive) { + return -1; + } + + if (activeQueryID === null) { + return 0; + } + + return panels[panelID].queryIds.indexOf(activeQueryID); + }, }); function mapStateToProps(state) { diff --git a/ui/src/chronograf/containers/DataExplorer.js b/ui/src/chronograf/containers/DataExplorer.js index 7303d86d5..f1fa92ee4 100644 --- a/ui/src/chronograf/containers/DataExplorer.js +++ b/ui/src/chronograf/containers/DataExplorer.js @@ -49,14 +49,17 @@ const DataExplorer = React.createClass({ getInitialState() { return { - activePanelId: null, + activePanelID: null, + activeQueryID: null, }; }, handleSetActivePanel(id) { - this.setState({ - activePanelID: id, - }); + this.setState({activePanelID: id}); + }, + + handleSetActiveQuery(id) { + this.setState({activeQueryID: id}); }, render() { @@ -76,8 +79,18 @@ const DataExplorer = React.createClass({ explorerID={explorerID} /> - - + + ); diff --git a/ui/src/shared/components/Dygraph.js b/ui/src/shared/components/Dygraph.js index 74c6b3664..99b2a77ef 100644 --- a/ui/src/shared/components/Dygraph.js +++ b/ui/src/shared/components/Dygraph.js @@ -77,7 +77,6 @@ export default React.createClass({ fillGraph: this.props.isGraphFilled, axisLineWidth: 2, gridLineWidth: 1, - strokeWidth: 1.5, highlightCircleSize: 3, colors: finalLineColors, series: dygraphSeries, @@ -143,7 +142,7 @@ export default React.createClass({ } const timeSeries = this.getTimeSeries(); - const {labels, ranges} = this.props; + const {labels, ranges, options, dygraphSeries} = this.props; dygraph.updateOptions({ labels, @@ -156,7 +155,8 @@ export default React.createClass({ valueRange: getRange(timeSeries, ranges.y2), }, }, - underlayCallback: this.props.options.underlayCallback, + underlayCallback: options.underlayCallback, + series: dygraphSeries, }); dygraph.resize(); diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index a982fb64a..38e77e07c 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -7,20 +7,19 @@ import _ from 'lodash'; import timeSeriesToDygraph from 'utils/timeSeriesToDygraph'; import lastValues from 'src/shared/parsing/lastValues'; -const {array, string, arrayOf, bool, shape} = PropTypes; - export default React.createClass({ displayName: 'LineGraph', propTypes: { - data: arrayOf(shape({}).isRequired).isRequired, - title: string, + data: PropTypes.arrayOf(PropTypes.shape({}).isRequired).isRequired, + title: PropTypes.string, isFetchingInitially: PropTypes.bool, isRefreshing: PropTypes.bool, underlayCallback: PropTypes.func, - isGraphFilled: bool, - overrideLineColors: array, - queries: arrayOf(shape({}).isRequired).isRequired, - showSingleStat: bool, + isGraphFilled: PropTypes.bool, + overrideLineColors: PropTypes.array, + queries: PropTypes.arrayOf(PropTypes.shape({}).isRequired).isRequired, + showSingleStat: PropTypes.bool, + activeQueryIndex: PropTypes.number, }, getDefaultProps() { @@ -36,12 +35,13 @@ export default React.createClass({ }, componentWillMount() { - this._timeSeries = timeSeriesToDygraph(this.props.data); + this._timeSeries = timeSeriesToDygraph(this.props.data, this.props.activeQueryIndex); }, componentWillUpdate(nextProps) { - if (this.props.data !== nextProps.data) { - this._timeSeries = timeSeriesToDygraph(nextProps.data); + const {data, activeQueryIndex} = this.props; + if (data !== nextProps.data || activeQueryIndex !== nextProps.activeQueryIndex) { + this._timeSeries = timeSeriesToDygraph(nextProps.data, nextProps.activeQueryIndex); } }, diff --git a/ui/src/shared/constants/index.js b/ui/src/shared/constants/index.js index f0d3767af..aab4118e4 100644 --- a/ui/src/shared/constants/index.js +++ b/ui/src/shared/constants/index.js @@ -462,3 +462,8 @@ export const DEFAULT_LINE_COLORS = [ ], ], ]; + +export const STROKE_WIDTH = { + heavy: 3.5, + light: 1.5, +}; diff --git a/ui/src/utils/timeSeriesToDygraph.js b/ui/src/utils/timeSeriesToDygraph.js index 572b2b58e..9702842c4 100644 --- a/ui/src/utils/timeSeriesToDygraph.js +++ b/ui/src/utils/timeSeriesToDygraph.js @@ -1,9 +1,12 @@ +import {STROKE_WIDTH} from 'src/shared/constants'; /** * Accepts an array of raw influxdb responses and returns a format * that Dygraph understands. */ -export default function timeSeriesToDygraph(raw = []) { +// activeQueryIndex is an optional argument that indicated which query's series +// we want highlighted. +export default function timeSeriesToDygraph(raw = [], activeQueryIndex) { const labels = ['time']; // all of the effective field names (i.e. .) const fieldToIndex = {}; // see parseSeries const dates = {}; // map of date as string to date value to minimize string coercion @@ -90,7 +93,13 @@ export default function timeSeriesToDygraph(raw = []) { // ex given this timeSeries [Date, 10, 20, 30] field index at 2 would correspond to value 20 fieldToIndex[effectiveFieldName] = labels.length; labels.push(effectiveFieldName); - dygraphSeries[effectiveFieldName] = {axis: queryIndex === 0 ? 'y' : 'y2'}; + + const {light, heavy} = STROKE_WIDTH; + + dygraphSeries[effectiveFieldName] = { + axis: queryIndex === 0 ? 'y' : 'y2', + strokeWidth: queryIndex === activeQueryIndex ? heavy : light, + }; }); (series.values || []).forEach(parseRow);