From 23ed46e61424cf69648db1c7dab36a6f1cd8748d Mon Sep 17 00:00:00 2001 From: Dev Date: Thu, 9 Apr 2026 13:11:21 +0200 Subject: [PATCH] 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 --- .planning/REQUIREMENTS.md | 4 +- .planning/ROADMAP.md | 6 +- .planning/STATE.md | 13 +-- .../17-02-SUMMARY.md | 80 +++++++++++++++++++ 4 files changed, 92 insertions(+), 11 deletions(-) create mode 100644 .planning/phases/17-group-expansion-html-reports/17-02-SUMMARY.md diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 9249628..f14e461 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -23,7 +23,7 @@ Requirements for v2.3 Tenant Management & Report Enhancements. Each maps to road ### 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-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 @@ -56,7 +56,7 @@ Requirements for v2.3 Tenant Management & Report Enhancements. Each maps to road | APPREG-06 | Phase 19 | Pending | | OWN-01 | Phase 18 | Pending | | OWN-02 | Phase 18 | Pending | -| RPT-01 | Phase 17 | Pending | +| RPT-01 | Phase 17 | Complete | | RPT-02 | Phase 17 | Complete | | RPT-03 | Phase 16 | Complete | | RPT-04 | Phase 15 | Complete | diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index eeca2ae..4f81f70 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -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 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 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 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 -**Plans:** 1/2 plans executed +**Plans:** 2/2 plans complete Plans: - [ ] 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 @@ -125,6 +125,6 @@ Plans: | 10-14 | v2.2 | 14/14 | Shipped | 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 | -| 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 | — | | 19. App Registration & Removal | v2.3 | 0/? | Not started | — | diff --git a/.planning/STATE.md b/.planning/STATE.md index 1435285..2276b83 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,14 +3,14 @@ gsd_state_version: 1.0 milestone: v2.3 milestone_name: Tenant Management & Report Enhancements status: planning -stopped_at: Completed 17-01-PLAN.md -last_updated: "2026-04-09T11:06:09.877Z" +stopped_at: Completed 17-02-PLAN.md +last_updated: "2026-04-09T11:11:11.458Z" last_activity: 2026-04-09 — Roadmap created for v2.3 (phases 15-19) progress: total_phases: 5 - completed_phases: 2 + completed_phases: 3 total_plans: 6 - completed_plans: 5 + completed_plans: 6 --- # 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 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]: 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 @@ -81,7 +82,7 @@ None. ## Session Continuity -Last session: 2026-04-09T11:06:05.361Z -Stopped at: Completed 17-01-PLAN.md +Last session: 2026-04-09T11:11:03.823Z +Stopped at: Completed 17-02-PLAN.md Resume file: None Next step: `/gsd:plan-phase 15` diff --git a/.planning/phases/17-group-expansion-html-reports/17-02-SUMMARY.md b/.planning/phases/17-group-expansion-html-reports/17-02-SUMMARY.md new file mode 100644 index 0000000..b784af3 --- /dev/null +++ b/.planning/phases/17-group-expansion-html-reports/17-02-SUMMARY.md @@ -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>? groupMembers` parameter. When provided: + +- SharePoint group pills render as `` with a down-arrow indicator +- A hidden `` sub-row is injected immediately after the parent row, containing resolved member display names and logins +- Empty member lists (resolution failed) render `members unavailable` +- `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