mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 04:32:32 +00:00
fix(metrics): keep full failure-reason text for fleet-health display
BuildFleetFailureReasons truncated each reason to a 50-char prefix + "..." and used that as both the grouping key AND the display value. The full text never reached the UI, so its tooltip/tap-to-expand had nothing to reveal and near-identical rows were indistinguishable past the cut. Group by the 50-char prefix as before (variants still collapse into one row), but carry the longest full reason as the display value. The UI truncates and expands it on demand, so the complete text is now available end-to-end. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -213,30 +213,31 @@ public class FleetHealthPayloadTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FailureReasons_TruncateLongReasons_AndTakeTopFive()
|
||||
public void FailureReasons_GroupByPrefix_KeepFullestText_AndTakeTopFive()
|
||||
{
|
||||
var sessions = new List<SessionSummary>();
|
||||
// 6 distinct reasons with descending frequency 6..1 → only top 5 kept.
|
||||
for (int freq = 6; freq >= 1; freq--)
|
||||
// 5 distinct short reasons with descending frequency 5..1.
|
||||
for (int freq = 5; freq >= 1; freq--)
|
||||
for (int i = 0; i < freq; i++)
|
||||
sessions.Add(S(SessionStatus.Failed, failureReason: $"Reason {freq}"));
|
||||
// A null reason folds into "Unknown".
|
||||
sessions.Add(S(SessionStatus.Failed, failureReason: null));
|
||||
// An over-long reason is collapsed to a 50-char prefix + ellipsis.
|
||||
var longReason = new string('x', 60);
|
||||
sessions.Add(S(SessionStatus.Failed, failureReason: longReason));
|
||||
|
||||
// Two variants that share the first 50 characters collapse into one group;
|
||||
// the longest variant is kept for display so the UI can show the full text.
|
||||
var prefix = new string('x', 50);
|
||||
var shortVariant = prefix + " short";
|
||||
var longVariant = prefix + " a much longer tail that must survive intact";
|
||||
for (int i = 0; i < 6; i++)
|
||||
sessions.Add(S(SessionStatus.Failed, failureReason: shortVariant));
|
||||
sessions.Add(S(SessionStatus.Failed, failureReason: longVariant));
|
||||
|
||||
var payload = MetricsMath.BuildFleetHealthPayload(sessions, days: 30);
|
||||
|
||||
Assert.Equal(5, payload.FailureReasons.Count);
|
||||
Assert.Equal("Reason 6", payload.FailureReasons[0].Reason);
|
||||
Assert.Equal(6, payload.FailureReasons[0].Count);
|
||||
// The collapsed long reason exists in the tally (50 chars + "...").
|
||||
var collapsed = new string('x', 50) + "...";
|
||||
Assert.True(
|
||||
sessions.Count(s => s.FailureReason == longReason) == 1,
|
||||
"sanity: exactly one long-reason session");
|
||||
Assert.Equal(53, collapsed.Length);
|
||||
// The grouped cluster (6 + 1 = 7) outranks every "Reason N".
|
||||
Assert.Equal(7, payload.FailureReasons[0].Count);
|
||||
// The full, longest text survives end-to-end — no 50-char truncation.
|
||||
Assert.Equal(longVariant, payload.FailureReasons[0].Reason);
|
||||
Assert.DoesNotContain("...", payload.FailureReasons[0].Reason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -194,19 +194,29 @@ public static class MetricsMath
|
||||
|
||||
private static List<FleetFailureReason> BuildFleetFailureReasons(IReadOnlyList<SessionSummary> sessions)
|
||||
{
|
||||
var reasons = new Dictionary<string, int>();
|
||||
// Group near-identical messages by a 50-char prefix so variants collapse
|
||||
// into one row, but keep the longest full reason as the display value. The
|
||||
// UI truncates and expands it on demand, so the complete text must survive.
|
||||
var groups = new Dictionary<string, (string Display, int Count)>();
|
||||
foreach (var s in sessions)
|
||||
{
|
||||
if (s.Status != SessionStatus.Failed) continue;
|
||||
var reason = string.IsNullOrEmpty(s.FailureReason) ? "Unknown" : s.FailureReason;
|
||||
// Collapse very long reasons to a 50-char prefix so near-identical messages group.
|
||||
if (reason.Length > 50) reason = reason.Substring(0, 50) + "...";
|
||||
reasons[reason] = reasons.GetValueOrDefault(reason) + 1;
|
||||
var key = reason.Length > 50 ? reason.Substring(0, 50) : reason;
|
||||
if (groups.TryGetValue(key, out var g))
|
||||
{
|
||||
var display = reason.Length > g.Display.Length ? reason : g.Display;
|
||||
groups[key] = (display, g.Count + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
groups[key] = (reason, 1);
|
||||
}
|
||||
}
|
||||
return reasons
|
||||
.OrderByDescending(kv => kv.Value)
|
||||
return groups
|
||||
.OrderByDescending(kv => kv.Value.Count)
|
||||
.Take(5)
|
||||
.Select(kv => new FleetFailureReason { Reason = kv.Key, Count = kv.Value })
|
||||
.Select(kv => new FleetFailureReason { Reason = kv.Value.Display, Count = kv.Value.Count })
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user