diff --git a/src/Agent/AutopilotMonitor.Agent.V2/buildcounter.txt b/src/Agent/AutopilotMonitor.Agent.V2/buildcounter.txt index 361dbd8b..a01f8275 100644 --- a/src/Agent/AutopilotMonitor.Agent.V2/buildcounter.txt +++ b/src/Agent/AutopilotMonitor.Agent.V2/buildcounter.txt @@ -1 +1 @@ -770 +771 diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Admin/TenantOffboardFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Admin/TenantOffboardFunction.cs index 34e80837..a586a84c 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Admin/TenantOffboardFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Admin/TenantOffboardFunction.cs @@ -19,15 +19,18 @@ public class TenantOffboardFunction { private readonly ILogger _logger; private readonly IMaintenanceRepository _maintenanceRepo; + private readonly IConfigRepository _configRepo; private readonly OpsEventService _opsEventService; public TenantOffboardFunction( ILogger logger, IMaintenanceRepository maintenanceRepo, + IConfigRepository configRepo, OpsEventService opsEventService) { _logger = logger; _maintenanceRepo = maintenanceRepo; + _configRepo = configRepo; _opsEventService = opsEventService; } @@ -65,6 +68,19 @@ public class TenantOffboardFunction var result = new OffboardResult { TenantId = requestCtx.TargetTenantId, InitiatedBy = upn!, InitiatedAt = DateTime.UtcNow }; + // Capture domain name BEFORE deletion — tenant config is wiped by DeleteAllTenantDataAsync, + // and the ops alert / Telegram message is friendlier with the domain than the bare GUID. + string? domainName = null; + try + { + var tenantConfig = await _configRepo.GetTenantConfigurationAsync(requestCtx.TargetTenantId); + domainName = tenantConfig?.DomainName; + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to read tenant config for {TenantId} before offboard; proceeding without domain name", requestCtx.TargetTenantId); + } + try { var deletedCounts = await _maintenanceRepo.DeleteAllTenantDataAsync(requestCtx.TargetTenantId); @@ -87,7 +103,7 @@ public class TenantOffboardFunction upn!, details); - await _opsEventService.RecordTenantOffboardedAsync(requestCtx.TargetTenantId, upn!, deletedCounts); + await _opsEventService.RecordTenantOffboardedAsync(requestCtx.TargetTenantId, upn!, deletedCounts, domainName); var okResponse = req.CreateResponse(HttpStatusCode.OK); await okResponse.WriteAsJsonAsync(result); diff --git a/src/Backend/AutopilotMonitor.Functions/Services/OpsEventService.cs b/src/Backend/AutopilotMonitor.Functions/Services/OpsEventService.cs index 52a0fbd4..114e3273 100644 --- a/src/Backend/AutopilotMonitor.Functions/Services/OpsEventService.cs +++ b/src/Backend/AutopilotMonitor.Functions/Services/OpsEventService.cs @@ -256,10 +256,24 @@ namespace AutopilotMonitor.Functions.Services // ── Tenant ───────────────────────────────────────────────────────────── - public Task RecordTenantOffboardedAsync(string tenantId, string performedBy, Dictionary deletedCounts) - => WriteAsync(OpsEventCategory.Tenant, "TenantOffboarded", OpsEventSeverity.Warning, - $"Tenant {tenantId} offboarded — all data deleted", - tenantId, performedBy, deletedCounts); + public Task RecordTenantOffboardedAsync(string tenantId, string performedBy, Dictionary deletedCounts, string? domainName = null) + { + var tenantLabel = string.IsNullOrWhiteSpace(domainName) + ? tenantId + : $"{domainName} ({tenantId})"; + + // Wrap deletedCounts + domainName so the OpsEvents details panel still surfaces the + // per-table counts AND the domain (needed because Table Storage is gone by emit time). + var details = new Dictionary + { + ["domainName"] = domainName, + ["deletedCounts"] = deletedCounts, + }; + + return WriteAsync(OpsEventCategory.Tenant, "TenantOffboarded", OpsEventSeverity.Warning, + $"Tenant {tenantLabel} offboarded — all data deleted", + tenantId, performedBy, details); + } // ── Agent ──────────────────────────────────────────────────────────────