--- phase: 08-simplified-permissions plan: 04 type: execute wave: 3 depends_on: ["08-01"] files_modified: - SharepointToolbox/Services/Export/HtmlExportService.cs - SharepointToolbox/Services/Export/CsvExportService.cs autonomous: true requirements: - SIMP-01 - SIMP-02 must_haves: truths: - "HTML export includes a Simplified Labels column and color-coded permission cells when simplified entries are provided" - "HTML summary section shows risk level counts with color indicators" - "CSV export includes a Simplified Labels column after the raw Permission Levels column" - "Both export services accept SimplifiedPermissionEntry via overloaded methods — original PermissionEntry methods remain unchanged" artifacts: - path: "SharepointToolbox/Services/Export/HtmlExportService.cs" provides: "HTML export with simplified labels and risk-level color coding" contains: "BuildHtml.*SimplifiedPermissionEntry" - path: "SharepointToolbox/Services/Export/CsvExportService.cs" provides: "CSV export with simplified labels column" contains: "BuildCsv.*SimplifiedPermissionEntry" key_links: - from: "SharepointToolbox/Services/Export/HtmlExportService.cs" to: "SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs" via: "Overloaded BuildHtml and WriteAsync methods" pattern: "SimplifiedPermissionEntry" - from: "SharepointToolbox/Services/Export/CsvExportService.cs" to: "SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs" via: "Overloaded BuildCsv and WriteAsync methods" pattern: "SimplifiedPermissionEntry" --- Add simplified-mode export support to HtmlExportService and CsvExportService. Both services get new overloaded methods that accept SimplifiedPermissionEntry and include plain-language labels and risk-level color coding. Original PermissionEntry methods are NOT modified. Purpose: Exports reflect the simplified view (SIMP-01 labels, SIMP-02 colors) so exported reports match what the user sees in the UI. Output: Updated HtmlExportService.cs, Updated CsvExportService.cs @C:/Users/dev/.claude/get-shit-done/workflows/execute-plan.md @C:/Users/dev/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/08-simplified-permissions/08-01-SUMMARY.md From SharepointToolbox/Core/Models/RiskLevel.cs: ```csharp public enum RiskLevel { High, Medium, Low, ReadOnly } ``` From SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs: ```csharp public class SimplifiedPermissionEntry { public PermissionEntry Inner { get; } public string SimplifiedLabels { get; } public RiskLevel RiskLevel { get; } // Passthrough: ObjectType, Title, Url, HasUniquePermissions, Users, UserLogins, // PermissionLevels, GrantedThrough, PrincipalType } ``` 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 Build(IEnumerable entries); } ``` From SharepointToolbox/Services/Export/CsvExportService.cs: ```csharp public class CsvExportService { public string BuildCsv(IReadOnlyList entries); public async Task WriteAsync(IReadOnlyList entries, string filePath, CancellationToken ct); } ``` From SharepointToolbox/Services/Export/HtmlExportService.cs: ```csharp public class HtmlExportService { public string BuildHtml(IReadOnlyList entries); public async Task WriteAsync(IReadOnlyList entries, string filePath, CancellationToken ct); } ``` Task 1: Add simplified export overloads to CsvExportService SharepointToolbox/Services/Export/CsvExportService.cs Modify `SharepointToolbox/Services/Export/CsvExportService.cs`. Add `using SharepointToolbox.Core.Models;` if not already present (it is). Keep ALL existing methods unchanged. Add these new overloaded methods: ```csharp /// /// Header for simplified CSV export — includes "SimplifiedLabels" and "RiskLevel" columns. /// private const string SimplifiedHeader = "\"Object\",\"Title\",\"URL\",\"HasUniquePermissions\",\"Users\",\"UserLogins\",\"Type\",\"Permissions\",\"SimplifiedLabels\",\"RiskLevel\",\"GrantedThrough\""; /// /// Builds a CSV string from simplified permission entries. /// Includes SimplifiedLabels and RiskLevel columns after raw Permissions. /// Uses the same merge logic as the standard BuildCsv. /// public string BuildCsv(IReadOnlyList entries) { var sb = new StringBuilder(); sb.AppendLine(SimplifiedHeader); var merged = entries .GroupBy(e => (e.Users, e.PermissionLevels, e.GrantedThrough)) .Select(g => new { ObjectType = g.First().ObjectType, Title = string.Join(" | ", g.Select(e => e.Title).Distinct()), Url = string.Join(" | ", g.Select(e => e.Url).Distinct()), HasUnique = g.First().HasUniquePermissions, Users = g.Key.Users, UserLogins = g.First().UserLogins, PrincipalType = g.First().PrincipalType, Permissions = g.Key.PermissionLevels, SimplifiedLabels = g.First().SimplifiedLabels, RiskLevel = g.First().RiskLevel.ToString(), GrantedThrough = g.Key.GrantedThrough }); foreach (var row in merged) sb.AppendLine(string.Join(",", new[] { Csv(row.ObjectType), Csv(row.Title), Csv(row.Url), Csv(row.HasUnique.ToString()), Csv(row.Users), Csv(row.UserLogins), Csv(row.PrincipalType), Csv(row.Permissions), Csv(row.SimplifiedLabels), Csv(row.RiskLevel), Csv(row.GrantedThrough) })); return sb.ToString(); } /// /// Writes simplified CSV to the specified file path. /// public async Task WriteAsync(IReadOnlyList entries, string filePath, CancellationToken ct) { var csv = BuildCsv(entries); await File.WriteAllTextAsync(filePath, csv, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true), ct); } ``` Do NOT modify the existing `BuildCsv(IReadOnlyList)` or `WriteAsync(IReadOnlyList, ...)` methods. The new methods are overloads (same name, different parameter type). cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-incremental 2>&1 | tail -5 CsvExportService has overloaded BuildCsv and WriteAsync accepting SimplifiedPermissionEntry. CSV includes SimplifiedLabels and RiskLevel columns. Original PermissionEntry methods unchanged. Task 2: Add simplified export overloads to HtmlExportService SharepointToolbox/Services/Export/HtmlExportService.cs Modify `SharepointToolbox/Services/Export/HtmlExportService.cs`. Keep ALL existing methods unchanged. Add these new overloaded methods and helpers: Add to the class a risk-level-to-CSS-color mapping method: ```csharp /// Returns inline CSS background and text color for a risk level. private static (string bg, string text, string border) RiskLevelColors(RiskLevel level) => level switch { RiskLevel.High => ("#FEE2E2", "#991B1B", "#FECACA"), RiskLevel.Medium => ("#FEF3C7", "#92400E", "#FDE68A"), RiskLevel.Low => ("#D1FAE5", "#065F46", "#A7F3D0"), RiskLevel.ReadOnly => ("#DBEAFE", "#1E40AF", "#BFDBFE"), _ => ("#F3F4F6", "#374151", "#E5E7EB") }; ``` Add the simplified BuildHtml overload. This is a full method — include the complete implementation. It extends the existing HTML template with: - Risk-level summary cards (instead of just stats) - A "Simplified Labels" column in the table - Color-coded risk badges on each row ```csharp /// /// Builds a self-contained HTML string from simplified permission entries. /// Includes risk-level summary cards, color-coded rows, and simplified labels column. /// public string BuildHtml(IReadOnlyList entries) { var summaries = PermissionSummaryBuilder.Build(entries); var totalEntries = entries.Count; var uniquePermSets = entries.Select(e => e.PermissionLevels).Distinct().Count(); var distinctUsers = entries .SelectMany(e => e.UserLogins.Split(';', StringSplitOptions.RemoveEmptyEntries)) .Select(u => u.Trim()) .Where(u => u.Length > 0) .Distinct() .Count(); var sb = new StringBuilder(); sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine("SharePoint Permissions Report (Simplified)"); sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine("

SharePoint Permissions Report (Simplified)

"); // Stats cards sb.AppendLine("
"); sb.AppendLine($"
{totalEntries}
Total Entries
"); sb.AppendLine($"
{uniquePermSets}
Unique Permission Sets
"); sb.AppendLine($"
{distinctUsers}
Distinct Users/Groups
"); sb.AppendLine("
"); // Risk-level summary cards sb.AppendLine("
"); foreach (var summary in summaries) { var (bg, text, border) = RiskLevelColors(summary.RiskLevel); sb.AppendLine($"
"); sb.AppendLine($"
{summary.Count}
"); sb.AppendLine($"
{HtmlEncode(summary.Label)}
"); sb.AppendLine($"
{summary.DistinctUsers} user(s)
"); sb.AppendLine("
"); } sb.AppendLine("
"); // Filter input sb.AppendLine("
"); sb.AppendLine(" "); sb.AppendLine("
"); // Table with simplified columns sb.AppendLine("
"); sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine(" "); sb.AppendLine(""); sb.AppendLine(""); foreach (var entry in entries) { var typeCss = ObjectTypeCss(entry.ObjectType); var uniqueCss = entry.HasUniquePermissions ? "badge unique" : "badge inherited"; var uniqueLbl = entry.HasUniquePermissions ? "Unique" : "Inherited"; var (riskBg, riskText, riskBorder) = RiskLevelColors(entry.RiskLevel); var logins = entry.UserLogins.Split(';', StringSplitOptions.RemoveEmptyEntries); var names = entry.Inner.Users.Split(';', StringSplitOptions.RemoveEmptyEntries); var pillsBuilder = new StringBuilder(); for (int i = 0; i < logins.Length; i++) { var login = logins[i].Trim(); var name = i < names.Length ? names[i].Trim() : login; var isExt = login.Contains("#EXT#", StringComparison.OrdinalIgnoreCase); var pillCss = isExt ? "user-pill external-user" : "user-pill"; pillsBuilder.Append($"{HtmlEncode(name)}"); } sb.AppendLine(""); sb.AppendLine($" "); sb.AppendLine($" "); sb.AppendLine($" "); sb.AppendLine($" "); sb.AppendLine($" "); sb.AppendLine($" "); sb.AppendLine($" "); sb.AppendLine($" "); sb.AppendLine($" "); sb.AppendLine(""); } sb.AppendLine(""); sb.AppendLine("
ObjectTitleURLUniqueUsers/GroupsPermission LevelSimplifiedRiskGranted Through
{HtmlEncode(entry.ObjectType)}{HtmlEncode(entry.Title)}Link{uniqueLbl}{pillsBuilder}{HtmlEncode(entry.PermissionLevels)}{HtmlEncode(entry.SimplifiedLabels)}{HtmlEncode(entry.RiskLevel.ToString())}{HtmlEncode(entry.GrantedThrough)}
"); sb.AppendLine("
"); sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine(""); return sb.ToString(); } /// /// Writes the simplified HTML report to the specified file path. /// public async Task WriteAsync(IReadOnlyList entries, string filePath, CancellationToken ct) { var html = BuildHtml(entries); await File.WriteAllTextAsync(filePath, html, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), ct); } ``` Add the required using statements at the top of the file: ```csharp using SharepointToolbox.Core.Models; // Already present ``` Note: PermissionSummaryBuilder is in the SharepointToolbox.Core.Models namespace so no additional using is needed. Do NOT modify the existing `BuildHtml(IReadOnlyList)` or `WriteAsync(IReadOnlyList, ...)` methods. The new methods are overloads.
cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-incremental 2>&1 | tail -5 HtmlExportService has overloaded BuildHtml and WriteAsync accepting SimplifiedPermissionEntry. HTML includes risk-level summary cards, Simplified column, and color-coded Risk badges. CsvExportService has overloaded methods with SimplifiedLabels and RiskLevel columns. Original methods for PermissionEntry remain unchanged.
- `dotnet build SharepointToolbox/SharepointToolbox.csproj` succeeds with 0 errors - HtmlExportService has both `BuildHtml(IReadOnlyList)` and `BuildHtml(IReadOnlyList)` - CsvExportService has both `BuildCsv(IReadOnlyList)` and `BuildCsv(IReadOnlyList)` - Simplified HTML output includes risk-card section and Risk column - Simplified CSV output includes SimplifiedLabels and RiskLevel headers Both export services support simplified mode. The PermissionsViewModel export commands (which will be updated to pass SimplifiedResults when IsSimplifiedMode is true — wired in plan 08-05) can produce exports that match the simplified UI view. Original export paths for non-simplified mode remain untouched. After completion, create `.planning/phases/08-simplified-permissions/08-04-SUMMARY.md`