Draft: Add AiTracking for the Duo Chat IDE code tools

Draft — blocked on !244736. This MR reserves event ids 39-42; ids 34/35/38 belong to !244736, which should merge first (both MRs touch ai_tracking.rb, the usage-event enum, and ai_tracking_spec.rb in additive blocks). This MR will be rebased and undrafted once !244736 lands.

What does this MR do and why?

Adds AiTracking internal events for the four GitLab Duo Chat IDE code tools, closing four rows of the AiTracking audit:

Audit row Slash command Event id
Fix Code /fix fix_code 39
Refactor Code /refactor refactor_code 40
Test Generation /tests write_tests 41
Code Explanation /explain explain_code 42

With these events the four features land in ai_usage_events and become visible to AiUsageData, per-user AiUserMetrics, and the Duo dashboards — today they are invisible to feature-diversification measurement.

Design

All four tools are SlashCommandTool subclasses with the same shape (RESOURCE_NAME = nil, allow_blank_message? == false), so tracking is implemented once in the base class rather than four times: SlashCommandTool#perform gains an opt-in hook that fires track_internal_event when a subclass declares its usage_event_name.

  • After the blank-input guard: an invocation that returns the "no code to work with" message is not executed, so it is not counted as usage.
  • Before the request: the event records the request, not its outcome — the same convention as Llm::ChatService and Tools::TroubleshootJob.
  • Opt-in: tools that declare no event are untouched (usage_event_name defaults to nil), so existing tools with their own tracking (TroubleshootJob) keep their explicit placement and nothing double-fires.

Attribution is user + context.project (nil-safe: GitlabContext#project is the resource itself when the resource is a Project, otherwise resource.try(:project)); namespace is not passed — Gitlab::InternalEvents derives it from the project when one is present. The chat path also fires the generic request_duo_chat_response — intentional (generic chat usage vs the specific feature), same as troubleshoot_job.

Naming

The event action names match each feature's canonical id (the tool's unit primitive and register_feature name), so one name traces the feature across ai_usage_events, the GraphQL enum, and the generated <Feature>UserMetrics types. Noted as a comment above action: in each event definition.

product_group: duo_chat / product_categories: [duo_chat]: unlike troubleshoot_job (pipeline_execution) or explain_vulnerability (security_insights), these four features have no non-chat domain home — the slash command is the feature — so they follow the request_duo_chat_response convention.

Note on Code Explanation: the legacy explainCode aiAction execution path is unreachable (AiFeaturesCatalogue has no explain_code entry); the /explain chat tool is the only live execution path, which is where this event fires. (Llm::ExplainCodeService still exists — gon_helper.rb consumes its token-limit constants — but nothing executes it.)

Authorization

The four new auto-generated <Feature>UserMetrics GraphQL types are covered by the factory-level authorize_granular_token skip_reason: :parent_authorizes merged in !245901 (merged) — no authorization_todo.txt entries required.

Tests

  • Positive tracking example per executor (trigger_internal_events with category).
  • Negative path per executor: unauthorized context does not track.
  • Blank-input negative (representative, in FixCode): a not-executed invocation does not track.
  • Base-class negative: a SlashCommandTool that declares no usage_event_name does not track.
  • Exact-events guard + PG/ClickHouse storage shared example per feature in ai_tracking_spec.rb.
  • Enum spec updated.

References

MR acceptance checklist

This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.

Merge request reports

Loading