mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
Rename monitored system reason timestamp
This commit is contained in:
@@ -236,7 +236,8 @@ operators can interpret warning, offline, and unknown states without inventing
|
||||
local status semantics.
|
||||
Those status details are now structured as well: lifecycle-adjacent consumers
|
||||
must preserve the canonical reason list from the ledger read so operators can
|
||||
see which grouped source or surface degraded and when it last reported,
|
||||
see which grouped source or surface degraded and its canonical `reported_at`
|
||||
timestamp,
|
||||
instead of only seeing a generic warning/offline paragraph.
|
||||
That same ledger read also treats the canonical `latest_included_signal`
|
||||
object as the freshest included grouped observation. Lifecycle-adjacent
|
||||
|
||||
@@ -246,8 +246,9 @@ what online, warning, offline, or unknown means.
|
||||
That nested status explanation is now a structured contract, not summary-only
|
||||
copy: `/api/license/monitored-system-ledger` must preserve the canonical
|
||||
summary plus the ordered reason list from unified resources, including the
|
||||
degraded source or surface, its status, and its last-seen timestamp, so mixed
|
||||
fresh/stale grouped systems remain explainable through one governed API shape.
|
||||
degraded source or surface, its status, and its canonical `reported_at`
|
||||
timestamp, so mixed fresh/stale grouped systems remain explainable through one
|
||||
governed API shape.
|
||||
That canonical summary must also carry the mixed-source freshness explanation
|
||||
when the freshest grouped observation came from a different source than the
|
||||
degraded one, so API consumers can show a fresh `Last Seen` value without
|
||||
@@ -259,6 +260,9 @@ The backend payload contract now emits only that structured object, and the
|
||||
frontend monitored-system client should parse that canonical wire contract
|
||||
directly rather than keeping flat alias fallback for
|
||||
`latest_included_signal_at`, `latest_included_signal_source`, or `last_seen`.
|
||||
The canonical nested status-reason timestamp is `reported_at`; older raw
|
||||
payloads may map a legacy nested `last_seen` input forward during rollout, but
|
||||
the normalized client contract must expose only `reported_at`.
|
||||
That client contract must also fail closed when older or partial payloads omit
|
||||
the nested explanation object: the frontend may normalize missing explanation
|
||||
fields to empty reasons/surfaces plus a safe default summary, but it must not
|
||||
|
||||
@@ -382,8 +382,8 @@ That same shared `internal/api/` dependency now also assumes monitored-system
|
||||
ledger status details stay canonical and source-aware: storage- or recovery-
|
||||
adjacent consumers may read the ledger’s nested status explanation, but they
|
||||
must preserve the backend-provided reason list for stale or offline grouped
|
||||
sources instead of reducing those mixed fresh/stale system states back to a
|
||||
generic label.
|
||||
sources, including the canonical `reported_at` timestamp, instead of reducing
|
||||
those mixed fresh/stale system states back to a generic label.
|
||||
That same ledger dependency also treats the canonical `latest_included_signal`
|
||||
object as the freshest grouped observation. Storage- or recovery-adjacent
|
||||
consumers must not present that data with bare single-source `Last Seen`
|
||||
|
||||
@@ -89,6 +89,7 @@ cross-source deduplication.
|
||||
67. `frontend-modern/src/utils/resourceTypePresentation.ts`
|
||||
68. `frontend-modern/src/utils/resourceIdentity.ts`
|
||||
69. `frontend-modern/src/components/Infrastructure/resourceDetailDrawerIdentityModel.ts`
|
||||
70. `frontend-modern/src/hooks/useDashboardTrends.ts`
|
||||
|
||||
## Shared Boundaries
|
||||
|
||||
@@ -190,9 +191,9 @@ explanations. When a grouped monitored system resolves to warning, offline, or
|
||||
unknown, unified resources must expose the shared summary plus structured
|
||||
degraded-status reasons derived from the grouped top-level resources and their
|
||||
source freshness state, including which source or surface degraded and the
|
||||
corresponding last-seen timestamp. Billing and support surfaces must consume
|
||||
that shared reason list instead of trying to infer why a fresh overall
|
||||
`last_seen` can still coincide with warning status.
|
||||
canonical degraded-signal `reported_at` timestamp. Billing and support
|
||||
surfaces must consume that shared reason list instead of trying to infer why a
|
||||
fresh overall `last_seen` can still coincide with warning status.
|
||||
That same status contract must choose the canonical monitored-system runtime
|
||||
status from the actual grouped top-level resources rather than from an
|
||||
implicit `unknown` baseline. Severity ordering is canonical: `offline`
|
||||
@@ -261,6 +262,14 @@ shape: `InfrastructureSummary.tsx` is the render shell,
|
||||
org-scope lifecycle, and focused-summary state, and
|
||||
`infrastructureSummaryModel.ts` owns chart matching, focused-summary display
|
||||
selection, empty-state wording, and summary-series/metric derivation.
|
||||
The dashboard overview trend hook now follows that same canonical consumer
|
||||
contract for infrastructure sparklines: `frontend-modern/src/hooks/useDashboardTrends.ts`
|
||||
must consume the infrastructure summary chart cache and shared unified-resource
|
||||
series matching logic instead of issuing bespoke per-resource
|
||||
`/api/metrics-store/history` fetches for top-CPU and top-memory cards. That
|
||||
keeps dashboard summary sparklines aligned with canonical resource identity
|
||||
matching, agent-facet fallback behavior, and first-sample empty-state semantics
|
||||
already owned by the infrastructure summary surface.
|
||||
The backend AI and Patrol context renderers now derive their canonical change
|
||||
kind, source type, source adapter, actor, reason, and related-resource
|
||||
fragments from `internal/unifiedresources/change_presentation.go`, so the
|
||||
|
||||
@@ -194,6 +194,55 @@ describe('MonitoredSystemLedgerAPI', () => {
|
||||
});
|
||||
|
||||
it('preserves canonical status explanation reasons from the API contract', async () => {
|
||||
vi.mocked(apiFetchJSON).mockResolvedValueOnce({
|
||||
systems: [
|
||||
{
|
||||
name: 'Tower',
|
||||
type: 'host',
|
||||
status: 'warning',
|
||||
status_explanation: {
|
||||
summary: 'At least one included source is stale, so Pulse marks this monitored system as warning.',
|
||||
reasons: [
|
||||
{
|
||||
kind: 'source-stale',
|
||||
name: 'Tower',
|
||||
type: 'host',
|
||||
source: 'agent',
|
||||
status: 'stale',
|
||||
reported_at: '2026-03-23T11:55:00Z',
|
||||
summary: 'Agent data for Tower is stale (last reported 2026-03-23T11:55:00Z).',
|
||||
},
|
||||
],
|
||||
},
|
||||
latest_included_signal: {
|
||||
name: 'tower.local',
|
||||
type: 'docker-host',
|
||||
source: 'docker',
|
||||
at: '2026-03-23T11:59:50Z',
|
||||
},
|
||||
source: 'multiple',
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
limit: 5,
|
||||
});
|
||||
|
||||
const result = await MonitoredSystemLedgerAPI.getLedger();
|
||||
|
||||
expect(result.systems[0]?.status_explanation?.reasons).toEqual([
|
||||
{
|
||||
kind: 'source-stale',
|
||||
name: 'Tower',
|
||||
type: 'host',
|
||||
source: 'agent',
|
||||
status: 'stale',
|
||||
reported_at: '2026-03-23T11:55:00Z',
|
||||
summary: 'Agent data for Tower is stale (last reported 2026-03-23T11:55:00Z).',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('maps legacy status reason last_seen fields onto the canonical reported_at contract', async () => {
|
||||
vi.mocked(apiFetchJSON).mockResolvedValueOnce({
|
||||
systems: [
|
||||
{
|
||||
@@ -236,7 +285,7 @@ describe('MonitoredSystemLedgerAPI', () => {
|
||||
type: 'host',
|
||||
source: 'agent',
|
||||
status: 'stale',
|
||||
last_seen: '2026-03-23T11:55:00Z',
|
||||
reported_at: '2026-03-23T11:55:00Z',
|
||||
summary: 'Agent data for Tower is stale (last reported 2026-03-23T11:55:00Z).',
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -41,7 +41,7 @@ export interface MonitoredSystemLedgerStatusReason {
|
||||
type: string;
|
||||
source: string;
|
||||
status: MonitoredSystemLedgerStatusReasonStatus;
|
||||
last_seen: string;
|
||||
reported_at: string;
|
||||
summary: string;
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ type MonitoredSystemLedgerRawEntry = Omit<
|
||||
MonitoredSystemLedgerEntry,
|
||||
'status_explanation' | 'latest_included_signal' | 'explanation'
|
||||
> & {
|
||||
status_explanation?: MonitoredSystemLedgerStatusExplanation;
|
||||
status_explanation?: MonitoredSystemLedgerRawStatusExplanation;
|
||||
latest_included_signal?: MonitoredSystemLedgerLatestSignal;
|
||||
explanation?: MonitoredSystemLedgerExplanation;
|
||||
};
|
||||
@@ -81,6 +81,17 @@ type MonitoredSystemLedgerRawResponse = Omit<MonitoredSystemLedgerResponse, 'sys
|
||||
systems?: MonitoredSystemLedgerRawEntry[];
|
||||
};
|
||||
|
||||
interface MonitoredSystemLedgerRawStatusExplanation
|
||||
extends Omit<MonitoredSystemLedgerStatusExplanation, 'reasons'> {
|
||||
reasons?: MonitoredSystemLedgerRawStatusReason[];
|
||||
}
|
||||
|
||||
interface MonitoredSystemLedgerRawStatusReason
|
||||
extends Omit<MonitoredSystemLedgerStatusReason, 'reported_at'> {
|
||||
reported_at?: string;
|
||||
last_seen?: string;
|
||||
}
|
||||
|
||||
export class MonitoredSystemLedgerAPI {
|
||||
private static readonly baseUrl = '/api/license/monitored-system-ledger';
|
||||
|
||||
@@ -135,12 +146,16 @@ function normalizeMonitoredSystemLedgerStatus(
|
||||
}
|
||||
|
||||
function normalizeMonitoredSystemLedgerStatusReason(
|
||||
reason: MonitoredSystemLedgerStatusReason,
|
||||
reason: MonitoredSystemLedgerRawStatusReason,
|
||||
): MonitoredSystemLedgerStatusReason {
|
||||
return {
|
||||
...reason,
|
||||
kind: reason.kind,
|
||||
name: reason.name,
|
||||
type: reason.type,
|
||||
source: reason.source,
|
||||
status: normalizeMonitoredSystemLedgerStatusReasonStatus(reason.status),
|
||||
last_seen: reason.last_seen ?? '',
|
||||
reported_at: reason.reported_at ?? reason.last_seen ?? '',
|
||||
summary: reason.summary,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -181,7 +181,7 @@ describe('MonitoredSystemLedgerPanel', () => {
|
||||
type: 'pbs-server',
|
||||
source: 'pbs',
|
||||
status: 'offline',
|
||||
last_seen: '2026-01-01T23:55:00Z',
|
||||
reported_at: '2026-01-01T23:55:00Z',
|
||||
summary:
|
||||
'PBS data for server-b is offline or disconnected (last reported 2026-01-01T23:55:00Z).',
|
||||
},
|
||||
|
||||
+3
-1
@@ -252,9 +252,11 @@ describe('monitored-system model guardrails', () => {
|
||||
expect(monitoredSystemLedgerApiSource).toContain('getMonitoredSystemExplanationFallbackSummary');
|
||||
expect(monitoredSystemLedgerApiSource).toContain('type MonitoredSystemLedgerRawEntry =');
|
||||
expect(monitoredSystemLedgerApiSource).toContain('systems?: MonitoredSystemLedgerRawEntry[];');
|
||||
expect(monitoredSystemLedgerApiSource).toContain('reported_at: string;');
|
||||
expect(monitoredSystemLedgerApiSource).not.toContain('last_seen: string;');
|
||||
expect(monitoredSystemLedgerApiSource).not.toContain('latest_included_signal_at?: string;');
|
||||
expect(monitoredSystemLedgerApiSource).not.toContain('latest_included_signal_source?: string;');
|
||||
expect(monitoredSystemLedgerApiSource).not.toContain('last_seen?: string;');
|
||||
expect(monitoredSystemLedgerApiSource).toContain('last_seen?: string;');
|
||||
expect(monitoredSystemLedgerApiSource).not.toContain(
|
||||
'All included top-level collection paths currently report online status.',
|
||||
);
|
||||
|
||||
@@ -584,13 +584,13 @@ func TestContract_MonitoredSystemLedgerJSONSnapshot(t *testing.T) {
|
||||
Summary: "At least one included source is stale, so Pulse marks this monitored system as warning.",
|
||||
Reasons: []MonitoredSystemLedgerStatusReason{
|
||||
{
|
||||
Kind: "source-stale",
|
||||
Name: "Tower",
|
||||
Type: "host",
|
||||
Source: "agent",
|
||||
Status: "stale",
|
||||
LastSeen: "2026-03-18T17:25:00Z",
|
||||
Summary: "Agent data for Tower is stale (last reported 2026-03-18T17:25:00Z).",
|
||||
Kind: "source-stale",
|
||||
Name: "Tower",
|
||||
Type: "host",
|
||||
Source: "agent",
|
||||
Status: "stale",
|
||||
ReportedAt: "2026-03-18T17:25:00Z",
|
||||
Summary: "Agent data for Tower is stale (last reported 2026-03-18T17:25:00Z).",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -644,7 +644,7 @@ func TestContract_MonitoredSystemLedgerJSONSnapshot(t *testing.T) {
|
||||
"type":"host",
|
||||
"source":"agent",
|
||||
"status":"stale",
|
||||
"last_seen":"2026-03-18T17:25:00Z",
|
||||
"reported_at":"2026-03-18T17:25:00Z",
|
||||
"summary":"Agent data for Tower is stale (last reported 2026-03-18T17:25:00Z)."
|
||||
}
|
||||
]
|
||||
|
||||
@@ -35,13 +35,13 @@ type MonitoredSystemLedgerStatusExplanation struct {
|
||||
}
|
||||
|
||||
type MonitoredSystemLedgerStatusReason struct {
|
||||
Kind string `json:"kind"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Source string `json:"source"`
|
||||
Status string `json:"status"`
|
||||
LastSeen string `json:"last_seen"`
|
||||
Summary string `json:"summary"`
|
||||
Kind string `json:"kind"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Source string `json:"source"`
|
||||
Status string `json:"status"`
|
||||
ReportedAt string `json:"reported_at"`
|
||||
Summary string `json:"summary"`
|
||||
}
|
||||
|
||||
type MonitoredSystemLedgerExplanation struct {
|
||||
@@ -177,13 +177,13 @@ func monitoredSystemLedgerStatusExplanation(
|
||||
reasons := make([]MonitoredSystemLedgerStatusReason, 0, len(explanation.Reasons))
|
||||
for _, reason := range explanation.Reasons {
|
||||
reasons = append(reasons, MonitoredSystemLedgerStatusReason{
|
||||
Kind: reason.Kind,
|
||||
Name: reason.Name,
|
||||
Type: reason.Type,
|
||||
Source: reason.Source,
|
||||
Status: normalizeMonitoredSystemLedgerReasonStatus(reason.Status),
|
||||
LastSeen: formatLastSeen(reason.LastSeen),
|
||||
Summary: reason.Summary,
|
||||
Kind: reason.Kind,
|
||||
Name: reason.Name,
|
||||
Type: reason.Type,
|
||||
Source: reason.Source,
|
||||
Status: normalizeMonitoredSystemLedgerReasonStatus(reason.Status),
|
||||
ReportedAt: formatMonitoredSystemTime(reason.ReportedAt),
|
||||
Summary: reason.Summary,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -236,11 +236,11 @@ func monitoredSystemLedgerLatestSignal(
|
||||
Name: signal.Name,
|
||||
Type: signal.Type,
|
||||
Source: normalizeMonitoredSystemLedgerSource(signal.Source),
|
||||
At: formatLastSeen(signal.At),
|
||||
At: formatMonitoredSystemTime(signal.At),
|
||||
}
|
||||
}
|
||||
|
||||
func formatLastSeen(t time.Time) string {
|
||||
func formatMonitoredSystemTime(t time.Time) string {
|
||||
if t.IsZero() {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -85,16 +85,16 @@ func TestNormalizeStatus(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatLastSeen(t *testing.T) {
|
||||
func TestFormatMonitoredSystemTime(t *testing.T) {
|
||||
zero := time.Time{}
|
||||
if got := formatLastSeen(zero); got != "" {
|
||||
t.Errorf("formatLastSeen(zero) = %q, want empty", got)
|
||||
if got := formatMonitoredSystemTime(zero); got != "" {
|
||||
t.Errorf("formatMonitoredSystemTime(zero) = %q, want empty", got)
|
||||
}
|
||||
|
||||
ts := time.Date(2025, 6, 15, 10, 30, 0, 0, time.UTC)
|
||||
got := formatLastSeen(ts)
|
||||
got := formatMonitoredSystemTime(ts)
|
||||
if got != "2025-06-15T10:30:00Z" {
|
||||
t.Errorf("formatLastSeen = %q, want 2025-06-15T10:30:00Z", got)
|
||||
t.Errorf("formatMonitoredSystemTime = %q, want 2025-06-15T10:30:00Z", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,13 +103,13 @@ func TestMonitoredSystemLedgerStatusExplanation(t *testing.T) {
|
||||
Summary: "At least one included source is stale, so Pulse marks this monitored system as warning.",
|
||||
Reasons: []unifiedresources.MonitoredSystemStatusReason{
|
||||
{
|
||||
Kind: "source-stale",
|
||||
Name: "Tower",
|
||||
Type: "host",
|
||||
Source: "agent",
|
||||
Status: "stale",
|
||||
LastSeen: time.Date(2026, 3, 23, 11, 55, 0, 0, time.UTC),
|
||||
Summary: "Agent data for Tower is stale (last reported 2026-03-23T11:55:00Z).",
|
||||
Kind: "source-stale",
|
||||
Name: "Tower",
|
||||
Type: "host",
|
||||
Source: "agent",
|
||||
Status: "stale",
|
||||
ReportedAt: time.Date(2026, 3, 23, 11, 55, 0, 0, time.UTC),
|
||||
Summary: "Agent data for Tower is stale (last reported 2026-03-23T11:55:00Z).",
|
||||
},
|
||||
},
|
||||
}, "warning")
|
||||
@@ -122,8 +122,8 @@ func TestMonitoredSystemLedgerStatusExplanation(t *testing.T) {
|
||||
if got.Reasons[0].Status != "stale" {
|
||||
t.Fatalf("expected stale status reason, got %+v", got.Reasons[0])
|
||||
}
|
||||
if got.Reasons[0].LastSeen != "2026-03-23T11:55:00Z" {
|
||||
t.Fatalf("expected formatted reason last_seen, got %+v", got.Reasons[0])
|
||||
if got.Reasons[0].ReportedAt != "2026-03-23T11:55:00Z" {
|
||||
t.Fatalf("expected formatted reason reported_at, got %+v", got.Reasons[0])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,17 @@ func TestMonitoredSystemLedgerEntryDoesNotEmitCompatibilityAliases(t *testing.T)
|
||||
Status: unifiedresources.StatusWarning,
|
||||
StatusExplanation: unifiedresources.MonitoredSystemStatusExplanation{
|
||||
Summary: "At least one included source is stale, so Pulse marks this monitored system as warning.",
|
||||
Reasons: []unifiedresources.MonitoredSystemStatusReason{},
|
||||
Reasons: []unifiedresources.MonitoredSystemStatusReason{
|
||||
{
|
||||
Kind: "source-stale",
|
||||
Name: "Tower",
|
||||
Type: "host",
|
||||
Source: "agent",
|
||||
Status: "stale",
|
||||
ReportedAt: time.Date(2026, 3, 23, 11, 55, 0, 0, time.UTC),
|
||||
Summary: "Agent data for Tower is stale (last reported 2026-03-23T11:55:00Z).",
|
||||
},
|
||||
},
|
||||
},
|
||||
LastSeen: time.Date(2026, 3, 23, 12, 5, 0, 0, time.UTC),
|
||||
LatestIncludedSignal: unifiedresources.MonitoredSystemLatestSignal{
|
||||
@@ -171,6 +181,24 @@ func TestMonitoredSystemLedgerEntryDoesNotEmitCompatibilityAliases(t *testing.T)
|
||||
if _, ok := decoded["last_seen"]; ok {
|
||||
t.Fatalf("expected last_seen to be absent, got %+v", decoded)
|
||||
}
|
||||
statusExplanation, ok := decoded["status_explanation"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("expected status_explanation object, got %+v", decoded)
|
||||
}
|
||||
reasons, ok := statusExplanation["reasons"].([]any)
|
||||
if !ok || len(reasons) != 1 {
|
||||
t.Fatalf("expected one status reason, got %+v", statusExplanation)
|
||||
}
|
||||
reason, ok := reasons[0].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("expected status reason object, got %+v", reasons[0])
|
||||
}
|
||||
if _, ok := reason["last_seen"]; ok {
|
||||
t.Fatalf("expected nested reason last_seen to be absent, got %+v", reason)
|
||||
}
|
||||
if reason["reported_at"] != "2026-03-23T11:55:00Z" {
|
||||
t.Fatalf("expected nested reason reported_at, got %+v", reason)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMonitoredSystemLedgerResponseEmptyState(t *testing.T) {
|
||||
|
||||
@@ -52,13 +52,13 @@ type MonitoredSystemStatusExplanation struct {
|
||||
// MonitoredSystemStatusReason captures one canonical degraded-status signal
|
||||
// that contributed to the monitored-system runtime status.
|
||||
type MonitoredSystemStatusReason struct {
|
||||
Kind string
|
||||
Name string
|
||||
Type string
|
||||
Source string
|
||||
Status string
|
||||
LastSeen time.Time
|
||||
Summary string
|
||||
Kind string
|
||||
Name string
|
||||
Type string
|
||||
Source string
|
||||
Status string
|
||||
ReportedAt time.Time
|
||||
Summary string
|
||||
}
|
||||
|
||||
// MonitoredSystemLatestSignal captures the freshest included grouped signal
|
||||
@@ -512,8 +512,8 @@ func monitoredSystemStatusReasons(resources []*Resource) []MonitoredSystemStatus
|
||||
if reasons[i].Source != reasons[j].Source {
|
||||
return reasons[i].Source < reasons[j].Source
|
||||
}
|
||||
if !reasons[i].LastSeen.Equal(reasons[j].LastSeen) {
|
||||
return reasons[i].LastSeen.Before(reasons[j].LastSeen)
|
||||
if !reasons[i].ReportedAt.Equal(reasons[j].ReportedAt) {
|
||||
return reasons[i].ReportedAt.Before(reasons[j].ReportedAt)
|
||||
}
|
||||
return reasons[i].Summary < reasons[j].Summary
|
||||
})
|
||||
@@ -555,13 +555,13 @@ func monitoredSystemResourceStatusReasons(resource *Resource) []MonitoredSystemS
|
||||
continue
|
||||
}
|
||||
reasons = append(reasons, MonitoredSystemStatusReason{
|
||||
Kind: "source-" + normalizedStatus,
|
||||
Name: name,
|
||||
Type: resourceType,
|
||||
Source: string(source),
|
||||
Status: normalizedStatus,
|
||||
LastSeen: sourceStatus.LastSeen,
|
||||
Summary: monitoredSystemSourceStatusReasonSummary(name, source, normalizedStatus, sourceStatus.LastSeen),
|
||||
Kind: "source-" + normalizedStatus,
|
||||
Name: name,
|
||||
Type: resourceType,
|
||||
Source: string(source),
|
||||
Status: normalizedStatus,
|
||||
ReportedAt: sourceStatus.LastSeen,
|
||||
Summary: monitoredSystemSourceStatusReasonSummary(name, source, normalizedStatus, sourceStatus.LastSeen),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -581,13 +581,13 @@ func monitoredSystemResourceStatusReasons(resource *Resource) []MonitoredSystemS
|
||||
}
|
||||
return []MonitoredSystemStatusReason{
|
||||
{
|
||||
Kind: "surface-" + normalizedStatus,
|
||||
Name: name,
|
||||
Type: resourceType,
|
||||
Source: source,
|
||||
Status: normalizedStatus,
|
||||
LastSeen: resource.LastSeen,
|
||||
Summary: monitoredSystemSurfaceStatusReasonSummary(name, resourceType, source, normalizedStatus, resource.LastSeen),
|
||||
Kind: "surface-" + normalizedStatus,
|
||||
Name: name,
|
||||
Type: resourceType,
|
||||
Source: source,
|
||||
Status: normalizedStatus,
|
||||
ReportedAt: resource.LastSeen,
|
||||
Summary: monitoredSystemSurfaceStatusReasonSummary(name, resourceType, source, normalizedStatus, resource.LastSeen),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -650,12 +650,12 @@ func monitoredSystemMixedStateStatusSummary(
|
||||
}
|
||||
|
||||
degraded := reasons[0]
|
||||
if degraded.LastSeen.IsZero() {
|
||||
if degraded.ReportedAt.IsZero() {
|
||||
return ""
|
||||
}
|
||||
|
||||
latest := monitoredSystemLatestOnlineObservation(resources)
|
||||
if latest.LastSeen.IsZero() || !latest.LastSeen.After(degraded.LastSeen) {
|
||||
if latest.LastSeen.IsZero() || !latest.LastSeen.After(degraded.ReportedAt) {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -798,17 +798,17 @@ func monitoredSystemStatusReasonClause(reason MonitoredSystemStatusReason) strin
|
||||
sourceLabel := monitoredSystemStatusSourceLabel(reason.Source)
|
||||
switch reason.Kind {
|
||||
case "source-stale":
|
||||
return sourceLabel + " data for " + subject + " is stale (last reported " + reason.LastSeen.UTC().Format(time.RFC3339) + ")"
|
||||
return sourceLabel + " data for " + subject + " is stale (last reported " + reason.ReportedAt.UTC().Format(time.RFC3339) + ")"
|
||||
case "source-offline":
|
||||
return sourceLabel + " data for " + subject + " is offline or disconnected" + monitoredSystemStatusLastSeenSuffix(reason.LastSeen)
|
||||
return sourceLabel + " data for " + subject + " is offline or disconnected" + monitoredSystemStatusReportedAtSuffix(reason.ReportedAt)
|
||||
case "source-unknown":
|
||||
return sourceLabel + " data for " + subject + " does not report a canonical status yet" + monitoredSystemStatusLastSeenSuffix(reason.LastSeen)
|
||||
return sourceLabel + " data for " + subject + " does not report a canonical status yet" + monitoredSystemStatusReportedAtSuffix(reason.ReportedAt)
|
||||
case "surface-stale":
|
||||
return monitoredSystemGroupingTypeLabel(reason.Type) + " view for " + subject + " currently reports warning status from " + sourceLabel + monitoredSystemStatusLastSeenSuffix(reason.LastSeen)
|
||||
return monitoredSystemGroupingTypeLabel(reason.Type) + " view for " + subject + " currently reports warning status from " + sourceLabel + monitoredSystemStatusReportedAtSuffix(reason.ReportedAt)
|
||||
case "surface-offline":
|
||||
return monitoredSystemGroupingTypeLabel(reason.Type) + " view for " + subject + " currently reports offline status from " + sourceLabel + monitoredSystemStatusLastSeenSuffix(reason.LastSeen)
|
||||
return monitoredSystemGroupingTypeLabel(reason.Type) + " view for " + subject + " currently reports offline status from " + sourceLabel + monitoredSystemStatusReportedAtSuffix(reason.ReportedAt)
|
||||
case "surface-unknown":
|
||||
return monitoredSystemGroupingTypeLabel(reason.Type) + " view for " + subject + " currently reports unknown status from " + sourceLabel + monitoredSystemStatusLastSeenSuffix(reason.LastSeen)
|
||||
return monitoredSystemGroupingTypeLabel(reason.Type) + " view for " + subject + " currently reports unknown status from " + sourceLabel + monitoredSystemStatusReportedAtSuffix(reason.ReportedAt)
|
||||
default:
|
||||
clause := strings.TrimSpace(reason.Summary)
|
||||
clause = strings.TrimSuffix(clause, ".")
|
||||
@@ -816,11 +816,11 @@ func monitoredSystemStatusReasonClause(reason MonitoredSystemStatusReason) strin
|
||||
}
|
||||
}
|
||||
|
||||
func monitoredSystemStatusLastSeenSuffix(lastSeen time.Time) string {
|
||||
if lastSeen.IsZero() {
|
||||
func monitoredSystemStatusReportedAtSuffix(reportedAt time.Time) string {
|
||||
if reportedAt.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return " (last reported " + lastSeen.UTC().Format(time.RFC3339) + ")"
|
||||
return " (last reported " + reportedAt.UTC().Format(time.RFC3339) + ")"
|
||||
}
|
||||
|
||||
func monitoredSystemStatusPriority(status ResourceStatus) int {
|
||||
|
||||
@@ -332,8 +332,8 @@ func TestMonitoredSystemsExplainsStaleGroupedSourceWhileLastSeenStaysFresh(t *te
|
||||
if reason.Status != "stale" {
|
||||
t.Fatalf("expected stale reason status, got %+v", reason)
|
||||
}
|
||||
if !reason.LastSeen.Equal(agentResource.LastSeen) {
|
||||
t.Fatalf("expected stale reason last_seen %s, got %s", agentResource.LastSeen, reason.LastSeen)
|
||||
if !reason.ReportedAt.Equal(agentResource.LastSeen) {
|
||||
t.Fatalf("expected stale reason reported_at %s, got %s", agentResource.LastSeen, reason.ReportedAt)
|
||||
}
|
||||
if reason.Summary == "" {
|
||||
t.Fatalf("expected stale reason summary, got %+v", reason)
|
||||
|
||||
Reference in New Issue
Block a user