Show all top-level groups in Orbit admin settings
What does this MR do and why?
On self-managed and Dedicated, the Admin Orbit page shows only the top-level groups that the admin is a member of, and only the first 25. Admins are often not members of most groups, so they cannot see or turn on indexing for them. This MR makes the page list every top-level group, with pagination and search.
How it works
1. The problem: the page asks for member groups
The admin page reused the dashboard query, which sets allAvailable: false (member_namespaces.query.graphql):
query getMemberNamespaces($first: Int = 25) {
groups(topLevelOnly: true, allAvailable: false, first: $first, sort: "name_asc") {With allAvailable: false, GroupsFinder returns only the groups of the user. The "admin sees all groups" path needs allAvailable: true (groups_finder.rb):
return [Group.all] if can_read_all_groups? && all_available?The backend already lets an admin turn indexing on or off for any top-level group. The admin role inherits update_knowledge_graph_setting from owner. Only the list was wrong.
2. A query for the admin page
A new query asks for all top-level groups, 20 at a time (admin_namespaces.query.graphql):
groups(
topLevelOnly: true
allAvailable: true
withKnowledgeGraphEnabled: $withKnowledgeGraphEnabled
search: $search
first: 20
after: $afterThe dashboard keeps its member query. Its scope is correct for user data.
3. Two lists from one query
The page runs the query twice (orbit_settings_page.vue):
enabledNamespaces: adminNamespacesSmartQuery({
variables: { withKnowledgeGraphEnabled: true },
}),
availableNamespaces: adminNamespacesSmartQuery({
variables() {
return { search: this.searchTerm || null };
},
}),- Indexed groups shows only enrolled groups, with their status cards.
- Available groups shows all top-level groups and has a search box. A row that is already indexed shows an "Indexed" badge instead of a button.
Both lists share one load-more method for the Load more button (orbit_settings_page.vue).
References
- Admin role grants: admin.yml, owner.yml
- The
orbit { indexingStatus itemCounts }group field from 9a5ee3ee also works for an admin who is not a member. A new request spec covers it.
Screenshots or screen recordings
The user is orbit-admin, an admin who is not a member of any group. The instance has 114 top-level groups, and 111 of them are indexed.
| Before | After |
|---|---|
![]() |
![]() |
More screenshots: available groups, search, turn on
| Available groups | Search for "twit" | After "Turn on indexing" |
|---|---|---|
![]() |
![]() |
![]() |
How to set up and validate locally
- Use a self-managed GDK with an Ultimate license and the Orbit stack running. Set
GITLAB_SIMULATE_SAASto0. - Create an admin user who is not a member of any group.
- Sign in as that user and go to Admin > Orbit.
- Check that Indexed groups lists the indexed top-level groups, and that Load more adds the next 20.
- In Available groups, search for a group that is not indexed and select Turn on indexing.
Testing
The test ran on gitlab-labcode-vm-2 with the full Orbit stack (schema v100, 271,950 definitions).
| Check | master | This MR |
|---|---|---|
| Indexed groups that orbit-admin sees | 0 of 111 | 111 of 111, 20 per page |
| Available groups that orbit-admin sees | 0 of 114 | 114, with search |
| Turn on and turn off a group that orbit-admin is not a member of | Not possible | Works; the database row is added and removed |
orbit { indexingStatus itemCounts } for that group |
Works | Works (READY, 15 of 15 projects) |
The new request spec runs the real frontend query file as an admin who is not a member. With the old allAvailable: false, both examples fail with expected [] to include "group1". With this MR, they pass.
More test results
- Jest: orbit_settings_page_spec.js passes 10 of 10.
- RSpec: admin_namespaces_query_spec.rb and namespace_spec.rb pass 55 examples with 0 failures. The 2 pending examples are skipped by the shared example.
- Lint: ESLint, Prettier, RuboCop, gettext and Danger pass in lefthook.
- Pagination: Load more went 20, 40, 60, 80, 100, then 112 groups, and then the button went away.
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.
Agent context: decisions, cost, and limits
Per-row cost. Each page loads 20 groups. knowledgeGraphEnabled is preloaded. For an admin, knowledgeGraphAvailable checks only the license and a feature flag, so it makes no extra query per row.
Why a new query file. Reusing excludable_namespaces.query.graphql would add the knowledgeGraphAvailable policy check to every row of the exclude modal, and that query has no pageInfo. Adding the field to the dashboard queries would add the same cost to every dashboard load.
Why indexed rows stay in Available groups. The GraphQL API has no "not indexed" filter. If the page hid indexed rows in the browser, pages with auto-index on would be almost empty. A badge keeps every page full.
Known limit. After a toggle, both lists refetch, and only the first page comes back. Groups that were loaded with Load more must be loaded again.
Not in scope.
- The turn-on button does not know if a group is excluded. The mutation returns the existing "excluded from indexing" error.
- Item counts cover only the admin's organization. Self-managed and Dedicated normally have one.
Diff size. The MR adds 331 lines and removes 60. About 165 added lines are tests. The rest is the second list, pagination and search.




