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
memberGroupsfield onorbitlists 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.groupskeeps the exact behavior ofQuery.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 11Plan: 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.
2. Member groups with search
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 |
zzqxanddevran afterdataon 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 50Plan: 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_pathin 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.
When Orbit is down
The list loads without Orbit. Each row shows "Status not available", and the page explains why.
| Group status | Overview |
|---|---|
![]() |
![]() |
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:
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
- 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.
- 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. - 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.












