Skip to content

[RUM-15788] Add User & Account info#135

Open
cdn34dd wants to merge 9 commits into
mainfrom
carlosnogueira/RUM-15788/add-user-account-info
Open

[RUM-15788] Add User & Account info#135
cdn34dd wants to merge 9 commits into
mainfrom
carlosnogueira/RUM-15788/add-user-account-info

Conversation

@cdn34dd

@cdn34dd cdn34dd commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Motivation

Adds user and account identity context APIs to the Electron SDK, closing RUM-15788. Previously there was no way to attach user or account information to RUM events from the main process. This is a common requirement for any app that has authenticated users and multi-tenant/account structures.

Changes

New public API (src/index.ts)

Eight new customer-context functions are exported from the SDK.

User info

  • setUserInfo(user: UserInfo & { id: string }) — sets { id, name?, email?, extraInfo? } for subsequent supported events.
  • getUserInfo() — returns a copy of the current user info, with custom fields under extraInfo.
  • clearUserInfo() — removes user info from subsequent supported events.
  • addUserExtraInfo(extraInfo) — merges custom attributes into extraInfo.
    • Standard fields (id, name, email) can only be set via setUserInfo.
    • Works even if no user id is set, matching mobile SDK behavior for anonymous-id use cases.
    • Passing null for a custom attribute removes it.

Account info

  • setAccountInfo(accountInfo) — sets { id, name?, extraInfo? } for subsequent supported events.
  • getAccountInfo() — returns a copy of the current account info.
  • clearAccountInfo() — removes account info from subsequent supported events.
  • addAccountExtraInfo(extraInfo) — merges custom attributes into extraInfo.
    • Standard fields (id, name) can only be set via setAccountInfo.
    • Requires an account to be set first.
    • Passing null for a custom attribute removes it.

Event enrichment

User and account context are attached to RUM events as usr.* and account.*. Custom extraInfo fields are flattened into those objects; no extraInfo key is emitted.

User and account context are also attached to spans as meta.usr.* and meta.account.*, matching mobile SDK naming.

Historical context

User/account context is persisted in a small disk-backed history so events can be enriched using their startTime. This allows crash events processed after relaunch to receive the context that was active when the crash happened.

Renderer pipeline

Renderer-originated RUM events are enriched with main-process context through RendererPipeline. If the renderer event already contains non-empty usr or account context, the renderer value is preserved; otherwise the main-process context is injected.

Log enrichment is explicitly left as a TODO until Logs are implemented.

Playground (playground/src/)

Added a "User & Account Context" section with buttons to exercise:

  • set user info
  • add user extra info
  • clear user info
  • set account info
  • add account extra info
  • clear account info

E2E tests (e2e/scenarios/customer-context.scenario.ts)

Ten scenarios cover the full pipeline:

  • user and account info attached to events
  • clearing removes the fields
  • no usr / account field is emitted when unset
  • extra info is flattened into emitted usr / account
  • user email is attached
  • both user and account are present on the same event

Unit tests

Added focused coverage for:

  • validation and copy semantics
  • standard field precedence over custom attributes
  • id-less user extra info
  • account extra-info guard when no account is set
  • span metadata enrichment
  • historical context lookup by event start time
  • null removal for user/account extra info

Checklist

  • Tested locally (playground)
  • Added unit tests for this change.
  • Added e2e/integration tests for this change.
  • Updated related documentation.

@cdn34dd cdn34dd requested a review from a team as a code owner June 12, 2026 12:35

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 473ab60ab8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/assembly/userContext.ts Outdated
Comment thread src/assembly/userContext.ts Outdated
Comment thread src/assembly/userContext.ts Outdated
Comment thread src/assembly/userContext.ts Outdated
Comment thread src/assembly/userContext.ts Outdated
@cdn34dd cdn34dd force-pushed the carlosnogueira/RUM-15788/add-user-account-info branch from 473ab60 to bbea694 Compare June 18, 2026 17:52
@bcaudan bcaudan requested a review from Copilot June 22, 2026 14:46

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

Adds user/account identity context APIs to the Electron SDK so main-process code can attach usr and account attributes to subsequent RUM events (including renderer-originated events), with interactive playground controls and new unit/e2e coverage.

Changes:

  • Introduces new public APIs for user/account context management (set/get/clear plus property-level setters/removers) and exports UserInfo/AccountInfo types.
  • Adds UserContext/AccountContext hook-based context injection and forwards usr/account when assembling renderer RUM events.
  • Extends playground UI + IPC and adds a new e2e scenario file for user/account context behavior.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/index.ts Exposes new public user/account context APIs and wires them into SDK initialization.
src/assembly/contextManager.ts Adds shared context storage/validation + flatten/unflatten helpers for info objects.
src/assembly/userContext.ts Implements usr context injection via format hooks.
src/assembly/userContext.spec.ts Unit tests for user context behavior, validation, and cloning semantics.
src/assembly/accountContext.ts Implements account context injection via format hooks.
src/assembly/accountContext.spec.ts Unit tests for account context behavior, validation, and cloning semantics.
src/assembly/index.ts Re-exports new context classes/types for consumption by the root entrypoint.
src/assembly/Assembly.ts For renderer RUM events, forwards main-process usr/account (when set) along with session/app/view overrides.
playground/src/index.html Adds UI buttons for exercising new user/account APIs.
playground/src/renderer.ts Wires new UI buttons to preload-exposed API calls.
playground/src/preload.ts Exposes new IPC invocations for user/account demo actions.
playground/src/main.ts Adds IPC handlers that call the new SDK APIs for the playground demo.
e2e/scenarios/user-account.scenario.ts Adds end-to-end scenarios validating user/account propagation through intake.
e2e/lib/mainPage.ts Adds page-object helpers for new user/account IPC calls in e2e tests.
e2e/app/src/preload.ts Exposes new IPC calls from renderer to main in the e2e app.
e2e/app/src/main.ts Implements IPC handlers calling the new SDK user/account APIs in the e2e app.

Comment thread src/index.ts Outdated
Comment thread src/index.ts
Comment thread e2e/scenarios/user-account.scenario.ts Outdated
Comment thread src/assembly/contextManager.ts Outdated
Comment thread src/assembly/userContext.ts Outdated
Comment thread src/assembly/Assembly.ts Outdated
Comment thread src/assembly/userContext.ts Outdated
Comment thread src/assembly/contextManager.ts Outdated
Comment thread src/assembly/contextManager.ts Outdated
Comment thread src/assembly/contextManager.ts Outdated
Comment thread e2e/scenarios/user-account.scenario.ts Outdated
Comment thread e2e/scenarios/user-account.scenario.ts Outdated
Comment thread e2e/scenarios/user-account.scenario.ts Outdated
@cdn34dd cdn34dd force-pushed the carlosnogueira/RUM-15788/add-user-account-info branch from bbea694 to e52ef2b Compare June 23, 2026 16:11
Comment thread e2e/scenarios/identity-context.scenario.ts Outdated
Comment thread e2e/scenarios/identity-context.scenario.ts Outdated
Comment thread src/assembly/contextManager.ts Outdated
Comment thread src/assembly/contextManager.ts Outdated
@cdn34dd cdn34dd force-pushed the carlosnogueira/RUM-15788/add-user-account-info branch from e52ef2b to bb127d6 Compare June 25, 2026 18:22
@cdn34dd cdn34dd requested a review from bcaudan June 25, 2026 18:30
Comment thread e2e/scenarios/customer-context.scenario.ts
@cdn34dd cdn34dd requested a review from bcaudan June 26, 2026 08:26
Comment on lines +75 to +85
setContextProperty(key: string, value: unknown): void {
if (this.protectedKeys?.has(key)) return;

if (this.isStandardKey(key)) {
const candidate = { ...this.standardFields, [key]: deepClone(value) };
if (!this.validateProperties(candidate)) return;
this.standardFields = candidate;
} else {
this.extraInfo = { ...this.extraInfo, [key]: deepClone(value) };
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

❗ warning: ‏Non-standard keys (extraInfo) can be set even when no context exists, producing { usr: { plan: 'premium' } } with no id violates the RUM schema.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, that is a valid concern, this is a regression due to all the changes, I'll add a test to prevent this.

@cdn34dd cdn34dd Jun 26, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Actually this is allowed on mobile, we recently needed to make this change on react-native to replicate the other mobile platforms in order to address a customer issue: https://github.com/DataDog/dd-sdk-reactnative/pull/1288/changes.

Their issue was the following:

  • SDKs generates anonymous_id -> backend feature flag copies it to user id field
  • User logs in -> you want to call addUserExtraInfo to add the authenticated id without overwriting the user id field

But if you were to call setUserInfo({ id: consumerId }) it would break your setup, since user id field is auto set by the feature flag.


And looking at the schema id seems to be optional for user but required for account: https://github.com/DataDog/rum-events-format/blob/8dc61166ee818608892d13b6565ff04a3f2a7fe9/schemas/rum/_common-schema.json#L127

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I've addressed this in the latest commit.

Comment thread src/domain/consumer-context/contextManager.ts Outdated
Comment on lines +146 to +159
/**
* Filters out entries whose value is `undefined`, so unset standard fields are not stored as
* explicit `undefined` keys.
*
* @param context - The object to filter.
* @returns A shallow copy of `context` keeping only entries whose value is defined.
*/
function pickDefined(context: Context): Context {
const result: Context = {};
for (const [key, value] of Object.entries(context)) {
if (value !== undefined) result[key] = value;
}
return result;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

❓ question: ‏do we care though? won't those values be removed when serialized for transport anyway?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

We do, because it's not about transport only, for transport they are indeed removed when serialized, but some public APIs like getUserInfo/getAccountInfo preserve the undefined keys, if not removed by this function.

@bcaudan bcaudan Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What issue do you see with preserving undefined keys with those APIs?

@cdn34dd cdn34dd Jun 26, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It's not an issue, it's about correctness, if you get you user/account data, you get a clean object without any undefined values inside. This seems like a very minor thing, is it really that important that we're striping away undefineds instead of leaving them ?

@sbarrio sbarrio requested a review from mormubis June 26, 2026 08:49
@cdn34dd cdn34dd force-pushed the carlosnogueira/RUM-15788/add-user-account-info branch from 6127a87 to 0d40d55 Compare June 26, 2026 11:10
@cdn34dd cdn34dd removed the request for review from mormubis June 26, 2026 11:11
@cdn34dd cdn34dd force-pushed the carlosnogueira/RUM-15788/add-user-account-info branch from 9cf58d7 to 643a83f Compare June 26, 2026 16:37
cdn34dd added 3 commits June 28, 2026 22:47
Persist user and account context history so events can be enriched using
their start time, including crash events from a previous process. Also
propagate customer context to span metadata as meta.usr.* and
meta.account.*.
Treat null values in user/account extra info updates as removals,
matching mobile SDK behavior. Add coverage for removed properties across
stored info, RUM payloads, and span metadata.
@cdn34dd cdn34dd force-pushed the carlosnogueira/RUM-15788/add-user-account-info branch from 643a83f to 281f65a Compare June 28, 2026 21:47
@datadog-official

This comment has been minimized.

@bcaudan bcaudan force-pushed the carlosnogueira/RUM-15788/add-user-account-info branch from a9f72b2 to b9a664d Compare July 9, 2026 08:41
bcaudan added 2 commits July 9, 2026 10:42
Replace `displayWarn` import with `display.warn` in contextManager.ts
and userContext.ts - the named export was removed in main and replaced
by the `display` object from `createDisplay`.
@bcaudan

bcaudan commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d74008e753

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

for (const [key, value] of Object.entries(context)) {
const metaValue = toSpanMetaValue(value);
if (metaValue !== undefined) {
meta[`meta.${prefix}.${key}`] = metaValue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Remove the extra meta prefix from span tags

When a user or account context is set and dd-trace spans are emitted, SpanProcessor merges this result into the span's existing meta object, so these keys are sent literally as meta.usr.id/meta.account.id rather than the Datadog span tags usr.id/account.id. In that scenario, RUM events are enriched, but span user/account attribution won't be recognized because the tags are nested under span.meta already.

Useful? React with 👍 / 👎.

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

PR Review — Score: 4.4 / 5

This is a well-structured feature PR that adds user/account identity APIs with solid internals (ContextManager, disk-backed history, renderer precedence) and strong unit + e2e coverage for the happy paths. Prior review feedback (shared context manager, renderer precedence, deep cloning, standard-field protection, mobile SDK parity for id-less user extra info) has been addressed thoughtfully. I would approve with minor follow-ups on customer-facing docs and crash-attribution e2e.

Why 4.4: Clean domain separation, thorough validation/copy semantics, hook-based enrichment for RUM and spans, renderer injection that respects existing renderer context, and broad test coverage including parametrized e2e scenarios.

Why not 5: README/customer docs are not updated for eight new public APIs, crash-time context attribution (a stated motivation) lacks an end-to-end scenario, and log enrichment is explicitly deferred.


Findings

  • [Minor] Missing README docs — Eight new public APIs are exported but README.md has no User & Account section; PR checklist docs item is still unchecked.
  • [Minor] Crash attribution untested e2e — Disk-backed history is motivated by post-crash relaunch enrichment, but only unit tests cover it; crash.scenario.ts could assert usr/account on the crash error after relaunch.
  • [Nit] Public JSDoc lacks @example — New APIs in src/index.ts follow the descriptive style of existing exports but omit @example blocks called for in docs/REVIEW.md.

Architectural flow

sequenceDiagram
    participant App as Main-process app
    participant API as setUserInfo / setAccountInfo
    participant CM as ContextManager
    participant Disk as DiskValueHistory
    participant Hooks as FormatHooks
    participant Pipe as MainAssembly / RendererPipeline
    participant Intake as EventManager

    App->>API: setUserInfo / addUserExtraInfo
    API->>CM: setContext / addExtraInfo
    CM->>Disk: closeActive + add (timestamped snapshot)

    Note over Pipe: RUM event assembly
    Pipe->>Hooks: triggerRum({ startTime: event.date })
    Hooks->>CM: getContext(startTime)
    CM->>Disk: find(startTime) when historical
    CM-->>Hooks: flat usr / account object
    Hooks-->>Pipe: hook overrides
    Note over Pipe: Renderer: skip usr/account override when renderer already has non-empty context
    Pipe->>Intake: ServerRumEvent with usr.* / account.*
Loading

Before: Main-process code had no API to attach usr or account identity to RUM events; renderer events were enriched only with session/application/view context.

After: Customer context is stored in ContextManager (standard fields + extraInfo, validated and deep-cloned), snapshotted to disk for crash attribution, injected into main-process and span assembly via format hooks, and conditionally merged into renderer-originated RUM events when the renderer event does not already carry non-empty usr/account context.

Open in Web View Automation 

Sent by Cursor Automation: electron-sdk reviews

Comment thread src/index.ts
* user whose `id` is managed elsewhere (e.g. derived from `anonymous_id`), use `addUserExtraInfo`.
* @param user - The user information, including an `id`.
*/
export function setUserInfo(user: UserInfo & { id: string }): void {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[Nit] Missing @example blocks

docs/REVIEW.md asks new public APIs to include at least one @example. These exports have good param docs but no usage sample. Even a short example (set user → add extra info → clear) would help IDE tooltips and generated API docs, matching the README pattern used for Operation Monitoring.

/**
* Creates a crash-attribution history for customer context.
*
* The active entry from the previous process is closed during SDK startup so it can still enrich

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[Minor] Crash attribution lacks e2e coverage

The disk-backed history and startup closeActive logic exist specifically so crash events processed after relaunch get the context that was active at crash time. Unit tests mock find() and contextHistory.spec.ts covers startup closure, but there is no scenario that sets user/account, crashes, relaunches, and asserts usr/account on the recovered crash error.

Extending e2e/scenarios/crash.scenario.ts (or a sibling scenario) would protect the full persistence → lookup → hook injection path that is called out in the PR motivation.

break;
case 'log':
// TODO(RUM-15047)
// TODO(RUM-15047): when Logs are implemented, enrich them with user/account context

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[Nit] Log enrichment deferred

The TODO correctly notes that logs should eventually get the same usr.* / account.* enrichment as mobile SDKs. Not blocking for this RUM-focused PR, but worth tracking so log support does not ship without parity.

Comment thread package.json
Comment on lines +59 to +66
"dist/index.chunk.cjs",
"dist/index.chunk.cjs.map",
"dist/index.chunk.mjs",
"dist/index.chunk.mjs.map",
"dist/contextHistory.chunk.cjs",
"dist/contextHistory.chunk.cjs.map",
"dist/contextHistory.chunk.mjs",
"dist/contextHistory.chunk.mjs.map",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

❓ question: ‏what is the reasoning for going this way?

*/
getContext(startTime?: TimeStamp): Context {
if (startTime !== undefined && this.history) {
return this.history.find(startTime) ?? {};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

💬 suggestion: ‏do we have a test covering this case: no context was active at that time → emit nothing?

Comment on lines +107 to +109
expect(error.usr).toEqual({ id: 'user-1', name: 'Alice' });
expect(error.account).toEqual({ id: 'account-1', name: 'Acme Corp' });
});

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

💬 suggestion: ‏what about testing the span enrichment as well?

Comment on lines +23 to +26
static async init(hooks: FormatHooks): Promise<AccountContext> {
const { initContextHistory } = await import('./contextHistory');
const history = await initContextHistory(ACCOUNT_CONTEXT_HISTORY_FILE_NAME);
return new AccountContext(hooks, history);

@bcaudan bcaudan Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

💬 suggestion: ‏it seems to be pretty similar on the UserContext as well, could it be pulled in the base class?

with something like:

 static init(hooks: FormatHooks) {                                                                                             
   return ContextManager.init(AccountContext, hooks, ACCOUNT_CONTEXT_HISTORY_FILE_NAME);                                       
 }

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.

3 participants