Skip to content

Conversation

@vlidholt
Copy link
Contributor

@vlidholt vlidholt commented Jan 28, 2026

This PR adds:

  • Support for PostHog analytics.
  • Support for CompoundAnalytics - the ability to combine multiple providers into a single one.
  • More detailed analytics (the full command, with any free form text parameters masked).

Breaking change:
OnAnalyticsEvent now requires an additional properties parameter.

Summary by CodeRabbit

  • New Features

    • Support for multiple analytics providers (Mixpanel, PostHog).
    • Analytics events include enriched, sanitized command-context properties.
    • Analytics API adds user identification capability.
  • Breaking Changes

    • Analytics callback/API now receives an additional properties argument when events are emitted.
  • Tests

    • Added unit tests validating command-property masking and full-command composition for analytics.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 28, 2026

📝 Walkthrough

Walkthrough

Refactors analytics into a multi-provider system: adds CompoundAnalytics delegating to MixPanelAnalytics and PostHogAnalytics, extends Analytics.track to accept properties and adds identify, introduces command-properties extraction/masking, and updates BetterCommandRunner and tests to emit and handle enriched analytics properties.

Changes

Cohort / File(s) Summary
Analytics Public Export
packages/cli_tools/lib/analytics.dart
Added exports for new providers: src/analytics/mixpanel.dart and src/analytics/posthog.dart.
Analytics Core Refactor
packages/cli_tools/lib/src/analytics/analytics.dart
Replaced single implementation with CompoundAnalytics(this.providers); track now accepts event + optional properties; added identify; cleanUp delegates to providers.
Analytics Providers
packages/cli_tools/lib/src/analytics/mixpanel.dart, packages/cli_tools/lib/src/analytics/posthog.dart
Added MixPanelAnalytics and PostHogAnalytics implementing Analytics; each builds enriched payloads and posts to their endpoints with non-fatal error handling.
Analytics Utilities
packages/cli_tools/lib/src/analytics/helpers.dart, packages/cli_tools/lib/src/analytics/command_properties.dart
Added getPlatformString() and buildCommandPropertiesForAnalytics(...) to extract, mask, and reconstruct a sanitized full_command and per-option/flag properties (handles nested commands, inline/short forms, and masking).
Command Runner Integration
packages/cli_tools/lib/src/better_command_runner/better_command_runner.dart
OnAnalyticsEvent typedef changed to void Function(String event, Map<String, dynamic> properties); sendAnalyticsEvent accepts optional properties and forwards properties built via buildCommandPropertiesForAnalytics.
Tests — Command Properties
packages/cli_tools/test/analytics/command_properties_test.dart
New tests validating masking, flags/options, multi-options, nested subcommands, inline/short forms, -- handling, and full_command composition.
Tests — BetterCommandRunner Updates
packages/cli_tools/test/better_command_runner/analytics_test.dart, packages/cli_tools/test/better_command_runner/better_command_test.dart, packages/cli_tools/test/better_command_runner/default_flags_test.dart
Updated test callbacks to new analytics callback arity (event, properties) (second parameter typically ignored).

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI Command Execution
    participant BCR as BetterCommandRunner
    participant CPA as buildCommandPropertiesForAnalytics
    participant CA as CompoundAnalytics
    participant MP as MixPanelAnalytics
    participant PH as PostHogAnalytics
    participant HTTP as HTTP Endpoint

    CLI->>BCR: Execute command with ArgResults
    BCR->>CPA: Extract command properties (mask sensitive values)
    CPA-->>BCR: Return properties map with masked options/flags and full_command
    BCR->>CA: sendAnalyticsEvent(event, properties)
    CA->>MP: track(event, properties)
    CA->>PH: track(event, properties)
    MP->>HTTP: POST to Mixpanel endpoint
    PH->>HTTP: POST to PostHog endpoint
    HTTP-->>MP: Response (ignored)
    HTTP-->>PH: Response (ignored)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • christerswahn
  • SandPod
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Adds support for PostHog analytics' accurately describes the primary change but is incomplete. While PostHog support is a significant addition, the PR equally introduces CompoundAnalytics for combining multiple providers and adds detailed command property tracking—changes that are equally central to the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@packages/cli_tools/lib/src/analytics/mixpanel.dart`:
- Around line 58-75: The track method currently ignores the caller-supplied
properties parameter; update the payload construction in track to merge the
incoming properties into the payload's 'properties' map so caller metadata is
sent to Mixpanel. Inside track, create a merged Map (e.g., start with
...properties and then add the internal keys 'distinct_id', 'token', 'platform',
'dart_version', 'is_ci', 'version' so internal metadata remains present) and use
that merged map as the value for 'properties' in the jsonEncode payload before
calling _quietPost.

In `@packages/cli_tools/lib/src/better_command_runner/better_command_runner.dart`:
- Around line 220-229: The function sendAnalyticsEvent currently prints the
event and properties unconditionally, leaking analytics payloads even when
analyticsEnabled() is false; update sendAnalyticsEvent so the print/log line is
executed only after confirming analyticsEnabled() returns true (or replace the
print with a debug/logger call that is gated by analyticsEnabled()), and keep
the try/catch invoking onAnalyticsEvent unchanged; reference sendAnalyticsEvent,
analyticsEnabled(), and onAnalyticsEvent to locate and change the unconditional
print to be conditional or routed to a non-stdout debug logger.

In `@packages/cli_tools/test/analytics/command_properties_test.dart`:
- Around line 1-241: The file containing tests for
buildCommandPropertiesForAnalytics (the test suite with CreateCommand and
TemplateCommand and the main() group) is not formatted; run the Dart formatter
to fix styling. Fix by running `dart format` on the affected file (or `dart
format .` for the package), review the changes to the tests referencing
CreateCommand, TemplateCommand and buildCommandPropertiesForAnalytics, and
commit the formatted file so CI passes.
🧹 Nitpick comments (2)
packages/cli_tools/lib/src/analytics/analytics.dart (1)

24-55: Consider isolating provider failures to keep analytics best‑effort.

If a provider throws, the exception can bubble and prevent other providers from running. Wrapping each call avoids that.

♻️ Suggested defensive wrapping
   void cleanUp() {
     for (final provider in providers) {
-      provider.cleanUp();
+      try {
+        provider.cleanUp();
+      } catch (_) {}
     }
   }
@@
   void track({
     required final String event,
     final Map<String, dynamic> properties = const {},
   }) {
     for (final provider in providers) {
-      provider.track(
-        event: event,
-        properties: properties,
-      );
+      try {
+        provider.track(
+          event: event,
+          properties: properties,
+        );
+      } catch (_) {}
     }
   }
@@
   void identify({
     final String? email,
     final Map<String, dynamic>? properties,
   }) {
     for (final provider in providers) {
-      provider.identify(
-        email: email,
-        properties: properties,
-      );
+      try {
+        provider.identify(
+          email: email,
+          properties: properties,
+        );
+      } catch (_) {}
     }
   }
packages/cli_tools/lib/src/better_command_runner/better_command_runner.dart (1)

337-343: Skip building analytics properties when analytics are disabled.

buildCommandPropertiesForAnalytics is computed even when analytics are disabled. Guarding avoids unnecessary work and reduces accidental exposure.

♻️ Proposed tweak
-          sendAnalyticsEvent(
-            command.name!,
-            buildCommandPropertiesForAnalytics(
-              topLevelResults: topLevelResults,
-              argParser: argParser,
-              commands: commands,
-            ),
-          );
+          if (analyticsEnabled()) {
+            sendAnalyticsEvent(
+              command.name!,
+              buildCommandPropertiesForAnalytics(
+                topLevelResults: topLevelResults,
+                argParser: argParser,
+                commands: commands,
+              ),
+            );
+          }

Copy link
Collaborator

@christerswahn christerswahn left a comment

Choose a reason for hiding this comment

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

Looks good to me! Just had a code structure suggestion.

Copy link
Collaborator

@christerswahn christerswahn left a comment

Choose a reason for hiding this comment

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

Snyggt! 🚀

@vlidholt vlidholt merged commit af4786f into serverpod:main Jan 28, 2026
12 checks passed
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