Skip to content

Premium Analytics: describe the applied date configuration in the section header subtitle - #50897

Merged
retrofox merged 28 commits into
trunkfrom
add/pa-section-header-subtitle
Aug 3, 2026
Merged

retrofox merged 28 commits into
trunkfrom
add/pa-section-header-subtitle

Conversation

@retrofox

@retrofox retrofox commented Jul 29, 2026 •

Copy link
Copy Markdown
Contributor

Proposed changes

Closes WOOA7S-1824.

SectionHeader shipped with the subtitle empty because nothing derived it. This fills it in: the applied range spelled out, how long that range is, and the comparison when one is active.

Tuesday, July 21 – Monday, July 27 (7 days)
Tuesday, July 21 – Monday, July 27 (7 days) vs. Previous period
July 1, 2025 – June 30, 2026 (12 months)
Tuesday, July 28 (24 hours)

The length comes from the range, not the preset

The spec's second example steps back a period and still reads (7 days), but by then the preset is custom. So getDateRangeSpan measures the range itself, and the subtitle will keep working once period navigation (WOOA7S-1793's prev/next sub-issue) lands, with no change here.

It picks the coarsest unit that divides the range evenly, with two floors that keep the copy honest: a whole month stays in days below two months, so "Last 30 days" does not become "1 month", and years only kick in from two years up, so a twelve-month window keeps reading as 12 months the way the design spells it. Without the second floor the year surface's "All time" would read (72 months).

A window of a day or less is named by one date rather than two endpoints, which covers both today (whose endpoints are the same date) and the rolling last-24-hours (which straddles two calendar days without being about either in full).

Where the pieces live

The issue placed the formatter in packages/formatters/, but the subtitle is user-facing copy with a plural form and a comparison clause, which belongs next to the component that renders it. So the split follows the boundary the package already draws, the same one getComparisonTriggerLabel sits on:

Package Adds Concern
formatters getDateRangeSpan, formatDateRangeLong pure date math and formatting
ui getSectionSubtitle the assembled copy, next to SectionHeader

The subtitle follows applied state

It describes what the widgets are showing, so it must not move while a range edit is staged. useReportDateFilters exposed only the staged comparison; this adds appliedComparisonPresetId, read from committed state.

That distinction matters in one case: a comparison change normally commits on its own, but it rides along uncommitted when a primary edit is already open. Reading the staged value there would announce a comparison the widgets are not showing yet.

Rebased onto the locale-aware formatter

#50888 landed while this was in review, moving date formatting from date-fns patterns to @wordpress/date and narrowing DateFormatName to medium | short | year | iso. formatDateRangeLong used three of the names that went away.

long was already what medium renders, so it maps straight across. The two weekday-bearing forms come back as named formats derived from the site's date_format, the same way short is: withWeekday() in php-format.ts puts the weekday in front, and full / fullNoYear pair it with the site format and with its year-stripped variant. No custom-pattern escape hatch is reintroduced.

The weekday name still comes from WordPress's translation tables. Only the comma joining it to the date is ours, since core publishes no weekday-bearing format to take one from.

getDateRangeSpan is date arithmetic rather than formatting, so it keeps using date-fns and the dependency goes back on the package. That does not touch the lockfile: pnpm-workspace.yaml globs projects/*/*, so premium-analytics/packages/* are link: folders rather than pnpm importers.

A timezone hazard the swap exposed

Five tests were building dates in the runner's local zone while dateI18n renders in the site's, which moved from back a day. Production is not affected: ranges are TZDates anchored to the site's zone, so date-fns day boundaries and dateI18n output read the same frame. The fixtures now do the same rather than the code being changed to paper over it.

format-date.test.ts covers the two new names, including the case where the site's own date_format already names the weekday and must not get a second one.

Storybook

The SectionHeader stories passed a static subtitle arg. They now derive it from applied state, so the Default story demonstrates the staging behavior directly: change the range and the subtitle holds until Apply, pick a comparison and it moves right away.

Related product discussion/links

Does this pull request change what data or activity we track or use?

No.

Testing instructions

Setup

Build with jetpack build packages/premium-analytics --deps, then open wp-admin/admin.php?page=jetpack-premium-analytics.

The subtitle

Under each section title, the subtitle states the applied range and its length. With the default Last 30 days, expect the explicit dates followed by (30 days).

Screen.Recording.2026-07-30.at.3.56.19.PM.mov

Check the shape across presets:

Preset Expected
Last 24 hours one date, (24 hours)
Last 7 days two dates with weekdays, no year, (7 days)
Last 30 days (30 days), not (1 month)
Last 90 days (90 days)
Last 12 months month names with years, no weekday, (12 months)

Applied, not staged

Open the date picker and pick a different preset without applying. The subtitle must not move. Apply, and it updates.

Then pick a comparison. It commits on its own, so the subtitle gains vs. Previous period immediately. Repeat with a range edit already staged: the comparison rides along, and neither part of the subtitle moves until Apply.

Past periods

Hand-edit the URL to a range in a previous year, e.g. from=2024-07-16T00:00:00.000Z&to=2024-07-22T23:59:59.999Z. The year appears on both dates, so a past window is not mistaken for the current one. A range in the current year omits it.

Site locale

Switch the site to a locale that orders dates differently (es_ES puts the day first) and change Settings → General → Date Format. The subtitle follows both, since it renders through the site's own format rather than a pattern written here. The weekday leads the date in every locale.

Storybook

pnpm run storybook, then Packages / Premium Analytics / UI / SectionHeader. The Default story wires the real controls, so the staging behavior above is reproducible there. The YearSurface story shows the multi-year path (All time reads in years).

Pre-merge checks

  • Changelog file present (changelog/add-pa-section-header-subtitle)
  • Do not check "Generate changelog entries for this PR (using AI)" — entry already exists

@retrofox
retrofox requested a review from a team July 29, 2026 10:59
@retrofox
retrofox requested a review from a team as a code owner July 29, 2026 10:59
@retrofox retrofox added Enhancement Changes to an existing feature — removing, adding, or changing parts of it [Status] Needs Review This PR is ready for review. [Package] Premium Analytics labels Jul 29, 2026
@retrofox retrofox self-assigned this Jul 29, 2026
@github-actions

github-actions Bot commented Jul 29, 2026 •

Copy link
Copy Markdown
Contributor

Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.

  • To test on WoA, go to the Plugins menu on a WoA dev site. Click on the "Upload" button and follow the upgrade flow to be able to upload, install, and activate the Jetpack Beta plugin. Once the plugin is active, go to Jetpack > Jetpack Beta, select your plugin (Jetpack or WordPress.com Site Helper), and enable the add/pa-section-header-subtitle branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack add/pa-section-header-subtitle
bin/jetpack-downloader test jetpack-mu-wpcom-plugin add/pa-section-header-subtitle

Interested in more tips and information?

  • In your local development environment, use the jetpack rsync command to sync your changes to a WoA dev blog.
  • Read more about our development workflow here: PCYsg-eg0-p2
  • Figure out when your changes will be shipped to customers here: PCYsg-eg5-p2

@github-actions github-actions Bot added the Docs label Jul 29, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • ✅ Add a "[Status]" label (In Progress, Needs Review, ...).
  • ✅ Add testing instructions.
  • ✅ Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖


Follow this PR Review Process:

  1. Ensure all required checks appearing at the bottom of this PR are passing.
  2. Make sure to test your changes on all platforms that it applies to. You're responsible for the quality of the code you ship.
  3. You can use GitHub's Reviewers functionality to request a review.
  4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!

@jp-launch-control

jp-launch-control Bot commented Jul 29, 2026 •

Copy link
Copy Markdown

Code Coverage Summary

Coverage changed in 5 files.

File Coverage Δ% Δ Uncovered
projects/packages/premium-analytics/packages/routing/src/hooks/use-report-date-filters/use-report-date-filters.tsx 0/32 (0.00%) 0.00% 2 ❤️‍🩹
projects/packages/premium-analytics/routes/dashboard/stage.tsx 0/17 (0.00%) 0.00% 2 ❤️‍🩹
projects/packages/premium-analytics/packages/formatters/src/date/format-date.ts 17/17 (100.00%) 0.00% 0 💚
projects/packages/premium-analytics/packages/formatters/src/date/php-format.ts 41/41 (100.00%) 0.00% 0 💚
projects/packages/premium-analytics/packages/datetime/src/presets/comparison.ts 4/8 (50.00%) 37.50% -3 💚

4 files are newly checked for coverage.

File Coverage
projects/packages/premium-analytics/packages/ui/src/section-header/get-section-subtitle.ts 14/15 (93.33%) 💚
projects/packages/premium-analytics/packages/formatters/src/date/format-date-range-long.ts 19/20 (95.00%) 💚
projects/packages/premium-analytics/packages/formatters/src/date/get-date-range-span.ts 25/25 (100.00%) 💚
projects/packages/premium-analytics/packages/formatters/src/date/types.ts 0/0 (—%) 🤷

Full summary · PHP report · JS report

retrofox added 7 commits July 29, 2026 12:29
Add getDateRangeSpan and formatDateRangeLong. The span is derived from the range, not the preset that produced it, so a window stepped off a preset still reports its own length.
Surfaces describing what the widgets show need the committed comparison, not the staged one: a comparison change rides along uncommitted when a primary edit is already open.
Adds getSectionSubtitle, which frames the range, its length, and any active comparison as translatable copy. The story now derives it from applied state instead of a static arg.
Feeds the subtitle from the applied range and comparison, so it holds still while a range edit is staged and moves only on Apply.
A day-aligned single day was repeating the same endpoint twice, and a rolling 24-hour window named two calendar days it only partly covers.
@retrofox
retrofox force-pushed the add/pa-section-header-subtitle branch from da653f4 to bc59fa1 Compare July 29, 2026 11:31
retrofox added 4 commits July 29, 2026 13:09
The applied comparison rebuilt hasComparisonEnabled inline and dropped its trim, so a whitespace compare_from would announce a comparison the report params reject.
The long range formatter spelled out the pattern for DATE_FORMATS.full and its year-less variant. Register both as named presets instead.
Three date modules each redeclared it with the same note about the pending sibling-package wiring.

@chihsuan chihsuan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the thoughtful implementation! @retrofox

I left a few comments about cases that may need adjustment before merging

Also, I'm merging #50888 shortly, so formatDateRangeLong will need a rebase off the narrowed DateFormatName. Thanks!

Comment thread projects/packages/premium-analytics/routes/dashboard/stage.tsx
const handleComparisonChange = useCallback(
( _range: DateRange | undefined, nextPresetId?: ComparisonPresetId ) => {
setComparisonPresetId( nextPresetId );
onAppliedChange( { range: committed.range, comparisonPresetId: nextPresetId } );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not quite mirror useReportDateFilters. When a primary range edit is already staged, production keeps the comparison change staged as well and does not update the applied subtitle until Apply. Here, onAppliedChange always runs immediately with the committed range.

Should the story track whether the primary state is dirty and defer this applied-state update in that case?

if ( months ) {
if ( months >= MIN_MONTHS_FOR_YEARS && months % MONTHS_PER_YEAR === 0 ) {
return { unit: 'year', value: months / MONTHS_PER_YEAR };
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that the YearSurface story today shows Friday, January 1, 2021 – Thursday, July 30, 2026 (2037 days).

It seems like this is because getWholeMonths needs exact calendar-month alignment, but All time ends at endOfToday. So the unit ends up tracking today's date rather than the range:

If today is… All time reads
mid-month (most of the year) (2037 days)
the last day of a month (67 months)
Dec 31 (6 years)

Last 365 days has a milder version of the same thing. This may require preserving the applied preset ID or defining how partial multi-year ranges should be summarized?

Image

Base automatically changed from add/pa-section-header to trunk July 30, 2026 11:53
retrofox added 10 commits July 30, 2026 15:32
measured, it tracked today's date instead of the range
defer comparison commit on staged edits, pass the preset
escaped literals and the numeric weekday tokens
an open-ended range measures by the day it is read on
plus a site-zone reference year and a translatable dash
@retrofox
retrofox merged commit 0c82fca into trunk Aug 3, 2026
78 checks passed
@retrofox
retrofox deleted the add/pa-section-header-subtitle branch August 3, 2026 11:17
@github-actions github-actions Bot added [Status] UI Changes Add this to PRs that change the UI so documentation can be updated. and removed [Status] Needs Review This PR is ready for review. labels Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Docs Enhancement Changes to an existing feature — removing, adding, or changing parts of it [Package] Premium Analytics [Status] UI Changes Add this to PRs that change the UI so documentation can be updated.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants