Add Orbit dashboard shell with namespace settings
What does this MR do and why?
This MR is a copy of Add Orbit dashboard shell with namespace settings (!225813 - merged), some weird state got merged in that one. This MR has already been approved by several maintainers, see the threads in the other MR for context
Adds the Orbit dashboard shell and namespace index settings. Group owners can enable or disable Knowledge Graph indexing for their top-level groups at /dashboard/orbit/configuration.
Only the scaffolding and GraphQL namespace settings are included here — no gRPC client, JWT auth, or REST API backend dependencies. The Data Explorer and Schema pages land in follow-up MRs once the REST API is in place.
Important ⚠️
Important: All code here is experimental and we acknowledge that there will be bugs. We are working to get GKG into staging as soon as possible so we can begin fixing bugs, improving resiliency, and making this a world-class product.
References
- Pulls code from !225557
- GKG repo: https://gitlab.com/gitlab-org/orbit/knowledge-graph
- Epic: gitlab-org#20566
- Part of #591396
- Getting GKG into staging (high priority): gitlab-org#20991
- Monetization and license-tier checks tracked separately: gitlab-org#21198
- Figma designs
- Design track: gitlab-org#20666
- Orbit demo and context
- Demo recording
Problem
Orbit has no UI in the monolith. Admins and group owners have no way to configure which groups are indexed by the Knowledge Graph.
Solution
- Dashboard controller at
/dashboard/orbit, gated by:knowledge_graphfeature flag - Sidebar menu under "Your work" with three sub-items — each links to a distinct URL path (
/dashboard/orbit,/dashboard/orbit/schema,/dashboard/orbit/configuration) - Vue 3 app shell that renders the active page based on
params[:vueroute] - Breadcrumbs: Your work > Orbit > [current page]
- Configuration page with namespace index table — lists the user's owned top-level groups with enable/disable toggles, backed by the
orbitUpdateGraphQL mutation -
ToggleNamespaceService— creates or deletesknowledge_graph_enabled_namespacesrecords. Rejects subgroups (only top-level groups allowed). -
knowledgeGraphEnabledfield onGroupTypewith preload inResolvesGroups
Important notes
- The
:knowledge_graphfeature flag (default disabled) is the only access gate for now. It will only be turned on in staging. License-tier checks and monetization gates are tracked in gitlab-org#21198. - This MR does not touch the gRPC client, proto files, JWT auth, or the REST API. Those come in follow-up MRs.
- All Vue components use
defineComponentwithcompatConfig: { MODE: 3 }and Options API per monolith conventions. - Both the GraphQL query (
topLevelOnly: true,ownedOnly: true) and the mutation service (@group.root?check) enforce that only top-level groups can be toggled.
Not included (follow-up MRs)
- REST API endpoints (
/api/v4/orbit/*) - Data Explorer (Three.js graph visualization)
- Schema viewer
- Status and tools sections on Configuration page
- Onboarding flow
Database
This MR reads from and writes to knowledge_graph_enabled_namespaces, which was added in a prior MR. No migrations here.
Table schema:
| Column | Type | Constraint |
|---|---|---|
id |
bigint |
NOT NULL, primary key |
root_namespace_id |
bigint |
NOT NULL, unique index, FK to namespaces
|
created_at |
timestamptz |
NOT NULL |
updated_at |
timestamptz |
NOT NULL |
Index: index_knowledge_graph_enabled_namespaces_on_root_namespace_id (unique) on root_namespace_id
All queries hit indexes. No sequential scans.
Query plans
1. Owned top-level groups (GraphQL getOwnedNamespaces)
The configuration page lists the user's owned top-level groups. Uses GroupsFinder with owned: true, top_level_only: true.
Limit (cost=7.66..7.67 rows=1 width=441) (actual time=0.640..0.641 rows=8 loops=1)
-> Sort (cost=7.66..7.67 rows=1 width=441) (actual time=0.639..0.640 rows=8 loops=1)
Sort Key: namespaces.name
Sort Method: quicksort Memory: 27kB
-> Nested Loop (cost=0.41..7.65 rows=1 width=441) (actual time=0.461..0.625 rows=8 loops=1)
-> Index Scan using index_members_on_user_id_and_access_level_requested_at_is_null on members
Index Cond: ((user_id = 1) AND (access_level >= 10) AND (access_level = 50))
-> Index Scan using index_groups_on_parent_id_id on namespaces
Index Cond: ((parent_id IS NULL) AND (id = members.source_id))
Planning Time: 7.660 ms
Execution Time: 0.668 ms
2. knowledgeGraphEnabled field (preloaded via ResolvesGroups)
Index Scan using index_knowledge_graph_enabled_namespaces_on_root_namespace_id
on knowledge_graph_enabled_namespaces
Index Cond: (root_namespace_id = 22)
Planning Time: 0.457 ms
Execution Time: 0.017 ms
3. Enable namespace (INSERT)
Insert on knowledge_graph_enabled_namespaces (cost=0.00..0.02 rows=0 width=0) (actual time=1.062..1.062 rows=0 loops=1)
Conflict Resolution: NOTHING
Tuples Inserted: 1
Conflicting Tuples: 0
Planning Time: 0.012 ms
Execution Time: 1.269 ms
4. Disable namespace (DELETE)
Delete on knowledge_graph_enabled_namespaces (cost=0.15..2.17 rows=0 width=0) (actual time=0.003..0.003 rows=0 loops=1)
-> Index Scan using index_knowledge_graph_enabled_namespaces_on_root_namespace_id
Index Cond: (root_namespace_id = 22)
Planning Time: 0.020 ms
Execution Time: 0.140 ms
How to set up and validate locally
1. Enable the feature flag
Feature.enable(:knowledge_graph)
2. Verify the route and sidebar
Rails.application.routes.url_helpers.dashboard_orbit_path
# => "/dashboard/orbit"
user = User.first
menu = Sidebars::YourWork::Menus::OrbitMenu.new(OpenStruct.new(current_user: user))
menu.title # => "Orbit"
menu.sprite_icon # => "rocket"
menu.render? # => true
3. Test namespace toggling
Find a top-level group you own and toggle indexing on and off:
user = User.first
group = Group.where(parent_id: nil).first
# Enable
svc = Analytics::KnowledgeGraph::ToggleNamespaceService.new(
group: group, current_user: user, enabled: true
)
result = svc.execute
result.status # => :success
group.reload.knowledge_graph_enabled_namespace.present? # => true
# Disable
svc = Analytics::KnowledgeGraph::ToggleNamespaceService.new(
group: group, current_user: user, enabled: false
)
result = svc.execute
result.status # => :success
group.reload.knowledge_graph_enabled_namespace.nil? # => true
4. Verify top-level group enforcement
Subgroups are rejected by the service:
subgroup = Group.where.not(parent_id: nil).first
svc = Analytics::KnowledgeGraph::ToggleNamespaceService.new(
group: subgroup, current_user: user, enabled: true
)
result = svc.execute
result.status # => :error
result.message # => "Only top-level groups can be enabled for Orbit"
Non-owners are rejected:
developer = User.find_by(username: 'some_developer')
svc = Analytics::KnowledgeGraph::ToggleNamespaceService.new(
group: group, current_user: developer, enabled: true
)
result = svc.execute
result.status # => :error
result.message # => "You do not have permission to manage Orbit settings for this group"
5. Visit the dashboard
Navigate to /dashboard/orbit/configuration. Breadcrumbs should show Your work > Orbit > Configuration. The page lists your owned top-level groups with enable/disable toggles.
The sidebar links navigate between /dashboard/orbit (Data Explorer), /dashboard/orbit/schema (Schema), and /dashboard/orbit/configuration (Configuration).
6. Run specs
bundle exec rspec ee/spec/services/analytics/knowledge_graph/toggle_namespace_service_spec.rb \
ee/spec/graphql/mutations/analytics/knowledge_graph/orbit_update_spec.rb
# 12 examples, 0 failures
MR acceptance checklist
- Follows merge request performance guidelines
- Follows code review guidelines
- Follows separation of EE-specific content
