diff --git a/pkg/services/ngalert/models/instance.go b/pkg/services/ngalert/models/instance.go index 7935bfe1dc9..8cb42bfc42d 100644 --- a/pkg/services/ngalert/models/instance.go +++ b/pkg/services/ngalert/models/instance.go @@ -15,6 +15,7 @@ type AlertInstance struct { CurrentStateEnd time.Time LastEvalTime time.Time LastSentAt *time.Time + FiredAt *time.Time ResolvedAt *time.Time ResultFingerprint string } diff --git a/pkg/services/ngalert/state/cache.go b/pkg/services/ngalert/state/cache.go index 2b311e9d2b4..24b40061fa4 100644 --- a/pkg/services/ngalert/state/cache.go +++ b/pkg/services/ngalert/state/cache.go @@ -296,6 +296,7 @@ func (c *cache) GetAlertInstances() []ngModels.AlertInstance { LastEvalTime: v2.LastEvaluationTime, CurrentStateSince: v2.StartsAt, CurrentStateEnd: v2.EndsAt, + FiredAt: v2.FiredAt, ResolvedAt: v2.ResolvedAt, LastSentAt: v2.LastSentAt, ResultFingerprint: v2.ResultFingerprint.String(), diff --git a/pkg/services/ngalert/state/compat.go b/pkg/services/ngalert/state/compat.go index 60bd143ae4e..e94c9a6e486 100644 --- a/pkg/services/ngalert/state/compat.go +++ b/pkg/services/ngalert/state/compat.go @@ -91,7 +91,9 @@ func StateToPostableAlert(transition StateTransition, appURL *url.URL, featureTo startsAt := strfmt.DateTime(alertState.StartsAt) if featureToggles.IsEnabledGlobally(featuremgmt.FlagAlertRuleUseFiredAtForStartsAt) { - startsAt = strfmt.DateTime(alertState.FiredAt) + if alertState.FiredAt != nil { + startsAt = strfmt.DateTime(*alertState.FiredAt) + } } return &models.PostableAlert{ diff --git a/pkg/services/ngalert/state/compat_test.go b/pkg/services/ngalert/state/compat_test.go index 7ea378b3e6d..7597687a2be 100644 --- a/pkg/services/ngalert/state/compat_test.go +++ b/pkg/services/ngalert/state/compat_test.go @@ -106,7 +106,7 @@ func Test_StateToPostableAlert(t *testing.T) { // feature flag is enabled result = StateToPostableAlert(alertState, appURL, featuremgmt.WithFeatures(featuremgmt.FlagAlertRuleUseFiredAtForStartsAt)) - require.Equal(t, strfmt.DateTime(alertState.FiredAt), result.StartsAt) + require.Equal(t, strfmt.DateTime(*alertState.FiredAt), result.StartsAt) }) t.Run("should copy annotations", func(t *testing.T) { @@ -361,7 +361,7 @@ func randomTransition(from, to eval.State) StateTransition { State: to, AlertRuleUID: util.GenerateShortUID(), StartsAt: time.Now(), - FiredAt: randomTimeInPast(), + FiredAt: util.Pointer(randomTimeInPast()), EndsAt: randomTimeInFuture(), LastEvaluationTime: randomTimeInPast(), EvaluationDuration: randomDuration(), diff --git a/pkg/services/ngalert/state/manager.go b/pkg/services/ngalert/state/manager.go index 65af2e40255..5a1a01c2af4 100644 --- a/pkg/services/ngalert/state/manager.go +++ b/pkg/services/ngalert/state/manager.go @@ -206,6 +206,7 @@ func (st *Manager) Warm(ctx context.Context, orgReader OrgReader, rulesReader Ru LastEvaluationString: "", StartsAt: entry.CurrentStateSince, EndsAt: entry.CurrentStateEnd, + FiredAt: entry.FiredAt, LastEvaluationTime: entry.LastEvalTime, Annotations: annotations, ResultFingerprint: resultFp, diff --git a/pkg/services/ngalert/state/manager_private_test.go b/pkg/services/ngalert/state/manager_private_test.go index 7b0608e9596..1e56070455c 100644 --- a/pkg/services/ngalert/state/manager_private_test.go +++ b/pkg/services/ngalert/state/manager_private_test.go @@ -428,7 +428,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t1, eval.Alerting), StartsAt: t1, EndsAt: t1.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t1, LastSentAt: &t1, }, @@ -506,7 +506,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.Alerting), StartsAt: t2, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t2, + FiredAt: &t2, LastEvaluationTime: t2, LastSentAt: &t2, }, @@ -575,7 +575,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.Alerting), StartsAt: t3, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t3, + FiredAt: &t3, LastEvaluationTime: t3, LastSentAt: &t3, }, @@ -630,7 +630,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.Normal), StartsAt: t2, EndsAt: t2, - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t2, ResolvedAt: &t2, LastSentAt: &t2, @@ -691,7 +691,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t1, eval.Alerting), StartsAt: t1, EndsAt: t3, - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t3, ResolvedAt: &t3, LastSentAt: &t3, @@ -753,7 +753,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t1, eval.Alerting), StartsAt: t1, EndsAt: t1.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t1, LastSentAt: &t1, }, @@ -812,7 +812,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t1, eval.Alerting), StartsAt: t1, EndsAt: t1.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t1, LastSentAt: &t1, }, @@ -827,7 +827,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.Normal), StartsAt: t2, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t2, LastSentAt: &t1, }, @@ -842,7 +842,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.Normal), StartsAt: t2, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t3, LastSentAt: &t1, }, @@ -857,7 +857,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t4, eval.Normal), StartsAt: t4, EndsAt: t4, - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t4, LastSentAt: &t4, ResolvedAt: &t4, @@ -890,7 +890,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t1, eval.Alerting), StartsAt: t1, EndsAt: t1.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t1, LastSentAt: &t1, }, @@ -905,7 +905,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.Normal), StartsAt: t2, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t2, LastSentAt: &t1, }, @@ -920,7 +920,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.Alerting), StartsAt: t3, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t3, + FiredAt: &t3, LastEvaluationTime: t3, LastSentAt: &t1, }, @@ -955,7 +955,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.Alerting), StartsAt: t2, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t2, + FiredAt: &t2, LastEvaluationTime: t2, LastSentAt: &t2, }, @@ -970,7 +970,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.Normal), StartsAt: t3, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t2, + FiredAt: &t2, LastEvaluationTime: t3, LastSentAt: &t2, }, @@ -985,7 +985,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t4, eval.Alerting), StartsAt: t4, EndsAt: t4.Add(ResendDelay * 4), - FiredAt: t4, + FiredAt: &t4, LastEvaluationTime: t4, LastSentAt: &t2, }, @@ -1017,7 +1017,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t1, eval.Alerting), StartsAt: t1, EndsAt: t1.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t1, LastSentAt: &t1, }, @@ -1061,7 +1061,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t1, eval.Alerting), StartsAt: t1, EndsAt: t3, - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t3, ResolvedAt: &t3, LastSentAt: &t3, @@ -1126,7 +1126,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t1, eval.Alerting), StartsAt: t1, EndsAt: t4, - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t4, ResolvedAt: &t4, LastSentAt: &t4, @@ -1159,7 +1159,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t1, eval.Alerting), StartsAt: t1, EndsAt: t1.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t1, LastSentAt: &t1, }, @@ -1189,7 +1189,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t1, eval.Alerting), StartsAt: t1, EndsAt: t2, - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t2, ResolvedAt: &t2, LastSentAt: &t2, @@ -1237,7 +1237,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t1, eval.Alerting), StartsAt: t1, EndsAt: t1.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t1, LastSentAt: &t1, }, @@ -1250,7 +1250,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t1, eval.Alerting), StartsAt: t1, EndsAt: t1.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t1, LastSentAt: &t1, }, @@ -1265,7 +1265,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.Alerting), StartsAt: t1, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t2, LastSentAt: &t1, }, @@ -1281,7 +1281,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t1, eval.Alerting), StartsAt: t1, EndsAt: t2, - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t2, ResolvedAt: &t2, LastSentAt: &t2, @@ -1297,7 +1297,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.Alerting), StartsAt: t1, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t3, LastSentAt: &t1, }, @@ -1326,7 +1326,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.Alerting), StartsAt: t2, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t2, + FiredAt: &t2, LastEvaluationTime: t2, LastSentAt: &t2, }, @@ -1410,7 +1410,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t1, eval.NoData), StartsAt: t1, EndsAt: t1.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t1, LastSentAt: &t1, }, @@ -1495,7 +1495,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluationWithValues(t2, eval.NoData, map[string]float64{"A": float64(-1)}), StartsAt: t2, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t2, + FiredAt: &t2, LastEvaluationTime: t2, LastSentAt: &t2, Values: map[string]float64{"A": float64(-1)}, @@ -1593,7 +1593,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t1, eval.Alerting), StartsAt: t1, EndsAt: t3, - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t3, ResolvedAt: &t3, LastSentAt: &t3, @@ -1625,7 +1625,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.NoData), StartsAt: t2, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t2, + FiredAt: &t2, LastEvaluationTime: t2, LastSentAt: &t2, }, @@ -1640,7 +1640,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.NoData), StartsAt: t1, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t2, LastSentAt: &t1, }, @@ -1658,7 +1658,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.NoData), StartsAt: t2, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t2, + FiredAt: &t2, LastEvaluationTime: t3, LastSentAt: &t2, }, @@ -1674,7 +1674,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.NoData), StartsAt: t1, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t3, LastSentAt: &t1, }, @@ -1706,7 +1706,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.NoData), StartsAt: t2, EndsAt: t2, - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t2, ResolvedAt: &t2, LastSentAt: &t2, @@ -1739,7 +1739,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.NoData), StartsAt: t2, EndsAt: t2, - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t3, ResolvedAt: &t2, LastSentAt: &t2, @@ -1772,7 +1772,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.NoData), StartsAt: t1, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t2, LastSentAt: &t1, }, @@ -1804,7 +1804,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.NoData), StartsAt: t1, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t3, LastSentAt: &t1, }, @@ -1908,7 +1908,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.NoData), StartsAt: t2, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t2, + FiredAt: &t2, LastEvaluationTime: t2, LastSentAt: &t2, }, @@ -1926,7 +1926,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.NoData), StartsAt: t3, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t3, + FiredAt: &t3, LastEvaluationTime: t3, LastSentAt: &t3, }, @@ -1942,7 +1942,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.NoData), StartsAt: t2, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t2, + FiredAt: &t2, LastEvaluationTime: t3, LastSentAt: &t2, }, @@ -2034,7 +2034,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.NoData), StartsAt: t2, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t2, + FiredAt: &t2, LastEvaluationTime: t2, LastSentAt: &t2, }, @@ -2066,7 +2066,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.NoData), StartsAt: t2, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t2, + FiredAt: &t2, LastEvaluationTime: t3, LastSentAt: &t2, }, @@ -2100,7 +2100,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.Alerting), StartsAt: t3, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t3, + FiredAt: &t3, LastEvaluationTime: t3, LastSentAt: &t3, }, @@ -2118,7 +2118,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.Alerting), StartsAt: t3, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t3, + FiredAt: &t3, LastEvaluationTime: t3, LastSentAt: &t3, }, @@ -2152,7 +2152,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.Alerting), StartsAt: t3, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t3, + FiredAt: &t3, LastEvaluationTime: t3, LastSentAt: &t3, }, @@ -2205,7 +2205,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t1, eval.Alerting), StartsAt: t1, EndsAt: t3, - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t3, ResolvedAt: &t3, LastSentAt: &t3, @@ -2253,7 +2253,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.NoData), StartsAt: t1, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t2, LastSentAt: &t1, }, @@ -2271,7 +2271,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.NoData), StartsAt: t1, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t3, LastSentAt: &t1, }, @@ -2289,7 +2289,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t4, eval.NoData), StartsAt: t1, EndsAt: t4.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t4, LastSentAt: &t4, }, @@ -2308,7 +2308,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.NoData), StartsAt: t2, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t2, LastSentAt: &t1, }, @@ -2326,7 +2326,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.NoData), StartsAt: t2, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t3, LastSentAt: &t1, }, @@ -2344,7 +2344,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t4, eval.NoData), StartsAt: t4, EndsAt: t4, - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t4, LastSentAt: &t4, ResolvedAt: &t4, @@ -2364,7 +2364,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.NoData), StartsAt: t1, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t2, LastSentAt: &t1, }, @@ -2382,7 +2382,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.NoData), StartsAt: t1, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t3, LastSentAt: &t1, }, @@ -2400,7 +2400,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t4, eval.NoData), StartsAt: t1, EndsAt: t4.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t4, LastSentAt: &t4, }, @@ -2514,7 +2514,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.NoData), StartsAt: t3, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t3, + FiredAt: &t3, LastEvaluationTime: t3, LastSentAt: &t3, }, @@ -2530,7 +2530,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t4, eval.Normal), StartsAt: t4, EndsAt: t4.Add(ResendDelay * 4), - FiredAt: t3, + FiredAt: &t3, LastEvaluationTime: t4, LastSentAt: &t3, }, @@ -2546,7 +2546,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t5, eval.NoData), StartsAt: t5, EndsAt: t5.Add(ResendDelay * 4), - FiredAt: t5, + FiredAt: &t5, LastEvaluationTime: t5, LastSentAt: &t3, Annotations: mergeLabels(baseRule.Annotations, noDataAnnotations), @@ -2716,7 +2716,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t1, eval.NoData), StartsAt: t1, EndsAt: t3, - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t3, ResolvedAt: &t3, LastSentAt: &t3, @@ -2821,7 +2821,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.NoData), StartsAt: t2, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t2, + FiredAt: &t2, LastEvaluationTime: t2, LastSentAt: &t2, }, @@ -2903,7 +2903,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t1, eval.Alerting), StartsAt: t1, EndsAt: t3, - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t3, ResolvedAt: &t3, LastSentAt: &t3, @@ -2935,7 +2935,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.NoData), StartsAt: t1, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t2, LastSentAt: &t1, }, @@ -2953,7 +2953,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.NoData), StartsAt: t1, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t3, LastSentAt: &t1, }, @@ -2972,7 +2972,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.NoData), StartsAt: t2, EndsAt: t2, - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t2, ResolvedAt: &t2, LastSentAt: &t2, @@ -2991,7 +2991,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.NoData), StartsAt: t2, EndsAt: t2, - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t3, ResolvedAt: &t2, LastSentAt: &t2, @@ -3011,7 +3011,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.NoData), StartsAt: t1, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t2, LastSentAt: &t1, }, @@ -3029,7 +3029,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.NoData), StartsAt: t1, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t3, LastSentAt: &t1, }, @@ -3077,7 +3077,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.Alerting), StartsAt: t3, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t3, + FiredAt: &t3, LastEvaluationTime: t3, LastSentAt: &t3, }, @@ -3110,7 +3110,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.Alerting), StartsAt: t3, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t3, + FiredAt: &t3, LastEvaluationTime: t3, LastSentAt: &t3, }, @@ -3174,7 +3174,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.Alerting), StartsAt: t3, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t3, + FiredAt: &t3, LastEvaluationTime: t3, LastSentAt: &t3, }, @@ -3267,7 +3267,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t1, eval.Error), StartsAt: t1, EndsAt: t1.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t1, LastSentAt: &t1, }, @@ -3349,7 +3349,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t1, eval.Error), StartsAt: t1, EndsAt: t1.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t1, LastSentAt: &t1, }, @@ -3438,7 +3438,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluationWithValues(t2, eval.Error, map[string]float64{"A": float64(-1)}), StartsAt: t2, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t2, + FiredAt: &t2, LastEvaluationTime: t2, LastSentAt: &t2, Values: map[string]float64{"A": float64(-1)}, @@ -3476,7 +3476,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluationWithValues(t2, eval.Error, map[string]float64{"A": float64(-1)}), StartsAt: t2, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t2, + FiredAt: &t2, LastEvaluationTime: t2, LastSentAt: &t2, Values: map[string]float64{"A": float64(-1)}, @@ -3531,7 +3531,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluationWithValues(t2, eval.Error, map[string]float64{"A": float64(-1)}), StartsAt: t2, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t2, + FiredAt: &t2, LastEvaluationTime: t2, LastSentAt: &t2, Values: map[string]float64{"A": float64(-1)}, @@ -3650,7 +3650,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t1, eval.Error), StartsAt: t1, EndsAt: t3, - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t3, ResolvedAt: &t3, LastSentAt: &t3, @@ -3791,7 +3791,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.Normal), StartsAt: t2, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t2, LastSentAt: &t1, }, @@ -3806,7 +3806,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.Normal), StartsAt: t2, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t3, LastSentAt: &t1, }, @@ -3821,7 +3821,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t4, eval.Normal), StartsAt: t4, EndsAt: t4, - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t4, ResolvedAt: &t4, LastSentAt: &t4, @@ -4000,7 +4000,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.Error), StartsAt: t2, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t2, + FiredAt: &t2, LastEvaluationTime: t2, LastSentAt: &t2, Annotations: datasourceErrorAnnotations, @@ -4017,7 +4017,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.Normal), StartsAt: t3, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t2, + FiredAt: &t2, LastEvaluationTime: t3, LastSentAt: &t2, }, @@ -4034,7 +4034,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t4, eval.Error), StartsAt: t4, EndsAt: t4.Add(ResendDelay * 4), - FiredAt: t4, + FiredAt: &t4, LastEvaluationTime: t4, LastSentAt: &t2, Annotations: datasourceErrorAnnotations, @@ -4183,7 +4183,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.Error), StartsAt: t2, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t2, + FiredAt: &t2, LastEvaluationTime: t2, LastSentAt: &t2, }, @@ -4285,7 +4285,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.Error), StartsAt: t2, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t2, + FiredAt: &t2, LastEvaluationTime: t2, LastSentAt: &t2, }, @@ -4321,7 +4321,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.Error), StartsAt: t2, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t2, + FiredAt: &t2, LastEvaluationTime: t2, LastSentAt: &t2, }, @@ -4407,7 +4407,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.Alerting), StartsAt: t3, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t3, + FiredAt: &t3, LastEvaluationTime: t3, LastSentAt: &t3, }, @@ -4471,7 +4471,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t3, eval.Alerting), StartsAt: t3, EndsAt: t3.Add(ResendDelay * 4), - FiredAt: t3, + FiredAt: &t3, LastEvaluationTime: t3, LastSentAt: &t3, }, @@ -4518,7 +4518,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.Normal), StartsAt: t2, EndsAt: t2, - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t2, ResolvedAt: &t2, LastSentAt: &t2, @@ -4623,7 +4623,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t1, eval.Error), StartsAt: t1, EndsAt: t1.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t1, LastSentAt: &t1, }, @@ -4642,7 +4642,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { LatestResult: newEvaluation(t2, eval.Error), StartsAt: t1, EndsAt: t2.Add(ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t2, LastSentAt: &t1, }, diff --git a/pkg/services/ngalert/state/manager_test.go b/pkg/services/ngalert/state/manager_test.go index 4ce84497c6d..050bb2e37f7 100644 --- a/pkg/services/ngalert/state/manager_test.go +++ b/pkg/services/ngalert/state/manager_test.go @@ -476,7 +476,7 @@ func TestProcessEvalResults(t *testing.T) { LatestResult: newEvaluation(t1, eval.Alerting), StartsAt: t1, EndsAt: t1.Add(state.ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t1, LastSentAt: &t1, }, @@ -525,7 +525,7 @@ func TestProcessEvalResults(t *testing.T) { LatestResult: newEvaluation(t2, eval.Alerting), StartsAt: t2, EndsAt: t2.Add(state.ResendDelay * 4), - FiredAt: t2, + FiredAt: &t2, LastEvaluationTime: t2, LastSentAt: &t2, }, @@ -557,7 +557,7 @@ func TestProcessEvalResults(t *testing.T) { LatestResult: newEvaluation(tn(4), eval.Alerting), StartsAt: tn(4), EndsAt: tn(4).Add(state.ResendDelay * 4), - FiredAt: tn(4), + FiredAt: util.Pointer(tn(4)), LastEvaluationTime: tn(4), LastSentAt: util.Pointer(tn(4)), }, @@ -583,7 +583,7 @@ func TestProcessEvalResults(t *testing.T) { LatestResult: newEvaluation(t2, eval.Normal), StartsAt: t2, EndsAt: t2, - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t2, ResolvedAt: &t2, LastSentAt: &t2, @@ -613,7 +613,7 @@ func TestProcessEvalResults(t *testing.T) { LatestResult: newEvaluation(t3, eval.Normal), StartsAt: t2, EndsAt: t2, - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t3, ResolvedAt: &t2, LastSentAt: &t2, @@ -646,7 +646,7 @@ func TestProcessEvalResults(t *testing.T) { LatestResult: newEvaluation(tn(4), eval.Alerting), StartsAt: tn(4), EndsAt: tn(4).Add(state.ResendDelay * 4), - FiredAt: t2, + FiredAt: &t2, LastEvaluationTime: tn(4), ResolvedAt: &t3, LastSentAt: &t3, @@ -713,7 +713,7 @@ func TestProcessEvalResults(t *testing.T) { LatestResult: newEvaluation(tn(4), eval.NoData), StartsAt: tn(4), EndsAt: tn(4).Add(state.ResendDelay * 4), - FiredAt: t3, + FiredAt: &t3, LastEvaluationTime: tn(4), LastSentAt: &t3, // Resend delay is 30s, so last sent at is t3. }, @@ -967,7 +967,7 @@ func TestProcessEvalResults(t *testing.T) { LatestResult: newEvaluation(tn(5), eval.Error), StartsAt: tn(5), EndsAt: tn(5).Add(state.ResendDelay * 4), - FiredAt: tn(5), + FiredAt: util.Pointer(tn(5)), LastEvaluationTime: tn(5), LastSentAt: util.Pointer(tn(5)), }, @@ -1035,7 +1035,7 @@ func TestProcessEvalResults(t *testing.T) { LatestResult: newEvaluation(t3, eval.Alerting), StartsAt: t3, EndsAt: t3.Add(state.ResendDelay * 4), - FiredAt: t3, + FiredAt: &t3, LastEvaluationTime: t3, LastSentAt: &t1, // Resend delay is 30s, so last sent at is t1. }, @@ -1067,7 +1067,7 @@ func TestProcessEvalResults(t *testing.T) { }), StartsAt: t1, EndsAt: t1.Add(state.ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t1, LastSentAt: &t1, Values: map[string]float64{ @@ -1103,7 +1103,7 @@ func TestProcessEvalResults(t *testing.T) { }), StartsAt: t1, EndsAt: t1.Add(state.ResendDelay * 4), - FiredAt: t1, + FiredAt: &t1, LastEvaluationTime: t1, LastSentAt: &t1, Values: map[string]float64{ @@ -1136,7 +1136,7 @@ func TestProcessEvalResults(t *testing.T) { LatestResult: newEvaluation(t3, eval.Normal), StartsAt: t3, EndsAt: t3.Add(state.ResendDelay * 4), - FiredAt: t2, + FiredAt: &t2, LastEvaluationTime: t3, LastSentAt: util.Pointer(t2), }, @@ -1171,7 +1171,7 @@ func TestProcessEvalResults(t *testing.T) { LatestResult: newEvaluation(tn(5), eval.Normal), StartsAt: tn(5), EndsAt: tn(5), - FiredAt: t2, + FiredAt: &t2, LastEvaluationTime: tn(5), LastSentAt: util.Pointer(tn(5)), ResolvedAt: util.Pointer(tn(5)), @@ -1210,7 +1210,7 @@ func TestProcessEvalResults(t *testing.T) { LatestResult: newEvaluation(tn(6), eval.Normal), StartsAt: tn(6), EndsAt: tn(6).Add(state.ResendDelay * 4), - FiredAt: tn(4), + FiredAt: util.Pointer(tn(4)), LastEvaluationTime: tn(6), LastSentAt: util.Pointer(tn(5)), }, @@ -1245,7 +1245,7 @@ func TestProcessEvalResults(t *testing.T) { LatestResult: newEvaluation(tn(5), eval.Alerting), StartsAt: tn(5), EndsAt: tn(5).Add(state.ResendDelay * 4), - FiredAt: tn(5), + FiredAt: util.Pointer(tn(5)), LastEvaluationTime: tn(5), LastSentAt: util.Pointer(t3), }, diff --git a/pkg/services/ngalert/state/persister_sync.go b/pkg/services/ngalert/state/persister_sync.go index 39eddd2f794..8a22afb9ca3 100644 --- a/pkg/services/ngalert/state/persister_sync.go +++ b/pkg/services/ngalert/state/persister_sync.go @@ -94,6 +94,7 @@ func (a *SyncStatePersister) saveAlertStates(ctx context.Context, states ...Stat LastEvalTime: s.LastEvaluationTime, CurrentStateSince: s.StartsAt, CurrentStateEnd: s.EndsAt, + FiredAt: s.FiredAt, ResolvedAt: s.ResolvedAt, LastSentAt: s.LastSentAt, ResultFingerprint: s.ResultFingerprint.String(), diff --git a/pkg/services/ngalert/state/state.go b/pkg/services/ngalert/state/state.go index 05437181100..f1cdaeecae1 100644 --- a/pkg/services/ngalert/state/state.go +++ b/pkg/services/ngalert/state/state.go @@ -64,7 +64,7 @@ type State struct { Values map[string]float64 // FiredAt is the time the state first transitions to Alerting. - FiredAt time.Time + FiredAt *time.Time StartsAt time.Time // EndsAt is different from the Prometheus EndsAt as EndsAt is updated for both Normal states @@ -103,6 +103,7 @@ func newState(ctx context.Context, log log.Logger, alertRule *models.AlertRule, EndsAt: result.EvaluatedAt, ResolvedAt: nil, LastSentAt: nil, + FiredAt: nil, LastEvaluationString: "", LastEvaluationTime: result.EvaluatedAt, EvaluationDuration: result.EvaluationDuration, @@ -165,7 +166,7 @@ func (a *State) SetAlerting(reason string, startsAt, endsAt time.Time) { a.Error = nil // FiredAt is only ever set when the state is set to Alerting. - a.FiredAt = startsAt + a.FiredAt = &startsAt } // SetPending sets the state to Pending. It changes both the start and end time. diff --git a/pkg/services/ngalert/state/state_test.go b/pkg/services/ngalert/state/state_test.go index 5c2b043815b..fb2b5f4bd68 100644 --- a/pkg/services/ngalert/state/state_test.go +++ b/pkg/services/ngalert/state/state_test.go @@ -46,7 +46,7 @@ func TestSetAlerting(t *testing.T) { StateReason: "this is a reason", StartsAt: mock.Now(), EndsAt: mock.Now().Add(time.Minute), - FiredAt: mock.Now(), + FiredAt: util.Pointer(mock.Now()), }, }, { name: "previous state is removed", @@ -61,7 +61,7 @@ func TestSetAlerting(t *testing.T) { State: eval.Alerting, StartsAt: mock.Now(), EndsAt: mock.Now().Add(time.Minute), - FiredAt: mock.Now(), + FiredAt: util.Pointer(mock.Now()), }, }} diff --git a/pkg/services/ngalert/store/instance_database.go b/pkg/services/ngalert/store/instance_database.go index 8d16ae9711d..eae19de174c 100644 --- a/pkg/services/ngalert/store/instance_database.go +++ b/pkg/services/ngalert/store/instance_database.go @@ -73,6 +73,7 @@ func (st InstanceDBStore) SaveAlertInstance(ctx context.Context, alertInstance m alertInstance.CurrentStateSince.Unix(), alertInstance.CurrentStateEnd.Unix(), alertInstance.LastEvalTime.Unix(), + nullableTimeToUnix(alertInstance.FiredAt), nullableTimeToUnix(alertInstance.ResolvedAt), nullableTimeToUnix(alertInstance.LastSentAt), alertInstance.ResultFingerprint, @@ -81,7 +82,7 @@ func (st InstanceDBStore) SaveAlertInstance(ctx context.Context, alertInstance m upsertSQL := st.SQLStore.GetDialect().UpsertSQL( "alert_instance", []string{"rule_org_id", "rule_uid", "labels_hash"}, - []string{"rule_org_id", "rule_uid", "labels", "labels_hash", "current_state", "current_reason", "current_state_since", "current_state_end", "last_eval_time", "resolved_at", "last_sent_at", "result_fingerprint"}) + []string{"rule_org_id", "rule_uid", "labels", "labels_hash", "current_state", "current_reason", "current_state_since", "current_state_end", "last_eval_time", "fired_at", "resolved_at", "last_sent_at", "result_fingerprint"}) _, err = sess.SQL(upsertSQL, params...).Query() if err != nil { return err @@ -255,10 +256,10 @@ func (st InstanceDBStore) insertInstancesBatch(sess *sqlstore.DBSession, batch [ query := strings.Builder{} placeholders := make([]string, 0, len(batch)) - args := make([]any, 0, len(batch)*11) + args := make([]any, 0, len(batch)*12) query.WriteString("INSERT INTO alert_instance ") - query.WriteString("(rule_org_id, rule_uid, labels, labels_hash, current_state, current_reason, current_state_since, current_state_end, last_eval_time, resolved_at, last_sent_at) VALUES ") + query.WriteString("(rule_org_id, rule_uid, labels, labels_hash, current_state, current_reason, current_state_since, current_state_end, last_eval_time, fired_at, resolved_at, last_sent_at) VALUES ") for _, instance := range batch { if err := models.ValidateAlertInstance(instance); err != nil { @@ -272,7 +273,7 @@ func (st InstanceDBStore) insertInstancesBatch(sess *sqlstore.DBSession, batch [ continue } - placeholders = append(placeholders, "(?,?,?,?,?,?,?,?,?,?,?)") + placeholders = append(placeholders, "(?,?,?,?,?,?,?,?,?,?,?,?)") args = append(args, instance.RuleOrgID, instance.RuleUID, @@ -283,6 +284,7 @@ func (st InstanceDBStore) insertInstancesBatch(sess *sqlstore.DBSession, batch [ instance.CurrentStateSince.Unix(), instance.CurrentStateEnd.Unix(), instance.LastEvalTime.Unix(), + nullableTimeToUnix(instance.FiredAt), nullableTimeToUnix(instance.ResolvedAt), nullableTimeToUnix(instance.LastSentAt), ) diff --git a/pkg/services/ngalert/store/instance_database_test.go b/pkg/services/ngalert/store/instance_database_test.go index d6385d832b9..7724c2a5d48 100644 --- a/pkg/services/ngalert/store/instance_database_test.go +++ b/pkg/services/ngalert/store/instance_database_test.go @@ -594,6 +594,7 @@ func generateTestAlertInstance(orgID int64, ruleID string) models.AlertInstance CurrentStateSince: time.Now(), LastEvalTime: time.Now(), LastSentAt: util.Pointer(time.Now()), + FiredAt: util.Pointer(time.Now()), ResolvedAt: util.Pointer(time.Now()), CurrentReason: "abc", } diff --git a/pkg/services/ngalert/store/proto/v1/alert_rule_state.pb.go b/pkg/services/ngalert/store/proto/v1/alert_rule_state.pb.go index bcfab58904f..4c32cc2f879 100644 --- a/pkg/services/ngalert/store/proto/v1/alert_rule_state.pb.go +++ b/pkg/services/ngalert/store/proto/v1/alert_rule_state.pb.go @@ -34,6 +34,7 @@ type AlertInstance struct { LastSentAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=last_sent_at,json=lastSentAt,proto3" json:"last_sent_at,omitempty"` ResolvedAt *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=resolved_at,json=resolvedAt,proto3" json:"resolved_at,omitempty"` ResultFingerprint string `protobuf:"bytes,10,opt,name=result_fingerprint,json=resultFingerprint,proto3" json:"result_fingerprint,omitempty"` + FiredAt *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=fired_at,json=firedAt,proto3" json:"fired_at,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -138,6 +139,13 @@ func (x *AlertInstance) GetResultFingerprint() string { return "" } +func (x *AlertInstance) GetFiredAt() *timestamppb.Timestamp { + if x != nil { + return x.FiredAt + } + return nil +} + type AlertInstances struct { state protoimpl.MessageState `protogen:"open.v1"` Instances []*AlertInstance `protobuf:"bytes,1,rep,name=instances,proto3" json:"instances,omitempty"` @@ -189,7 +197,7 @@ var file_alert_rule_state_proto_rawDesc = string([]byte{ 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x6e, 0x67, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xfc, 0x04, 0x0a, 0x0d, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb3, 0x05, 0x0a, 0x0d, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x48, 0x61, 0x73, 0x68, 0x12, 0x43, @@ -225,21 +233,24 @@ var file_alert_rule_state_proto_rawDesc = string([]byte{ 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x64, 0x41, 0x74, 0x12, 0x2d, 0x0a, 0x12, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x72, 0x65, - 0x73, 0x75, 0x6c, 0x74, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x1a, - 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x4f, 0x0a, 0x0e, 0x41, 0x6c, - 0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x09, - 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x1f, 0x2e, 0x6e, 0x67, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, - 0x76, 0x31, 0x2e, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, - 0x52, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x42, 0x40, 0x5a, 0x3e, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, - 0x61, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x6e, 0x67, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x2f, 0x73, - 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x75, 0x6c, 0x74, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x12, + 0x35, 0x0a, 0x08, 0x66, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x66, + 0x69, 0x72, 0x65, 0x64, 0x41, 0x74, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x22, 0x4f, 0x0a, 0x0e, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, + 0x63, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6e, 0x67, 0x61, 0x6c, 0x65, 0x72, 0x74, + 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x49, + 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, + 0x65, 0x73, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, + 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x6e, 0x67, + 0x61, 0x6c, 0x65, 0x72, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -268,12 +279,13 @@ var file_alert_rule_state_proto_depIdxs = []int32{ 3, // 3: ngalert.store.v1.AlertInstance.last_eval_time:type_name -> google.protobuf.Timestamp 3, // 4: ngalert.store.v1.AlertInstance.last_sent_at:type_name -> google.protobuf.Timestamp 3, // 5: ngalert.store.v1.AlertInstance.resolved_at:type_name -> google.protobuf.Timestamp - 0, // 6: ngalert.store.v1.AlertInstances.instances:type_name -> ngalert.store.v1.AlertInstance - 7, // [7:7] is the sub-list for method output_type - 7, // [7:7] is the sub-list for method input_type - 7, // [7:7] is the sub-list for extension type_name - 7, // [7:7] is the sub-list for extension extendee - 0, // [0:7] is the sub-list for field type_name + 3, // 6: ngalert.store.v1.AlertInstance.fired_at:type_name -> google.protobuf.Timestamp + 0, // 7: ngalert.store.v1.AlertInstances.instances:type_name -> ngalert.store.v1.AlertInstance + 8, // [8:8] is the sub-list for method output_type + 8, // [8:8] is the sub-list for method input_type + 8, // [8:8] is the sub-list for extension type_name + 8, // [8:8] is the sub-list for extension extendee + 0, // [0:8] is the sub-list for field type_name } func init() { file_alert_rule_state_proto_init() } diff --git a/pkg/services/ngalert/store/proto/v1/alert_rule_state.proto b/pkg/services/ngalert/store/proto/v1/alert_rule_state.proto index 3f0ec7de689..e80ac93cd09 100644 --- a/pkg/services/ngalert/store/proto/v1/alert_rule_state.proto +++ b/pkg/services/ngalert/store/proto/v1/alert_rule_state.proto @@ -17,6 +17,7 @@ message AlertInstance { google.protobuf.Timestamp last_sent_at = 8; google.protobuf.Timestamp resolved_at = 9; string result_fingerprint = 10; + google.protobuf.Timestamp fired_at = 11; } message AlertInstances { diff --git a/pkg/services/ngalert/store/proto_instance_database.go b/pkg/services/ngalert/store/proto_instance_database.go index ae45d3b34d8..b08955e6a66 100644 --- a/pkg/services/ngalert/store/proto_instance_database.go +++ b/pkg/services/ngalert/store/proto_instance_database.go @@ -149,6 +149,7 @@ func alertInstanceModelToProto(modelInstance models.AlertInstance) *pb.AlertInst CurrentReason: modelInstance.CurrentReason, LastEvalTime: timestamppb.New(modelInstance.LastEvalTime), LastSentAt: nullableTimeToTimestamp(modelInstance.LastSentAt), + FiredAt: nullableTimeToTimestamp(modelInstance.FiredAt), ResolvedAt: nullableTimeToTimestamp(modelInstance.ResolvedAt), ResultFingerprint: modelInstance.ResultFingerprint, } @@ -191,6 +192,7 @@ func alertInstanceProtoToModel(ruleUID string, ruleOrgID int64, protoInstance *p CurrentReason: protoInstance.CurrentReason, LastEvalTime: protoInstance.LastEvalTime.AsTime(), LastSentAt: nullableTimestampToTime(protoInstance.LastSentAt), + FiredAt: nullableTimestampToTime(protoInstance.FiredAt), ResolvedAt: nullableTimestampToTime(protoInstance.ResolvedAt), ResultFingerprint: protoInstance.ResultFingerprint, } diff --git a/pkg/services/ngalert/store/proto_instance_database_test.go b/pkg/services/ngalert/store/proto_instance_database_test.go index 0993a68da72..04428a0efae 100644 --- a/pkg/services/ngalert/store/proto_instance_database_test.go +++ b/pkg/services/ngalert/store/proto_instance_database_test.go @@ -17,6 +17,7 @@ func TestAlertInstanceModelToProto(t *testing.T) { currentStateEnd := currentStateSince.Add(time.Minute) lastEvalTime := currentStateSince.Add(-time.Minute) lastSentAt := currentStateSince.Add(-2 * time.Minute) + firedAt := currentStateSince.Add(-2 * time.Minute) resolvedAt := currentStateSince.Add(-3 * time.Minute) tests := []struct { @@ -39,6 +40,7 @@ func TestAlertInstanceModelToProto(t *testing.T) { CurrentReason: "Some reason", LastEvalTime: lastEvalTime, LastSentAt: &lastSentAt, + FiredAt: &firedAt, ResolvedAt: &resolvedAt, ResultFingerprint: "fingerprint", }, @@ -51,6 +53,7 @@ func TestAlertInstanceModelToProto(t *testing.T) { CurrentReason: "Some reason", LastEvalTime: timestamppb.New(lastEvalTime), LastSentAt: toProtoTimestampPtr(&lastSentAt), + FiredAt: toProtoTimestampPtr(&firedAt), ResolvedAt: toProtoTimestampPtr(&resolvedAt), ResultFingerprint: "fingerprint", }, @@ -70,6 +73,7 @@ func TestAlertInstanceProtoToModel(t *testing.T) { currentStateEnd := currentStateSince.Add(time.Minute).UTC() lastEvalTime := currentStateSince.Add(-time.Minute).UTC() lastSentAt := currentStateSince.Add(-2 * time.Minute).UTC() + firedAt := currentStateSince.Add(-2 * time.Minute).UTC() resolvedAt := currentStateSince.Add(-3 * time.Minute).UTC() ruleUID := "rule-uid-1" orgID := int64(1) @@ -89,6 +93,7 @@ func TestAlertInstanceProtoToModel(t *testing.T) { CurrentStateEnd: timestamppb.New(currentStateEnd), LastEvalTime: timestamppb.New(lastEvalTime), LastSentAt: toProtoTimestampPtr(&lastSentAt), + FiredAt: toProtoTimestampPtr(&firedAt), ResolvedAt: toProtoTimestampPtr(&resolvedAt), ResultFingerprint: "fingerprint", }, @@ -104,6 +109,7 @@ func TestAlertInstanceProtoToModel(t *testing.T) { CurrentStateEnd: currentStateEnd, LastEvalTime: lastEvalTime, LastSentAt: &lastSentAt, + FiredAt: &firedAt, ResolvedAt: &resolvedAt, ResultFingerprint: "fingerprint", }, @@ -126,7 +132,7 @@ func TestModelAlertInstanceMatchesProtobuf(t *testing.T) { // and update them accordingly. t.Run("when AlertInstance model changes", func(t *testing.T) { modelType := reflect.TypeOf(models.AlertInstance{}) - require.Equal(t, 10, modelType.NumField(), "AlertInstance model has changed, update the protobuf") + require.Equal(t, 11, modelType.NumField(), "AlertInstance model has changed, update the protobuf") }) } @@ -142,6 +148,7 @@ func TestCompressAndDecompressAlertInstances(t *testing.T) { CurrentStateEnd: timestamppb.New(now.Add(time.Hour)), CurrentReason: "reason-1", LastEvalTime: timestamppb.New(now.Add(-time.Minute)), + FiredAt: timestamppb.New(now.Add(-time.Minute * 2)), ResolvedAt: timestamppb.New(now.Add(time.Hour * 2)), ResultFingerprint: "fingerprint-1", }, diff --git a/pkg/services/sqlstore/migrations/migrations.go b/pkg/services/sqlstore/migrations/migrations.go index 99ccb2e4522..e907ddf7180 100644 --- a/pkg/services/sqlstore/migrations/migrations.go +++ b/pkg/services/sqlstore/migrations/migrations.go @@ -153,4 +153,6 @@ func (oss *OSSMigrations) AddMigration(mg *Migrator) { accesscontrol.AddDatasourceDrilldownRemovalMigration(mg) ualert.DropTitleUniqueIndexMigration(mg) + + ualert.AddStateFiredAtColumn(mg) } diff --git a/pkg/services/sqlstore/migrations/ualert/state_firedat_mig.go b/pkg/services/sqlstore/migrations/ualert/state_firedat_mig.go new file mode 100644 index 00000000000..614de08b4ca --- /dev/null +++ b/pkg/services/sqlstore/migrations/ualert/state_firedat_mig.go @@ -0,0 +1,12 @@ +package ualert + +import "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + +// AddStateFiredAtColumn adds a fired_at column to alert_instance to represent FiredAt. +func AddStateFiredAtColumn(mg *migrator.Migrator) { + mg.AddMigration("add fired_at column to alert_instance table", migrator.NewAddColumnMigration(migrator.Table{Name: "alert_instance"}, &migrator.Column{ + Name: "fired_at", + Type: migrator.DB_BigInt, // BigInt, to match existing time fields. + Nullable: true, + })) +}