docs(17-02): complete group expansion HTML reports plan
- 17-02-SUMMARY.md created - STATE.md updated: session, decisions, progress - ROADMAP.md: phase 17 marked complete (2/2 plans) - REQUIREMENTS.md: RPT-01 marked complete
This commit is contained in:
@@ -23,7 +23,7 @@ Requirements for v2.3 Tenant Management & Report Enhancements. Each maps to road
|
|||||||
|
|
||||||
### Report Enhancements
|
### Report Enhancements
|
||||||
|
|
||||||
- [ ] **RPT-01**: User can expand SharePoint groups in HTML reports to see group members
|
- [x] **RPT-01**: User can expand SharePoint groups in HTML reports to see group members
|
||||||
- [x] **RPT-02**: Group member resolution uses transitive membership to include nested group members
|
- [x] **RPT-02**: Group member resolution uses transitive membership to include nested group members
|
||||||
- [x] **RPT-03**: User can enable/disable entry consolidation per export (toggle in export settings)
|
- [x] **RPT-03**: User can enable/disable entry consolidation per export (toggle in export settings)
|
||||||
- [x] **RPT-04**: Consolidated reports merge rows for the same user with identical access levels across multiple locations into a single row
|
- [x] **RPT-04**: Consolidated reports merge rows for the same user with identical access levels across multiple locations into a single row
|
||||||
@@ -56,7 +56,7 @@ Requirements for v2.3 Tenant Management & Report Enhancements. Each maps to road
|
|||||||
| APPREG-06 | Phase 19 | Pending |
|
| APPREG-06 | Phase 19 | Pending |
|
||||||
| OWN-01 | Phase 18 | Pending |
|
| OWN-01 | Phase 18 | Pending |
|
||||||
| OWN-02 | Phase 18 | Pending |
|
| OWN-02 | Phase 18 | Pending |
|
||||||
| RPT-01 | Phase 17 | Pending |
|
| RPT-01 | Phase 17 | Complete |
|
||||||
| RPT-02 | Phase 17 | Complete |
|
| RPT-02 | Phase 17 | Complete |
|
||||||
| RPT-03 | Phase 16 | Complete |
|
| RPT-03 | Phase 16 | Complete |
|
||||||
| RPT-04 | Phase 15 | Complete |
|
| RPT-04 | Phase 15 | Complete |
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
|
|
||||||
- [x] **Phase 15: Consolidation Data Model** (2 plans) — PermissionConsolidator service and merged-row model; zero API calls, pure data shapes (completed 2026-04-09)
|
- [x] **Phase 15: Consolidation Data Model** (2 plans) — PermissionConsolidator service and merged-row model; zero API calls, pure data shapes (completed 2026-04-09)
|
||||||
- [x] **Phase 16: Report Consolidation Toggle** (2 plans) — Export settings toggle wired to PermissionConsolidator; first user-visible consolidation behavior (completed 2026-04-09)
|
- [x] **Phase 16: Report Consolidation Toggle** (2 plans) — Export settings toggle wired to PermissionConsolidator; first user-visible consolidation behavior (completed 2026-04-09)
|
||||||
- [ ] **Phase 17: Group Expansion in HTML Reports** (2 plans) — Clickable group expansion in HTML exports with transitive membership resolution
|
- [x] **Phase 17: Group Expansion in HTML Reports** (2 plans) — Clickable group expansion in HTML exports with transitive membership resolution (completed 2026-04-09)
|
||||||
- [ ] **Phase 18: Auto-Take Ownership** — Global toggle and automatic site collection admin elevation on access denied
|
- [ ] **Phase 18: Auto-Take Ownership** — Global toggle and automatic site collection admin elevation on access denied
|
||||||
- [ ] **Phase 19: App Registration & Removal** — Automated Entra app registration with guided fallback and clean removal
|
- [ ] **Phase 19: App Registration & Removal** — Automated Entra app registration with guided fallback and clean removal
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ Plans:
|
|||||||
2. Member resolution includes transitive membership: nested groups are recursively resolved so every leaf user is shown
|
2. Member resolution includes transitive membership: nested groups are recursively resolved so every leaf user is shown
|
||||||
3. Group expansion is triggered at export time via Graph API — the permission scan itself is unchanged
|
3. Group expansion is triggered at export time via Graph API — the permission scan itself is unchanged
|
||||||
4. When Graph cannot resolve a group's members (throttled or insufficient scope), the report shows the group row with a "members unavailable" label rather than failing the export
|
4. When Graph cannot resolve a group's members (throttled or insufficient scope), the report shows the group row with a "members unavailable" label rather than failing the export
|
||||||
**Plans:** 1/2 plans executed
|
**Plans:** 2/2 plans complete
|
||||||
Plans:
|
Plans:
|
||||||
- [ ] 17-01-PLAN.md — ResolvedMember model + ISharePointGroupResolver service (CSOM + Graph transitive resolution) + DI registration
|
- [ ] 17-01-PLAN.md — ResolvedMember model + ISharePointGroupResolver service (CSOM + Graph transitive resolution) + DI registration
|
||||||
- [ ] 17-02-PLAN.md — HtmlExportService expandable group pills + toggleGroup JS + PermissionsViewModel wiring
|
- [ ] 17-02-PLAN.md — HtmlExportService expandable group pills + toggleGroup JS + PermissionsViewModel wiring
|
||||||
@@ -125,6 +125,6 @@ Plans:
|
|||||||
| 10-14 | v2.2 | 14/14 | Shipped | 2026-04-09 |
|
| 10-14 | v2.2 | 14/14 | Shipped | 2026-04-09 |
|
||||||
| 15. Consolidation Data Model | v2.3 | 2/2 | Complete | 2026-04-09 |
|
| 15. Consolidation Data Model | v2.3 | 2/2 | Complete | 2026-04-09 |
|
||||||
| 16. Report Consolidation Toggle | v2.3 | 2/2 | Complete | 2026-04-09 |
|
| 16. Report Consolidation Toggle | v2.3 | 2/2 | Complete | 2026-04-09 |
|
||||||
| 17. Group Expansion in HTML Reports | 1/2 | In Progress| | — |
|
| 17. Group Expansion in HTML Reports | 2/2 | Complete | 2026-04-09 | — |
|
||||||
| 18. Auto-Take Ownership | v2.3 | 0/? | Not started | — |
|
| 18. Auto-Take Ownership | v2.3 | 0/? | Not started | — |
|
||||||
| 19. App Registration & Removal | v2.3 | 0/? | Not started | — |
|
| 19. App Registration & Removal | v2.3 | 0/? | Not started | — |
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ gsd_state_version: 1.0
|
|||||||
milestone: v2.3
|
milestone: v2.3
|
||||||
milestone_name: Tenant Management & Report Enhancements
|
milestone_name: Tenant Management & Report Enhancements
|
||||||
status: planning
|
status: planning
|
||||||
stopped_at: Completed 17-01-PLAN.md
|
stopped_at: Completed 17-02-PLAN.md
|
||||||
last_updated: "2026-04-09T11:06:09.877Z"
|
last_updated: "2026-04-09T11:11:11.458Z"
|
||||||
last_activity: 2026-04-09 — Roadmap created for v2.3 (phases 15-19)
|
last_activity: 2026-04-09 — Roadmap created for v2.3 (phases 15-19)
|
||||||
progress:
|
progress:
|
||||||
total_phases: 5
|
total_phases: 5
|
||||||
completed_phases: 2
|
completed_phases: 3
|
||||||
total_plans: 6
|
total_plans: 6
|
||||||
completed_plans: 5
|
completed_plans: 6
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project State
|
# Project State
|
||||||
@@ -70,6 +70,7 @@ Decisions are logged in PROJECT.md Key Decisions table.
|
|||||||
- [Phase 16-report-consolidation-toggle]: Separate locIdx counter for location groups (loc0, loc1...) distinct from grpIdx for user groups (ugrp0...) prevents ID collision
|
- [Phase 16-report-consolidation-toggle]: Separate locIdx counter for location groups (loc0, loc1...) distinct from grpIdx for user groups (ugrp0...) prevents ID collision
|
||||||
- [Phase 17]: Static helpers IsAadGroup/ExtractAadGroupId/StripClaims declared internal to enable unit testing via InternalsVisibleTo without polluting public API
|
- [Phase 17]: Static helpers IsAadGroup/ExtractAadGroupId/StripClaims declared internal to enable unit testing via InternalsVisibleTo without polluting public API
|
||||||
- [Phase 17]: Graph client created lazily on first AAD group encountered to avoid unnecessary auth overhead for groups with no nested AAD members
|
- [Phase 17]: Graph client created lazily on first AAD group encountered to avoid unnecessary auth overhead for groups with no nested AAD members
|
||||||
|
- [Phase 17]: groupMembers optional param in HtmlExportService — null produces identical pre-Phase-17 output; ISharePointGroupResolver injected as optional last param in PermissionsViewModel; resolution failure degrades gracefully with LogWarning
|
||||||
|
|
||||||
### Pending Todos
|
### Pending Todos
|
||||||
|
|
||||||
@@ -81,7 +82,7 @@ None.
|
|||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-04-09T11:06:05.361Z
|
Last session: 2026-04-09T11:11:03.823Z
|
||||||
Stopped at: Completed 17-01-PLAN.md
|
Stopped at: Completed 17-02-PLAN.md
|
||||||
Resume file: None
|
Resume file: None
|
||||||
Next step: `/gsd:plan-phase 15`
|
Next step: `/gsd:plan-phase 15`
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
---
|
||||||
|
phase: 17-group-expansion-html-reports
|
||||||
|
plan: "02"
|
||||||
|
subsystem: html-export
|
||||||
|
tags: [html-export, group-expansion, permissions-viewmodel, tdd]
|
||||||
|
dependency_graph:
|
||||||
|
requires: ["17-01"]
|
||||||
|
provides: ["expandable-group-pills-html", "group-resolver-wired-to-export"]
|
||||||
|
affects: ["HtmlExportService", "PermissionsViewModel"]
|
||||||
|
tech_stack:
|
||||||
|
added: []
|
||||||
|
patterns: ["optional-parameter-backward-compat", "pre-render-resolution", "inline-js-toggle"]
|
||||||
|
key_files:
|
||||||
|
created: []
|
||||||
|
modified:
|
||||||
|
- SharepointToolbox/Services/Export/HtmlExportService.cs
|
||||||
|
- SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs
|
||||||
|
- SharepointToolbox.Tests/Services/Export/HtmlExportServiceTests.cs
|
||||||
|
decisions:
|
||||||
|
- "groupMembers added as optional last parameter to both BuildHtml overloads and WriteAsync methods — null produces byte-identical output to pre-Phase 17"
|
||||||
|
- "Group pill expansion uses name-based lookup in groupMembers dict (not login) — matches how SharePoint groups are identified in Users field"
|
||||||
|
- "ISharePointGroupResolver injected as optional last parameter in PermissionsViewModel main constructor — DI works without explicit resolver if not registered"
|
||||||
|
- "Resolution failure logged as Warning and export proceeds without expansion — never blocks user export"
|
||||||
|
metrics:
|
||||||
|
duration_seconds: 204
|
||||||
|
completed_date: "2026-04-09"
|
||||||
|
tasks_completed: 2
|
||||||
|
files_modified: 3
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 17 Plan 02: Group Expansion Wire-up Summary
|
||||||
|
|
||||||
|
Expandable SharePoint group pills in HTML reports with toggleGroup JS and PermissionsViewModel pre-render resolution via ISharePointGroupResolver.
|
||||||
|
|
||||||
|
## Tasks Completed
|
||||||
|
|
||||||
|
| Task | Description | Commit | Status |
|
||||||
|
|------|-------------|--------|--------|
|
||||||
|
| 1 (RED) | Failing tests for group pill expansion | c35ee76 | Done |
|
||||||
|
| 1 (GREEN) | HtmlExportService: groupMembers param + expandable pills + toggleGroup JS | 07ed6e2 | Done |
|
||||||
|
| 2 | PermissionsViewModel: ISharePointGroupResolver injection + ExportHtmlAsync wiring | aab3aee | Done |
|
||||||
|
|
||||||
|
## What Was Built
|
||||||
|
|
||||||
|
Both `BuildHtml` overloads in `HtmlExportService` now accept an optional `IReadOnlyDictionary<string, IReadOnlyList<ResolvedMember>>? groupMembers` parameter. When provided:
|
||||||
|
|
||||||
|
- SharePoint group pills render as `<span class="user-pill group-expandable" onclick="toggleGroup('grpmem0')">` with a down-arrow indicator
|
||||||
|
- A hidden `<tr data-group="grpmem0" style="display:none">` sub-row is injected immediately after the parent row, containing resolved member display names and logins
|
||||||
|
- Empty member lists (resolution failed) render `<em style="color:#888">members unavailable</em>`
|
||||||
|
- `toggleGroup(id)` JS function is added to the inline script block
|
||||||
|
- `filterTable()` updated to skip `data-group` sub-rows
|
||||||
|
- CSS `.group-expandable` adds cursor pointer and hover opacity
|
||||||
|
|
||||||
|
`PermissionsViewModel` now injects `ISharePointGroupResolver?` as an optional last constructor parameter. In `ExportHtmlAsync`, before calling `WriteAsync`, it:
|
||||||
|
1. Collects all distinct group names from `Results` where `PrincipalType == "SharePointGroup"`
|
||||||
|
2. Calls `_groupResolver.ResolveGroupsAsync(ctx, clientId, groupNames, ct)` if resolver is non-null and groups were found
|
||||||
|
3. Passes the resulting `groupMembers` dict to both `WriteAsync` call sites (standard and simplified paths)
|
||||||
|
4. On resolution failure, logs a warning and exports without expansion (graceful degradation)
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
None — plan executed exactly as written.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- `dotnet build` — 0 errors, 0 warnings
|
||||||
|
- `dotnet test --filter "FullyQualifiedName~HtmlExportServiceTests"` — 32 tests pass (includes 6 new group expansion tests)
|
||||||
|
- `dotnet test` — 320 passed, 0 failed, 28 skipped (full suite green)
|
||||||
|
|
||||||
|
## Self-Check: PASSED
|
||||||
|
|
||||||
|
Files verified:
|
||||||
|
- SharepointToolbox/Services/Export/HtmlExportService.cs — contains `toggleGroup`, `groupMembers`, `group-expandable`
|
||||||
|
- SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs — contains `_groupResolver`, `ResolveGroupsAsync`
|
||||||
|
- SharepointToolbox.Tests/Services/Export/HtmlExportServiceTests.cs — contains `grpmem` test patterns
|
||||||
|
|
||||||
|
Commits verified:
|
||||||
|
- c35ee76 — test(17-02): RED phase
|
||||||
|
- 07ed6e2 — feat(17-02): HtmlExportService implementation
|
||||||
|
- aab3aee — feat(17-02): PermissionsViewModel wiring
|
||||||
Reference in New Issue
Block a user