diff --git a/superset/assets/javascripts/components/CachedLabel.jsx b/superset/assets/javascripts/components/CachedLabel.jsx new file mode 100644 index 0000000000..e649fad3b2 --- /dev/null +++ b/superset/assets/javascripts/components/CachedLabel.jsx @@ -0,0 +1,68 @@ +import React, { PropTypes } from 'react'; +import { Label } from 'react-bootstrap'; +import moment from 'moment'; +import TooltipWrapper from './TooltipWrapper'; + +const propTypes = { + onClick: PropTypes.func, + cachedTimestamp: PropTypes.string, + className: PropTypes.string, +}; + +class CacheLabel extends React.PureComponent { + constructor(props) { + super(props); + this.state = { + tooltipContent: '', + hovered: false, + }; + } + + updateTooltipContent() { + const cachedText = this.props.cachedTimestamp ? ( + + Loaded data cached {moment(this.props.cachedTimestamp).fromNow()} + ) : + 'Loaded from cache'; + + const tooltipContent = ( + + {cachedText}. + Click to force-refresh + + ); + this.setState({ tooltipContent }); + } + + mouseOver() { + this.updateTooltipContent(); + this.setState({ hovered: true }); + } + + mouseOut() { + this.setState({ hovered: false }); + } + + render() { + const labelStyle = this.state.hovered ? 'primary' : 'default'; + return ( + + + ); + } +} +CacheLabel.propTypes = propTypes; + +export default CacheLabel; diff --git a/superset/assets/javascripts/components/TooltipWrapper.jsx b/superset/assets/javascripts/components/TooltipWrapper.jsx index 56f0c148e2..fb476c410c 100644 --- a/superset/assets/javascripts/components/TooltipWrapper.jsx +++ b/superset/assets/javascripts/components/TooltipWrapper.jsx @@ -4,7 +4,7 @@ import { slugify } from '../modules/utils'; const propTypes = { label: PropTypes.string.isRequired, - tooltip: PropTypes.string.isRequired, + tooltip: PropTypes.node.isRequired, children: PropTypes.node.isRequired, placement: PropTypes.string, }; diff --git a/superset/assets/javascripts/dashboard/Dashboard.jsx b/superset/assets/javascripts/dashboard/Dashboard.jsx index 2094fb2ddc..59e8e8874e 100644 --- a/superset/assets/javascripts/dashboard/Dashboard.jsx +++ b/superset/assets/javascripts/dashboard/Dashboard.jsx @@ -2,6 +2,7 @@ import React from 'react'; import { render } from 'react-dom'; import d3 from 'd3'; import { Alert } from 'react-bootstrap'; +import moment from 'moment'; import GridLayout from './components/GridLayout'; import Header from './components/Header'; @@ -143,13 +144,15 @@ export function dashboardContainer(dashboard, datasources) { done(slice) { const refresh = slice.getWidgetHeader().find('.refresh'); const data = slice.data; + const cachedWhen = moment(data.cached_dttm).fromNow(); if (data !== undefined && data.is_cached) { refresh .addClass('danger') - .attr('title', - 'Served from data cached at ' + data.cached_dttm + - '. Click to force refresh') - .tooltip('fixTitle'); + .attr( + 'title', + `Served from data cached ${cachedWhen}. ` + + 'Click to force refresh') + .tooltip('fixTitle'); } else { refresh .removeClass('danger') diff --git a/superset/assets/javascripts/explorev2/components/ChartContainer.jsx b/superset/assets/javascripts/explorev2/components/ChartContainer.jsx index f6ea3469fb..cc0524251b 100644 --- a/superset/assets/javascripts/explorev2/components/ChartContainer.jsx +++ b/superset/assets/javascripts/explorev2/components/ChartContainer.jsx @@ -2,7 +2,7 @@ import $ from 'jquery'; import Mustache from 'mustache'; import React, { PropTypes } from 'react'; import { connect } from 'react-redux'; -import { Alert, Collapse, Label, Panel } from 'react-bootstrap'; +import { Alert, Collapse, Panel } from 'react-bootstrap'; import visMap from '../../../visualizations/main'; import { d3format } from '../../modules/utils'; import ExploreActionButtons from './ExploreActionButtons'; @@ -11,6 +11,7 @@ import TooltipWrapper from '../../components/TooltipWrapper'; import Timer from '../../components/Timer'; import { getExploreUrl } from '../exploreUtils'; import { getFormDataFromControls } from '../stores/store'; +import CachedLabel from '../../components/CachedLabel'; const CHART_STATUS_MAP = { failed: 'danger', @@ -265,17 +266,10 @@ class ChartContainer extends React.PureComponent { {this.props.chartStatus === 'success' && this.props.queryResponse && this.props.queryResponse.is_cached && - - - + } { try { vizMap[formData.viz_type](this, queryResponse); diff --git a/superset/assets/spec/javascripts/components/CachedLabel_spec.jsx b/superset/assets/spec/javascripts/components/CachedLabel_spec.jsx new file mode 100644 index 0000000000..a720a8ce57 --- /dev/null +++ b/superset/assets/spec/javascripts/components/CachedLabel_spec.jsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { expect } from 'chai'; +import { describe, it } from 'mocha'; +import { shallow } from 'enzyme'; +import { Label } from 'react-bootstrap'; + +import CachedLabel from '../../../javascripts/components/CachedLabel'; + +describe('CachedLabel', () => { + const defaultProps = { + onClick: () => {}, + cachedTimestamp: '2017-01-01', + }; + + it('is valid', () => { + expect( + React.isValidElement(), + ).to.equal(true); + }); + it('renders', () => { + const wrapper = shallow( + , + ); + expect(wrapper.find(Label)).to.have.length(1); + }); +});