Skip to content

Premium Analytics: add date range picker to the dashboard#49572

Open
Nikschavan wants to merge 5 commits into
trunkfrom
add/pa-date-range-picker
Open

Premium Analytics: add date range picker to the dashboard#49572
Nikschavan wants to merge 5 commits into
trunkfrom
add/pa-date-range-picker

Conversation

@Nikschavan

@Nikschavan Nikschavan commented Jun 12, 2026

Copy link
Copy Markdown
Member

Fixes WOOA7S-1587

Proposed changes

Google Chrome -2026-06-24 at 16 20 00@2x

Ports the date range picker from the next-woocommerce-analytics dashboard into the Premium Analytics dashboard, syncing the selected range to URL search params so every widget picks it up.

  • dashboard route: renders the DateFiltersPanel (already vendored in packages/ui) at the top of the dashboard, wired to useStagedSearch from @jetpack-premium-analytics/routing. Date range and preset edits are staged locally and committed atomically to the URL search params on Apply (from, to, preset); the to date is adjusted to end-of-day since the calendar component returns midnight. Comparison changes (compare_from, compare_to, compare_preset, comp) commit immediately, matching the original dashboard behavior.
  • widgets-toolkit: points WidgetRoot's default search source at / (the premium-analytics dashboard route) instead of the WooCommerce Analytics route path, so widgets resolve their report params from the dashboard URL via normalizeReportParams. No widget changes needed — they already read params through useWidgetRootContext().
  • package.json: adds link:packages/* deps for the data/datetime/formatters/routing/ui internal packages so wp-build can resolve them from the route bundle; documents the new deps in the dashboard route's package.json; updates pnpm-lock.yaml accordingly.

Related product discussion/links

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

No.

Testing instructions

  • Set up a site with the Premium Analytics plugin active.
  • Go to the Analytics dashboard.
  • Verify the date range picker and the Compare to dropdown render above the widgets.
  • Open the picker, choose a preset (e.g. Last 7 days), and click Apply.
  • Verify the URL updates (from, to and preset search params inside the p param) and widgets re-fetch for the new range.
  • Pick a custom range via the calendar or the From/To inputs and verify Apply/Cancel stage and revert correctly.
  • Enable comparison via the Compare to dropdown and verify the compare_from/compare_to/compare_preset/comp params commit immediately without pressing Apply.
  • Reload the page with the params in the URL and verify the picker restores the selected range.

@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!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Ports the Premium Analytics dashboard to support a shared date range picker (with comparison), syncing date state through URL search params so all widgets resolve consistent report parameters.

Changes:

  • Render DateFiltersPanel in the Premium Analytics dashboard route and wire it to staged URL search params with an Apply/Cancel flow.
  • Update widgets-toolkit WidgetRoot to default its search param source to / (the Premium Analytics dashboard route).
  • Add/adjust internal package dependencies and lockfile entries to ensure route bundling can resolve shared packages.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
projects/packages/premium-analytics/routes/dashboard/stage.tsx Adds the date range + comparison UI and stages/commits selected values to URL search params.
projects/packages/premium-analytics/routes/dashboard/package.json Adds required internal deps and date-fns for the dashboard route bundle.
projects/packages/premium-analytics/packages/widgets-toolkit/src/components/widget-root/widget-root.tsx Changes default search-param source route to / for report param normalization.
projects/packages/premium-analytics/package.json Adds link:packages/* deps so wp-build can resolve internal packages for the route bundle.
projects/packages/premium-analytics/changelog/add-pa-date-range-picker Changelog entry for the new dashboard date range picker.
pnpm-lock.yaml Lockfile updates for the new link dependencies.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

Comment thread projects/packages/premium-analytics/routes/dashboard/stage.tsx
Comment on lines +104 to +115
const onComparisonChange = useCallback(
( nextComparisonRange: DateRange | undefined, nextComparisonPresetId?: ComparisonPresetId ) => {
stage( {
compare_from: encodeDateToSearchParam( nextComparisonRange?.from ),
compare_to: encodeDateToSearchParam( nextComparisonRange?.to ),
compare_preset: nextComparisonPresetId ?? undefined,
comp: nextComparisonRange ? '1' : undefined,
} );

commit();
},
[ stage, commit ]
*/
stage( {
from: encodeDateToSearchParam( nextRange.from ),
to: encodeDateToSearchParam( endOfDay( nextRange.to ) ),
@jp-launch-control

jp-launch-control Bot commented Jun 12, 2026

Copy link
Copy Markdown

Code Coverage Summary

This PR did not change code coverage!

That could be good or bad, depending on the situation. Everything covered before, and still is? Great! Nothing was covered before? Not so great. 🤷

Full summary · PHP report

@Nikschavan Nikschavan marked this pull request as draft June 12, 2026 07:10
@nerrad nerrad force-pushed the update/pa-introduce-customize-dashboard branch 2 times, most recently from 5bb6dd9 to 7ff7f71 Compare June 17, 2026 11:19
@louwie17 louwie17 force-pushed the update/pa-introduce-customize-dashboard branch from 7ff7f71 to 22e10a7 Compare June 19, 2026 12:21
@Nikschavan Nikschavan force-pushed the add/pa-date-range-picker branch from acec56a to 979e1c4 Compare June 24, 2026 10:48
@Nikschavan Nikschavan changed the base branch from update/pa-introduce-customize-dashboard to trunk June 24, 2026 10:48
@Nikschavan Nikschavan requested a review from Copilot June 24, 2026 10:50
@Nikschavan

Copy link
Copy Markdown
Member Author

@claude Review the PR

@claude

claude Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Nikschavan's task in 3m 8s —— View job


Code Review

I read through the diff and the supporting packages (useStagedSearch, normalizeReportParams, the date utils, and DateFiltersPanel). Overall this is a clean port — staging primary edits and committing on Apply is the right model, and seeding defaults in route.ts is a nice improvement over the earlier widget-root default-route approach (it also resolves Copilot's "empty range on fresh load" comment). A few things worth addressing:

🔴 Bugs / correctness

1. Comparison commit() flushes un-applied primary edits (stage.tsx:131)
This is Copilot's comment #2, and it's valid against the current code. useStagedSearch accumulates all stage() calls into one bufferRef, and commit() flushes the entire buffer (use-staged-search.tsx:187). So if a user edits the primary range (staged, not yet applied) and then changes the Compare to dropdown, onComparisonChangecommit() commits the staged primary dates too — breaking "primary applies atomically; comparison commits immediately."

The inverse is also true: because the commit navigates and the URL→staged mirror effect (use-staged-search.tsx:125) resets bufferRef on every committed change, a comparison change can instead discard in-progress primary edits. Either way the two flows aren't actually independent.

Cleanest fix is to make comparison bypass the staged buffer with a direct navigate — the routing package already exposes writeComparisonToSearch for exactly this. Fix this →

2. endOfDay runs in the browser timezone, not the site timezone (stage.tsx:103)
This is Copilot's comment #3, and it's a real risk. endOfDay( nextRange.to ) sets 23:59:59.999 using the host's local wall-clock. encodeDateToSearchParam then calls localTZDate(...), which re-labels the same instant into the site TZ rather than re-deriving end-of-day there (date.ts:84, utils/date.ts). When browser TZ ≠ site TZ, the encoded to boundary shifts off midnight in the site timezone.

Convert first, then take end-of-day, so the boundary is computed in the site TZ regardless of host:

to: encodeDateToSearchParam( endOfDay( localTZDate( nextRange.to ) ) ),

(Worth confirming what DateRangePopover hands back in nextRange.to — if it's already a site-TZ TZDate the impact is limited, but the reorder is correct in both cases.) Fix this →

🟡 Worth a look

3. Default seeding can clobber partial deep-links (route.ts:53-59)
The redirect spreads { ...params, ...getDefaultQueryParams( true ) }, so the defaults win over params. The guard fires whenever any of from/to/interval is missing — meaning a URL with a real from/to but no interval gets its dates overwritten by the 30-day default rather than just filling in the gap. If the intent is "seed only what's missing," reverse the spread so user-provided params win: { ...getDefaultQueryParams( true ), ...params }. Fix this →

4. Redundant interval seeding — minor/informational. normalizeReportParams always recomputes interval from the resolved from/to (search.ts:115) and the widgets read params through it, so the seeded interval is never actually consumed by widgets — it only serves as a "has been seeded" marker in the route guard. Not a bug, just noting it so the interval check in route.ts isn't mistaken for granularity control. Note that onChange also never re-stages interval on range change, which is fine because of this recompute.

✅ Looks good

  • DateFiltersPanel wiring, containerElement measurement target, and the getSiteTimezone() prop.
  • presetId/comparisonPresetId validation is handled inside DateFiltersPanel (isPrimaryPreset/isComparisonPresetId), so garbage URL values degrade gracefully.
  • The documented as unknown as never cast for the dynamically-built / route schema is reasonable.
  • Changelog entry and the link:packages/* dep additions are correct.

  • Read changed files & gather context
  • Review stage.tsx (date range / comparison logic)
  • Review route.ts, deps, normalizeReportParams interaction
  • Post review feedback
    · branch add/pa-date-range-picker

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Comment thread projects/packages/premium-analytics/routes/dashboard/stage.tsx Outdated
Comment thread projects/packages/premium-analytics/routes/dashboard/route.ts
@Nikschavan Nikschavan marked this pull request as ready for review June 24, 2026 13:01
@Nikschavan Nikschavan requested review from a team as code owners June 24, 2026 13:01

@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 working on this! @Nikschavan

I verified the full flow on a local site. Overall looks good. Just two issues discovered during the testing, see inline comments below:

Comment thread projects/packages/premium-analytics/routes/dashboard/route.ts Outdated
Comment thread projects/packages/premium-analytics/routes/dashboard/route.ts Outdated
@Nikschavan Nikschavan force-pushed the add/pa-date-range-picker branch from fc4303e to 589e65f Compare June 25, 2026 05:37
@Nikschavan Nikschavan requested a review from chihsuan June 25, 2026 08:02
@Nikschavan

Copy link
Copy Markdown
Member Author

Thank you for the review @chihsuan, I have addressed the suggestions/bugs and Pr is ready for re-review

@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 adding feedback! @Nikschavan

I just went deeper this round and caught a few things I missed earlier, so apologies for the late notes 🙏

  1. I noticed that if I select "Last 7 days" without clicking Apply and then click outside, the UI still shows "Last 7 days." After changing "Compare to," the URL unexpectedly updates to preset=last-7-days. Clicking the Cancel button works as expected. See the video below:
Screen.Recording.2026-06-25.at.4.43.55.PM.mov
  1. Seed force-enables comparison on custom from/to deep-links (route.ts)
  2. Unguarded await ensureCoreSettingsReady() (route.ts) — matches upstream; non-blocking.

* values instead of being reset to the defaults.
*/
const seeded: Record< string, unknown > = {
...getDefaultQueryParams( true, preset ),

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.

Should the seed reuse normalizeReportParams instead of a raw default spread?

I found that opening a URL like …&p=/?from=2026-01-01T00:00:00.000+02:00&to=2026-01-31T23:59:59.999+02:00 → URL comes back with comp="1". It seems it's because { ...getDefaultQueryParams( true, preset ), ...params } always carries comp: '1', and ...params can't override a key it doesn't have.

Screen.Recording.2026-06-25.at.4.38.25.PM.mov

const params = ( search ?? {} ) as DashboardSearch;
if ( ! params.from || ! params.to || ! params.interval ) {
// Seed dates in the site timezone, not the browser's (see above).
await ensureCoreSettingsReady();

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.

minor: ensureCoreSettingsReady() can reject, and an uncaught rejection turns the first load into an error page. Same as upstream's loader, so non-blocking.

But it might be worth add try/catch that proceeds with the default seed.

@Nikschavan Nikschavan force-pushed the add/pa-date-range-picker branch from cbe92a9 to 37fee84 Compare June 26, 2026 06:33
Add the date range picker with comparison support to the dashboard stage, syncing the selected range to URL search params so all widgets share it. The dashboard route seeds default date params (last 30 days, previous-period comparison) on first visit.
… range

When a partial URL is supplied (e.g. preset deep-link or custom from/to without interval), seed only the missing params so user-supplied values are kept, deriving the range from a concrete preset when possible.

On the stage, re-derive the comparison window from the new primary range when comparison is enabled, so widgets compare against the matching previous period instead of stale dates.
Await ensureCoreSettingsReady() before seeding the default date range so getDefaultQueryParams encodes dates in the site timezone instead of falling back to the browser's. Also mark partial custom from/to URLs as 'custom' preset so normalizeReportParams keeps the supplied dates.
Resolve seed date params through the shared normalizeReportParams resolver so the URL and widgets agree on dates/interval/preset/comparison, and avoid forcing comp onto custom deep-links. Make ensureCoreSettingsReady non-fatal by falling back to the default seed on rejection, and overlay resolved params onto the original search to preserve deep-linked params like section.
@Nikschavan Nikschavan force-pushed the add/pa-date-range-picker branch from 37fee84 to acc5d52 Compare June 26, 2026 08:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants