From 516afbea6768af89b8e2d4f32bc8d8690ad9602e Mon Sep 17 00:00:00 2001 From: Sahil Rasaikar Date: Sun, 17 Aug 2025 16:18:02 +0530 Subject: [PATCH 1/4] Init Commit: fix for issue #4510 Signed-off-by: Sahil Rasaikar --- rules/alerting.go | 11 ++++++++- .../mantine-ui/src/api/responseTypes/rules.ts | 2 +- web/ui/mantine-ui/src/pages/AlertsPage.tsx | 23 ++++++++++++++++--- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/rules/alerting.go b/rules/alerting.go index 9a6ff0a113..6af3a46171 100644 --- a/rules/alerting.go +++ b/rules/alerting.go @@ -58,6 +58,8 @@ const ( // StateFiring is the state of an alert that has been active for longer than // the configured threshold duration. StateFiring + // StateUnknown is the state of an alert that has not yet been evaluated. + StateUnknown ) func (s AlertState) String() string { @@ -68,6 +70,8 @@ func (s AlertState) String() string { return "pending" case StateFiring: return "firing" + case StateUnknown: + return "unknown" } panic(fmt.Errorf("unknown alert state: %d", s)) } @@ -530,8 +534,13 @@ func (r *AlertingRule) Eval(ctx context.Context, queryOffset time.Duration, ts t } // State returns the maximum state of alert instances for this rule. -// StateFiring > StatePending > StateInactive. +// StateFiring > StatePending > StateInactive > StateUnknown. func (r *AlertingRule) State() AlertState { + // If the rule has never been evaluated, return StateUnknown + if r.GetEvaluationTimestamp().IsZero() { + return StateUnknown + } + r.activeMtx.Lock() defer r.activeMtx.Unlock() diff --git a/web/ui/mantine-ui/src/api/responseTypes/rules.ts b/web/ui/mantine-ui/src/api/responseTypes/rules.ts index ff04ad2cc9..89ce0f4acc 100644 --- a/web/ui/mantine-ui/src/api/responseTypes/rules.ts +++ b/web/ui/mantine-ui/src/api/responseTypes/rules.ts @@ -1,4 +1,4 @@ -type RuleState = "pending" | "firing" | "inactive"; +type RuleState = "pending" | "firing" | "inactive" | "unknown"; export interface Alert { labels: Record; diff --git a/web/ui/mantine-ui/src/pages/AlertsPage.tsx b/web/ui/mantine-ui/src/pages/AlertsPage.tsx index 00e6595fd4..2608117c8f 100644 --- a/web/ui/mantine-ui/src/pages/AlertsPage.tsx +++ b/web/ui/mantine-ui/src/pages/AlertsPage.tsx @@ -45,6 +45,7 @@ type AlertsPageData = { inactive: number; pending: number; firing: number; + unknown: number; }; groups: { name: string; @@ -55,6 +56,7 @@ type AlertsPageData = { inactive: number; pending: number; firing: number; + unknown: number; }; rules: { rule: AlertingRule; @@ -82,6 +84,7 @@ const buildAlertsPageData = ( inactive: 0, pending: 0, firing: 0, + unknown: 0, }, groups: [], }; @@ -92,6 +95,7 @@ const buildAlertsPageData = ( inactive: 0, pending: 0, firing: 0, + unknown: 0, }; for (const r of group.rules) { @@ -109,6 +113,10 @@ const buildAlertsPageData = ( pageData.globalCounts.pending++; groupCounts.pending++; break; + case "unknown": + pageData.globalCounts.unknown++; + groupCounts.unknown++; + break; default: throw new Error(`Unknown rule state: ${r.state}`); } @@ -239,6 +247,11 @@ export default function AlertsPage() { pending ({g.counts.pending}) )} + {g.counts.unknown > 0 && ( + + unknown ({g.counts.unknown}) + + )} {g.counts.inactive > 0 && ( inactive ({g.counts.inactive}) @@ -285,7 +298,9 @@ export default function AlertsPage() { ? panelClasses.panelHealthErr : r.counts.pending > 0 ? panelClasses.panelHealthWarn - : panelClasses.panelHealthOk + : r.rule.state === "unknown" + ? panelClasses.panelHealthUnknown + : panelClasses.panelHealthOk } > o === "inactive" ? badgeClasses.healthOk : o === "pending" ? badgeClasses.healthWarn - : badgeClasses.healthErr + : o === "firing" + ? badgeClasses.healthErr + : badgeClasses.healthUnknown } optionCount={(o) => alertsPageData.globalCounts[ From cc1e6e40f0c39f44e94f001d5f58ff975ce0b6ff Mon Sep 17 00:00:00 2001 From: Sahil Rasaikar Date: Sun, 5 Oct 2025 16:31:17 +0530 Subject: [PATCH 2/4] fix: Updates unknown state to -1,adds fix for failing test case Signed-off-by: Sahil Rasaikar --- rules/alerting.go | 13 +++++++------ rules/alerting_test.go | 2 ++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/rules/alerting.go b/rules/alerting.go index 6af3a46171..6e2dc3ffa7 100644 --- a/rules/alerting.go +++ b/rules/alerting.go @@ -50,6 +50,8 @@ const ( type AlertState int const ( + // StateUnknown is the state of an alert that has not yet been evaluated. + StateUnknown AlertState = -1 // StateInactive is the state of an alert that is neither firing nor pending. StateInactive AlertState = iota // StatePending is the state of an alert that has been active for less than @@ -58,8 +60,6 @@ const ( // StateFiring is the state of an alert that has been active for longer than // the configured threshold duration. StateFiring - // StateUnknown is the state of an alert that has not yet been evaluated. - StateUnknown ) func (s AlertState) String() string { @@ -536,13 +536,14 @@ func (r *AlertingRule) Eval(ctx context.Context, queryOffset time.Duration, ts t // State returns the maximum state of alert instances for this rule. // StateFiring > StatePending > StateInactive > StateUnknown. func (r *AlertingRule) State() AlertState { - // If the rule has never been evaluated, return StateUnknown - if r.GetEvaluationTimestamp().IsZero() { - return StateUnknown - } r.activeMtx.Lock() defer r.activeMtx.Unlock() + + // Check if the rule has been evaluated + if r.evaluationTimestamp.Load().IsZero() { + return StateUnknown + } maxState := StateInactive for _, a := range r.active { diff --git a/rules/alerting_test.go b/rules/alerting_test.go index 49817cc286..fff1d40048 100644 --- a/rules/alerting_test.go +++ b/rules/alerting_test.go @@ -85,6 +85,8 @@ func TestAlertingRuleState(t *testing.T) { for i, test := range tests { rule := NewAlertingRule(test.name, nil, 0, 0, labels.EmptyLabels(), labels.EmptyLabels(), labels.EmptyLabels(), "", true, nil) rule.active = test.active + // Set evaluation timestamp to simulate that the rule has been evaluated + rule.SetEvaluationTimestamp(time.Now()) got := rule.State() require.Equal(t, test.want, got, "test case %d unexpected AlertState, want:%d got:%d", i, test.want, got) } From 96425ea9841874ab7385753c0dc9be1e382859ec Mon Sep 17 00:00:00 2001 From: Sahil Rasaikar Date: Sun, 5 Oct 2025 17:08:49 +0530 Subject: [PATCH 3/4] fix: linting errors Signed-off-by: Sahil Rasaikar --- rules/alerting.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/rules/alerting.go b/rules/alerting.go index 6e2dc3ffa7..74f199e9f0 100644 --- a/rules/alerting.go +++ b/rules/alerting.go @@ -536,10 +536,8 @@ func (r *AlertingRule) Eval(ctx context.Context, queryOffset time.Duration, ts t // State returns the maximum state of alert instances for this rule. // StateFiring > StatePending > StateInactive > StateUnknown. func (r *AlertingRule) State() AlertState { - r.activeMtx.Lock() defer r.activeMtx.Unlock() - // Check if the rule has been evaluated if r.evaluationTimestamp.Load().IsZero() { return StateUnknown From 8a3ec78ae8c0b5d4bec965c539761d96090afe71 Mon Sep 17 00:00:00 2001 From: Sahil Rasaikar Date: Wed, 8 Oct 2025 22:56:35 +0530 Subject: [PATCH 4/4] fix: reorder AlertState constants to start with StateUnknown, reorder String method to match constant order. Signed-off-by: Sahil Rasaikar --- rules/alerting.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rules/alerting.go b/rules/alerting.go index 74f199e9f0..b0151d7cb3 100644 --- a/rules/alerting.go +++ b/rules/alerting.go @@ -51,9 +51,9 @@ type AlertState int const ( // StateUnknown is the state of an alert that has not yet been evaluated. - StateUnknown AlertState = -1 + StateUnknown AlertState = iota // StateInactive is the state of an alert that is neither firing nor pending. - StateInactive AlertState = iota + StateInactive // StatePending is the state of an alert that has been active for less than // the configured threshold duration. StatePending @@ -64,14 +64,14 @@ const ( func (s AlertState) String() string { switch s { + case StateUnknown: + return "unknown" case StateInactive: return "inactive" case StatePending: return "pending" case StateFiring: return "firing" - case StateUnknown: - return "unknown" } panic(fmt.Errorf("unknown alert state: %d", s)) }