Skip to content

WOOA7S-1495: add Authors widget backed by Jetpack Stats - #49570

Merged
Nikschavan merged 12 commits into
trunkfrom
wooa7s-1495-port-jetpack-stats-module-authors
Jun 30, 2026
Merged

WOOA7S-1495: add Authors widget backed by Jetpack Stats#49570
Nikschavan merged 12 commits into
trunkfrom
wooa7s-1495-port-jetpack-stats-module-authors

Conversation

@Nikschavan

@Nikschavan Nikschavan commented Jun 12, 2026

Copy link
Copy Markdown
Member

Proposed changes

Google Chrome -2026-06-12 at 11 15 10@2x

Ports the Authors widget from the Jetpack Stats module into the Premium Analytics dashboard, backed by the Jetpack Stats API. This is a self-contained widget — the Stats data layer and the widgets-toolkit primitives it builds on already live in trunk.

  • widgets/authors/: adds the jpa/authors dashboard widget — render.tsx (a presentational AuthorsLeaderboard plus a data-connected AuthorsReport, wrapped in WidgetRoot), widget.json, and widget.ts with a configurable integer max attribute. It reads its report params from the dashboard date range via WidgetRoot context, like the other Stats widgets.
  • data: consumes the existing useStatsTopAuthors hook and top-authors Stats sanitizer (already in trunk). A local buildTopAuthorsData helper flattens the report and shapes it into leaderboard chart data (share-of-max plus period-over-period deltas), mirroring the Top posts widget.
  • storybook + tests: ships a Storybook story (Default / WithComparison / Loading / Empty / size variants) and unit tests for buildTopAuthorsData.

Related product discussion/links

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

No.

Testing instructions

  • Set up a Jetpack-connected site with the Premium Analytics plugin active.
  • Go to the Premium Analytics dashboard and open the widget picker.
  • Add the Authors widget.
  • Verify it renders a leaderboard of top authors by views, sourced from Jetpack Stats (check the network tab for requests to /jetpack/v4/stats-app/sites/{blog_id}/stats/top-authors).
  • Change the Maximum authors attribute and verify the list respects the limit.
  • Adjust the dashboard date range and verify the widget re-fetches for the new range.
  • On a site with no traffic, verify the empty state copy renders instead of the chart.
  • You can also exercise the widget's states in Storybook under Packages → Premium Analytics → Widgets → Authors.

@github-actions

github-actions Bot commented Jun 12, 2026

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!


Premium Analytics plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.

@github-actions github-actions Bot added the [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. label Jun 12, 2026
Comment thread projects/packages/premium-analytics/packages/data/src/api/constants.ts Outdated
* TODO: Remove the default range once we have a way to pass the launched date to the widget.
*/
const getDefaultReportParams = () => ( {
from: '2000-01-01T00:00:00',

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Temporary hardcoded long date range to view the data in the widget, until we have date range picker in the dashboard

@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

@louwie17
louwie17 force-pushed the update/pa-introduce-customize-dashboard branch from 909996e to d060b2d Compare June 12, 2026 02:39
@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 wooa7s-1495-port-jetpack-stats-module-authors branch from e1f31ef to 49f10a6 Compare June 23, 2026 15:03
@Nikschavan
Nikschavan changed the base branch from update/pa-introduce-customize-dashboard to trunk June 23, 2026 15:04
@Nikschavan
Nikschavan requested a review from Copilot June 24, 2026 06:02
@Nikschavan

Copy link
Copy Markdown
Member Author

@claude Review the PR please

@claude

claude Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

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


PR Review — Authors widget

Reviewed the 11 files in this commit. Note: the data package (stats-fetch, statsTopAuthorsQuery, useStatsTopAuthors), the withDevtools provider prop, and the widget-picker CSS changes described in the PR body are not in this commit's diff (they already exist in trunk). So this review covers the toolkit widget + helper + render entry only. Overall this is clean, well-documented, and closely follows the established widget conventions (loading/refetch/error/empty handling, LeaderboardChart with emptyState* props, render/widget.json/widget.ts trio). A few things worth addressing:

🐛 Correctness / i18n

1. "Untracked authors" never shows the translated stringgetAuthorLabel in build-top-authors-data.ts:23-27 falls back to __( 'Untracked authors' ) only when author.label is empty. But the processing layer already substitutes a value at processing/stats/top-authors.ts:26:

label: item.name || 'Untracked Authors',

So by the time data reaches buildTopAuthorsData, label is always a non-empty string, and authors with no name render the untranslated 'Untracked Authors' (capital A) — the translated fallback in the helper is effectively dead code. Make the untracked label translatable and consistent in one place (probably the processing layer, or normalize in the helper by treating the sentinel as "untracked"). Fix this →

2. Authors are merged by display labelsummarizeAuthors keys the Map by label and sets id: existing?.id ?? label. Two distinct authors sharing a display name will be collapsed into one leaderboard row with summed views. Likely rare, but if the API exposes a stable author id, prefer keying on that for both dedup and id. Low severity — worth a comment acknowledging the trade-off if you keep it.

🔧 Type safety

3. reportParams as StatsReportParams cast (authors-widget.tsx:54) — this is the only widget in the toolkit that casts reportParams (others pass it straight to their hook). The context types it as ReportParams, while StatsReportParams = ReportParams & StatsQueryParams. The extra StatsQueryParams fields are all optional so it's not unsafe at runtime today, but the cast suppresses checking. Cleaner would be to type useStatsTopAuthors's param as ReportParams (since it only adds optional fields) so call sites don't need the assertion. Same goes for the primary.data as StatsTopAuthorsReport casts — if the hook can be made generic it avoids the assertions.

📝 Minor / consistency

4. widget.json description over-promises"Top authors by views, with their most viewed posts." The widget only renders the authors leaderboard; the nested children (posts) that the processing layer builds are never displayed. Either drop "with their most viewed posts" or wire up the posts UI.

5. max is a free-text attribute (widget.ts:18, type: 'text') — toPositiveInt safely falls back to 7 for non-numeric/negative input, so no breakage, but the user gets silent correction with no feedback. If the attribute framework supports a number/min input, that'd be a nicer UX. Non-blocking.

💬 Answers to your inline questions

  • constants.ts temporary stats endpoint → proxy: Agreed, and this matches the documented migration path — AGENTS.md shows stats-app/* maps onto the stats/jetpack-stats proxy prefixes already allowlisted in PREFIX_CONFIG, so the switch should be a config-only change. Good to flag.
  • Should conversion be backend or here? (processing/stats-top-authors): Keeping normalization client-side is consistent with every other stats widget (processing/stats/* all normalize on the frontend). I'd keep it here for consistency unless payload size becomes a concern — splitting one widget to the backend would fragment the pattern.
  • Hardcoded date range (render.tsx): Fine as a clearly-marked TODO. One note: getDefaultReportParams builds to from new Date() in local time, which is what you want for an all-time view.

✅ Nice touches

  • Division-by-zero guard (Math.max( …, 1 )) and comparison alignment by label in buildTopAuthorsData.
  • The isInitialLoading = ( isLoading || primary.isPending ) && ! hasData comment explaining the skipToken disabled window.
  • Changelog entry present and correctly typed.

  • Read changed files & diff
  • Review widgets-toolkit (AuthorsWidget, build-top-authors-data)
  • Review premium-analytics widget (render, widget.json/ts)
  • Answer inline questions
  • Post feedback

Note: the widget-dashboard CSS / data-package / devtools changes from the PR description aren't in this commit, so they weren't reviewed here.

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

Adds a new Authors widget to the Jetpack Premium Analytics dashboard, using the Jetpack Stats “top-authors” endpoint and a new widget/toolkit implementation to render a leaderboard with comparison support.

Changes:

  • Register new jpa/authors widget metadata (type/module JSON) with a configurable max attribute and a render entry using WidgetRoot.
  • Add AuthorsWidget to the widgets-toolkit plus a buildTopAuthorsData helper to transform Stats responses into leaderboard chart data.
  • Export the new widget/helper from the widgets-toolkit public entrypoints and add a changelog entry.

Reviewed changes

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

Show a summary per file
File Description
projects/packages/premium-analytics/widgets/authors/widget.ts Defines the widget type (name/title/icon) and its max attribute.
projects/packages/premium-analytics/widgets/authors/widget.json Widget picker metadata (title/description/category).
projects/packages/premium-analytics/widgets/authors/render.tsx Render entry wiring WidgetRoot + AuthorsWidget, including a wide default report range.
projects/packages/premium-analytics/widgets/authors/package.json Declares per-widget package dependencies.
projects/packages/premium-analytics/packages/widgets-toolkit/src/widgets/index.ts Exports AuthorsWidget from the widgets toolkit widget barrel.
projects/packages/premium-analytics/packages/widgets-toolkit/src/widgets/authors/index.ts Authors widget module re-export.
projects/packages/premium-analytics/packages/widgets-toolkit/src/widgets/authors/authors-widget.tsx Main widget component: fetches top authors and renders a leaderboard with comparison/empty/loading/error handling.
projects/packages/premium-analytics/packages/widgets-toolkit/src/index.ts Exposes AuthorsWidget from the toolkit public API.
projects/packages/premium-analytics/packages/widgets-toolkit/src/helpers/index.ts Exports buildTopAuthorsData helper.
projects/packages/premium-analytics/packages/widgets-toolkit/src/helpers/build-top-authors-data.ts Converts Stats “top-authors” report(s) into LeaderboardChart-ready data with deltas/shares.
projects/packages/premium-analytics/changelog/update-pa-authors-widget Changelog entry for introducing the Authors widget.

Comment thread projects/packages/premium-analytics/widgets/authors/widget.json
Comment thread projects/packages/premium-analytics/widgets/authors/build-top-authors-data.ts Outdated
Comment thread projects/packages/premium-analytics/widgets/authors/package.json
@Nikschavan
Nikschavan force-pushed the wooa7s-1495-port-jetpack-stats-module-authors branch 2 times, most recently from 4eb1e79 to 36d5586 Compare June 24, 2026 07:11
@Nikschavan
Nikschavan marked this pull request as ready for review June 24, 2026 08:46
@Nikschavan
Nikschavan requested review from a team as code owners June 24, 2026 08:46
@Nikschavan
Nikschavan force-pushed the wooa7s-1495-port-jetpack-stats-module-authors branch from fe70374 to 017a5ac Compare June 24, 2026 08:47

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

Nice work on the port! @Nikschavan

A couple of things before merge:

  • This PR doesn't include a Storybook story — AGENTS.md asks every widget to ship one. Could you add it?
  • The description mentions a withDevtools prop and a picker-preview change, but I don't see those in the diff. Did they land separately on rebase? Mind updating the description to match?

Also left a couple of inline comments.

Comment thread projects/packages/premium-analytics/widgets/authors/widget.ts Outdated
@Nikschavan
Nikschavan force-pushed the wooa7s-1495-port-jetpack-stats-module-authors branch from afac0cf to 3f8c8f0 Compare June 25, 2026 06:07
@Nikschavan Nikschavan self-assigned this Jun 25, 2026
@@ -0,0 +1,109 @@
/**

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.

Maybe we could coordinate those changes into https://github.com/Automattic/jetpack/blob/trunk/projects/packages/premium-analytics/packages/data/src/processing/stats/top-authors.ts, so that there's no need for more mappings?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I was digging into whether buildTopAuthorsData could fold into the top-authors sanitizer like you suggested, and I'm not sure it can — wanted to run my thinking by you:

  • useStatsReport looks like it runs the primary and comparison as two separate queries, each sanitized on its own (use-report.ts#L64-L67), so the sanitizer only ever sees one period — which seems to mean the period-over-period shares/deltas have to be computed in the widget, where both primary.data and comparison.data are in hand. Does that match how you're reading it?
  • The shaping also pulls LeaderboardChartData/calculateDelta from widgets-toolkit, and since data is the leaf the toolkit depends on, moving it into processing/stats would invert that dependency. Is there a cleaner spot for it I might be missing?

For what it's worth, the current split seems to match the recently-merged widgets — sales-by-utm-channel composes useReportOrderAttribution + buildSalesByUtmData( primary.data ) in its render, and top-posts does the same — so I leaned toward following that pattern here.

That said, I think you're right that there's real duplication: authors, top-posts, locations, and sales-by-utm all hand-roll the rows→LeaderboardChartData step. Would a shared buildLeaderboardData in widgets-toolkit make sense as a follow-up across all of them? I'd lean toward a separate PR rather than scope-creeping this one — but let me know your thoughts, happy to go a different way if you'd prefer.

Comment thread projects/packages/premium-analytics/widgets/authors/render.tsx Outdated
Comment thread projects/packages/premium-analytics/widgets/authors/render.tsx Outdated
@Nikschavan
Nikschavan force-pushed the wooa7s-1495-port-jetpack-stats-module-authors branch 2 times, most recently from 4bcf910 to e512898 Compare June 29, 2026 07:43
Nikschavan and others added 12 commits June 30, 2026 09:26
- widgets-toolkit: add AuthorsWidget (consumes the useStatsTopAuthors hook
  from the data package) and the buildTopAuthorsData leaderboard helper

- widgets/authors: register the jpa/authors dashboard widget; render.tsx
  fetches via the existing /jetpack-premium-analytics/v1/proxy Stats proxy
  (v1.1 stats/top-authors) and wraps the widget in WidgetRoot

- query client provider: add withDevtools prop so per-widget providers don't
  each render React Query devtools

- widget-dashboard: inset picker previews with padding instead of scaling so
  content clears the selection checkbox
- Forward `max` to the Stats top-authors query so the API limit matches
  the rendered leaderboard instead of relying on the endpoint default.
- Dedup and align authors by stable `author_id` (falling back to the
  display label) so distinct authors sharing a name aren't merged.
- Translate the "Untracked authors" label in a single place: the
  processing layer leaves an empty label and the widget supplies the
  translated fallback.
- Drop `StatsProxyParams`' index signature from `StatsReportParams` so
  `reportParams` no longer needs casting; make `useStatsReport` generic
  so `useStatsTopAuthors` returns a typed report (removes data casts).
- Declare the widget's runtime deps (widgets-toolkit, @wordpress/i18n,
  @wordpress/icons) in its package.json.
- Forward `setError` from the render entry to WidgetRoot.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
The empty-state icon was dropped earlier but the import was left behind,
failing ESLint and the exclude-list check in CI.
- Declare presentation (framed) in widget.json.
- Type render props via WidgetRenderProps<AuthorsAttributes> from
  @wordpress/widget-primitives and default attributes to {}.
- Add @wordpress/widget-primitives as a devDependency (type-only import,
  erased at build, keeps runtime deps host-agnostic).
Relocate buildTopAuthorsData and the authors render logic out of the shared widgets-toolkit package into widgets/authors, colocating the widget's tests. Wire the widget to its own dependencies, change the max authors field to integer, and stub CSS imports in jest so widgets-toolkit CSS doesn't get parsed as JS.
Extract AuthorsLeaderboard as an exported presentational component taking pre-built rows, and rename the data-connected wrapper to AuthorsReport so Storybook can exercise loading/empty/populated states. Trust the Stats API's server-side ranking and limiting: replace summarizeAuthors' cross-interval aggregation and sorting with a flat toAuthorItems, dropping the maxEntries param. Update tests accordingly.
…rebase

Trunk now exports formatLegendLabels from the widgets-toolkit barrel, so the
copy this branch added is redundant; keep trunk's and remove the duplicate to
avoid a TS2300 duplicate-identifier error.
Removes the hardcoded all-time default report params and the date-fns dependency, so the widget reads report params from the dashboard date range through WidgetRoot context like the other Stats widgets.
@Nikschavan
Nikschavan force-pushed the wooa7s-1495-port-jetpack-stats-module-authors branch from cbf6bb2 to 9be7729 Compare June 30, 2026 03:57

@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 @Nikschavan LGTM.

I left additional comments but non-blocking.

The rest is all fine as follow-ups:

  • The shared buildLeaderboardData in widgets-toolkit you've floated. Agree a separate PR is the right scope, not this one.
  • Storybook: the story only exercises the presentational AuthorsLeaderboard with fixtures right now. Aligning it with search-terms (a WidgetDashboardWithWidget story + a top-authors mock wired into routeStatsReport()) would cover the real data path — can land separately.

return typeof author.label === 'string' && author.label
? author.label
: __( 'Untracked authors', 'jetpack-premium-analytics' );
}

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.

Claude code found this is a dead code since sanitizeStatsTopAuthorsResponse already substitutes item.name || 'Untracked Authors':

'Untracked Authors'` is untranslated there though.


type AuthorsAttributes = NonNullable< ComponentProps< typeof WidgetRoot >[ 'attributes' ] > & {
max?: string | number;
};

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: Could AuthorsAttributes live in widget.ts instead?

AGENTS.md asks for the widget's own attribute shape to be declared and exported once from widget.ts and imported here

@Nikschavan

Copy link
Copy Markdown
Member Author

Thanks for the feedback, @chihsuan! I will merge this PR as-is and address your suggestions in a follow-up to keep the scope focused and expedite the release.

@Nikschavan
Nikschavan merged commit 34c86bf into trunk Jun 30, 2026
132 of 135 checks passed
@Nikschavan
Nikschavan deleted the wooa7s-1495-port-jetpack-stats-module-authors branch June 30, 2026 10:45
@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 Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. [Status] In Progress labels Jun 30, 2026
@Nikschavan

Copy link
Copy Markdown
Member Author

@chihsuan - Follow up feedback from this PR was addressed in - #50078

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.

4 participants