Skip to content

Commit

Permalink
Stats: navigate by full periods (#99140)
Browse files Browse the repository at this point in the history
* navigate by full periods

* allow range navigation up to 3 years

* no need to add another param for shortcut id

* fix today

* fix ending date after today for next arrow
  • Loading branch information
kangzj authored Feb 2, 2025
1 parent 8863e5c commit 6b54b03
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 12 deletions.
5 changes: 4 additions & 1 deletion client/my-sites/stats/hooks/use-moment-site-zone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import moment from 'moment';
import { useSelector } from 'calypso/state';
import { getSiteOption } from 'calypso/state/sites/selectors';
import { getSelectedSiteId } from 'calypso/state/ui/selectors';
import { DATE_FORMAT } from '../constants';

export const getMomentSiteZone = createSelector(
( state: object, siteId: number | null ) => {
Expand All @@ -16,7 +17,9 @@ export const getMomentSiteZone = createSelector(

const gmtOffset = getSiteOption( state, siteId, 'gmt_offset' ) as number;
if ( Number.isFinite( gmtOffset ) ) {
return localizedMoment.utcOffset( gmtOffset );
// In all the components, `moment` is directly used, which defaults to the browser's local timezone.
// As a result, we need to convert the moment object to the site's timezone for easier comparison like `isSame`.
return moment( localizedMoment.utcOffset( gmtOffset ).format( DATE_FORMAT ) );
}

// Falls back to the browser's local timezone if no GMT offset is found
Expand Down
98 changes: 87 additions & 11 deletions client/my-sites/stats/stats-period-navigation/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,60 @@ class StatsPeriodNavigation extends PureComponent {
this.handleArrowNavigation( false );
};

// TODO: refactor to extract logic with `dateForCustomRange` from `client/my-sites/stats/stats-date-picker/index.jsx`.
getFullPeriod = () => {
const { moment, dateRange, momentSiteZone } = this.props;
const localizedStartDate = moment( dateRange.chartStart );
const localizedEndDate = moment( dateRange.chartEnd );

// If it's a partial month but ends today.
if (
localizedStartDate.isSame( localizedStartDate.clone().startOf( 'month' ), 'day' ) &&
localizedEndDate.isSame( momentSiteZone, 'day' ) &&
localizedStartDate.isSame( localizedEndDate, 'month' ) &&
( ! dateRange.shortcutId || dateRange.shortcutId === 'month_to_date' )
) {
return 'month';
}

// If it's a partial year but ends today.
if (
localizedStartDate.isSame( localizedStartDate.clone().startOf( 'year' ), 'day' ) &&
localizedEndDate.isSame( momentSiteZone, 'day' ) &&
localizedStartDate.isSame( localizedEndDate, 'year' ) &&
( ! dateRange.shortcutId || dateRange.shortcutId === 'year_to_date' )
) {
return 'year';
}

// If it's the same day, show single date.
if ( localizedStartDate.isSame( localizedEndDate, 'day' ) ) {
return 'day';
}

// If it's a full month.
if (
localizedStartDate.isSame( localizedStartDate.clone().startOf( 'month' ), 'day' ) &&
localizedEndDate.isSame( localizedEndDate.clone().endOf( 'month' ), 'day' ) &&
localizedStartDate.isSame( localizedEndDate, 'month' )
) {
return 'month';
}

// If it's a full year.
if (
localizedStartDate.isSame( localizedStartDate.clone().startOf( 'year' ), 'day' ) &&
localizedEndDate.isSame( localizedEndDate.clone().endOf( 'year' ), 'day' ) &&
localizedStartDate.isSame( localizedEndDate, 'year' )
) {
return 'year';
}

return null;
};

handleArrowNavigation = ( previousOrNext = false ) => {
const { moment, period, slug, dateRange } = this.props;
const { moment, momentSiteZone, period, slug, dateRange } = this.props;

const isWPAdmin = config.isEnabled( 'is_odyssey' );
const event_from = isWPAdmin ? 'jetpack_odyssey' : 'calypso';
Expand All @@ -150,16 +202,39 @@ class StatsPeriodNavigation extends PureComponent {
} );

const navigationStart = moment( dateRange.chartStart );
const navigationEnd = moment( dateRange.chartEnd );

if ( previousOrNext ) {
// Navigate to the previous date range.
navigationStart.subtract( dateRange.daysInRange, 'days' );
navigationEnd.subtract( dateRange.daysInRange, 'days' );
let navigationEnd = moment( dateRange.chartEnd );

// If it's a full month then we need to navigate to the previous/next month.
// If it's a full year then we need to navigate to the previous/next year.

const fullPeriod = this.getFullPeriod();
if ( ! fullPeriod ) {
// Usual flow - only based on the days in range.
if ( previousOrNext ) {
// Navigate to the previous date range.
navigationStart.subtract( dateRange.daysInRange, 'days' );
navigationEnd.subtract( dateRange.daysInRange, 'days' );
} else {
// Navigate to the next date range.
navigationStart.add( dateRange.daysInRange, 'days' );
navigationEnd.add( dateRange.daysInRange, 'days' );
}
} else {
// Navigate to the next date range.
navigationStart.add( dateRange.daysInRange, 'days' );
navigationEnd.add( dateRange.daysInRange, 'days' );
// Navigate range by full periods.
if ( previousOrNext ) {
// Navigate to the previous period.
navigationStart.subtract( 1, fullPeriod );
navigationEnd.subtract( 1, fullPeriod );
} else {
// Navigate to the next period.
navigationStart.add( 1, fullPeriod );
navigationEnd.add( 1, fullPeriod );
}
navigationStart.startOf( fullPeriod );
navigationEnd.endOf( fullPeriod );
if ( navigationEnd.isAfter( momentSiteZone, 'day' ) ) {
navigationEnd = momentSiteZone;
}
}

const chartStart = navigationStart.format( 'YYYY-MM-DD' );
Expand Down Expand Up @@ -246,7 +321,8 @@ class StatsPeriodNavigation extends PureComponent {
momentSiteZone,
'day'
);
const showArrowsForDateRange = showArrows && dateRange?.daysInRange <= 31;
// Make sure we only show arrows for date ranges that are less than 3 years for performance reasons.
const showArrowsForDateRange = showArrows && dateRange?.daysInRange <= 365 * 3;

return (
<div
Expand Down

0 comments on commit 6b54b03

Please sign in to comment.