Files
Sharepoint-Toolbox/.planning/phases/13-user-directory-viewmodel/13-RESEARCH.md
Dev df6f4949a8 docs(13-02): complete User Directory ViewModel plan
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:44:56 +02:00

3.4 KiB

Phase 13 Research: User Directory ViewModel

What Exists

GraphUserDirectoryService (Phase 10)

  • GetUsersAsync(clientId, progress?, ct)IReadOnlyList<GraphDirectoryUser>
  • Filter: accountEnabled eq true and userType eq 'Member' (members only)
  • Select: displayName, userPrincipalName, mail, department, jobTitle
  • Uses PageIterator<User, UserCollectionResponse> for transparent pagination
  • Reports progress via IProgress<int> (running count)
  • Honors cancellation in page callback

GraphDirectoryUser Model

public record GraphDirectoryUser(
    string DisplayName, string UserPrincipalName,
    string? Mail, string? Department, string? JobTitle);

GAP: No UserType property — needed for SC3 member/guest in-memory filtering.

UserAccessAuditViewModel (Phase 7)

  • Inherits FeatureViewModelBase (IsRunning, StatusMessage, ProgressValue, RunCommand, CancelCommand)
  • People-picker search: SearchQuery → debounce → IGraphUserSearchService.SearchUsersAsyncSearchResults
  • User selection: SelectedUsers (ObservableCollection) → RunOperationAsync → audit
  • Results: Results (ObservableCollection) + ResultsView (ICollectionView with grouping/filtering)
  • Two constructors: full (DI) and test (omits export services)
  • _currentProfile tracks active tenant (via TenantSwitchedMessage)
  • OnTenantSwitched clears all state

ICollectionView Pattern (existing in same ViewModel)

var cvs = new CollectionViewSource { Source = Results };
ResultsView = cvs.View;
ResultsView.GroupDescriptions.Add(new PropertyGroupDescription(...));
ResultsView.Filter = FilterPredicate;
// On filter change: ResultsView.Refresh();

DI Registration

  • IGraphUserDirectoryService registered as Transient
  • UserAccessAuditViewModel registered as Transient
  • Currently NOT injected into UserAccessAuditViewModel

Gaps to Fill

  1. GraphDirectoryUser needs UserType — add string? UserType to record + update MapUser + select
  2. Service needs guest inclusion — add bool includeGuests parameter; when true, drop userType filter
  3. ViewModel needs IGraphUserDirectoryService — add to both constructors
  4. ViewModel needs browse mode — mode toggle, directory collection, load command, cancel, filter, sort
  5. DI registration — add IGraphUserDirectoryService to UserAccessAuditViewModel constructor resolution

Plan Breakdown

  1. 13-01 (Wave 1): Extend GraphDirectoryUser + GraphUserDirectoryService

    • Add UserType to model
    • Add userType to select fields
    • Add includeGuests parameter (default false for backward compat)
    • Update MapUser
    • Update tests
  2. 13-02 (Wave 2): UserAccessAuditViewModel directory browse mode

    • Inject IGraphUserDirectoryService
    • Add AuditMode enum (Search/Browse) + IsBrowseMode toggle
    • Add DirectoryUsers collection + DirectoryUsersView (ICollectionView)
    • Add LoadDirectoryCommand with own CTS, progress reporting
    • Add CancelDirectoryLoadCommand
    • Add IncludeGuests toggle + in-memory filter by UserType
    • Add DirectoryFilterText + filter predicate (DisplayName, UPN, Department, JobTitle)
    • Add SortDescription defaults (DisplayName ascending)
    • Add DirectoryLoadStatus string for "Loading... X users" display
    • Update OnTenantSwitched to clear directory state
    • Update DI in App.xaml.cs
    • Comprehensive tests