docs(07): create phase plan - 8 plans across 5 waves
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
167
.planning/phases/07-user-access-audit/07-03-PLAN.md
Normal file
167
.planning/phases/07-user-access-audit/07-03-PLAN.md
Normal file
@@ -0,0 +1,167 @@
|
||||
---
|
||||
phase: 07-user-access-audit
|
||||
plan: 03
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: ["07-01"]
|
||||
files_modified:
|
||||
- SharepointToolbox/Services/GraphUserSearchService.cs
|
||||
autonomous: true
|
||||
requirements:
|
||||
- UACC-01
|
||||
must_haves:
|
||||
truths:
|
||||
- "GraphUserSearchService queries Microsoft Graph /users endpoint with $filter for displayName/mail startsWith"
|
||||
- "Service returns GraphUserResult records with DisplayName, UPN, and Mail"
|
||||
- "Service handles empty queries and returns empty list"
|
||||
- "Service uses existing GraphClientFactory for authentication"
|
||||
artifacts:
|
||||
- path: "SharepointToolbox/Services/GraphUserSearchService.cs"
|
||||
provides: "Implementation of IGraphUserSearchService for people-picker autocomplete"
|
||||
contains: "class GraphUserSearchService"
|
||||
key_links:
|
||||
- from: "SharepointToolbox/Services/GraphUserSearchService.cs"
|
||||
to: "SharepointToolbox/Infrastructure/Auth/GraphClientFactory.cs"
|
||||
via: "Constructor injection, CreateClientAsync call"
|
||||
pattern: "CreateClientAsync"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Implement GraphUserSearchService that queries Microsoft Graph API to search tenant users by name or email. Powers the people-picker autocomplete in the audit tab.
|
||||
|
||||
Purpose: Enables administrators to find and select tenant users by typing partial names/emails, rather than typing exact login names manually.
|
||||
Output: GraphUserSearchService.cs
|
||||
</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/07-user-access-audit/07-CONTEXT.md
|
||||
@.planning/phases/07-user-access-audit/07-01-SUMMARY.md
|
||||
|
||||
<interfaces>
|
||||
<!-- From 07-01: Interface to implement -->
|
||||
From SharepointToolbox/Services/IGraphUserSearchService.cs:
|
||||
```csharp
|
||||
public interface IGraphUserSearchService
|
||||
{
|
||||
Task<IReadOnlyList<GraphUserResult>> SearchUsersAsync(
|
||||
string clientId, string query, int maxResults = 10,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
|
||||
public record GraphUserResult(string DisplayName, string UserPrincipalName, string? Mail);
|
||||
```
|
||||
|
||||
<!-- Existing auth infrastructure -->
|
||||
From SharepointToolbox/Infrastructure/Auth/GraphClientFactory.cs:
|
||||
```csharp
|
||||
public class GraphClientFactory
|
||||
{
|
||||
public async Task<GraphServiceClient> CreateClientAsync(string clientId, CancellationToken ct);
|
||||
}
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Implement GraphUserSearchService</name>
|
||||
<files>SharepointToolbox/Services/GraphUserSearchService.cs</files>
|
||||
<action>
|
||||
Create `SharepointToolbox/Services/GraphUserSearchService.cs`:
|
||||
|
||||
```csharp
|
||||
using SharepointToolbox.Infrastructure.Auth;
|
||||
|
||||
namespace SharepointToolbox.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Searches tenant users via Microsoft Graph API.
|
||||
/// Used by the people-picker autocomplete in the User Access Audit tab.
|
||||
/// </summary>
|
||||
public class GraphUserSearchService : IGraphUserSearchService
|
||||
{
|
||||
private readonly GraphClientFactory _graphClientFactory;
|
||||
|
||||
public GraphUserSearchService(GraphClientFactory graphClientFactory)
|
||||
{
|
||||
_graphClientFactory = graphClientFactory;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<GraphUserResult>> SearchUsersAsync(
|
||||
string clientId,
|
||||
string query,
|
||||
int maxResults = 10,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query) || query.Length < 2)
|
||||
return Array.Empty<GraphUserResult>();
|
||||
|
||||
var graphClient = await _graphClientFactory.CreateClientAsync(clientId, ct);
|
||||
|
||||
// Use $filter with startsWith on displayName and mail.
|
||||
// Graph API requires ConsistencyLevel=eventual for advanced queries.
|
||||
var escapedQuery = query.Replace("'", "''");
|
||||
var response = await graphClient.Users.GetAsync(config =>
|
||||
{
|
||||
config.QueryParameters.Filter =
|
||||
$"startsWith(displayName,'{escapedQuery}') or startsWith(mail,'{escapedQuery}') or startsWith(userPrincipalName,'{escapedQuery}')";
|
||||
config.QueryParameters.Select = new[] { "displayName", "userPrincipalName", "mail" };
|
||||
config.QueryParameters.Top = maxResults;
|
||||
config.QueryParameters.Orderby = new[] { "displayName" };
|
||||
config.Headers.Add("ConsistencyLevel", "eventual");
|
||||
config.QueryParameters.Count = true;
|
||||
}, ct);
|
||||
|
||||
if (response?.Value is null)
|
||||
return Array.Empty<GraphUserResult>();
|
||||
|
||||
return response.Value
|
||||
.Select(u => new GraphUserResult(
|
||||
DisplayName: u.DisplayName ?? u.UserPrincipalName ?? "Unknown",
|
||||
UserPrincipalName: u.UserPrincipalName ?? string.Empty,
|
||||
Mail: u.Mail))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Design notes:
|
||||
- Minimum 2 characters before searching (prevents overly broad queries)
|
||||
- Uses startsWith filter on displayName, mail, and UPN for broad matching
|
||||
- Single quotes in query are escaped to prevent OData injection
|
||||
- ConsistencyLevel=eventual header required for startsWith filter on directory objects
|
||||
- Count=true is required alongside ConsistencyLevel=eventual
|
||||
- Returns max 10 results by default (people picker dropdown)
|
||||
- Uses existing GraphClientFactory which handles MSAL token acquisition
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-incremental 2>&1 | tail -5</automated>
|
||||
</verify>
|
||||
<done>GraphUserSearchService.cs compiles, implements IGraphUserSearchService, uses GraphClientFactory for auth, queries Graph /users with startsWith filter, returns GraphUserResult list.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- `dotnet build SharepointToolbox/SharepointToolbox.csproj` succeeds with 0 errors
|
||||
- GraphUserSearchService implements IGraphUserSearchService
|
||||
- Uses GraphClientFactory.CreateClientAsync (not raw HTTP)
|
||||
- Handles empty/short queries gracefully (returns empty list)
|
||||
- Filter uses startsWith on displayName, mail, and UPN
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
The Graph people search service is implemented: given a partial name/email query, it returns matching tenant users via Microsoft Graph API. Ready for ViewModel consumption in 07-04 (people picker debounced autocomplete).
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/07-user-access-audit/07-03-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user