Skip to content

Commit

Permalink
Stats: Add The Last updated time to the StatsModule component (#11035)
Browse files Browse the repository at this point in the history
 - This also adds polling to the stats requests
  • Loading branch information
youknowriad committed Feb 10, 2017
1 parent e078ace commit 61dd891
Show file tree
Hide file tree
Showing 18 changed files with 345 additions and 43 deletions.
1 change: 1 addition & 0 deletions assets/stylesheets/_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@
@import 'my-sites/stats/stats-chart-tabs/style';
@import 'my-sites/stats/stats-comments/style';
@import 'my-sites/stats/stats-countries/style';
@import 'my-sites/stats/stats-date-picker/style';
@import 'my-sites/stats/stats-error/style';
@import 'my-sites/stats/stats-module/style';
@import 'my-sites/stats/stats-navigation/style';
Expand Down
45 changes: 31 additions & 14 deletions client/components/data/query-site-stats/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,47 @@ import shallowEqual from 'react-pure-render/shallowEqual';
*/
import { requestSiteStats } from 'state/stats/lists/actions';
import { isRequestingSiteStatsForQuery } from 'state/stats/lists/selectors';
import { isAutoRefreshAllowedForQuery } from 'state/stats/lists/utils';

class QuerySiteStats extends Component {
componentWillMount() {
this.request( this.props );
componentDidMount() {
this.request();
}

componentWillReceiveProps( nextProps ) {
if ( this.props.siteId === nextProps.siteId &&
this.props.statType === nextProps.statType &&
shallowEqual( this.props.query, nextProps.query ) ) {
componentDidUpdate( prevProps ) {
if ( this.props.siteId === prevProps.siteId &&
this.props.statType === prevProps.statType &&
shallowEqual( this.props.query, prevProps.query ) ) {
return;
}
this.request();
}

this.request( nextProps );
componentWillUnmount() {
this.clearInterval();
}

request( props ) {
if ( props.requesting ) {
request() {
const { requesting, siteId, statType, query, heartbeat } = this.props;
if ( requesting ) {
return;
}

props.requestSiteStats( props.siteId, props.statType, props.query );
this.props.requestSiteStats( siteId, statType, query );
this.clearInterval();
if ( heartbeat, isAutoRefreshAllowedForQuery( query ) ) {
this.interval = setInterval( () => {
if ( ! this.props.requesting ) {
this.props.requestSiteStats( siteId, statType, query );
}
}, heartbeat );
}
}

shouldComponentUpdate() {
return false;
clearInterval() {
if ( this.interval ) {
clearInterval( this.interval );
}
}

render() {
Expand All @@ -48,11 +63,13 @@ QuerySiteStats.propTypes = {
statType: PropTypes.string.isRequired,
query: PropTypes.object,
requesting: PropTypes.bool.isRequired,
requestSiteStats: PropTypes.func.isRequired
requestSiteStats: PropTypes.func.isRequired,
heartbeat: PropTypes.number
};

QuerySiteStats.defaultProps = {
query: {}
query: {},
heartbeat: 3 * 60 * 1000 // 3 minutes
};

export default connect(
Expand Down
13 changes: 5 additions & 8 deletions client/my-sites/stats/site.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ class StatsSite extends Component {
path="videoplays"
moduleStrings={ moduleStrings.videoplays }
period={ this.props.period }
date={ queryDate }
query={ query }
statType="statsVideoPlays"
showSummaryLink
Expand All @@ -99,7 +98,6 @@ class StatsSite extends Component {
path="podcastdownloads"
moduleStrings={ moduleStrings.podcastdownloads }
period={ this.props.period }
date={ queryDate }
query={ query }
statType="statsPodcastDownloads"
showSummaryLink
Expand Down Expand Up @@ -128,7 +126,11 @@ class StatsSite extends Component {
>
<DatePicker
period={ period }
date={ date } />
date={ date }
query={ query }
statsType="statsTopPosts"
showQueryDate
/>
</StatsPeriodNavigation>
</StickyPanel>
<div className="stats__module-list is-events">
Expand All @@ -138,30 +140,26 @@ class StatsSite extends Component {
moduleStrings={ moduleStrings.posts }
period={ this.props.period }
query={ query }
date={ queryDate }
statType="statsTopPosts"
showSummaryLink />
<StatsModule
path="referrers"
moduleStrings={ moduleStrings.referrers }
period={ this.props.period }
query={ query }
date={ queryDate }
statType="statsReferrers"
showSummaryLink />
<StatsModule
path="clicks"
moduleStrings={ moduleStrings.clicks }
period={ this.props.period }
query={ query }
date={ queryDate }
statType="statsClicks"
showSummaryLink />
<StatsModule
path="authors"
moduleStrings={ moduleStrings.authors }
period={ this.props.period }
date={ queryDate }
query={ query }
statType="statsTopAuthors"
className="stats__author-views"
Expand All @@ -177,7 +175,6 @@ class StatsSite extends Component {
path="searchterms"
moduleStrings={ moduleStrings.search }
period={ this.props.period }
date={ queryDate }
query={ query }
statType="statsSearchTerms"
showSummaryLink />
Expand Down
91 changes: 87 additions & 4 deletions client/my-sites/stats/stats-date-picker/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@
*/
import React, { PropTypes, Component } from 'react';
import { localize } from 'i18n-calypso';
import { get } from 'lodash';
import { flowRight, get } from 'lodash';
import { connect } from 'react-redux';

/**
* Internal dependencies
*/
import Tooltip from 'components/tooltip';
import { getSiteStatsQueryDate } from 'state/selectors';
import { getSelectedSiteId } from 'state/ui/selectors';
import { isRequestingSiteStatsForQuery } from 'state/stats/lists/selectors';
import { isAutoRefreshAllowedForQuery } from 'state/stats/lists/utils';

class StatsDatePicker extends Component {
static propTypes = {
Expand All @@ -14,6 +24,24 @@ class StatsDatePicker extends Component {
period: PropTypes.string.isRequired,
summary: PropTypes.bool,
query: PropTypes.object,
statType: PropTypes.string,
showQueryDate: PropTypes.bool,
};

static defaultProps = {
showQueryDate: false
};

state = {
isTooltipVisible: false
};

showTooltip = () => {
this.setState( { isTooltipVisible: true } );
};

hideTooltip = () => {
this.setState( { isTooltipVisible: false } );
};

dateForSummarize() {
Expand Down Expand Up @@ -75,8 +103,26 @@ class StatsDatePicker extends Component {
return formattedDate;
}

renderQueryDate() {
const { queryDate, moment, translate } = this.props;
if ( ! queryDate ) {
return null;
}

const today = moment();
const date = moment( queryDate );
const isToday = today.isSame( date, 'day' );
return translate( 'Last update: %(time)s', {
args: { time: isToday ? date.format( 'LT' ) : date.fromNow() }
} );
}

bindPulsingDot = ( ref ) => {
this.pulsingDot = ref;
}

render() {
const { summary, translate, query } = this.props;
const { summary, translate, query, showQueryDate } = this.props;
const isSummarizeQuery = get( query, 'summarize' );

const sectionTitle = translate( 'Stats for {{period/}}', {
Expand All @@ -95,11 +141,48 @@ class StatsDatePicker extends Component {
<div>
{ summary
? <span>{ sectionTitle }</span>
: <h3 className="stats-section-title">{ sectionTitle }</h3>
: <div className="stats-section-title">
<h3>{ sectionTitle }</h3>
{ showQueryDate && isAutoRefreshAllowedForQuery( query ) &&
<div className="stats-date-picker__refresh-status">
<span className="stats-date-picker__update-date">
{ this.renderQueryDate() }
</span>
<div className="stats-date-picker__pulsing-dot-wrapper"
ref={ this.bindPulsingDot }
onMouseEnter={ this.showTooltip }
onMouseLeave={ this.hideTooltip }
>
<div className="stats-date-picker__pulsing-dot" />
<Tooltip
isVisible={ this.state.isTooltipVisible }
onClose={ this.hideTooltip }
position="bottom"
context={ this.pulsingDot }
>
{ translate( 'Auto-refreshing every 3 minutes' )}
</Tooltip>
</div>
</div>
}
</div>
}
</div>
);
}
}

export default localize( StatsDatePicker );
const connectComponent = connect(
( state, { query, statsType, showQueryDate } ) => {
const siteId = getSelectedSiteId( state );
return {
queryDate: showQueryDate ? getSiteStatsQueryDate( state, siteId, statsType, query ) : null,
requesting: showQueryDate ? isRequestingSiteStatsForQuery( state, siteId, statsType, query ) : false,
};
}
);

export default flowRight(
connectComponent,
localize
)( StatsDatePicker );
33 changes: 33 additions & 0 deletions client/my-sites/stats/stats-date-picker/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
.stats-date-picker__refresh-status {
line-height: 16px;

.stats-date-picker__update-date {
color: $gray;
display: inline-block;
font-size: 13px;
}

.stats-date-picker__pulsing-dot-wrapper {
display: inline-block;
padding: 0 10px;

.stats-date-picker__pulsing-dot {
display: inline-block;
background: $gray;
transform: translate3d( 0, 0, 0 );
width: 6px;
height: 6px;
border: none;
box-shadow: 0 0 0 0 rgba( 168, 190, 206, 0.7 );
border-radius: 100%;
animation: stats-dot-pulse 1.25s infinite cubic-bezier( 0.66, 0, 0, 1 );
vertical-align: middle;
}
}

@keyframes stats-dot-pulse {
to {
box-shadow: 0 0 0 5px rgba( 90, 153, 220, 0 );
}
}
}
19 changes: 15 additions & 4 deletions client/my-sites/stats/stats-module/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,29 @@ class StatsModule extends Component {
path: PropTypes.string,
siteSlug: PropTypes.string,
siteId: PropTypes.number,
date: PropTypes.string,
data: PropTypes.array,
query: PropTypes.object,
statType: PropTypes.string,
showSummaryLink: PropTypes.bool,
translate: PropTypes.func,
moment: PropTypes.func,
};

static defaultProps = {
showSummaryLink: false,
query: {}
};

state = {
loaded: false
};

componentWillReceiveProps( nextProps ) {
if ( ! nextProps.requesting && this.props.requesting ) {
this.setState( { loaded: true } );
}
}

getModuleLabel() {
if ( ! this.props.summary ) {
return this.props.moduleStrings.title;
Expand Down Expand Up @@ -98,12 +108,12 @@ class StatsModule extends Component {

const noData = (
data &&
! requesting &&
this.state.loaded &&
! data.length
);

// Only show loading indicators when nothing is in state tree, and request in-flight
const isLoading = requesting && ! ( data && data.length );
const isLoading = ! this.state.loaded && ! ( data && data.length );

// TODO: Support error state in redux store
const hasError = false;
Expand All @@ -121,12 +131,13 @@ class StatsModule extends Component {
const summaryLink = this.getHref();
const displaySummaryLink = data && data.length >= 10;
const isAllTime = this.isAllTimeList();
const headerClass = classNames( 'stats-module__header', { 'is-refreshing': requesting && ! isLoading } );

return (
<div>
{ siteId && statType && <QuerySiteStats statType={ statType } siteId={ siteId } query={ query } /> }
{ ! isAllTime &&
<SectionHeader label={ this.getModuleLabel() } href={ ! summary ? summaryLink : null }>
<SectionHeader className={ headerClass } label={ this.getModuleLabel() } href={ ! summary ? summaryLink : null }>
{ summary && <DownloadCsv statType={ statType } query={ query } path={ path } period={ period } /> }
</SectionHeader>
}
Expand Down
9 changes: 9 additions & 0 deletions client/my-sites/stats/stats-module/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@
}

// Section title
@keyframes stats-date-picker__spin {
100% {
transform: rotate( 360deg );
}
}

.stats-section-title {
@include heading;
Expand Down Expand Up @@ -531,3 +536,7 @@ ul.module-header-actions {
justify-content: center;
align-items: center;
}

.stats-module__header.is-refreshing {
animation: loading-fade 1.6s ease-in-out infinite;
}
4 changes: 4 additions & 0 deletions client/my-sites/stats/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,8 @@
.stats-period-navigation {
margin: 9px 0;
}

.stats-date-picker__refresh-status {
display: none;
}
}
Loading

0 comments on commit 61dd891

Please sign in to comment.