Group the Azure tab by scope, and let one click take a subtree - #193
Merged
Merged
Conversation
A flat list does not scale: someone eligible for Contributor on sixty
subscriptions reads sixty sibling rows and clicks sixty times before the
single activation Elevate promised them. The scope string already carries
the hierarchy, so nothing extra has to be fetched to show it.
ArmScope reads an ARM path as the steps it is, and ScopeTree builds the
management group / subscription / resource group / resource tree from a
tenant's eligibilities and flattens it back into rows. Two shapes keep the
tree from costing more rows than it saves: a scope that only passes through
is folded into the node below it and named there ("Alpha / prod"), and a
scope leading to a single role gets no header at all.
The Windows Azure pivot draws that tree, each header opening and closing and
saying how many roles sit under it, with a three-state checkbox in select
mode that takes the whole subtree at once. Search narrows the tree rather
than sitting beside it, reaches the whole ARM path, and keeps matches in
place even under a header that was closed.
For scripts, --under takes everything at or below a scope, compared step by
step so /subscriptions/abc never swallows /subscriptions/abcdef, and --scope
becomes a glob when it carries a *. Neither is enough on its own, because a
name matching twelve roles is still an error, so activate --all takes every
match instead of insisting on one.
Management groups sit beside the subscriptions rather than above them, in
the panel and for --under: ARM writes a management group scope as its own
flat path and never repeats it in a subscription's, so the eligibilities
alone cannot say which subscriptions belong to which management group.
The macOS Azure tab still lists flat; its tree follows. That leaves
PanelFilter diverged for now — the C# copy searches the ARM path and the
Swift one does not.
Refs #186
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #186.
Azure resource eligibility does not scale in a flat list. Someone eligible for Contributor on sixty subscriptions reads sixty sibling rows with similar names and differing paths, then clicks sixty times before the single activation Elevate promised them. The ARM scope string already carries the hierarchy, so nothing extra has to be fetched to show it.
Scoped to the .NET surfaces. The macOS Azure tab still lists flat; its tree follows in a separate PR — I have no Swift toolchain on this machine and would rather not ship a SwiftUI tree I could not compile.
What changed
New in
Elevate.Core/SupportArmScopereads an ARM path as the steps it is, and answersIsAtOrUnder, glob matching and left-truncatingTailfrom them.ScopeTreebuilds the management group / subscription / resource group / resource tree from a tenant's eligibilities and flattens it back into rows.Two shapes keep the tree from costing more rows than it saves — without them the motivating case gets worse, since sixty subscriptions with one eligibility each would become a hundred and twenty rows:
Alpha / prod;Windows Azure pivot draws that tree in the panel's existing single list, as
ScopeRowitems with an indent, so nothing became aTreeView. Each header opens and closes and says how many roles sit under it, and in select mode carries a three-state checkbox that takes the whole subtree at once. Search narrows the tree rather than sitting beside it, now reaches the whole ARM path, and keeps matches in place even under a header that was closed.CLI gains
--under <scope>and a glob form of--scope(*crosses slashes, asArmActionsalready reads action strings).Three judgement calls worth a look
Search was already there.
PanelFilterand the filter box exist on both platforms — the issue predates them. I extended it to match the full ARM path (it only saw the caption) and left the rest alone.--allis not in the issue, but the CLI ask does not work without it.--underand a glob--scopeonly narrow candidates; a name that still matches twelve roles remains an error, which is exactly the twelve-subscription case the issue wants scripted.elevate activate --alltakes every match instead, and with no name at all takes every eligible role the filters leave. Existing single-match semantics are untouched, and it is onactivateonly.Management groups sit beside the subscriptions, not above them. The issue expected the whole tree to fall out of the scope string; that holds for subscription → resource group → resource, but ARM writes a management group scope as its own flat path and never repeats it in a subscription's. Nesting them would need extra ARM calls. This is called out in the option help, both READMEs and the changelog, and pinned by a test.
Testing
Core 555, App 180, CLI 224 — all passing; the WinUI app builds clean with warnings as errors. New coverage:
ArmScopeTests,ScopeTreeTests,AppModelScopeTreeTests,ScopeFilterTests.SubtreeKeysturned up a real inconsistency while I was writing those:CanActivateonly rules out view-only Entra roles, so a subtree checkbox would have selected rows whose own checkbox the view had disabled. The rule that lived inPanelListBuilder.Fillis nowAppModel.CanSelect, and both use it.Note for review
This leaves
PanelFilterdiverged until the macOS PR lands: the C# copy searches the ARM path and the Swift one does not. The follow-up should portArmScope,ScopeTreeand that one-line filter change together.🤖 Generated with Claude Code