Add Orbit dashboard shell with namespace settings

What does this MR do and why?

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.

Screenshot_2026-03-03_at_11.11.14_PM

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

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

  1. Dashboard controller at /dashboard/orbit, gated by :knowledge_graph feature flag
  2. Sidebar menu under "Your work" with three sub-items — each links to a distinct URL path (/dashboard/orbit, /dashboard/orbit/schema, /dashboard/orbit/configuration)
  3. Vue 3 app shell that renders the active page based on params[:vueroute]
  4. Breadcrumbs: Your work > Orbit > [current page]
  5. Configuration page with namespace index table — lists the user's owned top-level groups with enable/disable toggles, backed by the orbitUpdate GraphQL mutation
  6. ToggleNamespaceService — creates or deletes knowledge_graph_enabled_namespaces records. Rejects subgroups (only top-level groups allowed).
  7. knowledgeGraphEnabled field on GroupType with preload in ResolvesGroups

Important notes

  1. The :knowledge_graph feature 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.
  2. This MR does not touch the gRPC client, proto files, JWT auth, or the REST API. Those come in follow-up MRs.
  3. All Vue components use defineComponent with compatConfig: { MODE: 3 } and Options API per monolith conventions.
  4. 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

Edited by Michael Angelo Rivera

Merge request reports

Loading