Merge branch 'master' into bugfix/enable-disable

pull/10616/head
Andrew Watkins 2017-06-20 13:15:35 -07:00 committed by GitHub
commit 7a07fa4e70
8 changed files with 99 additions and 79 deletions

View File

@ -1,10 +1,10 @@
## v1.3.4.0 [unreleased]
## v1.3.3.1 [2017-06-20]
### Bug Fixes
1. [#1641](https://github.com/influxdata/chronograf/pull/1641): Fix enable / disable being out of sync on Kapacitor Rules Page
### Features
### UI Improvements
1. [#1642](https://github.com/influxdata/chronograf/pull/1642): Do not prefix basepath to external link for news feed
## v1.3.3.0 [2017-06-19]
@ -12,6 +12,7 @@
1. [#1512](https://github.com/influxdata/chronograf/pull/1512): Prevent legend from flowing over window bottom bound
1. [#1600](https://github.com/influxdata/chronograf/pull/1600): Prevent Kapacitor configurations from having the same name
1. [#1600](https://github.com/influxdata/chronograf/pull/1600): Limit Kapacitor configuration names to 33 characters to fix display bug
1. [#1622](https://github.com/influxdata/chronograf/pull/1622): Use function selector grid in Kapacitor rule builder query maker instead of dropdown
### Features
1. [#1512](https://github.com/influxdata/chronograf/pull/1512): Synchronize vertical crosshair at same time across all graphs in a dashboard
@ -23,6 +24,7 @@
1. [#1599](https://github.com/influxdata/chronograf/pull/1599): Bar graph option added to dashboard
1. [#1600](https://github.com/influxdata/chronograf/pull/1600): Redesign source management table to be more intuitive
1. [#1600](https://github.com/influxdata/chronograf/pull/1600): Redesign Line + Single Stat cells to appear more like a sparkline, and improve legibility
1. [#1639](https://github.com/influxdata/chronograf/pull/1639): Improve graph synchronization performance
## v1.3.2.1 [2017-06-06]

View File

@ -212,8 +212,14 @@ class DashboardPage extends Component {
synchronizer(dygraph) {
const dygraphs = [...this.state.dygraphs, dygraph]
if (dygraphs.length > 1) {
Dygraph.synchronize(dygraphs)
const {dashboards, params} = this.props
const dashboard = dashboards.find(d => d.id === +params.dashboardID)
if (dashboard && dygraphs.length === dashboard.cells.length) {
Dygraph.synchronize(dygraphs, {
selection: true,
zoom: false,
range: false,
})
}
this.setState({dygraphs})
}
@ -266,8 +272,10 @@ class DashboardPage extends Component {
values: [],
}
const templatesIncludingDashTime = (dashboard &&
dashboard.templates.concat(dashboardTime).concat(interval)) || []
const templatesIncludingDashTime =
(dashboard &&
dashboard.templates.concat(dashboardTime).concat(interval)) ||
[]
const {selectedCell, isEditMode, isTemplating} = this.state

View File

@ -3,9 +3,6 @@ import classnames from 'classnames'
import _ from 'lodash'
import FunctionSelector from 'shared/components/FunctionSelector'
import Dropdown from 'shared/components/Dropdown'
import {INFLUXQL_FUNCTIONS} from '../constants'
const {string, shape, func, arrayOf, bool} = PropTypes
const FieldListItem = React.createClass({
@ -41,7 +38,7 @@ const FieldListItem = React.createClass({
handleApplyFunctions(selectedFuncs) {
this.props.onApplyFuncsToField({
field: this.props.fieldFunc.field,
funcs: this.props.isKapacitorRule ? [selectedFuncs.text] : selectedFuncs,
funcs: selectedFuncs,
})
this.setState({isOpen: false})
},
@ -50,38 +47,6 @@ const FieldListItem = React.createClass({
const {isKapacitorRule, fieldFunc, isSelected} = this.props
const {isOpen} = this.state
const {field: fieldText} = fieldFunc
const items = INFLUXQL_FUNCTIONS.map(text => {
return {text}
})
if (isKapacitorRule) {
return (
<div
className={classnames('query-builder--list-item', {
active: isSelected,
})}
key={fieldFunc}
onClick={_.wrap(fieldFunc, this.handleToggleField)}
>
<span>
<div className="query-builder--checkbox" />
{fieldText}
</span>
{isSelected
? <Dropdown
className="dropdown-110"
menuClass="dropdown-malachite"
buttonSize="btn-xs"
items={items}
onChoose={this.handleApplyFunctions}
selected={
fieldFunc.funcs.length ? fieldFunc.funcs[0] : 'Function'
}
/>
: null}
</div>
)
}
let fieldFuncsLabel
if (!fieldFunc.funcs.length) {
@ -121,6 +86,7 @@ const FieldListItem = React.createClass({
? <FunctionSelector
onApply={this.handleApplyFunctions}
selectedItems={fieldFunc.funcs || []}
singleSelect={isKapacitorRule}
/>
: null}
</div>

View File

@ -5,6 +5,8 @@ import {bindActionCreators} from 'redux'
import _ from 'lodash'
import classnames from 'classnames'
import Dygraph from 'src/external/dygraph'
import LayoutRenderer from 'shared/components/LayoutRenderer'
import DashboardHeader from 'src/dashboards/components/DashboardHeader'
import FancyScrollbar from 'shared/components/FancyScrollbar'
@ -51,6 +53,7 @@ export const HostPage = React.createClass({
layouts: [],
hosts: [],
timeRange: timeRanges.find(tr => tr.lower === 'now() - 1h'),
dygraphs: [],
}
},
@ -100,6 +103,22 @@ export const HostPage = React.createClass({
this.setState({timeRange})
},
synchronizer(dygraph) {
const dygraphs = [...this.state.dygraphs, dygraph]
const numGraphs = this.state.layouts.reduce((acc, {cells}) => {
return acc + cells.length
}, 0)
if (dygraphs.length === numGraphs) {
Dygraph.synchronize(dygraphs, {
selection: true,
zoom: false,
range: false,
})
}
this.setState({dygraphs})
},
renderLayouts(layouts) {
const {timeRange} = this.state
const {source, autoRefresh} = this.props
@ -156,6 +175,7 @@ export const HostPage = React.createClass({
source={source}
host={this.props.params.hostID}
shouldNotBeEditable={true}
synchronizer={this.synchronizer}
/>
)
},

View File

@ -12,6 +12,7 @@ class FunctionSelector extends Component {
}
this.onSelect = ::this.onSelect
this.onSingleSelect = ::this.onSingleSelect
this.handleApplyFunctions = ::this.handleApplyFunctions
}
@ -36,6 +37,16 @@ class FunctionSelector extends Component {
this.setState({localSelectedItems: nextItems})
}
onSingleSelect(item) {
if (item === this.state.localSelectedItems[0]) {
this.props.onApply([])
this.setState({localSelectedItems: []})
} else {
this.props.onApply([item])
this.setState({localSelectedItems: [item]})
}
}
isSelected(item) {
return !!this.state.localSelectedItems.find(text => text === item)
}
@ -48,22 +59,25 @@ class FunctionSelector extends Component {
render() {
const {localSelectedItems} = this.state
const {singleSelect} = this.props
return (
<div className="function-selector">
<div className="function-selector--header">
<span>
{localSelectedItems.length > 0
? `${localSelectedItems.length} Selected`
: 'Select functions below'}
</span>
<div
className="btn btn-xs btn-success"
onClick={this.handleApplyFunctions}
>
Apply
</div>
</div>
{singleSelect
? null
: <div className="function-selector--header">
<span>
{localSelectedItems.length > 0
? `${localSelectedItems.length} Selected`
: 'Select functions below'}
</span>
<div
className="btn btn-xs btn-success"
onClick={this.handleApplyFunctions}
>
Apply
</div>
</div>}
<div className="function-selector--grid">
{INFLUXQL_FUNCTIONS.map((f, i) => {
return (
@ -72,7 +86,10 @@ class FunctionSelector extends Component {
className={classnames('function-selector--item', {
active: this.isSelected(f),
})}
onClick={_.wrap(f, this.onSelect)}
onClick={_.wrap(
f,
singleSelect ? this.onSingleSelect : this.onSelect
)}
>
{f}
</div>
@ -84,11 +101,12 @@ class FunctionSelector extends Component {
}
}
const {arrayOf, func, string} = PropTypes
const {arrayOf, bool, func, string} = PropTypes
FunctionSelector.propTypes = {
onApply: func.isRequired,
selectedItems: arrayOf(string.isRequired).isRequired,
singleSelect: bool,
}
export default FunctionSelector

View File

@ -1,10 +1,13 @@
import AJAX from 'utils/ajax'
export const fetchJSONFeed = url =>
AJAX({
method: 'GET',
url,
// For explanation of why this header makes this work:
// https://stackoverflow.com/questions/22968406/how-to-skip-the-options-preflight-request-in-angularjs
headers: {'Content-Type': 'text/plain; charset=UTF-8'},
})
AJAX(
{
method: 'GET',
url,
// For explanation of why this header makes this work:
// https://stackoverflow.com/questions/22968406/how-to-skip-the-options-preflight-request-in-angularjs
headers: {'Content-Type': 'text/plain; charset=UTF-8'},
},
true // don't prefix route of external link with basepath
)

View File

@ -33,10 +33,12 @@ $function-selector--text-active: $g20-white;
display: flex;
flex-wrap: wrap;
padding: $function-selector--gutter;
padding-top: 0;
border-radius: 0 0 $radius $radius;
background-color: $function-selector--bg;
}
.function-selector--header + .function-selector--grid {
padding-top: 0;
}
.function-selector--item {
@include no-user-select();
border-radius: $radius;

View File

@ -2,6 +2,13 @@ import axios from 'axios'
let links
// do not prefix route with basepath, ex. for external links
const addBasepath = (url, excludeBasepath) => {
const basepath = window.basepath || ''
return excludeBasepath ? url : `${basepath}${url}`
}
const generateResponseWithLinks = (response, {auth, logout, external}) => ({
...response,
auth: {links: auth},
@ -9,24 +16,18 @@ const generateResponseWithLinks = (response, {auth, logout, external}) => ({
external,
})
const AJAX = async ({
url,
resource,
id,
method = 'GET',
data = {},
params = {},
headers = {},
}) => {
const AJAX = async (
{url, resource, id, method = 'GET', data = {}, params = {}, headers = {}},
excludeBasepath
) => {
try {
const basepath = window.basepath || ''
let response
url = `${basepath}${url}`
url = addBasepath(url, excludeBasepath)
if (!links) {
const linksRes = (response = await axios({
url: `${basepath}/chronograf/v1`,
url: addBasepath('/chronograf/v1', excludeBasepath),
method: 'GET',
}))
links = linksRes.data
@ -34,8 +35,8 @@ const AJAX = async ({
if (resource) {
url = id
? `${basepath}${links[resource]}/${id}`
: `${basepath}${links[resource]}`
? addBasepath(`${links[resource]}/${id}`, excludeBasepath)
: addBasepath(`${links[resource]}`, excludeBasepath)
}
response = await axios({