mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-25 05:00:07 +00:00
feat(backend,web): geographic-sessions default-lean (?full=1 opt-out)
The full LocationSessionRow shape is ~40 fields per session — for locations with dozens of sessions the response easily blew past 100KB and tripped MCP token caps. Most fields aren't needed for the typical "which sessions enrolled from here" triage flow. Default is now a 17-field lean projection (sessionId, device basics, status/duration, geo, app/DO summary). UI keeps the full shape via ?full=1 — single-line addition in lib/api.ts. Also adds an optional tenantId filter on /api/global/metrics/geographic/ sessions so a GA can scope a single tenant's sessions for a location without leaving the global endpoint. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+43
-3
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="LocationSessionRow"/>. Callers needing the full shape
|
||||
/// pass <c>?full=1</c>.
|
||||
/// </summary>
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+21
-5
@@ -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)
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user