chore: archive v1.1 Enhanced Reports milestone
Some checks failed
Release SharePoint Toolbox v2 / release (push) Failing after 14s

v1.1 shipped with 4 phases (25 plans), 10/10 requirements complete:
- Global site selection (toolbar picker, all tabs consume)
- User access audit (Graph people-picker, direct/group/inherited)
- Simplified permissions (plain-language labels, risk levels, detail toggle)
- Storage visualization (LiveCharts2 pie/donut + bar charts)

Post-phase polish: centralized site selection (removed per-tab pickers),
claims prefix stripping, StorageMetrics backfill, chart tooltip fix,
summary stats in app + HTML exports.

205 tests passing, 10,484 LOC.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dev
2026-04-08 10:21:02 +02:00
parent fa793c5489
commit fd442f3b4c
35 changed files with 1062 additions and 760 deletions

View File

@@ -22,11 +22,9 @@ public partial class FolderStructureViewModel : FeatureViewModelBase
private readonly BulkResultCsvExportService _exportService;
private readonly ILogger<FeatureViewModelBase> _logger;
private TenantProfile? _currentProfile;
private bool _hasLocalSiteOverride;
private List<FolderStructureRow>? _validRows;
private BulkOperationSummary<string>? _lastResult;
[ObservableProperty] private string _siteUrl = string.Empty;
[ObservableProperty] private string _libraryTitle = string.Empty;
[ObservableProperty] private string _previewSummary = string.Empty;
[ObservableProperty] private string _resultSummary = string.Empty;
@@ -66,26 +64,6 @@ public partial class FolderStructureViewModel : FeatureViewModelBase
ExportFailedCommand = new AsyncRelayCommand(ExportFailedAsync, () => HasFailures);
}
protected override void OnGlobalSitesChanged(IReadOnlyList<SiteInfo> sites)
{
if (_hasLocalSiteOverride) return;
SiteUrl = sites.Count > 0 ? sites[0].Url : string.Empty;
}
partial void OnSiteUrlChanged(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
_hasLocalSiteOverride = false;
if (GlobalSites.Count > 0)
SiteUrl = GlobalSites[0].Url;
}
else if (GlobalSites.Count == 0 || value != GlobalSites[0].Url)
{
_hasLocalSiteOverride = true;
}
}
private void ImportCsv()
{
var dlg = new OpenFileDialog
@@ -128,26 +106,36 @@ public partial class FolderStructureViewModel : FeatureViewModelBase
if (_currentProfile == null) throw new InvalidOperationException("No tenant connected.");
if (_validRows == null || _validRows.Count == 0)
throw new InvalidOperationException("No valid rows. Import a CSV first.");
if (string.IsNullOrWhiteSpace(SiteUrl))
throw new InvalidOperationException("Site URL is required.");
if (string.IsNullOrWhiteSpace(LibraryTitle))
throw new InvalidOperationException("Library title is required.");
var urls = GlobalSites.Select(s => s.Url).Where(u => !string.IsNullOrWhiteSpace(u)).ToList();
if (urls.Count == 0)
throw new InvalidOperationException("Select at least one site from the toolbar.");
var uniquePaths = FolderStructureService.BuildUniquePaths(_validRows);
var message = string.Format(TranslationSource.Instance["bulk.confirm.message"],
$"{uniquePaths.Count} folders will be created in {LibraryTitle}");
$"{uniquePaths.Count} folders will be created in {LibraryTitle} on {urls.Count} site(s)");
if (ShowConfirmDialog != null && !ShowConfirmDialog(message))
return;
var profile = new TenantProfile
{
Name = _currentProfile.Name,
TenantUrl = SiteUrl,
ClientId = _currentProfile.ClientId,
};
var ctx = await _sessionManager.GetOrCreateContextAsync(profile, ct);
var allResults = new List<BulkItemResult<string>>();
_lastResult = await _folderService.CreateFoldersAsync(ctx, LibraryTitle, _validRows, progress, ct);
foreach (var url in urls)
{
var profile = new TenantProfile
{
Name = _currentProfile.Name,
TenantUrl = url,
ClientId = _currentProfile.ClientId,
};
var ctx = await _sessionManager.GetOrCreateContextAsync(profile, ct);
var result = await _folderService.CreateFoldersAsync(ctx, LibraryTitle, _validRows, progress, ct);
allResults.AddRange(result.Results);
}
_lastResult = new BulkOperationSummary<string>(allResults);
await Application.Current.Dispatcher.InvokeAsync(() =>
{
@@ -175,8 +163,6 @@ public partial class FolderStructureViewModel : FeatureViewModelBase
protected override void OnTenantSwitched(TenantProfile profile)
{
_currentProfile = profile;
_hasLocalSiteOverride = false;
SiteUrl = string.Empty;
LibraryTitle = string.Empty;
PreviewRows = new();
_validRows = null;