Skip to content

feat: track activation first-insert → first-save funnel#2866

Open
ineagu wants to merge 2 commits into
developmentfrom
feat/track-activation-first-save
Open

feat: track activation first-insert → first-save funnel#2866
ineagu wants to merge 2 commits into
developmentfrom
feat/track-activation-first-save

Conversation

@ineagu

@ineagu ineagu commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Outcome

We don't currently know how many people who add their first Otter block ever go on to actually save/publish a post with it. That first-insert → first-save step is the core activation moment for the plugin, and today it's a blind spot in our telemetry.

This adds two milestone events that let us compute that funnel:

  1. first-insert — the first time an Otter-category block exists in the editor.
  2. first-save — the first non-autosave publish/save of a post containing at least one Otter block, recorded once per site.

With both in hand we can answer: "What share of sites that try an Otter block reach the point of saving content with it, and how rich is that content?" That informs onboarding decisions — where to invest in nudges, defaults, or guidance if there's a meaningful drop-off between trying a block and committing to it.

What changed

  • inc/plugins/class-options-settings.php — registers a new boolean site option otter_activation_first_save (default false, show_in_rest) used as the per-site "already fired" marker for the first-save milestone, so it persists across browsers/devices rather than re-firing per session.
  • src/blocks/plugins/data-logging/index.js — adds the funnel logic to the existing editor subscribe loop:
    • countOtterBlocks() — recursively counts Otter (themeisle-blocks category) block instances, including inner blocks.
    • getDepthBucket() — buckets that count into a closed enum.
    • first-insert fires from the subscription as soon as an Otter block is present (once-per-session guard activationFirstInsertFired); intentionally not gated on save so the insert → save drop-off stays computable.
    • first-save fires on the first non-autosave publish/save of a post with at least one Otter block; reads/writes otter_activation_first_save via the Settings store (mirroring the otter_blocks_logger_data mechanism) so it fires exactly once per site. The state is guarded immediately before the async settings save so concurrent subscribe ticks can't double-fire.

Telemetry event shapes added (all via the existing window.oTrk?.set(...) accumulator):

// first-insert (once per session)
{ feature: 'activation', featureComponent: 'first-insert',      featureValue: 'true' }

// first-save (once per site)
{ feature: 'activation', featureComponent: 'first-save',        featureValue: 'true' }

// first-save content depth (once per site, alongside first-save)
{ feature: 'activation', featureComponent: 'first-save-depth',  featureValue: '1' | '2-3' | '4-10' | '10+' }

Compliance

  • Non-PII. The events carry no post content, titles, URLs, user identifiers, or block attributes — only the milestone flag ('true') and a bucketed block-count enum. featureValue for depth is restricted to a closed allowlist ('1', '2-3', '4-10', '10+'); raw counts are never sent.
  • Consent-gated, no bypass. Every milestone is gated on Boolean( window.themeisleGutenberg.canTrack ), which is derived server-side from the existing otter_blocks_logger_flag opt-in (inc/class-registration.php, inc/Tracker.php). No event sets { consent: true } (the oTrk per-event consent-override path) — there is no consent bypass in this change. If a site hasn't opted in, nothing fires.
  • The new otter_activation_first_save option only stores a boolean milestone marker; it holds no user data.

Test plan

On a site with usage tracking enabled (otter_blocks_logger_flag = yes, i.e. window.themeisleGutenberg.canTrack === true):

  1. Open a new post and insert any Otter block (e.g. Section, Button, Form). Confirm an activation / first-insert / true event is accumulated on oTrk. Insert more Otter blocks in the same session and confirm it does not fire again (once-per-session guard).
  2. Publish/save the post. Confirm activation / first-save / true plus activation / first-save-depth / <bucket> fire, that the bucket matches the number of Otter blocks present (1 → '1', e.g. 5 → '4-10'), and that otter_activation_first_save flips to true in Settings (/wp-json/wp/v2/settings).
  3. Reload, edit, and save again (even from a different browser): confirm first-save does not re-fire, since the marker is persisted per-site.
  4. Autosaves should not trigger first-save (guarded by ! isAutoSaving).
  5. Disable tracking and repeat: confirm no activation events fire.

Events can be observed on the window.oTrk accumulator in the editor console before flush, and downstream where the other oTrk/tiTrk events land (the /tracking/events endpoint → Metabase).

Related

Part of the telemetry-expansion roadmap, following the established data-logging pattern from PR #2862 (block add/remove tracking). Reuses the same oTrk accumulator and otter_blocks_logger_flag consent gate, and the per-site "fire once" mechanism mirrors the existing otter_blocks_logger_data option.

🤖 Generated with Claude Code

Adds first-insert (once/session) and first-save (once/site, persisted via a new boolean option) activation milestones + a bucketed depth signal. Consent-gated, non-PII.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@pirate-bot

pirate-bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Bundle Size Diff

Package Old Size New Size Diff
Animations 178.24 KB 178.24 KB 0 B (0.00%)
Blocks 1.5 MB 1.5 MB 1.18 KB (0.08%)
CSS 7.87 KB 7.87 KB 0 B (0.00%)
Dashboard 108.48 KB 108.48 KB 0 B (0.00%)
Onboarding 68.14 KB 68.14 KB 0 B (0.00%)
Export Import 4.7 KB 4.7 KB 0 B (0.00%)
Pro 320.08 KB 320.08 KB 0 B (0.00%)

@pirate-bot

pirate-bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Plugin build for 01834b7 is ready 🛎️!

@pirate-bot

pirate-bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

E2E Tests

Playwright Test Status: See serial and parallel matrix jobs

Performance Results serverResponse: {"q25":391.6,"q50":398.95,"q75":422.9,"cnt":10}, firstPaint: {"q25":508.5,"q50":605.35,"q75":675.2,"cnt":10}, domContentLoaded: {"q25":3308.9,"q50":3322.85,"q75":3335.9,"cnt":10}, loaded: {"q25":3309.6,"q50":3323.35,"q75":3336.4,"cnt":10}, firstContentfulPaint: {"q25":8931.2,"q50":9000.75,"q75":9044.5,"cnt":10}, firstBlock: {"q25":13506.8,"q50":13630.05,"q75":13661.2,"cnt":10}, type: {"q25":22.15,"q50":23.44,"q75":24.29,"cnt":10}, typeWithoutInspector: {"q25":19.83,"q50":20.09,"q75":22.23,"cnt":10}, typeWithTopToolbar: {"q25":27.43,"q50":28.61,"q75":30.98,"cnt":10}, typeContainer: {"q25":13.13,"q50":13.46,"q75":14.31,"cnt":10}, focus: {"q25":118.91,"q50":124.87,"q75":130.21,"cnt":10}, inserterOpen: {"q25":39.27,"q50":40.38,"q75":41.77,"cnt":10}, inserterSearch: {"q25":12.72,"q50":13.01,"q75":13.35,"cnt":10}, inserterHover: {"q25":5.12,"q50":5.31,"q75":5.47,"cnt":20}, loadPatterns: {"q25":1469.67,"q50":1478.47,"q75":1497.4,"cnt":10}, listViewOpen: {"q25":219.85,"q50":233.66,"q75":237.26,"cnt":10}

- first-insert is now once-per-site (new otter_activation_first_insert option) and fires only on a real insertion (count exceeds the load-time baseline), not on opening existing content
- treat established sites (existing otter_blocks_logger_data) as already-activated so long-time users aren't counted
- gate both milestones on settings-loaded to avoid a fast-publish double-fire; add .catch on the persistence writes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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