Files
Sharepoint-Toolbox/.planning/phases/08-simplified-permissions/08-02-PLAN.md
Dev c871effa87 docs(08-simplified-permissions): create phase plan (6 plans, 5 waves)
Plans cover plain-language permission labels, risk-level color coding,
summary counts, detail-level toggle, export integration, and unit tests.
PermissionEntry record is NOT modified — uses wrapper pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:00:08 +02:00

266 lines
10 KiB
Markdown

---
phase: 08-simplified-permissions
plan: 02
type: execute
wave: 2
depends_on: ["08-01"]
files_modified:
- SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs
autonomous: true
requirements:
- SIMP-01
- SIMP-02
- SIMP-03
must_haves:
truths:
- "IsSimplifiedMode toggle switches between raw and simplified permission labels in the DataGrid"
- "IsDetailView toggle controls whether individual rows are shown or collapsed into summary rows"
- "Toggling modes does NOT re-run the scan — it re-renders from existing Results data"
- "Summary counts per risk level are available as observable properties when simplified mode is on"
- "SimplifiedResults collection is computed from Results whenever Results changes or mode toggles"
- "ActiveItemsSource provides the correct collection for DataGrid binding depending on current mode"
artifacts:
- path: "SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs"
provides: "Extended PermissionsViewModel with simplified mode, detail toggle, and summary"
contains: "IsSimplifiedMode"
key_links:
- from: "SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs"
to: "SharepointToolbox/Core/Helpers/PermissionLevelMapping.cs"
via: "SimplifiedPermissionEntry.WrapAll uses PermissionLevelMapping internally"
pattern: "SimplifiedPermissionEntry\\.WrapAll"
- from: "SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs"
to: "SharepointToolbox/Core/Models/PermissionSummary.cs"
via: "PermissionSummaryBuilder.Build computes summary from simplified entries"
pattern: "PermissionSummaryBuilder\\.Build"
---
<objective>
Extend PermissionsViewModel with IsSimplifiedMode toggle, IsDetailView toggle, SimplifiedResults collection, summary statistics, and an ActiveItemsSource that the DataGrid binds to. All toggles re-render from cached data — no re-scan required.
Purpose: This is the ViewModel logic for all three SIMP requirements. The View (08-03) binds to these new properties.
Output: Updated PermissionsViewModel.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/08-simplified-permissions/08-01-SUMMARY.md
<interfaces>
<!-- From 08-01: New types this plan consumes -->
From SharepointToolbox/Core/Models/RiskLevel.cs:
```csharp
public enum RiskLevel { High, Medium, Low, ReadOnly }
```
From SharepointToolbox/Core/Helpers/PermissionLevelMapping.cs:
```csharp
public static class PermissionLevelMapping
{
public record MappingResult(string Label, RiskLevel RiskLevel);
public static MappingResult GetMapping(string roleName);
public static IReadOnlyList<MappingResult> GetMappings(string permissionLevels);
public static RiskLevel GetHighestRisk(string permissionLevels);
public static string GetSimplifiedLabels(string permissionLevels);
}
```
From SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs:
```csharp
public class SimplifiedPermissionEntry
{
public PermissionEntry Inner { get; }
public string SimplifiedLabels { get; }
public RiskLevel RiskLevel { get; }
public IReadOnlyList<PermissionLevelMapping.MappingResult> Mappings { get; }
// Passthrough: ObjectType, Title, Url, HasUniquePermissions, Users, UserLogins,
// PermissionLevels, GrantedThrough, PrincipalType
public static IReadOnlyList<SimplifiedPermissionEntry> WrapAll(IEnumerable<PermissionEntry> entries);
}
```
From SharepointToolbox/Core/Models/PermissionSummary.cs:
```csharp
public record PermissionSummary(string Label, RiskLevel RiskLevel, int Count, int DistinctUsers);
public static class PermissionSummaryBuilder
{
public static IReadOnlyList<PermissionSummary> Build(IEnumerable<SimplifiedPermissionEntry> entries);
}
```
<!-- Current PermissionsViewModel — the file being modified -->
From SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs:
```csharp
public partial class PermissionsViewModel : FeatureViewModelBase
{
// Existing fields and services — unchanged
[ObservableProperty] private ObservableCollection<PermissionEntry> _results = new();
// Existing commands — unchanged
public IAsyncRelayCommand ExportCsvCommand { get; }
public IAsyncRelayCommand ExportHtmlCommand { get; }
public RelayCommand OpenSitePickerCommand { get; }
// Full constructor and test constructor (internal)
}
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Add simplified mode properties and summary computation to PermissionsViewModel</name>
<files>SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs</files>
<action>
Modify `SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs` to add simplified mode support. Add the following new using statements at the top:
```csharp
using SharepointToolbox.Core.Helpers;
```
Add these new observable properties to the class (in the "Observable properties" section):
```csharp
/// <summary>
/// When true, displays simplified plain-language labels instead of raw SharePoint role names.
/// Toggling does not re-run the scan.
/// </summary>
[ObservableProperty]
private bool _isSimplifiedMode;
/// <summary>
/// When true, shows individual item-level rows (detailed view).
/// When false, shows only summary rows grouped by risk level (simple view).
/// Only meaningful when IsSimplifiedMode is true.
/// </summary>
[ObservableProperty]
private bool _isDetailView = true;
```
Add these computed collection properties (NOT ObservableProperty — manually raised):
```csharp
/// <summary>
/// Simplified wrappers computed from Results. Rebuilt when Results changes.
/// </summary>
private IReadOnlyList<SimplifiedPermissionEntry> _simplifiedResults = Array.Empty<SimplifiedPermissionEntry>();
public IReadOnlyList<SimplifiedPermissionEntry> SimplifiedResults
{
get => _simplifiedResults;
private set => SetProperty(ref _simplifiedResults, value);
}
/// <summary>
/// Summary counts grouped by risk level. Rebuilt when SimplifiedResults changes.
/// </summary>
private IReadOnlyList<PermissionSummary> _summaries = Array.Empty<PermissionSummary>();
public IReadOnlyList<PermissionSummary> Summaries
{
get => _summaries;
private set => SetProperty(ref _summaries, value);
}
/// <summary>
/// The collection the DataGrid actually binds to. Returns:
/// - Results (raw) when simplified mode is OFF
/// - SimplifiedResults when simplified mode is ON and detail view is ON
/// - (View handles summary display separately via Summaries property)
/// </summary>
public object ActiveItemsSource => IsSimplifiedMode
? (object)SimplifiedResults
: Results;
```
Add partial methods triggered by property changes:
```csharp
partial void OnIsSimplifiedModeChanged(bool value)
{
if (value && Results.Count > 0)
RebuildSimplifiedData();
OnPropertyChanged(nameof(ActiveItemsSource));
}
partial void OnIsDetailViewChanged(bool value)
{
OnPropertyChanged(nameof(ActiveItemsSource));
}
```
Add a private method to rebuild simplified data from existing Results:
```csharp
/// <summary>
/// Recomputes SimplifiedResults and Summaries from the current Results collection.
/// Called when Results changes or when simplified mode is toggled on.
/// </summary>
private void RebuildSimplifiedData()
{
SimplifiedResults = SimplifiedPermissionEntry.WrapAll(Results);
Summaries = PermissionSummaryBuilder.Build(SimplifiedResults);
}
```
Modify the existing `RunOperationAsync` method: after the line that sets `Results = new ObservableCollection<PermissionEntry>(allEntries);` (both in the dispatcher branch and the else branch), add:
```csharp
if (IsSimplifiedMode)
RebuildSimplifiedData();
OnPropertyChanged(nameof(ActiveItemsSource));
```
So the end of RunOperationAsync becomes (both branches):
```csharp
Results = new ObservableCollection<PermissionEntry>(allEntries);
if (IsSimplifiedMode)
RebuildSimplifiedData();
OnPropertyChanged(nameof(ActiveItemsSource));
```
Modify `OnTenantSwitched` to also reset simplified state:
After `Results = new ObservableCollection<PermissionEntry>();` add:
```csharp
SimplifiedResults = Array.Empty<SimplifiedPermissionEntry>();
Summaries = Array.Empty<PermissionSummary>();
OnPropertyChanged(nameof(ActiveItemsSource));
```
Do NOT change:
- Constructor signatures (both full and test constructors remain unchanged)
- Existing properties (SiteUrl, IncludeInherited, ScanFolders, etc.)
- ExportCsvCommand and ExportHtmlCommand implementations (export updates are in plan 08-04)
- OpenSitePickerCommand
- _hasLocalSiteOverride / OnGlobalSitesChanged logic
</action>
<verify>
<automated>cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-incremental 2>&1 | tail -5</automated>
</verify>
<done>PermissionsViewModel has IsSimplifiedMode, IsDetailView, SimplifiedResults, Summaries, and ActiveItemsSource properties. Toggling IsSimplifiedMode rebuilds simplified data from cached Results without re-scanning. Toggling IsDetailView triggers ActiveItemsSource change notification. Existing tests still compile (no constructor changes).</done>
</task>
</tasks>
<verification>
- `dotnet build SharepointToolbox/SharepointToolbox.csproj` succeeds with 0 errors
- `dotnet test SharepointToolbox.Tests/ --filter PermissionsViewModelTests` passes (no constructor changes)
- PermissionsViewModel has IsSimplifiedMode, IsDetailView, SimplifiedResults, Summaries, ActiveItemsSource
- Toggling IsSimplifiedMode calls RebuildSimplifiedData + raises ActiveItemsSource changed
- RunOperationAsync calls RebuildSimplifiedData when IsSimplifiedMode is true
- OnTenantSwitched resets SimplifiedResults and Summaries
</verification>
<success_criteria>
The ViewModel is the orchestration layer for SIMP-01/02/03. All mode toggles re-render from cached data. The View (08-03) can bind to IsSimplifiedMode, IsDetailView, ActiveItemsSource, and Summaries. Export services (08-04) can access SimplifiedResults and IsSimplifiedMode.
</success_criteria>
<output>
After completion, create `.planning/phases/08-simplified-permissions/08-02-SUMMARY.md`
</output>