--- phase: 07-user-access-audit plan: 04 subsystem: viewmodel tags: [viewmodel, wpf, people-picker, debounce, collectionview, grouping, filtering, export, mvvm] requires: - phase: 07-01 provides: [UserAccessEntry, AccessType, IUserAccessAuditService, IGraphUserSearchService, GraphUserResult] - phase: 07-02 provides: [UserAccessAuditService] - phase: 07-03 provides: [GraphUserSearchService] - phase: 07-06 provides: [UserAccessCsvExportService, UserAccessHtmlExportService] provides: - UserAccessAuditViewModel with full orchestration of people picker, site selection, audit execution, grouping, filtering, summary banner, export affects: [07-05, 07-07, 07-08] tech-stack: added: [] patterns: [CollectionViewSource grouping toggle, debounced CancellationTokenSource search, FeatureViewModelBase extension, dual-constructor pattern, _hasLocalSiteOverride site override] key-files: created: - SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs modified: [] key-decisions: - "CollectionViewSource is created over Results in constructor; ApplyGrouping() clears and re-adds PropertyGroupDescription on IsGroupByUser toggle (UserLogin or SiteUrl)" - "Debounced search uses _searchCts CancellationTokenSource cancelled on each SearchQuery change; Task.Delay(300, ct) pattern with OperationCanceledException swallowed" - "OnResultsChanged partial rebuilds grouping/filter when Results collection reference is replaced after RunOperationAsync" - "ExportCsvAsync calls WriteSingleFileAsync (combined single-file export) rather than WriteAsync (per-user directory) to match SaveFileDialog single-path UX" patterns-established: - "UserAccessAuditViewModel: same _hasLocalSiteOverride + OnGlobalSitesChanged guard as PermissionsViewModel" - "Dual constructor: full DI constructor + internal test constructor omitting export services — both initialize all commands and wire collection events" - "Summary properties (TotalAccessCount, SitesCount, HighPrivilegeCount) are computed getters calling Results LINQ — NotifySummaryProperties() triggers all three" requirements-completed: [UACC-01, UACC-02] duration: 2min completed: 2026-04-07 --- # Phase 7 Plan 04: UserAccessAuditViewModel Summary **UserAccessAuditViewModel wires people-picker (300ms debounced Graph search), multi-site selection with override guard, IUserAccessAuditService.AuditUsersAsync execution, CollectionViewSource group-by-user/site toggle with real-time filter, computed summary banner (TotalAccessCount, SitesCount, HighPrivilegeCount), and CSV/HTML export commands — zero-error build.** ## Performance - **Duration:** ~2 min - **Started:** 2026-04-07T10:42:51Z - **Completed:** 2026-04-07T10:44:56Z - **Tasks:** 1 - **Files modified:** 1 ## Accomplishments - UserAccessAuditViewModel.cs (~300 lines) extends FeatureViewModelBase and implements all 10 observable properties, 5 commands, CollectionViewSource grouping/filtering, and dual constructors - Debounced people-picker: _searchCts cancelled/recreated on SearchQuery change, 300ms Task.Delay, IsSearching spinner, 2-char minimum guard consistent with GraphUserSearchService - CollectionViewSource grouping: ApplyGrouping() swaps PropertyGroupDescription between UserLogin and SiteUrl; FilterPredicate applies to 6 fields case-insensitively - Summary banner computed properties (TotalAccessCount, SitesCount, HighPrivilegeCount) notified via NotifySummaryProperties() after each RunOperationAsync and tenant switch ## Task Commits 1. **Task 1: Implement UserAccessAuditViewModel** - `3de737a` (feat) **Plan metadata:** (docs commit pending) ## Files Created/Modified - `SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs` — Full orchestration ViewModel for User Access Audit tab ## Decisions Made 1. **CollectionViewSource bound at construction** — ResultsView is created from a `new CollectionViewSource { Source = Results }` in the constructor. When Results is replaced by a new collection in RunOperationAsync, OnResultsChanged re-applies grouping and filter. This avoids ICollectionView rebinding complexity in XAML. 2. **WriteSingleFileAsync for CSV export** — UserAccessCsvExportService has two modes: WriteAsync (per-user files to directory) and WriteSingleFileAsync (combined). The ViewModel uses WriteSingleFileAsync since the SaveFileDialog returns a single file path — the per-directory mode is for batch export scenarios. 3. **SelectedUsers UPNs as login keys** — AuditUsersAsync receives `SelectedUsers.Select(u => u.UserPrincipalName)` as the targetUserLogins parameter, matching the UPN-based bidirectional matching in UserAccessAuditService. ## Deviations from Plan None — plan executed exactly as written. ## Issues Encountered None. ## User Setup Required None - no external service configuration required. ## Next Phase Readiness - UserAccessAuditViewModel ready for XAML binding in 07-05 (View) - All observable properties, commands, and ResultsView ICollectionView available for DataGrid/ComboBox/AutoComplete binding - Export commands wired to UserAccessCsvExportService.WriteSingleFileAsync and UserAccessHtmlExportService.WriteAsync --- *Phase: 07-user-access-audit* *Completed: 2026-04-07*