Allow users to set Duo default namespace directly from drawer
What does this MR do and why?
When a user opens the Duo Chat panel outside of a group/project context and has no default namespace set, they see an empty state. Previously it only offered a link to their profile preferences, taking them out of their current context.
This MR replaces that empty state with an inline group selector that lets users pick their default Duo namespace directly from the drawer, without navigating away.
Key changes:
- Add GraphQL query
getDuoDefaultNamespaceCandidatesand mutationupdateDuoDefaultNamespace(viauserPreferencesUpdate) to fetch eligible namespaces and save the selection. - Rewrite
NoNamespaceEmptyStateto show aGlCollapsibleListboxin a "Group" field with a full-width Continue button, falling back to a "no groups available" message when none are eligible. - On a successful save, the app now triggers a full page reload (
refreshCurrentPage) in both places that show this empty state, the routed view and the blocked view. Chat availability and scope are computed server-side, so a reload is needed to pick up the newly saved default namespace. - Save and fetch failures surface as errors: save failures via a
save-errorevent in the panel's standard top error banner, fetch failures as inline text in the group dropdown. - Candidate groups are sorted alphabetically by name on the client, and the GraphQL query now explicitly caps results at the first 100.
References
Screenshots or screen recordings
| Screen recording |
|---|
| Before (master) | After | After with classic chat available | Save failure | Fetch failure | Continue without group selected |
|---|---|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
How to set up and validate locally
Setup
- Run GDK with SaaS simulation enabled (
GITLAB_SIMULATE_SAAS=1). - Register a new user (or use any user with no Duo default namespace set).
- Give that user two top-level groups with an active Duo add-on, so the candidate list
has more than one entry. With exactly one candidate,
duo_default_namespace_with_fallbackauto-selects it and the empty state never renders.
Rails console snippet
Start the console with SaaS simulation explicitly — it is not inherited from GDK's service environment, and without it the candidate query silently takes the self-managed code path:
GITLAB_SIMULATE_SAAS=1 bundle exec rails consoleuser = User.find_by_username('<your-user>')
org = Organizations::Organization.default_organization
groups = %w[duo-ns-alpha duo-ns-beta].map do |path|
Groups::CreateService.new(
user, name: path, path: path, organization_id: org.id, visibility_level: 0
).execute.payload[:group]
end
ultimate = Plan.find_by(name: 'ultimate')
add_on = GitlabSubscriptions::AddOn.find_or_create_by_name(:duo_enterprise)
groups.each do |group|
sub = group.gitlab_subscription || GitlabSubscription.new(namespace: group)
sub.update!(hosted_plan: ultimate, seats: 10, start_date: Date.current)
purchase = GitlabSubscriptions::AddOnPurchase.find_or_initialize_by(namespace: group, add_on: add_on)
purchase.update!(
organization_id: org.id,
quantity: 10,
started_at: Date.current,
expires_on: 1.year.from_now.to_date,
purchase_xid: "manual-#{group.path}"
)
# duo_enterprise is seat-assignable, so an explicit assignment is required
# for the namespace to become a candidate.
GitlabSubscriptions::UserAddOnAssignment.find_or_create_by!(user: user, add_on_purchase: purchase)
end
user.user_preference.update!(duo_default_namespace_id: nil, default_duo_add_on_assignment_id: nil)
# Candidates must list both groups; fallback must be nil
pp user.user_preference.duo_default_namespace_candidates.pluck(:id, :name)
pp user.user_preference.duo_default_namespace_with_fallback # => nilUse :duo_core instead of :duo_enterprise to skip seat assignment — duo_core and
gitlab_credits qualify on group membership alone.
Validate
- Sign in as that user and go to a page with no group/project context (e.g. your to-dos
page) — inside a group or project, its root namespace satisfies
governing_namespaceand the empty state won't appear. - Open the Duo Chat panel with the Agentic toggle on — the mode persists in the
duo_agentic_mode_oncookie/localStorage, so a prior "Return to non-agentic Chat" click will keep you in classic chat. - Observe the new inline group selector listing both eligible groups.
- Select a group and click Continue.
- Verify the chat becomes available without a page reload.
- Reload the page and verify the selection persisted (the empty state does not return).
Edge cases
- No eligible groups: remove the add-on purchases; expect the "No eligible groups available" message instead of the selector.
- Save failure: block the
UpdateDuoDefaultNamespacemutation in devtools; expect the error banner at the top of the panel, with the selector still interactive.
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.





