feat: add tenant picker before subscription prompt for multi-tenant users#8083
Conversation
…sers When users have access to multiple Azure tenants, the subscription list now shows a tenant selection step first to scope down the list. This addresses the UX issue where users with many tenants see an overwhelming list of subscriptions from all tenants. Key behaviors: - Single tenant: skipped, goes straight to subscription selection - Multiple tenants: shows picker with display names + "All tenants" option - AZURE_TENANT_ID env var: pre-filters subscriptions in both prompt and no-prompt modes - No-prompt mode: tenant picker skipped but env var filtering still applies Both PromptSubscription paths (DefaultPrompter and promptService) are updated so extensions get the tenant picker automatically via gRPC. Fixes #2993 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds a tenant-selection step ahead of subscription selection to improve UX for multi-tenant users by scoping the subscription list to a chosen tenant (with an “All tenants” escape hatch), while also introducing shared helper logic and tenant display-name lookup.
Changes:
- Introduces
tenant_helper.goto extract unique tenants, filter subscriptions by tenant, and drive the tenant picker UI. - Updates both
DefaultPrompter.PromptSubscriptionandpromptService.PromptSubscriptionto apply tenant filtering (includingAZURE_TENANT_ID) and optionally prompt for tenant selection. - Extends the account/subscription manager surface area to provide tenant display names (via
ListTenants) and updates mocks/tests accordingly.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| cli/azd/test/mocks/mockaccount/mock_subscriptions.go | Extends subscription manager mock with GetTenantDisplayNames. |
| cli/azd/test/mocks/mockaccount/mock_manager.go | Extends account manager mock with GetTenantDisplayNames. |
| cli/azd/pkg/prompt/tenant_helper.go | New helper for tenant extraction, filtering, env var handling, and tenant selection prompt. |
| cli/azd/pkg/prompt/tenant_helper_test.go | New unit/E2E-style tests covering tenant extraction/filtering and multi-tenant prompt flows. |
| cli/azd/pkg/prompt/prompter.go | Adds tenant pre-step to PromptSubscription and factors out subscription option formatting. |
| cli/azd/pkg/prompt/prompter_test.go | Updates tests to cover the extracted formatSubscriptionOptions helper. |
| cli/azd/pkg/prompt/prompter_extra_test.go | Adjusts prompt tests for new tenant behavior and helper extraction. |
| cli/azd/pkg/prompt/prompt_service.go | Adds tenant pre-step to prompt service subscription selection and adds display-name lookup hook. |
| cli/azd/pkg/account/subscriptions_manager.go | Implements GetTenantDisplayNames() via ListTenants. |
| cli/azd/pkg/account/manager.go | Adds GetTenantDisplayNames to the Manager interface and delegates implementation. |
- Fix loop variable pointer bug in subscription slice building - Clear loading message to avoid redundant spinner with pre-loaded data - Update filterByTenantEnvVar comment to accurately reflect fallback behavior Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Skip spinner in PromptCustomResource when LoadingMessage is empty - Remove unused preSelectedTenantId parameter from promptTenantSelection - Update PR description to reflect graceful fallback behavior Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add SkipLoadingSpinner field to SelectOptions for reliable spinner skip - Extract shared promptAndFilterByTenant function to eliminate duplication Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Make TenantInfo unexported (tenantInfo) to reduce public API surface - Update extractUniqueTenants comment to document TenantId fallback - Add test for SkipLoadingSpinner path in PromptCustomResource Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Guard mock GetTenantDisplayNames against nil panic - Skip subscriptions with empty tenant IDs - Add deterministic sort tie-breaker by tenant ID Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Hoist isDemoModeEnabled() call outside subscription format loop - Add nil guard for getTenantNames provider - Skip empty tenant IDs in mock GetTenantDisplayNames Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Use append-based filtering instead of clone+delete for efficiency - Fix test comment to accurately describe no-prompt behavior Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
hemarina
left a comment
There was a problem hiding this comment.
Really nice work on this one — the spinner/prompt separation and SkipLoadingSpinner design read cleanly, and threading the tenant picker through both prompter paths is a solid lift.
Just a few small nits, none blocking:
promptService.PromptSubscriptionmulti-tenant flow could use a test. The three multi-tenant tests intenant_helper_test.go(_TenantPickerShown,_AllTenantsOption,_NoPromptMode_SkipsTenantPicker) all build*DefaultPromptervianewTestPrompterWithCtx. The newerpromptServicepath got the same logic — even one happy-path test there would lock it in.GetTenantDisplayNamesfires after the loading spinner exits (prompt_service.go:99-115). Spinner wrapsGetSubscriptionsonly, so multi-tenant users get a brief silent ARM round-trip before the tenant picker shows up. Usually quick, but a tiny bit inconsistent with the lovely spinner/prompt separation you built. Optional: extend the spinner or pop a quick "Loading tenants…" one insidepromptAndFilterByTenant.extractUniqueTenantsis called twice inpromptAndFilterByTenant— once withnilto count, again with names. Totally fine functionally (N is tiny), but a single-pass + enrich might read a touch cleaner.TenantDisplayNameProvider(tenant_helper.go:138) — exported but only used withinpkg/prompt. Same rationale that tooktenantInfoto lowercase probably applies here too.
LGTM overall ✅
jongio
left a comment
There was a problem hiding this comment.
Clean separation in tenant_helper.go - the extraction of tenant logic into focused functions shared by both prompt paths is well-structured. The optimization of fetching display names only when there are multiple tenants avoids unnecessary API calls, and the SkipLoadingSpinner approach for the double-spinner problem is the right fix.
One suggestion on the silent fallback behavior below.
Adds debug logging when the env var filter produces no results, helping users diagnose stale AZURE_TENANT_ID values with --debug. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use log.Println with a fixed message instead of interpolating the env var value to satisfy the gosec taint analysis check. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Azure Dev CLI Install InstructionsInstall scriptsMacOS/Linux
bash: pwsh: WindowsPowerShell install MSI install Standalone Binary
MSI
Documentationlearn.microsoft.com documentationtitle: Azure Developer CLI reference
|
jongio
left a comment
There was a problem hiding this comment.
Addresses my previous feedback. The log.Println in the fallback branch gives debug visibility when AZURE_TENANT_ID doesn't match any subscription tenants. Clean fix.
jongio
left a comment
There was a problem hiding this comment.
Addresses my previous feedback. The log.Println in the fallback branch gives debug visibility when AZURE_TENANT_ID doesn't match any subscription tenants. Clean fix.
…sers (#8083) * feat: add tenant picker before subscription prompt for multi-tenant users When users have access to multiple Azure tenants, the subscription list now shows a tenant selection step first to scope down the list. This addresses the UX issue where users with many tenants see an overwhelming list of subscriptions from all tenants. Key behaviors: - Single tenant: skipped, goes straight to subscription selection - Multiple tenants: shows picker with display names + "All tenants" option - AZURE_TENANT_ID env var: pre-filters subscriptions in both prompt and no-prompt modes - No-prompt mode: tenant picker skipped but env var filtering still applies Both PromptSubscription paths (DefaultPrompter and promptService) are updated so extensions get the tenant picker automatically via gRPC. Fixes #2993 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address Copilot review feedback (iteration 1) - Fix loop variable pointer bug in subscription slice building - Clear loading message to avoid redundant spinner with pre-loaded data - Update filterByTenantEnvVar comment to accurately reflect fallback behavior Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address Copilot review feedback (iteration 2) - Skip spinner in PromptCustomResource when LoadingMessage is empty - Remove unused preSelectedTenantId parameter from promptTenantSelection - Update PR description to reflect graceful fallback behavior Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address Copilot review feedback (iteration 3) - Add SkipLoadingSpinner field to SelectOptions for reliable spinner skip - Extract shared promptAndFilterByTenant function to eliminate duplication Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address Copilot review feedback (iteration 4) - Make TenantInfo unexported (tenantInfo) to reduce public API surface - Update extractUniqueTenants comment to document TenantId fallback - Add test for SkipLoadingSpinner path in PromptCustomResource Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address Copilot review feedback (iteration 5) - Guard mock GetTenantDisplayNames against nil panic - Skip subscriptions with empty tenant IDs - Add deterministic sort tie-breaker by tenant ID Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address Copilot review feedback (iteration 6) - Hoist isDemoModeEnabled() call outside subscription format loop - Add nil guard for getTenantNames provider - Skip empty tenant IDs in mock GetTenantDisplayNames Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address Copilot review feedback (iteration 8) - Wrap GetSubscriptions error with context in promptService - Add no-prompt mode test verifying tenant picker is skipped and env var filtering works Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address Copilot review feedback (iteration 9) - Use append-based filtering instead of clone+delete for efficiency - Fix test comment to accurately describe no-prompt behavior Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: log when AZURE_TENANT_ID does not match any tenant Adds debug logging when the env var filter produces no results, helping users diagnose stale AZURE_TENANT_ID values with --debug. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: resolve gosec G706 log injection warning Use log.Println with a fixed message instead of interpolating the env var value to satisfy the gosec taint analysis check. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: therealjohn <1501196+therealjohn@users.noreply.github.com>


Summary
When users have access to multiple Azure tenants, the subscription prompt now shows a tenant selection step first to scope down the list. This addresses the UX issue where users with many tenants see an overwhelming list of subscriptions from all tenants mixed together.
Fixes #2993
Key Behaviors
AZURE_TENANT_IDenv var: Pre-filters subscriptions in both prompt and no-prompt modes. If the env var is set but no subscriptions match (e.g. stale tenant ID), the filter is a no-op and all subscriptions are shown to avoid blocking the user.Changes
pkg/prompt/tenant_helper.gopkg/prompt/tenant_helper_test.gopkg/account/manager.goGetTenantDisplayNamestoManagerinterfacepkg/account/subscriptions_manager.goGetTenantDisplayNames()method (delegates toListTenantsAPI)pkg/prompt/prompter.goDefaultPrompter.PromptSubscription; extractedformatSubscriptionOptionshelperpkg/prompt/prompt_service.gopromptService.PromptSubscriptionwith spinner/prompt separation; addedSkipLoadingSpinneroption toSelectOptionsandPromptCustomResourceDesign Decisions
ListTenants()API only when >1 tenant is detected (avoids unnecessary call for single-tenant users). Falls back to tenant IDs if the call fails.PromptCustomResourceskips its inner spinner whenSkipLoadingSpinneris set to true on theSelectOptions.AZURE_TENANT_IDfiltering is best-effort: if the env var doesn't match any available tenant, all subscriptions are shown rather than erroring out.