Skip to content

Premium Analytics: Show months with no subscriber data as a gap - #52523

Merged
adamwoodnz merged 13 commits into
trunkfrom
wooa7s-2181-premium-analytics-stop-turning-a-missing-subscriber-count
Sep 23, 2026
Merged

adamwoodnz merged 13 commits into
trunkfrom
wooa7s-2181-premium-analytics-stop-turning-a-missing-subscriber-count

Conversation

@adamwoodnz

@adamwoodnz adamwoodnz commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Fixes WOOA7S-2181

Why

The Subscriber summary drew a zero for every month before a site launched or turned on subscriptions. A site launched in April, viewed over the last 12 months, showed zero bars or a flat zero line from October to March, as if it had been open to subscribers and gained none. Those months are now empty, and hovering one reads "No data".

Its chart dependencies are all merged: #52522 (CHARTS-281, the bar chart) and #52580 (CHARTS-284, the line chart) teach both charts to draw a null as a gap, and #52588 (CHARTS-283) keeps a small whole-number axis from repeating its labels.

Proposed changes

  • The widget keeps the API's null. toPoints no longer turns a missing subscribers or subscribers_paid into 0, for bar and line mode alike. The headline figure still reads the latest point, falling back to 0.
  • The toolkit's chart point accepts null. MetricTabDatum.value is now number | null. Nothing downstream needed changing: the chart series types already accept null.
  • The toolkit tooltip reads a missing value as "No data". ChartTooltip typed its value as a number and handed it to MetricValue, and AbbreviatedValue formats null to an empty string, so the row printed no value at all. It now prints a translated "No data", as the charts package's own tooltip does. A genuine 0 still formats as 0.
  • The value-first tooltip reads "No data for Subscribers". The comparative charts moved to Premium-analytics: read chart tooltip rows as value, metric, then date #52527's inline layout, which writes the value into the label, so the row lost its value column. ChartTooltip now hands getLabel a null for a missing reading, and formatTooltipPointLabel gives it its own translatable sentence: No data for %1$s · %2$s.

Screenshots

A site that launched in April, over the last 12 months. Both modes draw the count on the same 0 to 1 axis.

Bar mode: a month before launch reads "No data for Subscribers"

Bar mode, February tooltip reads No data for Subscribers

Bar mode: a month after launch keeps its value

Bar mode, June tooltip reads 1

Line mode: the line starts at April, and February reads "No data for Subscribers"

Line mode, February tooltip reads No data for Subscribers

Line mode: a month after launch keeps its value

Line mode, May tooltip reads 1

"1 Subscribers" is the value-first label's missing plural, tracked in UNI-813.

To do before ready

  • Check line mode in wp-admin: the line starts at the launch month, and hovering an earlier month reads "No data".
  • Tests: toPoints keeps a null and still reads a real 0, and TooltipRow prints "No data" for null and a formatted 0 for 0, and a null in a raw matrix row survives sanitizeStatsSubscribersResponse.
  • Screenshots, of both modes.

Related product discussion/links

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

No.

Testing instructions

On a site that launched or turned on subscriptions inside the selected window (for example, in April):

  • Open the Subscriber summary, choose Last 12 months, and switch to bar mode.
  • The axis still runs the whole year, but there are no bars before the launch month.
  • Hover a month before launch: the tooltip reads No data, not 0 or an empty row.
  • A month with a real zero, if the site has one, still shows its zero bar.
  • Switch to line mode: the line starts at the launch month, and hovering an earlier month reads No data.

Verified so far:

  • PA typecheck: clean. ESLint: clean on every changed file.
  • Tests for the subscriber chart, chart tooltip, metric tabs chart, the comparative bar and line charts, and the stats data processing: 42 suites, 356 tests passing. The new widget and tooltip tests fail with the fix reverted.
  • Bar and line mode checked by hand in wp-admin, on a site launched in April, including the "No data" tooltip and a real value after launch.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GZqouXJVXUJX4ywMC8krzp

@github-actions

github-actions Bot commented Sep 21, 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!


Jetpack plugin:

The Jetpack plugin has different release cadences depending on the platform:

  • WordPress.com Simple releases happen as soon as you deploy your changes after merging this PR (PCYsg-Jjm-p2).
  • WoA releases happen weekly.
  • Releases to self-hosted sites happen monthly:
    • Scheduled release: October 6, 2026

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


Wpcomsh plugin:

  • Next scheduled release: Atomic deploys happen twice daily on weekdays (p9o2xV-2EN-p2)

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


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 Sep 21, 2026
@github-actions

github-actions Bot commented Sep 21, 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 wooa7s-2181-premium-analytics-stop-turning-a-missing-subscriber-count branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack wooa7s-2181-premium-analytics-stop-turning-a-missing-subscriber-count
bin/jetpack-downloader test jetpack-mu-wpcom-plugin wooa7s-2181-premium-analytics-stop-turning-a-missing-subscriber-count

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

@jp-launch-control

jp-launch-control Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

Code Coverage Summary

Coverage changed in 1 file.

File Coverage Δ% Δ Uncovered
projects/packages/premium-analytics/packages/widgets-toolkit/src/helpers/format-tooltip-point-label.ts 3/3 (100.00%) 0.00% 0 💚

Full summary · PHP report · JS report

@adamwoodnz adamwoodnz removed the [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. label Sep 21, 2026
@adamwoodnz
adamwoodnz force-pushed the wooa7s-2181-premium-analytics-stop-turning-a-missing-subscriber-count branch from 9e474a3 to 2b25e41 Compare September 21, 2026 22:14
@adamwoodnz
adamwoodnz changed the base branch from charts-281-bar-chart-cannot-render-a-bucket-with-no-data to charts-284-line-chart-cannot-render-a-bucket-with-no-data September 21, 2026 22:14
@adamwoodnz
adamwoodnz force-pushed the wooa7s-2181-premium-analytics-stop-turning-a-missing-subscriber-count branch from 2b25e41 to 747fadb Compare September 21, 2026 22:32
@adamwoodnz
adamwoodnz force-pushed the wooa7s-2181-premium-analytics-stop-turning-a-missing-subscriber-count branch from 747fadb to f068c59 Compare September 21, 2026 23:09
@adamwoodnz
adamwoodnz marked this pull request as ready for review September 22, 2026 01:37
@adamwoodnz
adamwoodnz requested a review from a team as a code owner September 22, 2026 01:37
@adamwoodnz adamwoodnz added [Status] Needs Review This PR is ready for review. DO NOT MERGE don't merge it! [Status] In Progress and removed [Status] In Progress [Status] Needs Review This PR is ready for review. labels Sep 22, 2026
@adamwoodnz

This comment was marked as resolved.

@adamwoodnz

This comment was marked as resolved.

@adamwoodnz

Copy link
Copy Markdown
Contributor Author

@claude please review this PR. Get the change set with a single gh pr diff 52523, then read only the changed source files it touches. Skip changelog entries and lockfiles. Use at most about 20 tool calls, and post your review before you reach that, even if it is incomplete.

@claude

claude Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @adamwoodnz's task in 2m 28s —— View job


Review

  • Get the change set (gh pr diff 52523)
  • Read the changed source files (plus the data-layer types and the normalizer the null has to survive)
  • Post review

The shape of the change is right, and the runtime half checks out: numericTimeSeriesRow in packages/data/src/processing/stats/time-series.ts:45-55 passes any value that is neither number nor string through untouched, so a null in a matrix row really does reach toPoints instead of being parsed to 0. Four things below, one of which I'd fix before merge.


1. The nullable contract lives in a cast, not in the type — use-subscribers-chart.ts:48-49

const subscribers = point.subscribers as number | null | undefined;
const paid = point.subscribers_paid as number | null | undefined;

Those casts exist because the data layer still says otherwise:

// packages/data/src/processing/stats/subscribers.ts:13-17
export type StatsSubscribersDataPoint = StatsNormalizedDataPoint & {
	value: number;
	subscribers?: number;
	subscribers_paid?: number;
};

Two costs. Every other consumer of StatsSubscribersDataPoint still believes those fields are numbers, so the next widget to read them repeats the discovery. And a cast isn't checked — if the field is later renamed or narrowed, the cast keeps compiling and the whole === null branch quietly becomes dead code, which is exactly the regression nobody would notice until a pre-launch month draws a zero bar again. Widen the two fields to number | null where they're declared and drop both casts; toPoints then reads as plain ?? logic with nothing asserted. Fix this →

2. A missing subscribers_paid key and an explicit null disagree — use-subscribers-chart.ts:56

paid === null ? null : Number( paid ?? 0 ) keeps an explicit null, but an absent key still becomes a real 0. So in the very window this PR is about, the Subscribers tab draws a gap while the Paid tab draws zero bars — if the endpoint omits the field rather than sending null for those buckets. If the endpoint always emits subscribers_paid alongside subscribers, this is moot and worth a word in the comment; otherwise ?? null makes the two shapes agree. (The subscribers line is different — its point.value fallback is pre-existing and serves responses that carry no subscribers field at all, so I'd leave it.)

3. The headline can still read a missing value as 0 — render.tsx:56

latest() now does accessor( last ) ?? 0. If the newest bucket is the missing one, the card reads 0 while the chart beside it draws a gap — the same contradiction the PR removes everywhere else, just relocated to the card. For a cumulative series the newest bucket is usually populated, so this is low-likelihood rather than wrong; but falling back to the last non-null point would close it without touching MetricTab.value (still number at metric-tabs-chart.tsx:69). Your call whether it's in scope.

4. The tooltip README is now stale — components/chart-tooltip/README.md:60,86-89

The prop table still documents getValue as (datum) => number, and the "Default Extractors" snippet still shows the old defaultGetValue returning number. Cheap to update in the same PR, and this package's own guidance treats these READMEs as documentation rather than commentary.


Notes, not asks

On the Codex P2 and your reply — your reasoning holds. A window that was all-zero classified as empty before this change and a window that is all-null classifies as empty after it, via the same isEmptyChartData; a window with at least one real reading was never empty and still isn't. So the set of non-interactive charts is unchanged by this PR, and the empty-state interactivity question really is a separate design call.

One adjacent gap the PR creates but can't reach. comparative-bar-chart.tsx:276 guards the re-attached previous-period row with if ( paired?.value != null ), so a comparison bucket whose value is null is dropped from the tooltip entirely rather than reading "No data". The subscribers widget passes no previous series, so nothing here hits it — but once MetricTabDatum.value is nullable, the first widget with a nullable comparison series will get silence where this PR promises "No data". Worth a follow-up issue rather than a change here.

Tests. Your to-do list has the right two cases. Given finding #1, I'd add a third: a fixture-level assertion that a null in a raw matrix row survives sanitizeStatsSubscribersResponse — that's the boundary the cast currently papers over, and it's the one that would break silently.

Everything else reads well — the 0 ?? null case is handled correctly in defaultGetValue, hasPaid's ( point.paid ?? 0 ) > 0 is right, and the two new JSDoc lines earn their space.
· branch wooa7s-2181-premium-analytics-stop-turning-a-missing-subscriber-count

@adamwoodnz
adamwoodnz force-pushed the wooa7s-2181-premium-analytics-stop-turning-a-missing-subscriber-count branch from daeb65a to 63eb0ea Compare September 22, 2026 02:26
@adamwoodnz
adamwoodnz force-pushed the wooa7s-2181-premium-analytics-stop-turning-a-missing-subscriber-count branch from b3cb37b to b9a845a Compare September 22, 2026 04:53
@adamwoodnz
adamwoodnz force-pushed the wooa7s-2181-premium-analytics-stop-turning-a-missing-subscriber-count branch 2 times, most recently from caebe46 to 47a128c Compare September 22, 2026 22:03
Base automatically changed from charts-283-value-axis-repeats-tick-labels-when-a-whole-number-range-is to trunk September 23, 2026 01:51
adamwoodnz and others added 13 commits September 23, 2026 13:51
The toolkit tooltip typed its value as a number and handed it straight to `MetricValue`, which formats null to an empty string, so a period with no reading printed a row with no value at all. It now prints "No data", as the charts package's own tooltip does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZqouXJVXUJX4ywMC8krzp
The API returns null for the periods before a site existed, and the widget turned each into 0, so a young site drew zero-height stubs across months it had no record for. The bar chart can now draw a null as a gap, so it gets the null. The line chart cannot yet and still gets 0 until CHARTS-284, which is why the choice is made per chart type.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZqouXJVXUJX4ywMC8krzp
The fix is visible wherever the dashboard ships, so the plugins that surface it carry it too, not only the package.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZqouXJVXUJX4ywMC8krzp
This PR only merges once CHARTS-284 teaches the line chart to draw a null, so there is no reason to keep coercing for it here. Line mode shows "Invalid data" until then. Dropping the per-chart-type split also lets the toolkit's chart point accept null directly, so the cast that hid the null from the type is gone, and the changelog entries now describe the whole chart.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZqouXJVXUJX4ywMC8krzp
The gap is the months before the site launched or turned on subscriptions, not the months before its first subscriber. A launched site with subscriptions on and no subscribers still reads a real zero.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DxvpmYsGipgDaUwR3zeVe6
The widget cast the two counts to number | null because the data layer still typed them as numbers. A cast is never checked, so a rename would have left the null branch compiling as dead code. Widen the type where it is declared and read the fields directly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DxvpmYsGipgDaUwR3zeVe6
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DxvpmYsGipgDaUwR3zeVe6
Pin the three places a null has to survive: the data layer passes it through instead of parsing it to zero, the widget charts it as a gap beside a real zero, and the tooltip reads it as No data while a zero still reads 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DxvpmYsGipgDaUwR3zeVe6
The row rendered "No data" in a plain span at the row's weight 400, so it read as a second label beside the bold values. Give the value class the face MetricValue uses at the sm size.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DxvpmYsGipgDaUwR3zeVe6
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DxvpmYsGipgDaUwR3zeVe6
The comparative line and bar charts moved to the inline layout, which
writes the value into the label through getLabel and never renders
the row's value. formatMetricValue gives an empty string for null, so
a month with no subscriber count read " Subscribers · March 1" with
no value at all. The tooltip now hands getLabel "No data" for a
missing reading, and the split row reads the same string from one
place.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T6rWwVGBVEcNmuR8kkLphs
No ChartTooltip story used the inline layout the comparative charts
render, and none carried a null value, so a reviewer could not see how
a bucket with no reading reads next to a real zero.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T6rWwVGBVEcNmuR8kkLphs
Spelling "No data" into the value slot of the value-first label read
"No data Subscribers · March 1". A missing reading needs its own
sentence with the metric as a parameter, so translators can place it.
The tooltip now hands getLabel null for a bucket with no reading, and
formatTooltipPointLabel turns that into "No data for Subscribers ·
March 1, 2026".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T6rWwVGBVEcNmuR8kkLphs
@adamwoodnz
adamwoodnz force-pushed the wooa7s-2181-premium-analytics-stop-turning-a-missing-subscriber-count branch from d161a40 to eeb4c66 Compare September 23, 2026 01:51
@adamwoodnz adamwoodnz added [Status] Ready to Merge Go ahead, you can push that green button! and removed DO NOT MERGE don't merge it! [Status] Needs Review This PR is ready for review. labels Sep 23, 2026
@adamwoodnz
adamwoodnz merged commit 649a837 into trunk Sep 23, 2026
85 checks passed
@adamwoodnz
adamwoodnz deleted the wooa7s-2181-premium-analytics-stop-turning-a-missing-subscriber-count branch September 23, 2026 22:04
@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] Ready to Merge Go ahead, you can push that green button! labels Sep 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Docs [Package] Premium Analytics [Plugin] Jetpack Issues about the Jetpack plugin. https://wordpress.org/plugins/jetpack/ [Plugin] Premium Analytics [Plugin] Wpcomsh [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