docs: create milestone v2.3 roadmap (5 phases, 15-19)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
275
.planning/phases/14-user-directory-view/14-01-PLAN.md
Normal file
275
.planning/phases/14-user-directory-view/14-01-PLAN.md
Normal file
@@ -0,0 +1,275 @@
|
||||
---
|
||||
phase: 14-user-directory-view
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- SharepointToolbox/Localization/Strings.resx
|
||||
- SharepointToolbox/Localization/Strings.fr.resx
|
||||
- SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs
|
||||
- SharepointToolbox/Views/Tabs/UserAccessAuditView.xaml.cs
|
||||
- SharepointToolbox.Tests/ViewModels/UserAccessAuditViewModelDirectoryTests.cs
|
||||
autonomous: true
|
||||
requirements:
|
||||
- UDIR-05
|
||||
- UDIR-01
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "SelectDirectoryUserCommand takes a GraphDirectoryUser, converts it to GraphUserResult, adds it to SelectedUsers via existing logic"
|
||||
- "After SelectDirectoryUserCommand, the user appears in SelectedUsers and can be audited with RunCommand"
|
||||
- "SelectDirectoryUserCommand does not add duplicates (same UPN check as existing AddUserCommand)"
|
||||
- "Localization keys for directory UI exist in both EN and FR resource files"
|
||||
- "Code-behind has a DirectoryDataGrid_MouseDoubleClick handler that invokes SelectDirectoryUserCommand"
|
||||
artifacts:
|
||||
- path: "SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs"
|
||||
provides: "SelectDirectoryUserCommand bridging directory selection to audit pipeline"
|
||||
contains: "SelectDirectoryUserCommand"
|
||||
- path: "SharepointToolbox/Views/Tabs/UserAccessAuditView.xaml.cs"
|
||||
provides: "Event handler for directory DataGrid double-click"
|
||||
contains: "DirectoryDataGrid_MouseDoubleClick"
|
||||
- path: "SharepointToolbox.Tests/ViewModels/UserAccessAuditViewModelDirectoryTests.cs"
|
||||
provides: "Tests for SelectDirectoryUserCommand"
|
||||
contains: "SelectDirectoryUser"
|
||||
key_links:
|
||||
- from: "SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs"
|
||||
to: "SharepointToolbox/Core/Models/GraphDirectoryUser.cs"
|
||||
via: "command parameter type"
|
||||
pattern: "GraphDirectoryUser"
|
||||
- from: "SharepointToolbox/Views/Tabs/UserAccessAuditView.xaml.cs"
|
||||
to: "SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs"
|
||||
via: "command invocation"
|
||||
pattern: "SelectDirectoryUserCommand"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Add localization keys for directory UI, the SelectDirectoryUserCommand that bridges directory selection to the audit pipeline, and a code-behind event handler for DataGrid double-click.
|
||||
|
||||
Purpose: Provides the infrastructure (localization, command, event handler) that Plan 14-02 needs to build the XAML view. SC2 requires selecting a directory user to trigger an audit — this command makes that possible.
|
||||
|
||||
Output: Localization keys (EN+FR), SelectDirectoryUserCommand with tests, code-behind event handler.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@C:/Users/dev/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@C:/Users/dev/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/phases/14-user-directory-view/14-RESEARCH.md
|
||||
|
||||
<interfaces>
|
||||
<!-- Current ViewModel command pattern (AddUserCommand) -->
|
||||
From SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs:
|
||||
```csharp
|
||||
public RelayCommand<GraphUserResult> AddUserCommand { get; }
|
||||
|
||||
private void ExecuteAddUser(GraphUserResult? user)
|
||||
{
|
||||
if (user == null) return;
|
||||
if (!SelectedUsers.Any(u => u.UserPrincipalName == user.UserPrincipalName))
|
||||
{
|
||||
SelectedUsers.Add(user);
|
||||
}
|
||||
SearchQuery = string.Empty;
|
||||
SearchResults.Clear();
|
||||
}
|
||||
```
|
||||
|
||||
<!-- GraphDirectoryUser record -->
|
||||
From SharepointToolbox/Core/Models/GraphDirectoryUser.cs:
|
||||
```csharp
|
||||
public record GraphDirectoryUser(
|
||||
string DisplayName, string UserPrincipalName,
|
||||
string? Mail, string? Department, string? JobTitle, string? UserType);
|
||||
```
|
||||
|
||||
<!-- GraphUserResult record -->
|
||||
From SharepointToolbox/Services/IGraphUserSearchService.cs:
|
||||
```csharp
|
||||
public record GraphUserResult(string DisplayName, string UserPrincipalName, string? Mail);
|
||||
```
|
||||
|
||||
<!-- Existing code-behind pattern -->
|
||||
From SharepointToolbox/Views/Tabs/UserAccessAuditView.xaml.cs:
|
||||
```csharp
|
||||
private void SearchResultsListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (sender is ListBox listBox && listBox.SelectedItem is GraphUserResult user)
|
||||
{
|
||||
var vm = (UserAccessAuditViewModel)DataContext;
|
||||
if (vm.AddUserCommand.CanExecute(user))
|
||||
vm.AddUserCommand.Execute(user);
|
||||
listBox.SelectedItem = null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!-- Existing localization key pattern -->
|
||||
From Strings.resx:
|
||||
```xml
|
||||
<data name="audit.grp.users" xml:space="preserve"><value>Select Users</value></data>
|
||||
<data name="audit.btn.run" xml:space="preserve"><value>Run Audit</value></data>
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Add localization keys for directory UI (EN + FR)</name>
|
||||
<files>
|
||||
SharepointToolbox/Localization/Strings.resx,
|
||||
SharepointToolbox/Localization/Strings.fr.resx
|
||||
</files>
|
||||
<behavior>
|
||||
- Both resx files contain matching keys for directory browse UI
|
||||
</behavior>
|
||||
<action>
|
||||
1. Add to `Strings.resx` (EN):
|
||||
- `audit.mode.search` = "Search"
|
||||
- `audit.mode.browse` = "Browse Directory"
|
||||
- `directory.grp.browse` = "User Directory"
|
||||
- `directory.btn.load` = "Load Directory"
|
||||
- `directory.btn.cancel` = "Cancel"
|
||||
- `directory.filter.placeholder` = "Filter users..."
|
||||
- `directory.chk.guests` = "Include guests"
|
||||
- `directory.status.count` = "users"
|
||||
- `directory.hint.doubleclick` = "Double-click a user to add to audit"
|
||||
- `directory.col.name` = "Name"
|
||||
- `directory.col.upn` = "Email"
|
||||
- `directory.col.department` = "Department"
|
||||
- `directory.col.jobtitle` = "Job Title"
|
||||
- `directory.col.type` = "Type"
|
||||
|
||||
2. Add to `Strings.fr.resx` (FR):
|
||||
- `audit.mode.search` = "Recherche"
|
||||
- `audit.mode.browse` = "Parcourir l'annuaire"
|
||||
- `directory.grp.browse` = "Annuaire utilisateurs"
|
||||
- `directory.btn.load` = "Charger l'annuaire"
|
||||
- `directory.btn.cancel` = "Annuler"
|
||||
- `directory.filter.placeholder` = "Filtrer les utilisateurs..."
|
||||
- `directory.chk.guests` = "Inclure les invités"
|
||||
- `directory.status.count` = "utilisateurs"
|
||||
- `directory.hint.doubleclick` = "Double-cliquez sur un utilisateur pour l'ajouter à l'audit"
|
||||
- `directory.col.name` = "Nom"
|
||||
- `directory.col.upn` = "Courriel"
|
||||
- `directory.col.department` = "Département"
|
||||
- `directory.col.jobtitle` = "Poste"
|
||||
- `directory.col.type` = "Type"
|
||||
</action>
|
||||
<verify>
|
||||
<automated>dotnet build --no-restore -warnaserror</automated>
|
||||
</verify>
|
||||
<done>14 localization keys present in both EN and FR resource files.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Add SelectDirectoryUserCommand to ViewModel</name>
|
||||
<files>
|
||||
SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs,
|
||||
SharepointToolbox.Tests/ViewModels/UserAccessAuditViewModelDirectoryTests.cs
|
||||
</files>
|
||||
<behavior>
|
||||
- SelectDirectoryUserCommand is a RelayCommand<GraphDirectoryUser>
|
||||
- It converts GraphDirectoryUser to GraphUserResult and adds to SelectedUsers
|
||||
- Duplicate UPN check (same as AddUserCommand)
|
||||
- Does NOT clear SearchQuery/SearchResults (not in search mode context)
|
||||
- After execution, IsBrowseMode stays true — user can continue selecting from directory
|
||||
</behavior>
|
||||
<action>
|
||||
1. Add command declaration in ViewModel:
|
||||
```csharp
|
||||
public RelayCommand<GraphDirectoryUser> SelectDirectoryUserCommand { get; }
|
||||
```
|
||||
|
||||
2. Initialize in BOTH constructors:
|
||||
```csharp
|
||||
SelectDirectoryUserCommand = new RelayCommand<GraphDirectoryUser>(ExecuteSelectDirectoryUser);
|
||||
```
|
||||
|
||||
3. Implement the command method:
|
||||
```csharp
|
||||
private void ExecuteSelectDirectoryUser(GraphDirectoryUser? dirUser)
|
||||
{
|
||||
if (dirUser == null) return;
|
||||
var userResult = new GraphUserResult(dirUser.DisplayName, dirUser.UserPrincipalName, dirUser.Mail);
|
||||
if (!SelectedUsers.Any(u => u.UserPrincipalName == userResult.UserPrincipalName))
|
||||
{
|
||||
SelectedUsers.Add(userResult);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. Add tests to `UserAccessAuditViewModelDirectoryTests.cs`:
|
||||
- Test: SelectDirectoryUserCommand adds user to SelectedUsers
|
||||
- Test: SelectDirectoryUserCommand skips duplicates
|
||||
- Test: SelectDirectoryUserCommand with null does nothing
|
||||
- Test: After SelectDirectoryUser, user can be audited with RunCommand (integration: add user + check SelectedUsers.Count > 0)
|
||||
</action>
|
||||
<verify>
|
||||
<automated>dotnet build --no-restore -warnaserror && dotnet test SharepointToolbox.Tests --filter "FullyQualifiedName~UserAccessAuditViewModelDirectory" --no-build -q</automated>
|
||||
</verify>
|
||||
<done>SelectDirectoryUserCommand bridges directory selection to audit pipeline. Tests pass.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Add code-behind event handler for directory DataGrid</name>
|
||||
<files>
|
||||
SharepointToolbox/Views/Tabs/UserAccessAuditView.xaml.cs
|
||||
</files>
|
||||
<behavior>
|
||||
- DirectoryDataGrid_MouseDoubleClick handler extracts the clicked GraphDirectoryUser
|
||||
- Invokes SelectDirectoryUserCommand with the selected user
|
||||
- Uses the same pattern as SearchResultsListBox_SelectionChanged
|
||||
</behavior>
|
||||
<action>
|
||||
1. Add to `UserAccessAuditView.xaml.cs`:
|
||||
```csharp
|
||||
private void DirectoryDataGrid_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is DataGrid grid && grid.SelectedItem is GraphDirectoryUser user)
|
||||
{
|
||||
var vm = (UserAccessAuditViewModel)DataContext;
|
||||
if (vm.SelectDirectoryUserCommand.CanExecute(user))
|
||||
vm.SelectDirectoryUserCommand.Execute(user);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Add the required using statement if not present:
|
||||
```csharp
|
||||
using System.Windows.Controls; // Already present
|
||||
using SharepointToolbox.Core.Models; // For GraphDirectoryUser
|
||||
```
|
||||
</action>
|
||||
<verify>
|
||||
<automated>dotnet build --no-restore -warnaserror</automated>
|
||||
</verify>
|
||||
<done>Code-behind event handler exists, ready to be wired in XAML (Plan 14-02).</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
```bash
|
||||
dotnet build --no-restore -warnaserror
|
||||
dotnet test SharepointToolbox.Tests --filter "FullyQualifiedName~UserAccessAuditViewModelDirectory" --no-build -q
|
||||
```
|
||||
Both must pass with zero failures.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 14 localization keys in both EN and FR resx files
|
||||
- SelectDirectoryUserCommand converts GraphDirectoryUser → GraphUserResult → SelectedUsers
|
||||
- Duplicate UPN check prevents adding same user twice
|
||||
- Code-behind event handler for DataGrid double-click
|
||||
- All tests pass, build clean
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/14-user-directory-view/14-01-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user