diff --git a/CHANGELOG.md b/CHANGELOG.md
index b176ab2f1..0f42054eb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/ui/src/dashboards/components/Dashboard.js b/ui/src/dashboards/components/Dashboard.js
index 1cf622cc7..41b31710d 100644
--- a/ui/src/dashboards/components/Dashboard.js
+++ b/ui/src/dashboards/components/Dashboard.js
@@ -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,
diff --git a/ui/src/dashboards/components/DashboardHeader.js b/ui/src/dashboards/components/DashboardHeader.js
index ff670787f..6566a713d 100644
--- a/ui/src/dashboards/components/DashboardHeader.js
+++ b/ui/src/dashboards/components/DashboardHeader.js
@@ -17,6 +17,7 @@ const DashboardHeader = ({
isHidden,
handleChooseTimeRange,
handleChooseAutoRefresh,
+ onManualRefresh,
handleClickPresentationButton,
onAddCell,
onEditDashboard,
@@ -76,6 +77,7 @@ const DashboardHeader = ({
: null}
@@ -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,
diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js
index fa518407b..4e48d5c6d 100644
--- a/ui/src/dashboards/containers/DashboardPage.js
+++ b/ui/src/dashboards/containers/DashboardPage.js
@@ -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)
+)
diff --git a/ui/src/data_explorer/components/VisView.js b/ui/src/data_explorer/components/VisView.js
index 59865c021..0790774a2 100644
--- a/ui/src/data_explorer/components/VisView.js
+++ b/ui/src/data_explorer/components/VisView.js
@@ -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,
}
diff --git a/ui/src/data_explorer/components/Visualization.js b/ui/src/data_explorer/components/Visualization.js
index 5d106e301..308084e84 100644
--- a/ui/src/data_explorer/components/Visualization.js
+++ b/ui/src/data_explorer/components/Visualization.js
@@ -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}
/>
@@ -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
diff --git a/ui/src/data_explorer/containers/DataExplorer.js b/ui/src/data_explorer/containers/DataExplorer.js
index ab04cf0e3..3aa12dea2 100644
--- a/ui/src/data_explorer/containers/DataExplorer.js
+++ b/ui/src/data_explorer/containers/DataExplorer.js
@@ -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 {
@@ -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))
)
diff --git a/ui/src/data_explorer/containers/Header.js b/ui/src/data_explorer/containers/Header.js
index 463907cea..df57ffb8f 100644
--- a/ui/src/data_explorer/containers/Header.js
+++ b/ui/src/data_explorer/containers/Header.js
@@ -8,64 +8,55 @@ 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,
- timeRange,
- } = this.props
-
- return (
-
-
-
-
Data Explorer
-
-
-
-
-
-
- Write Data
-
-
-
-
-
+const Header = ({
+ timeRange,
+ autoRefresh,
+ showWriteForm,
+ onManualRefresh,
+ onChooseTimeRange,
+ onChooseAutoRefresh,
+}) =>
+
+
+
+
Data Explorer
- )
- },
-})
+
+
+
+
+
+ Write Data
+
+
+
+
+
+
+
+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)
diff --git a/ui/src/hosts/containers/HostPage.js b/ui/src/hosts/containers/HostPage.js
index 74dd2014b..e8276427a 100644
--- a/ui/src/hosts/containers/HostPage.js
+++ b/ui/src/hosts/containers/HostPage.js
@@ -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 (
)
- },
+ }
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 (
{Object.keys(hosts).map((host, i) => {
return (
@@ -232,8 +213,34 @@ export const HostPage = React.createClass({
)
- },
-})
+ }
+}
+
+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)
+)
diff --git a/ui/src/shared/components/AutoRefresh.js b/ui/src/shared/components/AutoRefresh.js
index 081364481..3bbf5f206 100644
--- a/ui/src/shared/components/AutoRefresh.js
+++ b/ui/src/shared/components/AutoRefresh.js
@@ -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 (
{
isRefreshing={!isFirstFetch}
/>
)
- },
+ }
- renderNoResults() {
+ renderNoResults = () => {
return (
)
- },
+ }
- _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
}
diff --git a/ui/src/shared/components/AutoRefreshDropdown.js b/ui/src/shared/components/AutoRefreshDropdown.js
index be71c7461..b7975d62b 100644
--- a/ui/src/shared/components/AutoRefreshDropdown.js
+++ b/ui/src/shared/components/AutoRefreshDropdown.js
@@ -28,37 +28,51 @@ 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 (
-
-
-
0 ? 'refresh' : 'pause'
+
+
+
+ 0 ? 'refresh' : 'pause'
+ )}
+ />
+
+ {inputValue}
+
+
+
+
+ - AutoRefresh Interval
+ {autoRefreshItems.map(item =>
+ -
+
+ {item.menuOption}
+
+
)}
- />
-
- {inputValue}
-
-
+
-
+ {+milliseconds === 0
+ ?
+
+
+ : null}
)
}
@@ -69,6 +83,7 @@ const {number, func} = PropTypes
AutoRefreshDropdown.propTypes = {
selected: number.isRequired,
onChoose: func.isRequired,
+ onManualRefresh: func,
}
export default OnClickOutside(AutoRefreshDropdown)
diff --git a/ui/src/shared/components/Layout.js b/ui/src/shared/components/Layout.js
index 42c086a33..ea69fe6c6 100644
--- a/ui/src/shared/components/Layout.js
+++ b/ui/src/shared/components/Layout.js
@@ -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,
}),
diff --git a/ui/src/shared/components/LayoutRenderer.js b/ui/src/shared/components/LayoutRenderer.js
index 7ab90c4dd..42bcc2230 100644
--- a/ui/src/shared/components/LayoutRenderer.js
+++ b/ui/src/shared/components/LayoutRenderer.js
@@ -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,
}),
diff --git a/ui/src/shared/components/ManualRefresh.js b/ui/src/shared/components/ManualRefresh.js
new file mode 100644
index 000000000..d3edcd101
--- /dev/null
+++ b/ui/src/shared/components/ManualRefresh.js
@@ -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 (
+
+ )
+ }
+ }
+
+export default ManualRefresh
diff --git a/ui/src/shared/components/RefreshingGraph.js b/ui/src/shared/components/RefreshingGraph.js
index dbc983d34..2320f1b65 100644
--- a/ui/src/shared/components/RefreshingGraph.js
+++ b/ui/src/shared/components/RefreshingGraph.js
@@ -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 (
)
@@ -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
diff --git a/ui/src/style/unsorted.scss b/ui/src/style/unsorted.scss
index 13cb3776c..f5307a51f 100644
--- a/ui/src/style/unsorted.scss
+++ b/ui/src/style/unsorted.scss
@@ -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;
+ }
+}