Files
ITWorx Pulse release export bd774932d5
Public source validation / validate (push) Failing after 3m8s
Publish ITWorx Pulse source
2026-09-03 02:09:19 +02:00

105 lines
4.3 KiB
Go

package alert
import (
"context"
"errors"
"fmt"
"strings"
"github.com/jackc/pgx/v5"
)
type Alert struct {
Instance
RuleName string `json:"ruleName"`
Severity string `json:"severity"`
EntityType string `json:"entityType,omitempty"`
EntityName string `json:"entityName,omitempty"`
Occurrences []Occurrence `json:"occurrences,omitempty"`
}
type AlertReader interface {
ListAlerts(context.Context, int, string) ([]Alert, error)
GetAlert(context.Context, string, int) (Alert, error)
}
func (r StateRepository) ListAlerts(ctx context.Context, limit int, state string) ([]Alert, error) {
if r.Pool == nil {
return nil, ErrUnavailable
}
if limit < 1 || limit > 100 {
return nil, errors.New("alert limit is invalid")
}
if state != "" && !validState(State(state)) {
return nil, errors.New("alert state filter is invalid")
}
rows, err := r.Pool.Query(ctx, `SELECT i.id,i.rule_id,i.rule_version_id,i.fingerprint,COALESCE(i.entity_id::text,''),i.current_state,i.retained_state,i.active_since,i.recovery_since,i.cooldown_until,i.last_evaluated_at,i.last_known_at,i.last_value,i.reason,i.source_health,COALESCE(i.acknowledged_by,''),i.acknowledged_at,i.revision,i.created_at,i.updated_at,r.name,r.severity,COALESCE(e.entity_type,''),COALESCE(e.display_name,'') FROM alert_instances i JOIN alert_rules r ON r.id=i.rule_id LEFT JOIN entities e ON e.id=i.entity_id WHERE (($2='' AND i.current_state <> 'inactive') OR ($2<>'' AND i.current_state=$2)) ORDER BY CASE i.current_state WHEN 'firing' THEN 1 WHEN 'acknowledged' THEN 2 WHEN 'pending' THEN 3 WHEN 'unknown' THEN 4 WHEN 'resolved' THEN 5 ELSE 6 END,i.updated_at DESC,i.id ASC LIMIT $1`, limit, state)
if err != nil {
return nil, fmt.Errorf("list alerts: %w", err)
}
defer rows.Close()
items := make([]Alert, 0, limit)
for rows.Next() {
item, err := scanAlert(rows)
if err != nil {
return nil, err
}
items = append(items, item)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate alerts: %w", err)
}
return items, nil
}
func (r StateRepository) GetAlert(ctx context.Context, id string, occurrenceLimit int) (Alert, error) {
if r.Pool == nil {
return Alert{}, ErrUnavailable
}
if strings.TrimSpace(id) == "" {
return Alert{}, ErrInstanceNotFound
}
if occurrenceLimit < 1 || occurrenceLimit > 500 {
return Alert{}, errors.New("alert occurrence limit is invalid")
}
row := r.Pool.QueryRow(ctx, `SELECT i.id,i.rule_id,i.rule_version_id,i.fingerprint,COALESCE(i.entity_id::text,''),i.current_state,i.retained_state,i.active_since,i.recovery_since,i.cooldown_until,i.last_evaluated_at,i.last_known_at,i.last_value,i.reason,i.source_health,COALESCE(i.acknowledged_by,''),i.acknowledged_at,i.revision,i.created_at,i.updated_at,r.name,r.severity,COALESCE(e.entity_type,''),COALESCE(e.display_name,'') FROM alert_instances i JOIN alert_rules r ON r.id=i.rule_id LEFT JOIN entities e ON e.id=i.entity_id WHERE i.id=$1`, id)
item, err := scanAlert(row)
if errors.Is(err, ErrInstanceNotFound) || errors.Is(err, pgx.ErrNoRows) {
return Alert{}, ErrInstanceNotFound
}
if err != nil {
return Alert{}, fmt.Errorf("get alert: %w", err)
}
occurrences, err := r.ListOccurrences(ctx, id, occurrenceLimit)
if err != nil {
return Alert{}, err
}
item.Occurrences = occurrences
return item, nil
}
type alertRow interface{ Scan(...any) error }
func scanAlert(row alertRow) (Alert, error) {
var item Alert
var valueJSON, healthJSON []byte
var state, retained State
err := row.Scan(&item.ID, &item.RuleID, &item.RuleVersionID, &item.Fingerprint, &item.EntityID, &state, &retained, &item.ActiveSince, &item.RecoverySince, &item.CooldownUntil, &item.LastEvaluatedAt, &item.LastKnownAt, &valueJSON, &item.Reason, &healthJSON, &item.AcknowledgedBy, &item.AcknowledgedAt, &item.Revision, &item.CreatedAt, &item.UpdatedAt, &item.RuleName, &item.Severity, &item.EntityType, &item.EntityName)
if errors.Is(err, pgx.ErrNoRows) {
return Alert{}, ErrInstanceNotFound
}
if err != nil {
return Alert{}, fmt.Errorf("scan alert: %w", err)
}
item.State, item.RetainedState = state, retained
item.LastValue, err = decodeJSON(valueJSON)
if err != nil {
return Alert{}, fmt.Errorf("decode alert value: %w", err)
}
item.SourceHealth, err = decodeMap(healthJSON)
if err != nil {
return Alert{}, fmt.Errorf("decode alert source health: %w", err)
}
return item, nil
}