List the groups of the Orbit token on the Orbit dashboard

What does this MR do and why?

Group status and the Overview on Your work > Orbit now list the groups that Orbit answers for, so every listed group shows its index status and counts. Before, they also listed groups that Orbit refuses, which showed "Status not available" and were missing from the counts.

  • A new memberGroups field on orbit lists the highest group of each group tree where the user has the Reporter role or higher. A member of only a subgroup gets the subgroup, with the Orbit state of its top-level group.
  • The Orbit token, its scope check, the license check, and the lists now read the groups from one place, so the On list always matches the token. The token does not change.
  • The lists are in the order the groups were created, so each page loads from an index. Admins on GitLab Self-Managed and GitLab Dedicated still see every top-level group, in name order.

Replaces !261855 (closed), built on the orbit API from !261894 (merged).

Which groups are listed
User has Overview and the On filter The All filter
Reporter or higher in a top-level group, also through a group share The top-level group The top-level group
Reporter or higher in a subgroup only The subgroup The subgroup
A project membership or a project share only Not listed Not listed
Reporter or higher in a group with Orbit off Not listed The group, as Off
Admin on GitLab Self-Managed or GitLab Dedicated Their member groups, as above Every top-level group

The On list has at most 500 groups, the path limit of the token. The All list adds at most 500 groups with Orbit off.

Code structure

Each layer calls only the layers below it:

consumers   license check, groups finders, memberGroups resolver, Orbit token adapter (JWT, scope check)
per user    Analytics::Orbit::MemberGroups   the user's Reporter+ groups, split by Orbit state of the root
per root    Analytics::Orbit::Billing        the account rules, for one root or a list of root IDs
data        group memberships, enabled namespaces, add-ons, enrollments
  • The batch root rules move from the license module to Billing, next to the account rules they repeat. The promo gate rule now exists once.
  • The token adapter keeps only what the Orbit server needs: the token payload, the scope check, and the admin flag.
  • orbit.groups keeps the exact behavior of Query.groups.

Database

No new tables, columns, or indexes. Plans use a thin clone of gitlab-production-main (2026-10-15) and 1,000 real group IDs, the cap, shown as <ids>. The first run of each query is cold. The "before" plans are from the review.

1. Member groups, one page

SELECT namespaces.* FROM namespaces
WHERE namespaces.type = 'Group' AND namespaces.id IN (<ids>)
ORDER BY namespaces.id ASC LIMIT 11

Plan: https://console.postgres.ai/gitlab/projects/gitlab-production-main/sessions/66187/commands/170960

Index Scan on index_namespaces_on_type_and_id, stops after 11 rows, no Sort. 33 ms cold, 3 ms warm, buffers 29 hit / 26 read. Before (name order): 1.98 s cold, 4,276 buffers.

The search reads only the routes of the listed groups, so a subgroup still matches by the path or name of its parent.

SELECT namespaces.* FROM namespaces
WHERE namespaces.type = 'Group'
  AND namespaces.id IN (
    SELECT routes.source_id FROM routes
    WHERE routes.source_type = 'Namespace' AND routes.source_id IN (<ids>)
      AND (routes.path ILIKE '%<search>%' OR routes.name ILIKE '%<search>%'))
ORDER BY namespaces.id ASC LIMIT 11
Search term Listed groups that match Plan Cold Warm Buffers, cold
data 12 Index Scan on index_routes_on_source_type_and_source_id, then namespaces_pkey for the 11 matches 2.5 s 13 ms 1,795 hit / 2,027 read
zzqx 0 BitmapAnd of the two route trigram indexes and index_routes_on_source_type_and_source_id, no heap read 128 ms 11 ms 2,906 hit / 164 read
dev 61 Index Scan in ID order, stops after 11 matches 52 ms 8 ms 1,459 hit / 31 read
  • zzqx and dev ran after data on the same IDs, so part of the routes index was in cache.
  • Worst case: a term that matches few of the listed groups reads the route of most of them, about 2.5 s cold at the cap. The cost grows with the number of listed groups.
  • Before (Namespace.search(include_parents: true), name order): 3.55 s cold, 9,196 buffers.

3. Orbit state of the listed groups, one batch

SELECT knowledge_graph_enabled_namespaces.root_namespace_id, knowledge_graph_enabled_namespaces.created_at
FROM knowledge_graph_enabled_namespaces
WHERE knowledge_graph_enabled_namespaces.root_namespace_id IN (<top-level group ids>)
LIMIT 50

Plan: https://console.postgres.ai/gitlab/projects/gitlab-production-main/sessions/66187/commands/170968

Index Scan on the unique index_knowledge_graph_enabled_namespaces_on_root_namespace_id, 50 real top-level groups, 3.6 ms cold, buffers 0 hit / 3 read. The request spec checks that memberGroups { nodes { orbit { enabled enabledAt } } } adds no query for each group.

References

  • Orbit refuses a path that is not under a path of its token (authorize_traversal_path in the Orbit server).

Screenshots or screen recordings

Local GDK in SaaS mode with a local Orbit. The user has the memberships in "Token and Orbit answers per group" below. The screenshots show the earlier name order.

Page Before (master) After
Group status, All group-status-all-before group-status-all-after
Group status, On group-status-on-before group-status-on-after
Overview (the counts go from 12 to 18 items, with the subgroup project) overview-before overview-after
All to On, then search "autodevops"
Subgroup of a group with Orbit off group-status-off-subgroup-before group-status-off-subgroup-after
User with no Reporter groups Not captured empty-group-status-after
GitLab.com today

Rows that Orbit refuses show "Status not available":

gitlab-com-group-status-before

When Orbit is down

The list loads without Orbit. Each row shows "Status not available", and the page explains why.

Group status Overview
orbit-down-group-status-after orbit-down-overview-after
GraphQL

orbit { memberGroups } for the same user. The subgroups show the Orbit state of their top-level group, and enabled: true returns only the groups with Orbit on:

graphql-member-groups-after

Token and Orbit answers per group
Group Membership Listed before Listed after Orbit graph status
e2e-jg-gitlab-com, e2e-jg-gitlab-org Developer Yes Yes 200
e2e-jg-demo-fpotter Developer in the subgroup autodevops-poc only, through a group share Yes No 404
e2e-jg-demo-fpotter/autodevops-poc same No Yes 200
e2e-jg-allthethings a project share only In All only No 404
e2e-jg-off-root Reporter, Orbit off Yes, as Off Yes, as Off not in the token
e2e-jg-off-sub-root/team Reporter in the subgroup only, Orbit off No Yes, as Off not in the token

The listed groups with Orbit on are exactly the paths of the Orbit token. The token payload is the same on master and on this branch:

{"group_traversal_ids":[{"path":"1/1640/","access_levels":[30]},{"path":"1/1641/","access_levels":[30]},{"path":"1/1642/1643/","access_levels":[30]}]}

How to set up and validate locally

  1. In GDK, sign in as a user who is Reporter in only a subgroup of a top-level group with Orbit on, and a member of only a project in a second top-level group with Orbit on.
  2. Go to Your work > Orbit > Group status. On master, the top-level group of the subgroup shows "Status not available", and the second top-level group is listed. On this branch, the subgroup is listed with its index status, and the second top-level group is not listed.
  3. Open the Overview tab. The In your graph card includes the counts of the subgroup.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Jean-Gabriel Doyon

Merge request reports

Loading
Loading