Skip to content

feat(analytics-controller): add optional event fragments for journey-scoped properties - #10055

Open
gauthierpetetin wants to merge 11 commits into
mainfrom
feat/analytics-controller-event-fragments
Open

feat(analytics-controller): add optional event fragments for journey-scoped properties#10055
gauthierpetetin wants to merge 11 commits into
mainfrom
feat/analytics-controller-event-fragments

Conversation

@gauthierpetetin

@gauthierpetetin gauthierpetetin commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Explanation

Clients currently re-derive the same analytics properties for every event in a signature or transaction confirmation. That duplication is easy to get wrong and hard to keep consistent as more UI pieces contribute to the same journey.

This adds optional event fragments to AnalyticsController, disabled by default via isEventFragmentsEnabled. A fragment is a persisted bag of properties and sensitiveProperties that any part of a client can write to while a journey is in progress:

  • Funnel. Declare initialEvent, successEvent, and failureEvent. Creation can emit the initial event, and finalizeEventFragment emits success or failure.
  • Property bag. Declare no event names. The client reads the fragment back with getEventFragmentById when it emits its own event.

Emission goes through trackEvent, so consent gating, anonymous event splitting, the pre-consent queue, and geolocation enrichment all apply. Accumulation uses that same consent gate: fragments are only stored while the user is opted in, or while they are undecided and the pre-consent queue is enabled. eventFragments is persisted but excluded from state logs, debug snapshots, and UI, matching the other analytics queues.

This is not a breaking change. Clients must opt in with the constructor flag.

Here's a draft PR on Extension repo where this code can be tested: MetaMask/metamask-extension#45961

References

Contributes to: https://github.com/MetaMask/MetaMask-planning/issues/7582

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Made with Cursor

…scoped properties

Clients currently re-derive the same analytics properties for every event in a
signature or transaction flow. Fragments let those properties accumulate once,
with consent, persistence, and emission going through the existing trackEvent path.

Co-authored-by: Cursor <cursoragent@cursor.com>
@gauthierpetetin
gauthierpetetin requested review from a team as code owners September 1, 2026 17:56
@cursor

cursor Bot commented Sep 1, 2026

Copy link
Copy Markdown

Current version of PR was reviewed by /review-bugbot on Sep 1, 19:46 GMT+2. It flagged 0 findings.

Bugbot on commit 9b468c3 is skipped.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds an optional “event fragments” feature to @metamask/analytics-controller, allowing clients (when opted-in via isEventFragmentsEnabled) to persist and update journey-scoped analytics property bags and optionally emit initial/success/failure events through the existing trackEvent pipeline and consent gating.

Changes:

  • Introduces AnalyticsEventFragment* types and exports them from the package public API.
  • Extends AnalyticsController state, metadata, messenger methods, and implementation to support fragment lifecycle (create/upsert/update/get/delete/finalize) with consent-aware reconciliation on init.
  • Adds selectors + comprehensive tests, and documents the feature in the README/changelog.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/analytics-controller/src/selectors.ts Adds selectors for reading all fragments and a fragment by ID.
packages/analytics-controller/src/selectors.test.ts Adds unit tests for the new selectors.
packages/analytics-controller/src/index.ts Exposes event fragment types and new controller method action types from the public entrypoint.
packages/analytics-controller/src/EventFragment.types.ts Defines the event fragment data model and option/payload types.
packages/analytics-controller/src/AnalyticsController.ts Implements fragment storage, reconciliation on init, consent gating integration, and lifecycle methods.
packages/analytics-controller/src/AnalyticsController.test.ts Adds extensive tests covering fragment behavior, consent gating, persistence, and messenger exposure.
packages/analytics-controller/src/AnalyticsController-method-action-types.ts Adds messenger action type definitions for fragment lifecycle methods.
packages/analytics-controller/README.md Documents the new feature and its usage patterns.
packages/analytics-controller/CHANGELOG.md Adds an Unreleased entry describing the new feature.
Suppressed comments (3)

packages/analytics-controller/src/AnalyticsController.ts:541

  • mergeEventFragmentContext returns { ...base, ...override }, but base/override are AnalyticsContext | undefined. Coalesce to {} before spreading to avoid TypeScript errors and make the intent explicit.
  return { ...base, ...override };

packages/analytics-controller/src/AnalyticsController-method-action-types.ts:101

  • The getEventFragmentById action-type JSDoc omits the consent gate, but the handler returns undefined when capture is not allowed (opted out, or undecided without pre-consent queue). Align the docs with the implementation to avoid consumer confusion.
 * @returns The fragment, or `undefined` when no fragment has that ID or the
 * event fragments feature is disabled.
 */

packages/analytics-controller/src/AnalyticsController.ts:1629

  • finalizeEventFragment is a logged no-op when the feature is disabled or consent gating disallows capture, so the @throws doc should note that a missing fragment will not throw in those ignored-call states.
   * @throws Error if no fragment has that ID.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/analytics-controller/src/selectors.ts Outdated
Comment thread packages/analytics-controller/CHANGELOG.md Outdated
Comment thread packages/analytics-controller/src/AnalyticsController-method-action-types.ts Outdated
Comment thread packages/analytics-controller/src/AnalyticsController.ts
Comment thread packages/analytics-controller/src/AnalyticsController.ts Outdated
Comment thread packages/analytics-controller/src/AnalyticsController.ts Outdated
…ngelog to PR

Regenerate AnalyticsController method action types after JSDoc updates and
add the PR link required by the changelog CI check.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread packages/analytics-controller/src/AnalyticsController.ts
gauthierpetetin and others added 5 commits September 1, 2026 20:21
Keep a single event fragments bullet with the PR link and remove nested
detail bullets from the Unreleased section.

Co-authored-by: Cursor <cursoragent@cursor.com>
Prevent accidental mutation of the shared empty record returned by
selectEventFragments when state has no fragments yet.

Co-authored-by: Cursor <cursoragent@cursor.com>
…preading

Default omitted properties, sensitiveProperties, and context to empty
objects so fragment merge and create paths stay type-safe.

Co-authored-by: Cursor <cursoragent@cursor.com>
Document that updateEventFragment and finalizeEventFragment only throw for
a missing fragment when the call is not ignored by consent or feature flags.

Co-authored-by: Cursor <cursoragent@cursor.com>
Snapshot fragment IDs before awaited init work so reconciliation drops only
stale non-persistent leftovers, not in-flight journeys started while init runs.

Co-authored-by: Cursor <cursoragent@cursor.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1b12a93. Configure here.

Comment thread packages/analytics-controller/src/AnalyticsController.ts
gauthierpetetin and others added 2 commits September 2, 2026 06:27
Snapshot fragment createdAt at init start so purge can distinguish stale
leftovers from fragments replaced via createEventFragment while init runs.

Co-authored-by: Cursor <cursoragent@cursor.com>
Apply Prettier formatting for lint:misc:check and add tests covering
fragment context preservation and AnalyticsPlatformAdapterSetupError.

Co-authored-by: Cursor <cursoragent@cursor.com>
@gauthierpetetin

Copy link
Copy Markdown
Contributor Author

@metamaskbot publish-preview

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Preview builds have been published. Learn how to use preview builds in other projects.

Expand for full list of packages and versions.
@metamask-previews/account-tree-controller@8.0.0-preview-0d04fb622
@metamask-previews/accounts-controller@39.1.1-preview-0d04fb622
@metamask-previews/address-book-controller@7.1.2-preview-0d04fb622
@metamask-previews/ai-controllers@1.0.0-preview-0d04fb622
@metamask-previews/analytics-controller@2.0.0-preview-0d04fb622
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-0d04fb622
@metamask-previews/announcement-controller@8.1.0-preview-0d04fb622
@metamask-previews/app-metadata-controller@2.0.1-preview-0d04fb622
@metamask-previews/approval-controller@9.0.2-preview-0d04fb622
@metamask-previews/assets-controller@14.0.3-preview-0d04fb622
@metamask-previews/assets-controllers@111.1.3-preview-0d04fb622
@metamask-previews/authenticated-user-storage@3.0.2-preview-0d04fb622
@metamask-previews/base-controller@9.1.0-preview-0d04fb622
@metamask-previews/base-data-service@1.0.0-preview-0d04fb622
@metamask-previews/bitcoin-regtest-up@1.0.0-preview-0d04fb622
@metamask-previews/bridge-controller@80.1.1-preview-0d04fb622
@metamask-previews/bridge-status-controller@75.4.0-preview-0d04fb622
@metamask-previews/build-utils@3.0.4-preview-0d04fb622
@metamask-previews/chain-agnostic-permission@1.7.0-preview-0d04fb622
@metamask-previews/chomp-api-service@4.0.1-preview-0d04fb622
@metamask-previews/claims-controller@0.6.1-preview-0d04fb622
@metamask-previews/client-controller@1.0.1-preview-0d04fb622
@metamask-previews/client-utils@2.1.1-preview-0d04fb622
@metamask-previews/compliance-controller@2.1.0-preview-0d04fb622
@metamask-previews/composable-controller@12.0.1-preview-0d04fb622
@metamask-previews/config-registry-controller@3.1.0-preview-0d04fb622
@metamask-previews/connectivity-controller@0.3.0-preview-0d04fb622
@metamask-previews/controller-utils@12.3.0-preview-0d04fb622
@metamask-previews/core-backend@9.0.0-preview-0d04fb622
@metamask-previews/delegation-controller@3.0.2-preview-0d04fb622
@metamask-previews/earn-controller@12.2.6-preview-0d04fb622
@metamask-previews/eip-5792-middleware@3.0.5-preview-0d04fb622
@metamask-previews/eip-7702-internal-rpc-middleware@0.1.1-preview-0d04fb622
@metamask-previews/eip1193-permission-middleware@2.0.1-preview-0d04fb622
@metamask-previews/eth-block-tracker@15.0.1-preview-0d04fb622
@metamask-previews/eth-json-rpc-middleware@24.0.2-preview-0d04fb622
@metamask-previews/eth-json-rpc-provider@6.0.1-preview-0d04fb622
@metamask-previews/foundryup@1.0.1-preview-0d04fb622
@metamask-previews/gas-fee-controller@26.3.2-preview-0d04fb622
@metamask-previews/gator-permissions-controller@5.0.2-preview-0d04fb622
@metamask-previews/geolocation-controller@1.0.0-preview-0d04fb622
@metamask-previews/java-tron-up@1.0.0-preview-0d04fb622
@metamask-previews/json-rpc-engine@10.5.0-preview-0d04fb622
@metamask-previews/json-rpc-middleware-stream@8.0.8-preview-0d04fb622
@metamask-previews/keyring-controller@27.1.1-preview-0d04fb622
@metamask-previews/kyc-controller@0.0.0-preview-0d04fb622
@metamask-previews/local-node-utils@1.0.0-preview-0d04fb622
@metamask-previews/logging-controller@9.0.0-preview-0d04fb622
@metamask-previews/message-manager@14.1.2-preview-0d04fb622
@metamask-previews/messenger@2.0.0-preview-0d04fb622
@metamask-previews/messenger-cli@0.2.0-preview-0d04fb622
@metamask-previews/money-account-api-data-service@0.4.1-preview-0d04fb622
@metamask-previews/money-account-balance-service@2.4.3-preview-0d04fb622
@metamask-previews/money-account-controller@1.0.0-preview-0d04fb622
@metamask-previews/money-account-upgrade-controller@3.0.2-preview-0d04fb622
@metamask-previews/money-account-utils@1.1.0-preview-0d04fb622
@metamask-previews/multichain-account-service@13.0.2-preview-0d04fb622
@metamask-previews/multichain-api-middleware@4.0.3-preview-0d04fb622
@metamask-previews/multichain-network-controller@3.2.4-preview-0d04fb622
@metamask-previews/multichain-transactions-controller@7.1.2-preview-0d04fb622
@metamask-previews/name-controller@9.1.2-preview-0d04fb622
@metamask-previews/network-connection-banner-controller@0.2.1-preview-0d04fb622
@metamask-previews/network-controller@36.0.0-preview-0d04fb622
@metamask-previews/network-enablement-controller@6.0.5-preview-0d04fb622
@metamask-previews/notification-services-controller@26.0.1-preview-0d04fb622
@metamask-previews/passkey-controller@3.1.0-preview-0d04fb622
@metamask-previews/permission-controller@13.1.1-preview-0d04fb622
@metamask-previews/permission-log-controller@5.1.0-preview-0d04fb622
@metamask-previews/perps-controller@15.0.0-preview-0d04fb622
@metamask-previews/phishing-controller@17.4.0-preview-0d04fb622
@metamask-previews/platform-api-docs@0.1.0-preview-0d04fb622
@metamask-previews/polling-controller@16.0.9-preview-0d04fb622
@metamask-previews/preferences-controller@23.1.0-preview-0d04fb622
@metamask-previews/profile-metrics-controller@4.0.3-preview-0d04fb622
@metamask-previews/profile-sync-controller@29.0.0-preview-0d04fb622
@metamask-previews/ramps-controller@20.2.0-preview-0d04fb622
@metamask-previews/rate-limit-controller@7.0.1-preview-0d04fb622
@metamask-previews/react-data-query@1.0.0-preview-0d04fb622
@metamask-previews/remote-feature-flag-controller@6.1.0-preview-0d04fb622
@metamask-previews/sample-controllers@5.0.6-preview-0d04fb622
@metamask-previews/seedless-onboarding-controller@10.1.1-preview-0d04fb622
@metamask-previews/selected-network-controller@26.1.7-preview-0d04fb622
@metamask-previews/sentinel-api-service@1.0.1-preview-0d04fb622
@metamask-previews/shield-controller@6.0.1-preview-0d04fb622
@metamask-previews/signature-controller@39.2.10-preview-0d04fb622
@metamask-previews/smart-transactions-controller@26.0.0-preview-0d04fb622
@metamask-previews/snap-account-service@2.1.2-preview-0d04fb622
@metamask-previews/social-controllers@2.8.0-preview-0d04fb622
@metamask-previews/solana-test-validator-up@1.0.0-preview-0d04fb622
@metamask-previews/stellar-quickstart-up@0.0.0-preview-0d04fb622
@metamask-previews/storage-service@1.0.2-preview-0d04fb622
@metamask-previews/subscription-controller@8.0.1-preview-0d04fb622
@metamask-previews/transaction-controller@69.7.0-preview-0d04fb622
@metamask-previews/transaction-pay-controller@27.1.1-preview-0d04fb622
@metamask-previews/user-operation-controller@41.2.9-preview-0d04fb622
@metamask-previews/wallet@12.0.2-preview-0d04fb622
@metamask-previews/wallet-cli@0.0.0-preview-0d04fb622

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

suggestions for some changes on fragments

Comment thread packages/analytics-controller/src/AnalyticsController.ts
Comment thread packages/analytics-controller/src/AnalyticsController.ts Outdated
gauthierpetetin and others added 2 commits September 2, 2026 12:34
Drop persisted fragments whose lastUpdated is older than 24 hours on
init so abandoned journeys cannot keep properties in storage indefinitely.

Co-authored-by: Cursor <cursoragent@cursor.com>
Expose ReadonlyAnalyticsEventFragment from create and get so callers
cannot mutate controller state without going through update or upsert.

Co-authored-by: Cursor <cursoragent@cursor.com>
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.

3 participants