feat: add update-breakage observability (survivorship-free)#2864
Open
ineagu wants to merge 2 commits into
Open
feat: add update-breakage observability (survivorship-free)#2864ineagu wants to merge 2 commits into
ineagu wants to merge 2 commits into
Conversation
Server-side plugin/WP upgrade-transition events + editor block-render-error telemetry, gated by the existing otter_blocks_logger_flag consent flag. Non-PII: coarsened MAJOR.MINOR versions and Otter block-type slugs only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
Bundle Size Diff
|
Contributor
|
Plugin build for d0ee079 is ready 🛎️!
|
Contributor
E2E TestsPlaywright Test Status: See serial and parallel matrix jobs Performance ResultsserverResponse: {"q25":384.1,"q50":390.25,"q75":422.2,"cnt":10}, firstPaint: {"q25":469,"q50":524.3,"q75":552.7,"cnt":10}, domContentLoaded: {"q25":3252,"q50":3274.95,"q75":3316.7,"cnt":10}, loaded: {"q25":3252.5,"q50":3275.4,"q75":3317.3,"cnt":10}, firstContentfulPaint: {"q25":8778,"q50":8842.4,"q75":8857,"cnt":10}, firstBlock: {"q25":13376.1,"q50":13411.95,"q75":13515.1,"cnt":10}, type: {"q25":21.03,"q50":22.11,"q75":23.72,"cnt":10}, typeWithoutInspector: {"q25":19.12,"q50":19.55,"q75":20.72,"cnt":10}, typeWithTopToolbar: {"q25":29.13,"q50":30.33,"q75":31.96,"cnt":10}, typeContainer: {"q25":12.84,"q50":13.45,"q75":13.99,"cnt":10}, focus: {"q25":103.59,"q50":105.56,"q75":110.14,"cnt":10}, inserterOpen: {"q25":37.22,"q50":37.56,"q75":39.42,"cnt":10}, inserterSearch: {"q25":12.91,"q50":13.38,"q75":13.73,"cnt":10}, inserterHover: {"q25":5.03,"q50":5.23,"q75":5.62,"cnt":20}, loadPatterns: {"q25":1493.56,"q50":1525.5,"q75":1607.29,"cnt":10}, listViewOpen: {"q25":207.54,"q50":214.19,"q75":217.91,"cnt":10} |
- persist+flush the upgrade transition on a consented admin load (survives consent-off at upgrade; removes the blocking POST from the migration path; dedupes via delete after send) - block-health: gate on the block-editor store so Site Editor/FSE/widgets are covered; re-arm with a debounced check to catch late/async-resolved blocks; guard for oTrk + readiness without relying solely on the unstable editor-ready API - drop the two redundant conditions PHPStan flagged Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
0dc1352 to
d0ee079
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Outcome
When a site updates Otter (or WordPress) and a block stops rendering, the editor silently shows an invalid/missing block and the user just... leaves. Today we only learn about update breakage from the people who are unhappy enough to open a support ticket — a deeply survivorship-biased signal. We never see the cohort that hits a broken page and churns quietly.
This PR adds two lightweight, opt-in telemetry surfaces that answer a concrete product question:
"Which Otter block types break after an update, and across which version transitions?"
That lets us prioritize backward-compatibility fixes and deprecation handling for the blocks that actually break in the field, on the version jumps that actually happen — instead of guessing from the loud minority. It is "survivorship-free" because the editor-side check runs for every session that opens a post containing a broken Otter block, not only for users who complain.
What changed
inc/class-main.php— Inafter_update_migration(), when the stored DB version is older than the current plugin version (a real upgrade, not a fresh install — guarded by! empty( $db_version ) && '0' !== (string) $db_version), fire two server-side events viaTracker::track(). Adds a privatecoarsen_version()helper that reduces any version string toMAJOR.MINOR.Event shapes:
lifecycleplugin-upgrade"<fromMajorMinor>><toMajorMinor>"e.g.3.0>3.1lifecyclewp-at-upgrade"<wpMajorMinor>"e.g.6.8src/blocks/plugins/block-health/index.js(new) — Runs once per editor session after the editor is ready. Walks all blocks (including inner blocks), and flags an Otter block as broken when either: it isisValid === falseand its name is in thethemeisle-blockscategory, or it is acore/missingblock whoseoriginalNameis an Otter block. Reports only the block-type slug — never any attribute values or content.Event shapes:
block-healthrender-error<block-type-slug>e.g.themeisle-blocks/advanced-headingblock-healthpage-error"1"(emitted once if the page has any errored Otter block)src/blocks/plugins/registerPlugin.tsx— Registers the newblock-healthplugin alongside the existingdata-loggingimport.Compliance
MAJOR.MINORversion numbers and Otter block-type slugs. Block-type slugs come from registered block types in thethemeisle-blockscategory, so they are effectively an allowlist of our own block names — a user cannot inject an arbitrary string intofeatureValue. Versions are bucketed toMAJOR.MINORviacoarsen_version(), which both reduces cardinality and avoids exposing a precise build fingerprint.otter_blocks_logger_flagopt-in:Tracker::track()short-circuits unlessTracker::has_consent()(which readsotter_blocks_logger_flag) is true. We callTracker::track( $events )with nooptionsargument, so we do not passhasConsent(the documented bypass) — a user who has not opted in sends nothing.window.oTrk?.set( ... )and no{ consent: true }option. Unlike the AI-content paths (which intentionally pass{ consent: true }), block-health respectstiTrk's consent state, which is itself derived fromotter_blocks_logger_flag(surfaced to the editor ascanTrack).window.themeisleGutenberg?.isBlockEditorand a livecore/editorstore).Test plan
Server-side (
lifecycle):otter_blocks_logger_flag=yes.wp option update themeisle_blocks_db_version 3.0.5.after_update_migration()runs, or call it directly). With currentOTTER_BLOCKS_VERSIONyou should see two outbound POSTs tohttps://api.themeisle.com/tracking/events: alifecycle/plugin-upgradeevent withfeatureValuelike3.0>3.1, and alifecycle/wp-at-upgradeevent with the coarsened WP version.themeisle_blocks_db_versionto0(or empty) and re-run — nolifecycleevents fire (fresh-install guard).otter_blocks_logger_flagoff and repeat step 3 —Tracker::track()returns early, nothing is sent.Client-side (
block-health):canTrackis true in the editor).core/missing.block-health/render-errorevent whosefeatureValueis the affected block slug, plus a singleblock-health/page-errorevent withfeatureValue: "1".block-healthevents fire. Toggle tracking off — no events fire.Aggregated events are visible downstream in the tracking pipeline (api.themeisle.com → Metabase), the same place the existing Otter usage events land.
Related
Part of the telemetry-expansion roadmap. Follows the established data-logging / usage-tracking pattern introduced in the block add/remove work (PR #2862), reusing the same
oTrk/tiTrkaccumulator on the JS side and the sameTracker+otter_blocks_logger_flagconsent gate on the PHP side. Sibling to PR #2862; no schema or pipeline changes are required to ingest these events.🤖 Generated with Claude Code