Merge pull request #2103 from influxdata/dashboard-manual-refresh
[WIP] Manual Refresh Buttonpull/2144/head
commit
1b3ad86e3f
|
@ -13,6 +13,7 @@
|
|||
1. [#2045](https://github.com/influxdata/chronograf/pull/2045): Add CSV download option in dashboard cells
|
||||
1. [#2133](https://github.com/influxdata/chronograf/pull/2133): Implicitly prepend source urls with http://
|
||||
1. [#2127](https://github.com/influxdata/chronograf/pull/2127): Add support for graph zooming and point display on the millisecond-level
|
||||
1. [#2103](https://github.com/influxdata/chronograf/pull/2103): Add manual refresh button for Dashboard, Data Explorer, and Host Pages
|
||||
|
||||
### UI Improvements
|
||||
1. [#2111](https://github.com/influxdata/chronograf/pull/2111): Increase size of Cell Editor query tabs to reveal more of their query strings
|
||||
|
|
|
@ -13,6 +13,7 @@ const Dashboard = ({
|
|||
onAddCell,
|
||||
timeRange,
|
||||
autoRefresh,
|
||||
manualRefresh,
|
||||
onDeleteCell,
|
||||
synchronizer,
|
||||
onPositionChange,
|
||||
|
@ -57,6 +58,7 @@ const Dashboard = ({
|
|||
isEditable={true}
|
||||
timeRange={timeRange}
|
||||
autoRefresh={autoRefresh}
|
||||
manualRefresh={manualRefresh}
|
||||
synchronizer={synchronizer}
|
||||
onDeleteCell={onDeleteCell}
|
||||
onPositionChange={onPositionChange}
|
||||
|
@ -111,6 +113,7 @@ Dashboard.propTypes = {
|
|||
}).isRequired,
|
||||
sources: arrayOf(shape({})).isRequired,
|
||||
autoRefresh: number.isRequired,
|
||||
manualRefresh: number,
|
||||
timeRange: shape({}).isRequired,
|
||||
onOpenTemplateManager: func.isRequired,
|
||||
onSelectTemplate: func.isRequired,
|
||||
|
|
|
@ -17,6 +17,7 @@ const DashboardHeader = ({
|
|||
isHidden,
|
||||
handleChooseTimeRange,
|
||||
handleChooseAutoRefresh,
|
||||
onManualRefresh,
|
||||
handleClickPresentationButton,
|
||||
onAddCell,
|
||||
onEditDashboard,
|
||||
|
@ -76,6 +77,7 @@ const DashboardHeader = ({
|
|||
: null}
|
||||
<AutoRefreshDropdown
|
||||
onChoose={handleChooseAutoRefresh}
|
||||
onManualRefresh={onManualRefresh}
|
||||
selected={autoRefresh}
|
||||
iconName="refresh"
|
||||
/>
|
||||
|
@ -118,6 +120,7 @@ DashboardHeader.propTypes = {
|
|||
isHidden: bool.isRequired,
|
||||
handleChooseTimeRange: func.isRequired,
|
||||
handleChooseAutoRefresh: func.isRequired,
|
||||
onManualRefresh: func.isRequired,
|
||||
handleClickPresentationButton: func.isRequired,
|
||||
onAddCell: func,
|
||||
onEditDashboard: func,
|
||||
|
|
|
@ -11,6 +11,7 @@ import DashboardHeader from 'src/dashboards/components/DashboardHeader'
|
|||
import DashboardHeaderEdit from 'src/dashboards/components/DashboardHeaderEdit'
|
||||
import Dashboard from 'src/dashboards/components/Dashboard'
|
||||
import TemplateVariableManager from 'src/dashboards/components/template_variables/Manager'
|
||||
import ManualRefresh from 'src/shared/components/ManualRefresh'
|
||||
|
||||
import {errorThrown as errorThrownAction} from 'shared/actions/errors'
|
||||
import idNormalizer, {TYPE_ID} from 'src/normalizers/id'
|
||||
|
@ -171,7 +172,7 @@ class DashboardPage extends Component {
|
|||
}
|
||||
|
||||
synchronizer = dygraph => {
|
||||
const dygraphs = [...this.state.dygraphs, dygraph]
|
||||
const dygraphs = [...this.state.dygraphs, dygraph].filter(d => d.graphDiv)
|
||||
const {dashboards, params: {dashboardID}} = this.props
|
||||
|
||||
const dashboard = dashboards.find(
|
||||
|
@ -189,6 +190,7 @@ class DashboardPage extends Component {
|
|||
range: false,
|
||||
})
|
||||
}
|
||||
|
||||
this.setState({dygraphs})
|
||||
}
|
||||
|
||||
|
@ -213,6 +215,8 @@ class DashboardPage extends Component {
|
|||
dashboard,
|
||||
dashboards,
|
||||
autoRefresh,
|
||||
manualRefresh,
|
||||
onManualRefresh,
|
||||
cellQueryStatus,
|
||||
dashboardActions,
|
||||
inPresentationMode,
|
||||
|
@ -324,6 +328,7 @@ class DashboardPage extends Component {
|
|||
buttonText={dashboard ? dashboard.name : ''}
|
||||
showTemplateControlBar={showTemplateControlBar}
|
||||
handleChooseAutoRefresh={handleChooseAutoRefresh}
|
||||
onManualRefresh={onManualRefresh}
|
||||
handleChooseTimeRange={this.handleChooseTimeRange}
|
||||
onToggleTempVarControls={this.handleToggleTempVarControls}
|
||||
handleClickPresentationButton={handleClickPresentationButton}
|
||||
|
@ -345,6 +350,7 @@ class DashboardPage extends Component {
|
|||
dashboard={dashboard}
|
||||
timeRange={timeRange}
|
||||
autoRefresh={autoRefresh}
|
||||
manualRefresh={manualRefresh}
|
||||
onZoom={this.handleZoomedTimeRange}
|
||||
onAddCell={this.handleAddCell}
|
||||
synchronizer={this.synchronizer}
|
||||
|
@ -429,6 +435,8 @@ DashboardPage.propTypes = {
|
|||
status: shape(),
|
||||
}).isRequired,
|
||||
errorThrown: func,
|
||||
manualRefresh: number.isRequired,
|
||||
onManualRefresh: func.isRequired,
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, {params: {dashboardID}}) => {
|
||||
|
@ -474,4 +482,6 @@ const mapDispatchToProps = dispatch => ({
|
|||
errorThrown: bindActionCreators(errorThrownAction, dispatch),
|
||||
})
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DashboardPage)
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(
|
||||
ManualRefresh(DashboardPage)
|
||||
)
|
||||
|
|
|
@ -12,6 +12,7 @@ const VisView = ({
|
|||
templates,
|
||||
autoRefresh,
|
||||
heightPixels,
|
||||
manualRefresh,
|
||||
editQueryStatus,
|
||||
resizerBottomHeight,
|
||||
}) => {
|
||||
|
@ -41,6 +42,7 @@ const VisView = ({
|
|||
templates={templates}
|
||||
cellHeight={heightPixels}
|
||||
autoRefresh={autoRefresh}
|
||||
manualRefresh={manualRefresh}
|
||||
editQueryStatus={editQueryStatus}
|
||||
/>
|
||||
)
|
||||
|
@ -58,6 +60,7 @@ VisView.propTypes = {
|
|||
autoRefresh: number.isRequired,
|
||||
heightPixels: number,
|
||||
editQueryStatus: func.isRequired,
|
||||
manualRefresh: number,
|
||||
activeQueryIndex: number,
|
||||
resizerBottomHeight: number,
|
||||
}
|
||||
|
|
|
@ -55,9 +55,9 @@ class Visualization extends Component {
|
|||
autoRefresh,
|
||||
heightPixels,
|
||||
queryConfigs,
|
||||
manualRefresh,
|
||||
editQueryStatus,
|
||||
activeQueryIndex,
|
||||
isInDataExplorer,
|
||||
resizerBottomHeight,
|
||||
errorThrown,
|
||||
} = this.props
|
||||
|
@ -99,12 +99,12 @@ class Visualization extends Component {
|
|||
axes={axes}
|
||||
query={query}
|
||||
queries={queries}
|
||||
templates={templates}
|
||||
cellType={cellType}
|
||||
templates={templates}
|
||||
autoRefresh={autoRefresh}
|
||||
heightPixels={heightPixels}
|
||||
manualRefresh={manualRefresh}
|
||||
editQueryStatus={editQueryStatus}
|
||||
isInDataExplorer={isInDataExplorer}
|
||||
resizerBottomHeight={resizerBottomHeight}
|
||||
/>
|
||||
</div>
|
||||
|
@ -123,7 +123,7 @@ Visualization.defaultProps = {
|
|||
cellType: '',
|
||||
}
|
||||
|
||||
const {arrayOf, bool, func, number, shape, string} = PropTypes
|
||||
const {arrayOf, func, number, shape, string} = PropTypes
|
||||
|
||||
Visualization.contextTypes = {
|
||||
source: shape({
|
||||
|
@ -138,7 +138,6 @@ Visualization.propTypes = {
|
|||
cellType: string,
|
||||
autoRefresh: number.isRequired,
|
||||
templates: arrayOf(shape()),
|
||||
isInDataExplorer: bool,
|
||||
timeRange: shape({
|
||||
upper: string,
|
||||
lower: string,
|
||||
|
@ -156,6 +155,7 @@ Visualization.propTypes = {
|
|||
}),
|
||||
resizerBottomHeight: number,
|
||||
errorThrown: func.isRequired,
|
||||
manualRefresh: number,
|
||||
}
|
||||
|
||||
export default Visualization
|
||||
|
|
|
@ -12,6 +12,7 @@ import WriteDataForm from 'src/data_explorer/components/WriteDataForm'
|
|||
import Header from '../containers/Header'
|
||||
import ResizeContainer from 'shared/components/ResizeContainer'
|
||||
import OverlayTechnologies from 'shared/components/OverlayTechnologies'
|
||||
import ManualRefresh from 'src/shared/components/ManualRefresh'
|
||||
|
||||
import {VIS_VIEWS} from 'shared/constants'
|
||||
import {MINIMUM_HEIGHTS, INITIAL_HEIGHTS} from '../constants'
|
||||
|
@ -67,17 +68,22 @@ class DataExplorer extends Component {
|
|||
this.setState({showWriteForm: true})
|
||||
}
|
||||
|
||||
handleChooseTimeRange = bounds => {
|
||||
this.props.setTimeRange(bounds)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
autoRefresh,
|
||||
errorThrownAction,
|
||||
handleChooseAutoRefresh,
|
||||
timeRange,
|
||||
setTimeRange,
|
||||
queryConfigs,
|
||||
queryConfigActions,
|
||||
source,
|
||||
timeRange,
|
||||
autoRefresh,
|
||||
queryConfigs,
|
||||
manualRefresh,
|
||||
onManualRefresh,
|
||||
errorThrownAction,
|
||||
writeLineProtocol,
|
||||
queryConfigActions,
|
||||
handleChooseAutoRefresh,
|
||||
} = this.props
|
||||
|
||||
const {showWriteForm} = this.state
|
||||
|
@ -99,8 +105,10 @@ class DataExplorer extends Component {
|
|||
<Header
|
||||
timeRange={timeRange}
|
||||
autoRefresh={autoRefresh}
|
||||
actions={{handleChooseAutoRefresh, setTimeRange}}
|
||||
showWriteForm={this.handleOpenWriteData}
|
||||
onChooseTimeRange={this.handleChooseTimeRange}
|
||||
onChooseAutoRefresh={handleChooseAutoRefresh}
|
||||
onManualRefresh={onManualRefresh}
|
||||
/>
|
||||
<ResizeContainer
|
||||
containerClass="page-contents"
|
||||
|
@ -116,14 +124,14 @@ class DataExplorer extends Component {
|
|||
activeQuery={this.getActiveQuery()}
|
||||
/>
|
||||
<Visualization
|
||||
isInDataExplorer={true}
|
||||
autoRefresh={autoRefresh}
|
||||
timeRange={timeRange}
|
||||
queryConfigs={queryConfigs}
|
||||
errorThrown={errorThrownAction}
|
||||
activeQueryIndex={0}
|
||||
editQueryStatus={queryConfigActions.editQueryStatus}
|
||||
views={VIS_VIEWS}
|
||||
activeQueryIndex={0}
|
||||
timeRange={timeRange}
|
||||
autoRefresh={autoRefresh}
|
||||
queryConfigs={queryConfigs}
|
||||
manualRefresh={manualRefresh}
|
||||
errorThrown={errorThrownAction}
|
||||
editQueryStatus={queryConfigActions.editQueryStatus}
|
||||
/>
|
||||
</ResizeContainer>
|
||||
</div>
|
||||
|
@ -163,6 +171,8 @@ DataExplorer.propTypes = {
|
|||
}).isRequired,
|
||||
writeLineProtocol: func.isRequired,
|
||||
errorThrownAction: func.isRequired,
|
||||
onManualRefresh: func.isRequired,
|
||||
manualRefresh: number.isRequired,
|
||||
}
|
||||
|
||||
DataExplorer.childContextTypes = {
|
||||
|
@ -208,5 +218,5 @@ const mapDispatchToProps = dispatch => {
|
|||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(
|
||||
withRouter(DataExplorer)
|
||||
withRouter(ManualRefresh(DataExplorer))
|
||||
)
|
||||
|
|
|
@ -8,33 +8,14 @@ import GraphTips from 'shared/components/GraphTips'
|
|||
|
||||
const {func, number, shape, string} = PropTypes
|
||||
|
||||
const Header = React.createClass({
|
||||
propTypes: {
|
||||
actions: shape({
|
||||
handleChooseAutoRefresh: func.isRequired,
|
||||
setTimeRange: func.isRequired,
|
||||
}),
|
||||
autoRefresh: number.isRequired,
|
||||
showWriteForm: func.isRequired,
|
||||
timeRange: shape({
|
||||
lower: string,
|
||||
upper: string,
|
||||
}).isRequired,
|
||||
},
|
||||
|
||||
handleChooseTimeRange(bounds) {
|
||||
this.props.actions.setTimeRange(bounds)
|
||||
},
|
||||
|
||||
render() {
|
||||
const {
|
||||
autoRefresh,
|
||||
actions: {handleChooseAutoRefresh},
|
||||
showWriteForm,
|
||||
const Header = ({
|
||||
timeRange,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
autoRefresh,
|
||||
showWriteForm,
|
||||
onManualRefresh,
|
||||
onChooseTimeRange,
|
||||
onChooseAutoRefresh,
|
||||
}) =>
|
||||
<div className="page-header full-width">
|
||||
<div className="page-header__container">
|
||||
<div className="page-header__left">
|
||||
|
@ -52,20 +33,30 @@ const Header = React.createClass({
|
|||
Write Data
|
||||
</div>
|
||||
<AutoRefreshDropdown
|
||||
onChoose={handleChooseAutoRefresh}
|
||||
selected={autoRefresh}
|
||||
iconName="refresh"
|
||||
selected={autoRefresh}
|
||||
onChoose={onChooseAutoRefresh}
|
||||
onManualRefresh={onManualRefresh}
|
||||
/>
|
||||
<TimeRangeDropdown
|
||||
onChooseTimeRange={this.handleChooseTimeRange}
|
||||
selected={timeRange}
|
||||
page="DataExplorer"
|
||||
onChooseTimeRange={onChooseTimeRange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
Header.propTypes = {
|
||||
onChooseAutoRefresh: func.isRequired,
|
||||
onChooseTimeRange: func.isRequired,
|
||||
onManualRefresh: func.isRequired,
|
||||
autoRefresh: number.isRequired,
|
||||
showWriteForm: func.isRequired,
|
||||
timeRange: shape({
|
||||
lower: string,
|
||||
upper: string,
|
||||
}).isRequired,
|
||||
}
|
||||
|
||||
export default withRouter(Header)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import React, {PropTypes, Component} from 'react'
|
||||
import {Link} from 'react-router'
|
||||
import {connect} from 'react-redux'
|
||||
import {bindActionCreators} from 'redux'
|
||||
|
@ -10,6 +10,7 @@ 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'
|
||||
import ManualRefresh from 'src/shared/components/ManualRefresh'
|
||||
|
||||
import timeRanges from 'hson!shared/data/timeRanges.hson'
|
||||
import {
|
||||
|
@ -23,39 +24,16 @@ import {fetchLayouts} from 'shared/apis'
|
|||
import {setAutoRefresh} from 'shared/actions/app'
|
||||
import {presentationButtonDispatcher} from 'shared/dispatchers'
|
||||
|
||||
const {shape, string, bool, func, number} = PropTypes
|
||||
|
||||
export const HostPage = React.createClass({
|
||||
propTypes: {
|
||||
source: shape({
|
||||
links: shape({
|
||||
proxy: string.isRequired,
|
||||
}).isRequired,
|
||||
telegraf: string.isRequired,
|
||||
id: string.isRequired,
|
||||
}),
|
||||
params: shape({
|
||||
hostID: string.isRequired,
|
||||
}).isRequired,
|
||||
location: shape({
|
||||
query: shape({
|
||||
app: string,
|
||||
}),
|
||||
}),
|
||||
autoRefresh: number.isRequired,
|
||||
handleChooseAutoRefresh: func.isRequired,
|
||||
inPresentationMode: bool,
|
||||
handleClickPresentationButton: func,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
class HostPage extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
layouts: [],
|
||||
hosts: [],
|
||||
timeRange: timeRanges.find(tr => tr.lower === 'now() - 1h'),
|
||||
dygraphs: [],
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const {source, params, location} = this.props
|
||||
|
@ -96,19 +74,19 @@ export const HostPage = React.createClass({
|
|||
}
|
||||
|
||||
this.setState({layouts: filteredLayouts, hosts: filteredHosts}) // eslint-disable-line react/no-did-mount-set-state
|
||||
},
|
||||
}
|
||||
|
||||
handleChooseTimeRange({lower, upper}) {
|
||||
handleChooseTimeRange = ({lower, upper}) => {
|
||||
if (upper) {
|
||||
this.setState({timeRange: {lower, upper}})
|
||||
} else {
|
||||
const timeRange = timeRanges.find(range => range.lower === lower)
|
||||
this.setState({timeRange})
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
synchronizer(dygraph) {
|
||||
const dygraphs = [...this.state.dygraphs, dygraph]
|
||||
synchronizer = dygraph => {
|
||||
const dygraphs = [...this.state.dygraphs, dygraph].filter(d => d.graphDiv)
|
||||
const numGraphs = this.state.layouts.reduce((acc, {cells}) => {
|
||||
return acc + cells.length
|
||||
}, 0)
|
||||
|
@ -121,11 +99,11 @@ export const HostPage = React.createClass({
|
|||
})
|
||||
}
|
||||
this.setState({dygraphs})
|
||||
},
|
||||
}
|
||||
|
||||
renderLayouts(layouts) {
|
||||
renderLayouts = layouts => {
|
||||
const {timeRange} = this.state
|
||||
const {source, autoRefresh} = this.props
|
||||
const {source, autoRefresh, manualRefresh} = this.props
|
||||
|
||||
const autoflowLayouts = layouts.filter(layout => !!layout.autoflow)
|
||||
|
||||
|
@ -173,27 +151,29 @@ export const HostPage = React.createClass({
|
|||
|
||||
return (
|
||||
<LayoutRenderer
|
||||
timeRange={timeRange}
|
||||
cells={layoutCells}
|
||||
autoRefresh={autoRefresh}
|
||||
source={source}
|
||||
host={this.props.params.hostID}
|
||||
isEditable={false}
|
||||
cells={layoutCells}
|
||||
timeRange={timeRange}
|
||||
autoRefresh={autoRefresh}
|
||||
manualRefresh={manualRefresh}
|
||||
host={this.props.params.hostID}
|
||||
synchronizer={this.synchronizer}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
params: {hostID},
|
||||
location: {query: {app}},
|
||||
source: {id},
|
||||
autoRefresh,
|
||||
handleChooseAutoRefresh,
|
||||
inPresentationMode,
|
||||
handleClickPresentationButton,
|
||||
source,
|
||||
autoRefresh,
|
||||
source: {id},
|
||||
onManualRefresh,
|
||||
params: {hostID},
|
||||
inPresentationMode,
|
||||
handleChooseAutoRefresh,
|
||||
location: {query: {app}},
|
||||
handleClickPresentationButton,
|
||||
} = this.props
|
||||
const {layouts, timeRange, hosts} = this.state
|
||||
const appParam = app ? `?app=${app}` : ''
|
||||
|
@ -201,14 +181,15 @@ export const HostPage = React.createClass({
|
|||
return (
|
||||
<div className="page">
|
||||
<DashboardHeader
|
||||
source={source}
|
||||
buttonText={hostID}
|
||||
autoRefresh={autoRefresh}
|
||||
timeRange={timeRange}
|
||||
autoRefresh={autoRefresh}
|
||||
isHidden={inPresentationMode}
|
||||
onManualRefresh={onManualRefresh}
|
||||
handleChooseTimeRange={this.handleChooseTimeRange}
|
||||
handleChooseAutoRefresh={handleChooseAutoRefresh}
|
||||
handleClickPresentationButton={handleClickPresentationButton}
|
||||
source={source}
|
||||
>
|
||||
{Object.keys(hosts).map((host, i) => {
|
||||
return (
|
||||
|
@ -232,8 +213,34 @@ export const HostPage = React.createClass({
|
|||
</FancyScrollbar>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const {shape, string, bool, func, number} = PropTypes
|
||||
|
||||
HostPage.propTypes = {
|
||||
source: shape({
|
||||
links: shape({
|
||||
proxy: string.isRequired,
|
||||
}).isRequired,
|
||||
telegraf: string.isRequired,
|
||||
id: string.isRequired,
|
||||
}),
|
||||
params: shape({
|
||||
hostID: string.isRequired,
|
||||
}).isRequired,
|
||||
location: shape({
|
||||
query: shape({
|
||||
app: string,
|
||||
}),
|
||||
}),
|
||||
inPresentationMode: bool,
|
||||
autoRefresh: number.isRequired,
|
||||
manualRefresh: number.isRequired,
|
||||
onManualRefresh: func.isRequired,
|
||||
handleChooseAutoRefresh: func.isRequired,
|
||||
handleClickPresentationButton: func,
|
||||
}
|
||||
|
||||
const mapStateToProps = ({
|
||||
app: {ephemeral: {inPresentationMode}, persisted: {autoRefresh}},
|
||||
|
@ -247,4 +254,6 @@ const mapDispatchToProps = dispatch => ({
|
|||
handleClickPresentationButton: presentationButtonDispatcher(dispatch),
|
||||
})
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(HostPage)
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(
|
||||
ManualRefresh(HostPage)
|
||||
)
|
||||
|
|
|
@ -1,66 +1,19 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import React, {PropTypes, Component} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
import {fetchTimeSeriesAsync} from 'shared/actions/timeSeries'
|
||||
import {removeUnselectedTemplateValues} from 'src/dashboards/constants'
|
||||
|
||||
const {
|
||||
array,
|
||||
arrayOf,
|
||||
bool,
|
||||
element,
|
||||
func,
|
||||
number,
|
||||
oneOfType,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
|
||||
const AutoRefresh = ComposedComponent => {
|
||||
const wrapper = React.createClass({
|
||||
propTypes: {
|
||||
children: element,
|
||||
autoRefresh: number.isRequired,
|
||||
templates: arrayOf(
|
||||
shape({
|
||||
type: string.isRequired,
|
||||
tempVar: string.isRequired,
|
||||
query: shape({
|
||||
db: string,
|
||||
rp: string,
|
||||
influxql: string,
|
||||
}),
|
||||
values: arrayOf(
|
||||
shape({
|
||||
type: string.isRequired,
|
||||
value: string.isRequired,
|
||||
selected: bool,
|
||||
})
|
||||
).isRequired,
|
||||
})
|
||||
),
|
||||
queries: arrayOf(
|
||||
shape({
|
||||
host: oneOfType([string, arrayOf(string)]),
|
||||
text: string,
|
||||
}).isRequired
|
||||
).isRequired,
|
||||
axes: shape({
|
||||
bounds: shape({
|
||||
y: array,
|
||||
y2: array,
|
||||
}),
|
||||
}),
|
||||
editQueryStatus: func,
|
||||
grabDataForDownload: func,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
class wrapper extends Component {
|
||||
constructor() {
|
||||
super()
|
||||
this.state = {
|
||||
lastQuerySuccessful: false,
|
||||
timeSeries: [],
|
||||
resolution: null,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {queries, templates, autoRefresh} = this.props
|
||||
|
@ -71,7 +24,7 @@ const AutoRefresh = ComposedComponent => {
|
|||
autoRefresh
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const queriesDidUpdate = this.queryDifference(
|
||||
|
@ -100,18 +53,18 @@ const AutoRefresh = ComposedComponent => {
|
|||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
queryDifference(left, right) {
|
||||
queryDifference = (left, right) => {
|
||||
const leftStrs = left.map(q => `${q.host}${q.text}`)
|
||||
const rightStrs = right.map(q => `${q.host}${q.text}`)
|
||||
return _.difference(
|
||||
_.union(leftStrs, rightStrs),
|
||||
_.intersection(leftStrs, rightStrs)
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
executeQueries(queries, templates = []) {
|
||||
executeQueries = async (queries, templates = []) => {
|
||||
const {editQueryStatus, grabDataForDownload} = this.props
|
||||
const {resolution} = this.state
|
||||
|
||||
|
@ -148,28 +101,33 @@ const AutoRefresh = ComposedComponent => {
|
|||
)
|
||||
})
|
||||
|
||||
Promise.all(timeSeriesPromises).then(timeSeries => {
|
||||
try {
|
||||
const timeSeries = await Promise.all(timeSeriesPromises)
|
||||
const newSeries = timeSeries.map(response => ({response}))
|
||||
const lastQuerySuccessful = !this._noResultsForQuery(newSeries)
|
||||
const lastQuerySuccessful = this._resultsForQuery(newSeries)
|
||||
|
||||
this.setState({
|
||||
timeSeries: newSeries,
|
||||
lastQuerySuccessful,
|
||||
isFetching: false,
|
||||
})
|
||||
|
||||
if (grabDataForDownload) {
|
||||
grabDataForDownload(timeSeries)
|
||||
}
|
||||
})
|
||||
},
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.intervalID)
|
||||
this.intervalID = false
|
||||
},
|
||||
}
|
||||
|
||||
setResolution(resolution) {
|
||||
setResolution = resolution => {
|
||||
this.setState({resolution})
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
const {timeSeries} = this.state
|
||||
|
@ -179,7 +137,7 @@ const AutoRefresh = ComposedComponent => {
|
|||
}
|
||||
|
||||
if (
|
||||
this._noResultsForQuery(timeSeries) ||
|
||||
!this._resultsForQuery(timeSeries) ||
|
||||
!this.state.lastQuerySuccessful
|
||||
) {
|
||||
return this.renderNoResults()
|
||||
|
@ -192,13 +150,13 @@ const AutoRefresh = ComposedComponent => {
|
|||
setResolution={this.setResolution}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Graphs can potentially show mulitple kinds of spinners based on whether
|
||||
* a graph is being fetched for the first time, or is being refreshed.
|
||||
*/
|
||||
renderFetching(data) {
|
||||
renderFetching = data => {
|
||||
const isFirstFetch = !Object.keys(this.state.timeSeries).length
|
||||
return (
|
||||
<ComposedComponent
|
||||
|
@ -209,30 +167,76 @@ const AutoRefresh = ComposedComponent => {
|
|||
isRefreshing={!isFirstFetch}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
renderNoResults() {
|
||||
renderNoResults = () => {
|
||||
return (
|
||||
<div className="graph-empty">
|
||||
<p data-test="data-explorer-no-results">No Results</p>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
||||
_noResultsForQuery(data) {
|
||||
if (!data.length) {
|
||||
return true
|
||||
}
|
||||
|
||||
return data.every(({response}) => {
|
||||
return _.get(response, 'results', []).every(result => {
|
||||
return (
|
||||
Object.keys(result).filter(k => k !== 'statement_id').length === 0
|
||||
_resultsForQuery = data =>
|
||||
data.length
|
||||
? data.every(({response}) =>
|
||||
_.get(response, 'results', []).every(
|
||||
result =>
|
||||
Object.keys(result).filter(k => k !== 'statement_id').length !==
|
||||
0
|
||||
)
|
||||
)
|
||||
: false
|
||||
}
|
||||
|
||||
const {
|
||||
array,
|
||||
arrayOf,
|
||||
bool,
|
||||
element,
|
||||
func,
|
||||
number,
|
||||
oneOfType,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
|
||||
wrapper.propTypes = {
|
||||
children: element,
|
||||
autoRefresh: number.isRequired,
|
||||
templates: arrayOf(
|
||||
shape({
|
||||
type: string.isRequired,
|
||||
tempVar: string.isRequired,
|
||||
query: shape({
|
||||
db: string,
|
||||
rp: string,
|
||||
influxql: string,
|
||||
}),
|
||||
values: arrayOf(
|
||||
shape({
|
||||
type: string.isRequired,
|
||||
value: string.isRequired,
|
||||
selected: bool,
|
||||
})
|
||||
).isRequired,
|
||||
})
|
||||
},
|
||||
})
|
||||
),
|
||||
queries: arrayOf(
|
||||
shape({
|
||||
host: oneOfType([string, arrayOf(string)]),
|
||||
text: string,
|
||||
}).isRequired
|
||||
).isRequired,
|
||||
axes: shape({
|
||||
bounds: shape({
|
||||
y: array,
|
||||
y2: array,
|
||||
}),
|
||||
}),
|
||||
editQueryStatus: func,
|
||||
grabDataForDownload: func,
|
||||
}
|
||||
|
||||
return wrapper
|
||||
}
|
||||
|
|
|
@ -28,11 +28,16 @@ class AutoRefreshDropdown extends Component {
|
|||
toggleMenu = () => this.setState({isOpen: !this.state.isOpen})
|
||||
|
||||
render() {
|
||||
const {selected} = this.props
|
||||
const {selected, onManualRefresh} = this.props
|
||||
const {isOpen} = this.state
|
||||
const {milliseconds, inputValue} = this.findAutoRefreshItem(selected)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames('autorefresh-dropdown', {
|
||||
paused: +milliseconds === 0,
|
||||
})}
|
||||
>
|
||||
<div className={classnames('dropdown dropdown-160', {open: isOpen})}>
|
||||
<div
|
||||
className="btn btn-sm btn-default dropdown-toggle"
|
||||
|
@ -60,6 +65,15 @@ class AutoRefreshDropdown extends Component {
|
|||
)}
|
||||
</ul>
|
||||
</div>
|
||||
{+milliseconds === 0
|
||||
? <div
|
||||
className="btn btn-sm btn-default btn-square"
|
||||
onClick={onManualRefresh}
|
||||
>
|
||||
<span className="icon refresh" />
|
||||
</div>
|
||||
: null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -69,6 +83,7 @@ const {number, func} = PropTypes
|
|||
AutoRefreshDropdown.propTypes = {
|
||||
selected: number.isRequired,
|
||||
onChoose: func.isRequired,
|
||||
onManualRefresh: func,
|
||||
}
|
||||
|
||||
export default OnClickOutside(AutoRefreshDropdown)
|
||||
|
|
|
@ -52,6 +52,7 @@ const Layout = (
|
|||
isEditable,
|
||||
onEditCell,
|
||||
autoRefresh,
|
||||
manualRefresh,
|
||||
onDeleteCell,
|
||||
synchronizer,
|
||||
resizeCoords,
|
||||
|
@ -82,6 +83,7 @@ const Layout = (
|
|||
timeRange={timeRange}
|
||||
templates={templates}
|
||||
autoRefresh={autoRefresh}
|
||||
manualRefresh={manualRefresh}
|
||||
synchronizer={synchronizer}
|
||||
grabDataForDownload={grabDataForDownload}
|
||||
resizeCoords={resizeCoords}
|
||||
|
@ -102,6 +104,7 @@ Layout.contextTypes = {
|
|||
|
||||
const propTypes = {
|
||||
autoRefresh: number.isRequired,
|
||||
manualRefresh: number,
|
||||
timeRange: shape({
|
||||
lower: string.isRequired,
|
||||
}),
|
||||
|
|
|
@ -75,6 +75,7 @@ class LayoutRenderer extends Component {
|
|||
isEditable,
|
||||
onEditCell,
|
||||
autoRefresh,
|
||||
manualRefresh,
|
||||
onDeleteCell,
|
||||
synchronizer,
|
||||
onCancelEditCell,
|
||||
|
@ -114,6 +115,7 @@ class LayoutRenderer extends Component {
|
|||
onEditCell={onEditCell}
|
||||
resizeCoords={resizeCoords}
|
||||
autoRefresh={autoRefresh}
|
||||
manualRefresh={manualRefresh}
|
||||
onDeleteCell={onDeleteCell}
|
||||
synchronizer={synchronizer}
|
||||
onCancelEditCell={onCancelEditCell}
|
||||
|
@ -131,6 +133,7 @@ const {arrayOf, bool, func, number, shape, string} = PropTypes
|
|||
|
||||
LayoutRenderer.propTypes = {
|
||||
autoRefresh: number.isRequired,
|
||||
manualRefresh: number,
|
||||
timeRange: shape({
|
||||
lower: string.isRequired,
|
||||
}),
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import React, {Component} from 'react'
|
||||
|
||||
const ManualRefresh = WrappedComponent =>
|
||||
class extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
manualRefresh: Date.now(),
|
||||
}
|
||||
}
|
||||
|
||||
handleManualRefresh = () => {
|
||||
this.setState({
|
||||
manualRefresh: Date.now(),
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<WrappedComponent
|
||||
{...this.props}
|
||||
manualRefresh={this.state.manualRefresh}
|
||||
onManualRefresh={this.handleManualRefresh}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ManualRefresh
|
|
@ -18,6 +18,7 @@ const RefreshingGraph = ({
|
|||
timeRange,
|
||||
cellHeight,
|
||||
autoRefresh,
|
||||
manualRefresh, // when changed, re-mounts the component
|
||||
synchronizer,
|
||||
resizeCoords,
|
||||
editQueryStatus,
|
||||
|
@ -36,6 +37,7 @@ const RefreshingGraph = ({
|
|||
if (type === 'single-stat') {
|
||||
return (
|
||||
<RefreshingSingleStat
|
||||
key={manualRefresh}
|
||||
queries={[queries[0]]}
|
||||
templates={templates}
|
||||
autoRefresh={autoRefresh}
|
||||
|
@ -54,7 +56,7 @@ const RefreshingGraph = ({
|
|||
axes={axes}
|
||||
onZoom={onZoom}
|
||||
queries={queries}
|
||||
grabDataForDownload={grabDataForDownload}
|
||||
key={manualRefresh}
|
||||
templates={templates}
|
||||
timeRange={timeRange}
|
||||
autoRefresh={autoRefresh}
|
||||
|
@ -63,6 +65,7 @@ const RefreshingGraph = ({
|
|||
resizeCoords={resizeCoords}
|
||||
displayOptions={displayOptions}
|
||||
editQueryStatus={editQueryStatus}
|
||||
grabDataForDownload={grabDataForDownload}
|
||||
showSingleStat={type === 'line-plus-single-stat'}
|
||||
/>
|
||||
)
|
||||
|
@ -75,6 +78,7 @@ RefreshingGraph.propTypes = {
|
|||
lower: string.isRequired,
|
||||
}),
|
||||
autoRefresh: number.isRequired,
|
||||
manualRefresh: number,
|
||||
templates: arrayOf(shape()),
|
||||
synchronizer: func,
|
||||
type: string.isRequired,
|
||||
|
@ -87,4 +91,8 @@ RefreshingGraph.propTypes = {
|
|||
grabDataForDownload: func,
|
||||
}
|
||||
|
||||
RefreshingGraph.defaultProps = {
|
||||
manualRefresh: 0,
|
||||
}
|
||||
|
||||
export default RefreshingGraph
|
||||
|
|
|
@ -318,3 +318,19 @@ $tick-script-overlay-margin: 30px;
|
|||
|
||||
> a {display: block;}
|
||||
}
|
||||
|
||||
/*
|
||||
Auto Refresh Dropdown
|
||||
-----------------------------------------------------------------------------
|
||||
*/
|
||||
.autorefresh-dropdown {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
&.paused .dropdown {
|
||||
margin-right: 4px;
|
||||
}
|
||||
&.paused .dropdown > .btn.dropdown-toggle {
|
||||
width: 126px;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue