diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetGeographicLocationSessionsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetGeographicLocationSessionsFunction.cs index 7e071625..e68c0685 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetGeographicLocationSessionsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetGeographicLocationSessionsFunction.cs @@ -50,9 +50,13 @@ namespace AutopilotMonitor.Functions.Functions.Metrics if (days > 365) days = 365; var groupBy = query["groupBy"] ?? "city"; + // Default response shape is lean (~15 fields/row) so MCP-driven callers fit + // many sessions in a single response. UI sets ?full=1 to opt back into the + // full LocationSessionRow shape. + var full = string.Equals(query["full"], "1", StringComparison.Ordinal); - _logger.LogInformation("Fetching sessions for location '{LocationKey}' tenant {TenantId} ({Days}d, groupBy={GroupBy})", - locationKey, tenantId, days, groupBy); + _logger.LogInformation("Fetching sessions for location '{LocationKey}' tenant {TenantId} ({Days}d, groupBy={GroupBy}, full={Full})", + locationKey, tenantId, days, groupBy, full); var cutoff = DateTime.UtcNow.AddDays(-days); var sessions = await _maintenanceRepo.GetSessionsByDateRangeAsync(cutoff, DateTime.UtcNow.AddDays(1), tenantId); @@ -62,7 +66,15 @@ namespace AutopilotMonitor.Functions.Functions.Metrics var rows = BuildRows(filtered, appSummaries); var response = req.CreateResponse(HttpStatusCode.OK); - await response.WriteAsJsonAsync(new { success = true, sessions = rows, totalCount = rows.Count }); + if (full) + { + await response.WriteAsJsonAsync(new { success = true, sessions = rows, totalCount = rows.Count }); + } + else + { + var lean = rows.Select(ToLeanRow).ToList(); + await response.WriteAsJsonAsync(new { success = true, sessions = lean, totalCount = lean.Count }); + } return response; } catch (Exception ex) @@ -99,5 +111,33 @@ namespace AutopilotMonitor.Functions.Functions.Metrics } return rows; } + + /// + /// Lean projection used as the default response shape. Fields chosen to support + /// the typical MCP triage flow (which session, when, where, how big a deal, was + /// DO active) while keeping per-row payload an order of magnitude smaller than + /// the full . Callers needing the full shape + /// pass ?full=1. + /// + internal static object ToLeanRow(LocationSessionRow r) => new + { + sessionId = r.SessionId, + tenantId = r.TenantId, + serialNumber = r.SerialNumber, + deviceName = r.DeviceName, + manufacturer = r.Manufacturer, + model = r.Model, + startedAt = r.StartedAt, + completedAt = r.CompletedAt, + status = r.Status, + failureReason = r.FailureReason, + durationSeconds = r.DurationSeconds, + enrollmentType = r.EnrollmentType, + geoCountry = r.GeoCountry, + geoCity = r.GeoCity, + totalAppCount = r.TotalAppCount, + hasDoTelemetry = r.HasDoTelemetry, + doPercentPeerCaching = r.DoPercentPeerCaching, + }; } } diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetGlobalGeographicLocationSessionsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetGlobalGeographicLocationSessionsFunction.cs index 74c1cbcb..b447bfcd 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetGlobalGeographicLocationSessionsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetGlobalGeographicLocationSessionsFunction.cs @@ -47,19 +47,35 @@ namespace AutopilotMonitor.Functions.Functions.Metrics days = parsedDays; var groupBy = query["groupBy"] ?? "city"; + var full = string.Equals(query["full"], "1", StringComparison.Ordinal); + // Optional tenantId filter so a GA can scope a single tenant's sessions + // for a location without leaving the cross-tenant endpoint. + var filterTenantId = query["tenantId"]; - _logger.LogInformation("Fetching global sessions for location '{LocationKey}' ({Days}d, groupBy={GroupBy}) (User: {UserEmail})", - locationKey, days, groupBy, userEmail); + _logger.LogInformation("Fetching global sessions for location '{LocationKey}' ({Days}d, groupBy={GroupBy}, full={Full}, tenantFilter={Tenant}) (User: {UserEmail})", + locationKey, days, groupBy, full, filterTenantId ?? "(none)", userEmail); var cutoff = DateTime.UtcNow.AddDays(-days); - var sessions = await _maintenanceRepo.GetSessionsByDateRangeAsync(cutoff, DateTime.UtcNow.AddDays(1)); + var sessions = string.IsNullOrEmpty(filterTenantId) + ? await _maintenanceRepo.GetSessionsByDateRangeAsync(cutoff, DateTime.UtcNow.AddDays(1)) + : await _maintenanceRepo.GetSessionsByDateRangeAsync(cutoff, DateTime.UtcNow.AddDays(1), filterTenantId); var filtered = GetGeographicLocationSessionsFunction.FilterSessionsByLocation(sessions, locationKey, groupBy); - var appSummaries = await _metricsRepo.GetAllAppInstallSummariesAsync(); + var appSummaries = string.IsNullOrEmpty(filterTenantId) + ? await _metricsRepo.GetAllAppInstallSummariesAsync() + : await _metricsRepo.GetAppInstallSummariesByTenantAsync(filterTenantId); var rows = GetGeographicLocationSessionsFunction.BuildRows(filtered, appSummaries); var response = req.CreateResponse(HttpStatusCode.OK); - await response.WriteAsJsonAsync(new { success = true, sessions = rows, totalCount = rows.Count }); + if (full) + { + await response.WriteAsJsonAsync(new { success = true, sessions = rows, totalCount = rows.Count }); + } + else + { + var lean = rows.Select(GetGeographicLocationSessionsFunction.ToLeanRow).ToList(); + await response.WriteAsJsonAsync(new { success = true, sessions = lean, totalCount = lean.Count }); + } return response; } catch (Exception ex) diff --git a/src/Web/autopilot-monitor-web/lib/api.ts b/src/Web/autopilot-monitor-web/lib/api.ts index dcbca23d..cfb90665 100644 --- a/src/Web/autopilot-monitor-web/lib/api.ts +++ b/src/Web/autopilot-monitor-web/lib/api.ts @@ -176,10 +176,13 @@ export const api = { `${API_BASE_URL}/api/metrics/geographic${qs({ tenantId, days: String(days), groupBy })}`, globalGeographic: (days: number, groupBy: string, tenantId?: string) => `${API_BASE_URL}/api/global/metrics/geographic${qs({ days: String(days), groupBy, tenantId })}`, + // ?full=1 — UI needs the full LocationSessionRow shape for the page table; default + // is now lean (~15 fields/row) so MCP-driven callers fit many sessions in a single + // response. geographicSessions: (tenantId: string, days: number, groupBy: string, locationKey: string) => - `${API_BASE_URL}/api/metrics/geographic/sessions${qs({ tenantId, days: String(days), groupBy, locationKey })}`, + `${API_BASE_URL}/api/metrics/geographic/sessions${qs({ tenantId, days: String(days), groupBy, locationKey, full: '1' })}`, globalGeographicSessions: (days: number, groupBy: string, locationKey: string) => - `${API_BASE_URL}/api/global/metrics/geographic/sessions${qs({ days: String(days), groupBy, locationKey })}`, + `${API_BASE_URL}/api/global/metrics/geographic/sessions${qs({ days: String(days), groupBy, locationKey, full: '1' })}`, platform: (opts?: { limit?: number; days?: number }) => `${API_BASE_URL}/api/global/metrics/platform${qs({ limit: opts?.limit?.toString(),