Skip to content

Premium Analytics: add Regions to the Top locations "View by" control - #51267

Open
kangzj wants to merge 5 commits into
trunkfrom
echo/wooa7s-1940-locations-region-dropdown
Open

Premium Analytics: add Regions to the Top locations "View by" control#51267
kangzj wants to merge 5 commits into
trunkfrom
echo/wooa7s-1940-locations-region-dropdown

Conversation

@kangzj

@kangzj kangzj commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Fixes WOOA7S-1940

Why

Site owners could see which countries their visitors came from, and which cities — but not which regions, unless they clicked into one country at a time and read its regions in isolation. There was no way to ask "which regions send me the most traffic overall". The full Locations report has had a Regions tab all along; the dashboard widget just never offered it. Now it does.

Proposed changes

  • Add Regions to the Top locations widget's "View by" control, between Countries and Cities.
  • Regions mode is worldwide, like Cities: the leaderboard lists the top regions across every country, each with its country's flag.
  • The map summarises those regions back up to their country — Google GeoChart can't place sub-country rows on the world map. Cities mode already did this; the same path now serves both.
  • Switching to Regions (or Cities) clears an active country drill-down, so a country selected in Countries mode can't linger in a mode that has no way to clear it.
  • "View all" from Regions mode opens the Locations report on its Regions tab, matching what Countries and Cities already did.

Countries mode, its click-through drill-down into one country's regions, and the province map for the selected country are all unchanged.

Screenshots

The "View by" control, before and after — same widget, same data, same scroll position:

Top locations "View by" dropdown, before and after

Regions selected — worldwide regions in the leaderboard with their country flags, summarised by country on the map:

Top locations widget in Regions mode

Related product discussion/links

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

No. Regions mode reads the same stats/location-views/{geoMode} endpoint the widget already used for the country drill-down, and the Locations report already queries it unfiltered for its Regions tab.

Testing instructions

Acceptance criteria from the issue, as a checklist:

  • Go to Jetpack → Stats → Traffic on a Jetpack-connected site with location data.
  • Open the Top locations widget's "View by" dropdown — it lists Countries, Regions, Cities. Before this change only Countries and Cities were there.
  • Select Regions. The leaderboard lists regions from across the world (not one country's), each with its country flag, and the map highlights the countries those regions belong to.
  • Click View all — the Locations report opens on its Regions tab.
  • Switch back to Countries and click a country row. It still drills into that country's regions, still draws the province map, and the "All locations" back link still returns to the country list.
  • While drilled into a country, switch to Regions: the drill-down clears and the worldwide region list appears.
  • Cities is unchanged — city rows in the leaderboard, summarised by country on the map, "View all" opening the Cities tab.

The screenshots above come from a local docker site whose stats/location-views responses were mocked, since an offline-mode site has no live Stats data. Storybook covers the same states without a WordPress install: Packages/Premium Analytics/Widgets/Locations now has a RegionsMode story alongside Default and CitiesMode.

The second observation in the issue — "the Top platforms widget is missing platform distribution" — was checked separately and is not the same issue. Its dropdown offers Browser and OS and both render correctly (Windows / macOS / Android / iOS / Linux under OS). The Desktop / Mobile / Tablet split lives in the separate Devices widget. Nothing to fix there; if that split should also appear inside Top platforms, that is a product decision worth its own issue.

The widget's dropdown offered only Countries and Cities, so a site owner could
reach region data only by drilling into one country at a time — while the
Locations report has had a Regions tab all along.

Regions mode is worldwide, like Cities: the leaderboard lists regions and the
map sums them back up to their country, since Google GeoChart cannot place
sub-country rows on the world map.
@kangzj
kangzj requested a review from a team as a code owner August 14, 2026 01:13
@kangzj kangzj added Enhancement Changes to an existing feature — removing, adding, or changing parts of it [Status] Needs Review This PR is ready for review. labels Aug 14, 2026
@kangzj kangzj self-assigned this Aug 14, 2026
@kangzj kangzj added [Status] In Progress and removed [Status] Needs Review This PR is ready for review. labels Aug 14, 2026
@kangzj
kangzj marked this pull request as draft August 14, 2026 01:14
@github-actions

github-actions Bot commented Aug 14, 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 echo/wooa7s-1940-locations-region-dropdown branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack echo/wooa7s-1940-locations-region-dropdown
bin/jetpack-downloader test jetpack-mu-wpcom-plugin echo/wooa7s-1940-locations-region-dropdown

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

@kangzj

This comment has been minimized.

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

@kangzj

This comment has been minimized.

@claude

This comment has been minimized.

This comment has been minimized.

@jp-launch-control

jp-launch-control Bot commented Aug 14, 2026

Copy link
Copy Markdown

Code Coverage Summary

Coverage changed in 1 file.

File Coverage Δ% Δ Uncovered
projects/packages/premium-analytics/widgets/locations/render.tsx 60/84 (71.43%) 19.62% -16 💚

Full summary · PHP report · JS report

…reset

- REPORT_SECTIONS values are typed to the report's own tab ids so a slug typo
  fails the build; ReportLink takes a bare string and would not.
- Fall back to the Countries tab when a persisted layout carries a granularity
  outside the union.
- Test the round trip the reset actually guards: drill into a country, switch to
  Regions, and come back to Countries listing every country again.
- Fix an it.each title whose placeholders read in the opposite order to its tuple.
@kangzj

This comment has been minimized.

@kangzj

This comment has been minimized.

@kangzj

This comment has been minimized.

@claude

This comment has been minimized.

This comment has been minimized.

The report-link fallback only covered one of the two consumers: the same
unvalidated value became the geoMode path segment, so a stale layout would
still request stats/location-views/<unknown> and render an error under a link
that had quietly fallen back to Countries. Normalizing once covers both and
lets the fallback go.

Also type the test's hook fixture off the hook itself, so a new LocationView
field breaks the test rather than being cast away, and assert the map still
renders in Regions mode.
@kangzj

This comment has been minimized.

@kangzj

This comment has been minimized.

@claude

This comment has been minimized.

`in` also accepts inherited keys, so `toString` would have passed
normalization and reached the endpoint as a path segment — the same failure the
normalization was added to close.

Also correct a fixture comment: the typing protects the row literals passed to
mockReturnValue, not the empty-data constant it sat above.
@kangzj

This comment has been minimized.

@kangzj

kangzj commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

Review-cycle summary — 962db00aa47776c285ed

3 rounds; CI green (62 passing, 0 failing); 8 findings from 2 AI reviewers triaged, 6 applied and 2 declined with reasons.

What changed during the cycle

Commits added:

  • 7776c285ed — Address review: use an own-property check for the stored granularity
  • 2c0155afe1 — Address review: normalize the stored granularity at the widget boundary
  • 04566ecdac — Address review: type the report-section map and cover the drill-down reset

Diff summary: 2 files changed, 76 insertions(+), 7 deletions(-)

Findings addressed:

Round Source Finding Resolution
1 claude it.each title placeholders read in the opposite order to its tuple Reworded — 04566ecdac
1 claude No test for the behaviour the drill-down reset guards Added the round-trip test (drill in → Regions → back to Countries); verified it fails against the old guard. The originally-suggested assertion passes either way, since activeSelectedCountry masking already blanks the filter — 04566ecdac
1 claude REPORT_SECTIONS lookup not total at runtime Typed the values as the report's tab ids so a slug typo fails the build; added a fallback — 04566ecdac
2 claude The fallback covered only one of two consumers Correct: the same value became the stats/location-views/{geoMode} path segment, so a stale layout rendered an error state under a link that had silently fallen back. Normalized once at the boundary; fallback dropped as redundant — 2c0155afe1
2 claude as unknown as cast opted the test fixture out of type checking Typed off the hook's own return type — 2c0155afe1
2 claude geo-chart test id never queried Asserted in the drill-down test — 2c0155afe1
3 claude in also matches inherited keys Correct, and it reopened the same hole: 'toString' in REPORT_SECTIONS is true. Swapped for Object.prototype.hasOwnProperty.call7776c285ed
3 claude Fixture comment described protection the empty-data constant can't provide Reworded to name the row literals it actually guards — 7776c285ed

Declined:

  • Plugin changelog entries alongside the package entry. Every widget change in this package ships a package-only entry, and plugins/premium-analytics is pre-release. claude reached the same conclusion and raised it only to make the call deliberate.
  • Regions map can look sparse. Real, but Cities mode already makes the identical top-N-summarised-to-country tradeoff, and map and leaderboard describe the same visible rows. A design call across both modes rather than a divergence to introduce here.

copilot-swe-agent returned no actionable findings in either round.

Unaddressed (flagged for owner): none.

Verification: 7/7 locations tests, package typecheck and ESLint clean, and all three modes driven in a live dashboard after each rebuild — each renders and each "View all" opens its matching report tab.

@kangzj kangzj added [Status] Needs Team Review Obsolete. Use Needs Review instead. and removed [Status] In Progress labels Aug 14, 2026
@kangzj
kangzj marked this pull request as ready for review August 14, 2026 02:22
};

function getGeoChartCountryId( countryCode: string ): string {
if ( countryCode.toUpperCase() === 'TW' ) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

hum why would we need this special handling here 🤔

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.

Looked at it. I think this line does nothing now. It came in with #50251 the "Avoid unsupported Taiwan map" commit.

The actual fix turned out to be runtimeUnsupportedProvinceMapCountries. Worth deleting in a follow-up rather than here, keep the .toUpperCase() though.

cc @dognose24 for confirming.

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.

Confirmed — that line came out of the TW "Requested map does not exist" fight, and you're right that the actual fix was runtimeUnsupportedProvinceMapCountries; the province-map error happens on the region: 'TW' + resolution: 'provinces' path, which this row-value mapping never touches. One nuance before deleting: it does change the world-map datatable value from 'TW' to 'Taiwan'. GeoChart's regions mode documents ISO alpha-2 as accepted, so the bare code should highlight the same — worth a quick check against the Cities-mode story (its mock has TW rows) when the follow-up removes it. Keeping .toUpperCase() 👍

@kangzj
kangzj requested a review from chihsuan August 14, 2026 03:22
chihsuan
chihsuan previously approved these changes Aug 14, 2026

@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 @kangzj LGTM. Confirmed it works as expected.

Just nits inline, nothing blocking. 🚀

};

function getGeoChartCountryId( countryCode: string ): string {
if ( countryCode.toUpperCase() === 'TW' ) {

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.

Looked at it. I think this line does nothing now. It came in with #50251 the "Avoid unsupported Taiwan map" commit.

The actual fix turned out to be runtimeUnsupportedProvinceMapCountries. Worth deleting in a follow-up rather than here, keep the .toUpperCase() though.

cc @dognose24 for confirming.

// reach the endpoint as a path segment.
const geoGranularity = Object.prototype.hasOwnProperty.call( REPORT_SECTIONS, storedGranularity )
? storedGranularity
: 'country';

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.

nit: worth naming the default once?

'country' appears twice here and the two have to agree. Also 'country' carries two meanings in this file. I think a DEFAULT_GEO_GRANULARITY const would improve it, but it's a small thing.

const DEFAULT_GEO_GRANULARITY: GeoGranularity = 'country';

A package-only entry reaches the package CHANGELOG and nowhere else, so a
user-visible control gaining an option stays invisible to the people who read
the plugin changelog.

@dognose24 dognose24 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.

Read through the widget and the report wiring — the regions tab is already registered, the unfiltered request shape is right, and the stale-attribute normalization is a nice touch. Three small notes inline, none blocking.

// The "View by" control lives in the widget host header (the
// `relevance: 'high'` attribute). City mode disables country drill-down.
// `relevance: 'high'` attribute). Only Countries mode drills down, so leaving
// the other modes would strand a selected country the user can't clear.

@dognose24 dognose24 Aug 14, 2026

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.

nit: keying the filter off geoMode leaves the first render after switching a drilled-down Countries view → Regions in a self-contradictory state — the granularity says worldwide while the computed filter still says one country (the clearing effect only runs after render). In practice nothing visible happens: the stale params match the drill-down query already in cache so React Query dedupes the request, and the renderLocationState / isPlaceholderData machinery keeps the transition seamless. Still, deriving the filter from the granularity makes the render self-consistent and leaves the effect as pure UI-state cleanup:

countryFilter: geoGranularity === 'country' ? activeSelectedCountry?.code : undefined,

Significance: minor
Type: added

Locations: offer Regions in the widget's "View by" control.

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.

nit: the package entry says Locations: while the plugin entry says Top locations: — the card's product name is Top locations, so worth aligning the two.

} );

it.each( [
[ undefined, 'countries' ],

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.

nit: this case prints as opens the undefined granularity on the countries report tab in the runner output — a literal like 'default' (mapped to “no attribute” in the render call) would read better.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement Changes to an existing feature — removing, adding, or changing parts of it [Package] Premium Analytics [Plugin] Premium Analytics [Status] Needs Team Review Obsolete. Use Needs Review instead.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants