Skip to content

feat(analytics): hierarchical groups support - #106

Draft
tylerjroach wants to merge 17 commits into
masterfrom
hierarchical-groups
Draft

tylerjroach wants to merge 17 commits into
masterfrom
hierarchical-groups

Conversation

@tylerjroach

@tylerjroach tylerjroach commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Adds hierarchical groups (HG) support: a MixpanelIdentity carrying user and company ids, HG-aware profile updates, and company-bound group writes. HG behavior is gated on the SDK's hierarchical groups init flag; projects without it behave as before.

Draft, opened for an early automated review. Not ready to merge.

Source compatibility

MessageBuilder gains MixpanelIdentity overloads of event, importEvent, set, setOnce, delete, remove, union and unset. A call that passes a literal null id, such as event(null, "Server Started", props), no longer compiles, because null matches both the String and MixpanelIdentity overloads. Cast the literal to fix it: event((String) null, …).

Only a literal null in source is affected. A String variable that holds null still compiles and behaves as before, and already-compiled code is unaffected. We judged this an edge case and kept the identity overloads under the same names as the other SDKs.

Commits

  • feat(analytics): hierarchical groups support
  • feat(flags): resolve local assignment keys from context_value_template
  • feat(analytics): throw for unsupported groupMessage operations on hierarchical groups builders
  • feat(analytics): add company* profile methods to forCompany builders
  • chore(analytics): point the company-only identity error at the company profile API
  • revert(analytics): drop the company profile methods from for_company clients
  • feat(analytics): infer the company for $company_id group profile updates
  • feat(analytics): restore the company profile on for_company clients
  • feat(analytics): refuse a user from another company on a forCompany builder
  • feat(analytics): send a user without a company as given on hierarchical groups
  • fix(analytics): keep a user-only profile update within the bound company

🤖 Generated with Claude Code

tylerjroach and others added 10 commits November 2, 2026 09:58
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rarchical groups builders

$add, $append and $merge were logged and still built. They now throw
UnsupportedOperationException, like the people operations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y profile API

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…clients

The company profile is updated through the existing group methods instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings back the company profile methods in place of inferring the company from $company_id
group writes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uilder

A people message from a MessageBuilder bound with forCompany used the
identity's company and ignored the bound one, so a mismatched identity
silently built an update of the other company's user. It now throws
IllegalArgumentException.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…al groups

An identity with a user id but no company or device id threw on every
call. It is valid to send, like a plain distinct id, so events now send
it as given. User profile updates still need the company and fail with
the existing company-required error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The latest documentation changes appear safe to merge and address the previous finding.

Reviews (8) · Last reviewed commit: "fix: document that event and importEvent..." · Reviewed by Greptile

tylerjroach and others added 3 commits November 4, 2026 13:15
On a forCompany builder, a user profile update for a user-only identity
ignored the bound company: a $company_id modifier chose which company's
user profile was written, and without one it threw the company-required
error. The identity's company, then the bound company, now decides, and
either wins over a $company_id modifier. Without both, the modifier still
supplies the company. An empty or JSONObject.NULL $company_id modifier no
longer counts as a company.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ity events

A distinct_id, $user_id, $company_id or $device_id in the passed properties
no longer survives when the identity leaves that field unset, so an event
never carries a stale id alongside the identity's own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t hierarchical groups

The exposure check had started treating an empty distinct_id as missing on
every project, so a non-hierarchical-groups exposure with distinct_id ""
was dropped. Only a hierarchical groups provider now treats an empty value
as missing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tylerjroach tylerjroach changed the title Hierarchical groups support feat(analytics): hierarchical groups support Nov 4, 2026
tylerjroach and others added 3 commits November 4, 2026 16:20
Exclude "{" from the placeholder capture so a template full of "{" can't
make matching slow (CodeQL polynomial regular expression).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and let identity win in FlagContext

- MixpanelIdentity.applyTo writes only the keys the identity sets; keys it
  leaves unset keep the caller's values.
- On hierarchical groups, people and group profile messages refuse any
  $-prefixed modifier key outside $set, $set_once, $unset, $union, $remove and
  $delete (non-operation keys such as $time and $company_id are allowed) with
  UnsupportedOperationException. Without hierarchical groups, modifiers are
  copied as before.
- FlagContext applies identity keys last, so identity() and the explicit id
  setters win over group() and customProperty() whatever the call order; among
  them the later call wins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ity leaves unset

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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