Skip to content

feat(insights): conversion source-mix + time-to-convert distributions#373

Draft
dkoo wants to merge 10 commits into
feat/insights-rsmfrom
feat/insights-conversion-source-mix-and-distributions
Draft

feat(insights): conversion source-mix + time-to-convert distributions#373
dkoo wants to merge 10 commits into
feat/insights-rsmfrom
feat/insights-conversion-source-mix-and-distributions

Conversation

@dkoo

@dkoo dkoo commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

All Submissions:

Changes proposed in this Pull Request:

Brings five Insights → Conversion Journey (Tab 3) metrics from "coming soon" to live data, built on a WP/Woo identity spine with BigQuery supplying only the conversion source (gate / prompt / direct), attached to each local record by a timestamp-window match — no GA cookie is ever cast to a Woo customer ID.

  • 3.2 / 3.3 Source mix (new subscribers / new donors) — remediates the production bug where these returned ~0. Counts now come from Woo conversions in the window; each conversion's source is matched to the BigQuery checkout-exposure event just before it.
  • 4.2 / 4.3 Time to subscribe / donate — cumulative "what share converted by day N" curves, split by source. Registration→conversion lag comes from Woo; the per-source split comes from a BigQuery registration-source lookup behind a cheap presence probe. When that source layer is absent or unavailable, the curve still renders (unsegmented) instead of disappearing.
  • 4.4 Subscriber → donor lag — a pure-Woo cumulative curve of how long subscribers take to also donate, hidden until at least 50 readers have done both.

4.2 / 4.3 / 4.4 always use all available history regardless of the selected date range (a caption now says so on each); 3.2 / 3.3 follow the dashboard date range. This is primarily a backend change plus that one caption — the chart components and response types were already in place.

Part of NPPD-1692.

How to test the changes in this Pull Request:

  1. Check out this branch and run the PHP unit tests from plugins/newspack-plugin: n test-php --filter Test_Conversion_Metric (expect 87 passing) and n test-php --group insights (no regressions).
  2. Run the JS tests: npm test -- HowLongConversionsTakeSection — the new test asserts the all-history caption renders on exactly the three snapshot charts (4.2 / 4.3 / 4.4), not on 4.1.
  3. Lint on the host: from plugins/newspack-plugin, ./vendor/bin/phpcs includes/wizards/insights/storage includes/wizards/insights/metrics and npm run lint:js -- src/wizards/insights/tabs/conversion/HowLongConversionsTakeSection.tsx — expect no errors.
  4. Confirm graceful degradation logic: the time-to-convert curves fall back to a single unsegmented (all-"direct") series when the BigQuery presence probe returns zero or the source query errors (covered by the *_probe_zero_all_direct and *_registrations_error_all_direct unit tests).
  5. Live verification (required before this ships, after the hub deploys): on a real Audience site (e.g. blockclub), open the Conversion Journey tab and confirm 3.2 / 3.3 show non-zero, sensible source splits (vs the current ~0), the 4.2 / 4.3 / 4.4 curves populate, and 4.4 stays hidden below 50 cross-converters. The local dev site cannot exercise the hub BigQuery queries (it loads newspack-manager-client, not the hub).

Other information:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your changes, as applicable?
  • Have you successfully run tests with your changes locally?

Notes for reviewers: BigQuery event_timestamp is microseconds — converted to seconds before timestamp matching. Storage epochs are built in PHP from UTC datetime strings (not SQL UNIX_TIMESTAMP, which would use the session timezone). The new rows_to_records / rows_to_lags helpers are duplicated per storage class to match the existing self-contained convention (no shared base; id_list/fmt are likewise duplicated). Woo_Order_Resolver is intentionally left in place — it still backs the deferred influenced metrics and is removed in a later PR. Woo SQL bodies are live-verified rather than unit-tested (no WooCommerce tables in the PHPUnit bootstrap; Phase A precedent).

dkoo and others added 10 commits June 19, 2026 15:41
…source-mix

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…on) for Tab 3

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rewrite the three pre-existing source-mix tests that asserted the old
Woo_Order_Resolver bucketing behaviour against the new Source_Matcher
timestamp-proximity attribution path:

- test_source_mix_subscribers_returns_populated_slices_on_success:
  injects Subscribers_Metric returning 3 records; BQ supplies a gate
  event and a prompt event timed within 1800 s of two records; the third
  is unmatched → direct. Asserts gate=1, prompt=1, direct=1, total=3,
  pct=1/3 each.

- test_source_mix_subscribers_guards_zero_total: the zero-total with
  non-empty records is unreachable under Source_Matcher (every record
  gets exactly one source). Replaced with the equivalent guard: a single
  record with no matching event goes to direct; gate/prompt buckets have
  count=0 and must produce pct=0.0 (no division error via $safe guard).

- test_source_mix_donors_returns_populated_slices_on_success: mirrors
  the subscribers test using Donors_Metric and the donors query name.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… probe gate

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ility gate

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pendent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dkoo dkoo changed the title feat(insights): conversion source-mix remediation + time-to-convert distributions feat(insights): conversion source-mix + time-to-convert distributions Jun 19, 2026
@dkoo dkoo self-assigned this Jun 19, 2026
@dkoo dkoo requested a review from Copilot June 19, 2026 23:19

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

This PR wires several Insights → Conversion Journey (Tab 3) metrics to live data by using WooCommerce as the identity spine and attaching BigQuery-derived “source” (gate/prompt/direct) via timestamp-window matching, plus adds a UI caption clarifying which charts ignore the selected date range.

Changes:

  • Update Conversion_Metric to compute subscriber/donor source-mix from Woo conversion records, attributing source via timestamp proximity to BigQuery events.
  • Add new storage + metric-layer methods for “windowed conversion records” and “all-history conversion lags” needed by the new distributions.
  • Add UI captioning for snapshot (all-history) charts and expand PHP/JS test coverage for the new behavior.

Reviewed changes

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

Show a summary per file
File Description
plugins/newspack-plugin/includes/wizards/insights/metrics/class-conversion-metric.php Implements live source-mix attribution via Woo records + timestamp-matched BigQuery events; adds time-to-convert distributions and subscriber→donor lag CDF logic.
plugins/newspack-plugin/includes/wizards/insights/storage/class-storage-interface.php Extends subscribers storage contract with windowed subscriber records + all-history subscription lag rows.
plugins/newspack-plugin/includes/wizards/insights/storage/class-donors-storage-interface.php Extends donors storage contract with windowed donor records, donation lag rows, and subscriber→donor lag rows.
plugins/newspack-plugin/includes/wizards/insights/storage/class-hpos-storage.php Implements new subscriber record + subscription lag queries for HPOS backend.
plugins/newspack-plugin/includes/wizards/insights/storage/class-legacy-storage.php Implements new subscriber record + subscription lag queries for legacy CPT backend.
plugins/newspack-plugin/includes/wizards/insights/storage/class-hpos-donors-storage.php Implements new donor record, donation lag, and subscriber→donor lag queries for HPOS backend.
plugins/newspack-plugin/includes/wizards/insights/storage/class-legacy-donors-storage.php Implements new donor record, donation lag, and subscriber→donor lag queries for legacy CPT backend.
plugins/newspack-plugin/includes/wizards/insights/metrics/class-subscribers-metric.php Exposes new storage methods (windowed subscriber records + subscription conversion lags) at the metric layer.
plugins/newspack-plugin/includes/wizards/insights/metrics/class-donors-metric.php Exposes new storage methods (windowed donor records + donation lags + subscriber→donor lags) at the metric layer.
plugins/newspack-plugin/includes/wizards/insights/fixtures/conversion-fixture.php Updates fixture shape to reflect that 4.4 is now implemented (“populated” but hidden in the fixture).
plugins/newspack-plugin/tests/unit-tests/insights/class-test-conversion-metric.php Updates/expands unit tests for new source-mix attribution + new time-to-convert / lag distributions.
plugins/newspack-plugin/tests/unit-tests/insights/class-test-conversion-journey-storage.php Adds delegation/contract tests for the new storage/metric methods.
plugins/newspack-plugin/src/wizards/insights/tabs/conversion/HowLongConversionsTakeSection.tsx Adds optional captions to the 4.2/4.3/4.4 “snapshot” charts indicating they ignore date range.
plugins/newspack-plugin/src/wizards/insights/tabs/conversion/HowLongConversionsTakeSection.test.tsx Adds a test asserting the snapshot caption appears on exactly the intended charts.

Comment on lines +883 to +887
private function compute_source_mix( string $query_name, array $records, DateTimeInterface $start, DateTimeInterface $end ): array {
$bq = $this->proxy->query( $query_name, $start, $end );
// Precedence intentional: a genuine proxy error must surface as the 'error' envelope, not be masked as 'empty'.
if ( is_wp_error( $bq ) ) {
return $this->error_collection( 'slices', $bq );
Comment on lines 865 to +868
$proxy = $this->createMock( BigQuery_Proxy_Client::class );
$proxy->method( 'query' )
->with( 'conversion_journey_source_mix_subscribers' )
->willReturn( $rows );

// Each bucket gets count_completed_orders called once; return 1 for gate
// (2 rows), 1 for prompt (1 row), 1 for direct (1 row).
$resolver = $this->createMock( Woo_Order_Resolver::class );
$resolver->method( 'count_completed_orders' )->willReturn( 1 );
->willReturn( $bq_rows );
Comment on lines 1005 to +1008
$proxy = $this->createMock( BigQuery_Proxy_Client::class );
$proxy->method( 'query' )
->with( 'conversion_journey_source_mix_donors' )
->willReturn( $rows );
->willReturn( $bq_rows );
Comment on lines +1962 to +1966
$proxy->method( 'query' )->willReturnCallback(
static function ( $query_name ) use ( $map ) {
return $map[ $query_name ] ?? [];
}
);
Comment on lines +2021 to +2028
$subs->method( 'get_new_subscriber_records_in_window' )->willReturn(
[
[
'customer_id' => 1,
'ts' => 1700000000,
],
]
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants