2021-04-23 14:32:25 -05:00
|
|
|
package state
|
|
|
|
|
|
|
|
import (
|
2022-11-02 17:14:22 -05:00
|
|
|
"context"
|
2021-11-25 04:46:47 -06:00
|
|
|
"errors"
|
2022-04-13 13:45:29 -05:00
|
|
|
"fmt"
|
2022-04-27 13:59:13 -05:00
|
|
|
"math"
|
2022-04-05 13:36:42 -05:00
|
|
|
"strings"
|
2021-04-23 14:32:25 -05:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
2021-11-04 15:42:34 -05:00
|
|
|
|
2021-11-25 04:46:47 -06:00
|
|
|
"github.com/grafana/grafana/pkg/expr"
|
2022-12-07 04:45:56 -06:00
|
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
2021-04-23 14:32:25 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
2022-05-23 03:49:49 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
2022-11-02 17:14:22 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/screenshot"
|
2021-04-23 14:32:25 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
type State struct {
|
2022-05-23 03:49:49 -05:00
|
|
|
OrgID int64
|
2022-10-11 03:30:33 -05:00
|
|
|
AlertRuleUID string
|
|
|
|
|
|
|
|
// CacheID is a unique, opaque identifier for the state, and is used to find the state
|
|
|
|
// in the state cache. It tends to be derived from the state's labels.
|
|
|
|
CacheID string
|
|
|
|
|
|
|
|
// State represents the current state.
|
|
|
|
State eval.State
|
|
|
|
|
|
|
|
// StateReason is a textual description to explain why the state has its current state.
|
|
|
|
StateReason string
|
|
|
|
|
|
|
|
// Results contains the result of the current and previous evaluations.
|
|
|
|
Results []Evaluation
|
|
|
|
|
|
|
|
// Error is set if the current evaluation returned an error. If error is non-nil results
|
|
|
|
// can still contain the results of previous evaluations.
|
|
|
|
Error error
|
|
|
|
|
|
|
|
// Resolved is set to true if this state is the transitional state between Firing and Normal.
|
|
|
|
// All subsequent states will be false until the next transition from Firing to Normal.
|
|
|
|
Resolved bool
|
|
|
|
|
|
|
|
// Image contains an optional image for the state. It tends to be included in notifications
|
|
|
|
// as a visualization to show why the alert fired.
|
|
|
|
Image *models.Image
|
|
|
|
|
|
|
|
// Annotations contains the annotations from the alert rule. If an annotation is templated
|
|
|
|
// then the template is first evaluated to derive the final annotation.
|
|
|
|
Annotations map[string]string
|
|
|
|
|
|
|
|
// Labels contain the labels from the query and any custom labels from the alert rule.
|
|
|
|
// If a label is templated then the template is first evaluated to derive the final label.
|
|
|
|
Labels data.Labels
|
2022-05-23 03:49:49 -05:00
|
|
|
|
2022-10-11 03:30:33 -05:00
|
|
|
// Values contains the values of any instant vectors, reduce and math expressions, or classic
|
|
|
|
// conditions.
|
|
|
|
Values map[string]float64
|
2022-05-23 03:49:49 -05:00
|
|
|
|
2022-10-11 03:30:33 -05:00
|
|
|
StartsAt time.Time
|
|
|
|
EndsAt time.Time
|
|
|
|
LastSentAt time.Time
|
2022-02-02 12:18:20 -06:00
|
|
|
LastEvaluationString string
|
|
|
|
LastEvaluationTime time.Time
|
|
|
|
EvaluationDuration time.Duration
|
2021-04-23 14:32:25 -05:00
|
|
|
}
|
|
|
|
|
2022-08-18 08:40:33 -05:00
|
|
|
func (a *State) GetRuleKey() models.AlertRuleKey {
|
|
|
|
return models.AlertRuleKey{
|
|
|
|
OrgID: a.OrgID,
|
|
|
|
UID: a.AlertRuleUID,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-07 08:35:29 -06:00
|
|
|
func (a *State) GetAlertInstanceKey() (models.AlertInstanceKey, error) {
|
|
|
|
instanceLabels := models.InstanceLabels(a.Labels)
|
|
|
|
_, labelsHash, err := instanceLabels.StringAndHash()
|
|
|
|
if err != nil {
|
|
|
|
return models.AlertInstanceKey{}, err
|
|
|
|
}
|
|
|
|
return models.AlertInstanceKey{RuleOrgID: a.OrgID, RuleUID: a.AlertRuleUID, LabelsHash: labelsHash}, nil
|
|
|
|
}
|
|
|
|
|
2022-12-08 14:12:13 -06:00
|
|
|
// SetAlerting sets the state to Alerting. It changes both the start and end time.
|
|
|
|
func (a *State) SetAlerting(reason string, startsAt, endsAt time.Time) {
|
|
|
|
a.State = eval.Alerting
|
|
|
|
a.StateReason = reason
|
|
|
|
a.StartsAt = startsAt
|
|
|
|
a.EndsAt = endsAt
|
|
|
|
a.Error = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetPending the state to Pending. It changes both the start and end time.
|
|
|
|
func (a *State) SetPending(reason string, startsAt, endsAt time.Time) {
|
|
|
|
a.State = eval.Pending
|
|
|
|
a.StateReason = reason
|
|
|
|
a.StartsAt = startsAt
|
|
|
|
a.EndsAt = endsAt
|
|
|
|
a.Error = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetNoData sets the state to NoData. It changes both the start and end time.
|
|
|
|
func (a *State) SetNoData(reason string, startsAt, endsAt time.Time) {
|
|
|
|
a.State = eval.NoData
|
|
|
|
a.StateReason = reason
|
|
|
|
a.StartsAt = startsAt
|
|
|
|
a.EndsAt = endsAt
|
|
|
|
a.Error = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetError sets the state to Error. It changes both the start and end time.
|
|
|
|
func (a *State) SetError(err error, startsAt, endsAt time.Time) {
|
|
|
|
a.State = eval.Error
|
|
|
|
a.StateReason = models.StateReasonError
|
|
|
|
a.StartsAt = startsAt
|
|
|
|
a.EndsAt = endsAt
|
|
|
|
a.Error = err
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetNormal sets the state to Normal. It changes both the start and end time.
|
|
|
|
func (a *State) SetNormal(reason string, startsAt, endsAt time.Time) {
|
2022-11-09 05:08:32 -06:00
|
|
|
a.State = eval.Normal
|
|
|
|
a.StateReason = reason
|
2022-12-08 14:12:13 -06:00
|
|
|
a.StartsAt = startsAt
|
2022-11-09 05:08:32 -06:00
|
|
|
a.EndsAt = endsAt
|
2022-12-08 14:12:13 -06:00
|
|
|
a.Error = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resolve sets the State to Normal. It updates the StateReason, the end time, and sets Resolved to true.
|
|
|
|
func (a *State) Resolve(reason string, endsAt time.Time) {
|
|
|
|
a.State = eval.Normal
|
|
|
|
a.StateReason = reason
|
2022-11-09 05:08:32 -06:00
|
|
|
a.Resolved = true
|
2022-12-08 14:12:13 -06:00
|
|
|
a.EndsAt = endsAt
|
|
|
|
}
|
|
|
|
|
|
|
|
// Maintain updates the end time using the most recent evaluation.
|
|
|
|
func (a *State) Maintain(interval int64, evaluatedAt time.Time) {
|
|
|
|
a.EndsAt = nextEndsTime(interval, evaluatedAt)
|
2022-11-09 05:08:32 -06:00
|
|
|
}
|
|
|
|
|
2022-11-04 10:39:26 -05:00
|
|
|
// StateTransition describes the transition from one state to another.
|
|
|
|
type StateTransition struct {
|
|
|
|
*State
|
|
|
|
PreviousState eval.State
|
|
|
|
PreviousStateReason string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c StateTransition) Formatted() string {
|
|
|
|
return FormatStateAndReason(c.State.State, c.State.StateReason)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c StateTransition) PreviousFormatted() string {
|
|
|
|
return FormatStateAndReason(c.PreviousState, c.PreviousStateReason)
|
|
|
|
}
|
|
|
|
|
2022-12-06 11:33:15 -06:00
|
|
|
func (c StateTransition) Changed() bool {
|
2022-11-04 10:39:26 -05:00
|
|
|
return c.PreviousState != c.State.State || c.PreviousStateReason != c.State.StateReason
|
|
|
|
}
|
|
|
|
|
2021-04-23 14:32:25 -05:00
|
|
|
type Evaluation struct {
|
2022-02-02 12:18:20 -06:00
|
|
|
EvaluationTime time.Time
|
|
|
|
EvaluationState eval.State
|
2021-07-15 07:10:56 -05:00
|
|
|
// Values contains the RefID and value of reduce and math expressions.
|
2022-03-29 14:33:03 -05:00
|
|
|
// Classic conditions can have different values for the same RefID as they can include multiple conditions.
|
|
|
|
// For these, we use the index of the condition in addition RefID as the key e.g. "A0, A1, A2, etc.".
|
2022-01-31 10:56:43 -06:00
|
|
|
Values map[string]*float64
|
2022-04-05 13:36:42 -05:00
|
|
|
// Condition is the refID specified as the condition in the alerting rule at the time of the evaluation.
|
|
|
|
Condition string
|
2021-07-15 07:10:56 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewEvaluationValues returns the labels and values for each RefID in the capture.
|
2022-01-31 10:56:43 -06:00
|
|
|
func NewEvaluationValues(m map[string]eval.NumberValueCapture) map[string]*float64 {
|
|
|
|
result := make(map[string]*float64, len(m))
|
2021-07-15 07:10:56 -05:00
|
|
|
for k, v := range m {
|
2022-01-31 10:56:43 -06:00
|
|
|
result[k] = v.Value
|
2021-07-15 07:10:56 -05:00
|
|
|
}
|
|
|
|
return result
|
2021-04-23 14:32:25 -05:00
|
|
|
}
|
|
|
|
|
2022-12-07 04:45:56 -06:00
|
|
|
func resultNormal(state *State, _ *models.AlertRule, result eval.Result, logger log.Logger) {
|
2022-12-08 14:12:13 -06:00
|
|
|
if state.State == eval.Normal {
|
|
|
|
logger.Debug("Keeping state", "state", state.State)
|
|
|
|
} else {
|
2022-12-07 04:45:56 -06:00
|
|
|
logger.Debug("Changing state", "previous_state", state.State, "next_state", eval.Normal)
|
2022-12-08 14:12:13 -06:00
|
|
|
// Normal states have the same start and end timestamps
|
|
|
|
state.SetNormal("", result.EvaluatedAt, result.EvaluatedAt)
|
2021-04-23 14:32:25 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-07 04:45:56 -06:00
|
|
|
func resultAlerting(state *State, rule *models.AlertRule, result eval.Result, logger log.Logger) {
|
|
|
|
switch state.State {
|
2021-04-23 14:32:25 -05:00
|
|
|
case eval.Alerting:
|
2022-12-08 14:12:13 -06:00
|
|
|
logger.Debug("Keeping state", "state", state.State)
|
|
|
|
state.Maintain(rule.IntervalSeconds, result.EvaluatedAt)
|
2021-04-23 14:32:25 -05:00
|
|
|
case eval.Pending:
|
2022-12-07 04:45:56 -06:00
|
|
|
// If the previous state is Pending then check if the For duration has been observed
|
|
|
|
if result.EvaluatedAt.Sub(state.StartsAt) >= rule.For {
|
|
|
|
logger.Debug("Changing state", "previous_state", state.State, "next_state", eval.Alerting)
|
2022-12-08 14:12:13 -06:00
|
|
|
state.SetAlerting("", result.EvaluatedAt, nextEndsTime(rule.IntervalSeconds, result.EvaluatedAt))
|
2021-04-23 14:32:25 -05:00
|
|
|
}
|
|
|
|
default:
|
2022-12-07 04:45:56 -06:00
|
|
|
if rule.For > 0 {
|
|
|
|
// If the alert rule has a For duration that should be observed then the state should be set to Pending
|
|
|
|
logger.Debug("Changing state", "previous_state", state.State, "next_state", eval.Pending)
|
2022-12-08 14:12:13 -06:00
|
|
|
state.SetPending("", result.EvaluatedAt, nextEndsTime(rule.IntervalSeconds, result.EvaluatedAt))
|
2021-04-23 14:32:25 -05:00
|
|
|
} else {
|
2022-12-07 04:45:56 -06:00
|
|
|
logger.Debug("Changing state", "previous_state", state.State, "next_state", eval.Alerting)
|
2022-12-08 14:12:13 -06:00
|
|
|
state.SetAlerting("", result.EvaluatedAt, nextEndsTime(rule.IntervalSeconds, result.EvaluatedAt))
|
2021-04-23 14:32:25 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-12-07 04:45:56 -06:00
|
|
|
func resultError(state *State, rule *models.AlertRule, result eval.Result, logger log.Logger) {
|
|
|
|
switch rule.ExecErrState {
|
2022-05-23 03:49:49 -05:00
|
|
|
case models.AlertingErrState:
|
2022-12-08 14:12:13 -06:00
|
|
|
logger.Debug("Execution error state is Alerting", "handler", "resultAlerting", "previous_handler", "resultError")
|
2022-12-07 04:45:56 -06:00
|
|
|
resultAlerting(state, rule, result, logger)
|
2022-12-08 14:12:13 -06:00
|
|
|
// This is a special case where Alerting and Pending should also have an error and reason
|
2022-12-07 04:45:56 -06:00
|
|
|
state.Error = result.Error
|
2022-12-08 14:12:13 -06:00
|
|
|
state.StateReason = "error"
|
|
|
|
case models.ErrorErrState:
|
2022-12-07 04:45:56 -06:00
|
|
|
if state.State == eval.Error {
|
2022-12-08 14:12:13 -06:00
|
|
|
logger.Debug("Keeping state", "state", state.State)
|
|
|
|
state.Maintain(rule.IntervalSeconds, result.EvaluatedAt)
|
2022-03-31 03:57:58 -05:00
|
|
|
} else {
|
2022-12-07 04:45:56 -06:00
|
|
|
// This is the first occurrence of an error
|
|
|
|
logger.Debug("Changing state", "previous_state", state.State, "next_state", eval.Error)
|
2022-12-08 14:12:13 -06:00
|
|
|
state.SetError(result.Error, result.EvaluatedAt, nextEndsTime(rule.IntervalSeconds, result.EvaluatedAt))
|
|
|
|
|
|
|
|
if result.Error != nil {
|
|
|
|
// If the evaluation failed because a query returned an error then add the Ref ID and
|
|
|
|
// Datasource UID as labels
|
|
|
|
var queryError expr.QueryError
|
|
|
|
if errors.As(state.Error, &queryError) {
|
|
|
|
for _, next := range rule.Data {
|
|
|
|
if next.RefID == queryError.RefID {
|
|
|
|
state.Labels["ref_id"] = next.RefID
|
|
|
|
state.Labels["datasource_uid"] = next.DatasourceUID
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
state.Annotations["Error"] = queryError.Error()
|
|
|
|
}
|
|
|
|
}
|
2022-03-31 03:57:58 -05:00
|
|
|
}
|
2022-12-07 04:45:56 -06:00
|
|
|
case models.OkErrState:
|
2022-12-08 14:12:13 -06:00
|
|
|
logger.Debug("Execution error state is Normal", "handler", "resultNormal", "previous_handler", "resultError")
|
2022-12-07 04:45:56 -06:00
|
|
|
resultNormal(state, rule, result, logger)
|
|
|
|
default:
|
2022-12-08 14:12:13 -06:00
|
|
|
err := fmt.Errorf("unsupported execution error state: %s", rule.ExecErrState)
|
|
|
|
state.SetError(err, state.StartsAt, nextEndsTime(rule.IntervalSeconds, result.EvaluatedAt))
|
|
|
|
state.Annotations["Error"] = err.Error()
|
2021-04-23 14:32:25 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-07 04:45:56 -06:00
|
|
|
func resultNoData(state *State, rule *models.AlertRule, result eval.Result, _ log.Logger) {
|
|
|
|
state.Error = result.Error
|
2021-11-26 11:58:19 -06:00
|
|
|
|
2022-12-07 04:45:56 -06:00
|
|
|
if state.StartsAt.IsZero() {
|
|
|
|
state.StartsAt = result.EvaluatedAt
|
2021-04-23 14:32:25 -05:00
|
|
|
}
|
2022-12-08 14:12:13 -06:00
|
|
|
state.EndsAt = nextEndsTime(rule.IntervalSeconds, result.EvaluatedAt)
|
2021-04-23 14:32:25 -05:00
|
|
|
|
2022-12-07 04:45:56 -06:00
|
|
|
switch rule.NoDataState {
|
2022-05-23 03:49:49 -05:00
|
|
|
case models.Alerting:
|
2022-12-07 04:45:56 -06:00
|
|
|
state.State = eval.Alerting
|
2022-05-23 03:49:49 -05:00
|
|
|
case models.NoData:
|
2022-12-07 04:45:56 -06:00
|
|
|
state.State = eval.NoData
|
2022-05-23 03:49:49 -05:00
|
|
|
case models.OK:
|
2022-12-07 04:45:56 -06:00
|
|
|
state.State = eval.Normal
|
2021-04-23 14:32:25 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-19 15:15:09 -05:00
|
|
|
func (a *State) NeedsSending(resendDelay time.Duration) bool {
|
2022-09-15 11:25:05 -05:00
|
|
|
switch a.State {
|
|
|
|
case eval.Pending:
|
|
|
|
// We do not send notifications for pending states
|
2021-07-29 13:29:17 -05:00
|
|
|
return false
|
2022-09-15 11:25:05 -05:00
|
|
|
case eval.Normal:
|
|
|
|
// We should send a notification if the state is Normal because it was resolved
|
|
|
|
return a.Resolved
|
|
|
|
default:
|
|
|
|
// We should send, and re-send notifications, each time LastSentAt is <= LastEvaluationTime + resendDelay
|
|
|
|
nextSent := a.LastSentAt.Add(resendDelay)
|
|
|
|
return nextSent.Before(a.LastEvaluationTime) || nextSent.Equal(a.LastEvaluationTime)
|
2021-07-29 13:29:17 -05:00
|
|
|
}
|
2021-05-19 15:15:09 -05:00
|
|
|
}
|
|
|
|
|
2021-04-23 14:32:25 -05:00
|
|
|
func (a *State) Equals(b *State) bool {
|
|
|
|
return a.AlertRuleUID == b.AlertRuleUID &&
|
|
|
|
a.OrgID == b.OrgID &&
|
2022-10-11 03:30:33 -05:00
|
|
|
a.CacheID == b.CacheID &&
|
2021-04-23 14:32:25 -05:00
|
|
|
a.Labels.String() == b.Labels.String() &&
|
|
|
|
a.State.String() == b.State.String() &&
|
|
|
|
a.StartsAt == b.StartsAt &&
|
|
|
|
a.EndsAt == b.EndsAt &&
|
2021-04-30 13:23:12 -05:00
|
|
|
a.LastEvaluationTime == b.LastEvaluationTime &&
|
|
|
|
data.Labels(a.Annotations).String() == data.Labels(b.Annotations).String()
|
2021-04-23 14:32:25 -05:00
|
|
|
}
|
2021-05-18 12:56:14 -05:00
|
|
|
|
2022-05-23 03:49:49 -05:00
|
|
|
func (a *State) TrimResults(alertRule *models.AlertRule) {
|
2022-02-02 12:18:20 -06:00
|
|
|
numBuckets := int64(alertRule.For.Seconds()) / alertRule.IntervalSeconds
|
2021-05-18 12:56:14 -05:00
|
|
|
if numBuckets == 0 {
|
|
|
|
numBuckets = 10 // keep at least 10 evaluations in the event For is set to 0
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(a.Results) < int(numBuckets) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
newResults := make([]Evaluation, numBuckets)
|
|
|
|
copy(newResults, a.Results[len(a.Results)-int(numBuckets):])
|
|
|
|
a.Results = newResults
|
|
|
|
}
|
2021-06-17 12:01:46 -05:00
|
|
|
|
2022-12-08 14:12:13 -06:00
|
|
|
func nextEndsTime(interval int64, evaluatedAt time.Time) time.Time {
|
2021-09-02 10:22:59 -05:00
|
|
|
ends := ResendDelay
|
2022-12-08 14:12:13 -06:00
|
|
|
intv := time.Second * time.Duration(interval)
|
|
|
|
if intv > ResendDelay {
|
|
|
|
ends = intv
|
2021-06-17 12:01:46 -05:00
|
|
|
}
|
2022-12-08 14:12:13 -06:00
|
|
|
return evaluatedAt.Add(3 * ends)
|
2021-06-17 12:01:46 -05:00
|
|
|
}
|
2022-03-16 11:04:19 -05:00
|
|
|
|
2022-05-23 03:49:49 -05:00
|
|
|
func (a *State) GetLabels(opts ...models.LabelOption) map[string]string {
|
2022-03-16 11:04:19 -05:00
|
|
|
labels := a.Labels.Copy()
|
|
|
|
|
|
|
|
for _, opt := range opts {
|
|
|
|
opt(labels)
|
|
|
|
}
|
|
|
|
|
|
|
|
return labels
|
|
|
|
}
|
2022-04-05 13:36:42 -05:00
|
|
|
|
|
|
|
func (a *State) GetLastEvaluationValuesForCondition() map[string]float64 {
|
|
|
|
if len(a.Results) <= 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
lastResult := a.Results[len(a.Results)-1]
|
|
|
|
r := make(map[string]float64, len(lastResult.Values))
|
|
|
|
|
|
|
|
for refID, value := range lastResult.Values {
|
|
|
|
if strings.Contains(refID, lastResult.Condition) {
|
2022-04-27 13:59:13 -05:00
|
|
|
if value != nil {
|
|
|
|
r[refID] = *value
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
r[refID] = math.NaN()
|
2022-04-05 13:36:42 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return r
|
|
|
|
}
|
2022-11-02 17:14:22 -05:00
|
|
|
|
|
|
|
// shouldTakeImage returns true if the state just has transitioned to alerting from another state,
|
|
|
|
// transitioned to alerting in a previous evaluation but does not have a screenshot, or has just
|
|
|
|
// been resolved.
|
|
|
|
func shouldTakeImage(state, previousState eval.State, previousImage *models.Image, resolved bool) bool {
|
|
|
|
return resolved ||
|
|
|
|
state == eval.Alerting && previousState != eval.Alerting ||
|
|
|
|
state == eval.Alerting && previousImage == nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// takeImage takes an image for the alert rule. It returns nil if screenshots are disabled or
|
|
|
|
// the rule is not associated with a dashboard panel.
|
2022-11-09 15:06:49 -06:00
|
|
|
func takeImage(ctx context.Context, s ImageCapturer, r *models.AlertRule) (*models.Image, error) {
|
2022-11-02 17:14:22 -05:00
|
|
|
img, err := s.NewImage(ctx, r)
|
|
|
|
if err != nil {
|
|
|
|
if errors.Is(err, screenshot.ErrScreenshotsUnavailable) ||
|
2022-11-09 15:06:49 -06:00
|
|
|
errors.Is(err, models.ErrNoDashboard) ||
|
|
|
|
errors.Is(err, models.ErrNoPanel) {
|
2022-11-02 17:14:22 -05:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return img, nil
|
|
|
|
}
|
2022-11-04 10:39:26 -05:00
|
|
|
|
|
|
|
func FormatStateAndReason(state eval.State, reason string) string {
|
|
|
|
s := fmt.Sprintf("%v", state)
|
|
|
|
if len(reason) > 0 {
|
|
|
|
s += fmt.Sprintf(" (%v)", reason)
|
|
|
|
}
|
|
|
|
return s
|
|
|
|
}
|