Deployments AE: add group dimension

Background

The EI dashboard needs deployment counts broken down per group ("Deployment frequency by group"). Analytics::AggregationEngines::Deployments already provides the deployment count and monthly trends, but has no group dimension — today a per-group breakdown costs one scoped request per group, since prepare_base_aggregation_scope ORs the requested objects into a single result set instead of breaking it down.

Deployments are bound to projects, so "group" is defined here as the immediate parent namespace of the deployment's project. The siphon_deployments table carries no group column, but its traversal_path column is the full path including the organization — {organization_id}/{root_ns_id}/.../{parent_group_id}/{project_namespace_id}/ — so the immediate parent id is extractable in-query. Engine only; no schema or MV changes.

Proposal

group dimension (engine only)

Extract the second-to-last path segment (the segment before the project namespace id) and resolve the Group association by id:

column :group_id, :integer, -> {
  sql("toUInt64(splitByChar('/', traversal_path)[-3])")
}, description: 'Immediate parent group of the deployment project', association: {
  finder: ->(ids) { Group.where(id: ids).index_by(&:id) }
}

[-3] because the path is /-terminated (the last split element is the empty string and [-2] is the project namespace id). The organization prefix in traversal_path does not affect the extraction — only a fixed offset from the end is read.

The dimension complements (not replaces) scoping: scoping restricts the data set to the requested groups/projects, while the dimension breaks that result down per immediate parent group in one query (count per group id, combinable with the existing finished_at/created_at date buckets and the rate metrics).

Caveats

  • Personal-namespace projects: for a project directly under a user namespace the path is {organization_id}/{user_namespace_id}/{project_namespace_id}/, so [-3] extracts a user namespace id that resolves to no Group — the association finder returns nil. Decide whether to surface these rows as a nil bucket or exclude them from the dimension, and cover the choice with a spec.
  • No rollup: "immediate parent" means a project in a nested subgroup is attributed to that subgroup, not to its top-level group. This matches the metric definition; a depth-parameterized rollup is the shared group dimension helper planned for the MergeRequests/AiUsageEvents engines (#605525, #605518) and can be adopted here in a follow-up if rollup semantics are needed.

Acceptance criteria

  • group_id dimension extracting the immediate parent namespace id from traversal_path, with the Group association finder.
  • Specs: per-group deployment count breakdown with association resolution; subgroup projects attributed to the subgroup (not the root group); personal-namespace rows behave as decided above; dimension combined with the existing monthly date_bucket and range filters.

Metrics unblocked

Deployment frequency by group; per-group breakdowns of the existing deployment metrics (success/failure/canceled rates, duration quantiles).