Add manual failures UI and global list filtering
This commit is contained in:
117
internal/history/manual_failure.go
Normal file
117
internal/history/manual_failure.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package history
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RegisterManualComponentFailureCommand struct {
|
||||
ComponentID string
|
||||
MachineID *string
|
||||
FailureTime time.Time
|
||||
Description *string
|
||||
FailureType string
|
||||
FailureSource string
|
||||
FailureExternal string
|
||||
SourceRef *string
|
||||
IdempotencyKey *string
|
||||
}
|
||||
|
||||
type RegisterManualComponentFailureResult struct {
|
||||
HistoryApply ApplyPatchResult `json:"history_apply"`
|
||||
FailureEventID string `json:"failure_event_id"`
|
||||
FailureExternal string `json:"failure_external_id"`
|
||||
}
|
||||
|
||||
func (s *Service) RegisterManualComponentFailure(ctx context.Context, cmd RegisterManualComponentFailureCommand) (RegisterManualComponentFailureResult, error) {
|
||||
if s == nil || s.db == nil {
|
||||
return RegisterManualComponentFailureResult{}, fmt.Errorf("history service unavailable")
|
||||
}
|
||||
componentID := strings.TrimSpace(cmd.ComponentID)
|
||||
if componentID == "" {
|
||||
return RegisterManualComponentFailureResult{}, fmt.Errorf("%w: component_id is required", ErrInvalidPatch)
|
||||
}
|
||||
failureAt := cmd.FailureTime.UTC()
|
||||
if failureAt.IsZero() {
|
||||
return RegisterManualComponentFailureResult{}, fmt.Errorf("%w: failure_time is required", ErrInvalidPatch)
|
||||
}
|
||||
failureType := strings.TrimSpace(cmd.FailureType)
|
||||
if failureType == "" {
|
||||
failureType = "component_failed_manual"
|
||||
}
|
||||
failureSource := strings.TrimSpace(cmd.FailureSource)
|
||||
if failureSource == "" {
|
||||
failureSource = "manual_ui"
|
||||
}
|
||||
failureExternal := strings.TrimSpace(cmd.FailureExternal)
|
||||
if failureExternal == "" {
|
||||
failureExternal = buildManualFailureExternalID(componentID, failureAt, cmd.Description)
|
||||
}
|
||||
|
||||
tx, err := s.db.BeginTx(ctx, &sql.TxOptions{})
|
||||
if err != nil {
|
||||
return RegisterManualComponentFailureResult{}, err
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
patch := []PatchOp{
|
||||
{Op: "replace", Path: "/runtime/health_status", Value: "FAILED"},
|
||||
{Op: "replace", Path: "/runtime/health_status_at", Value: failureAt.Format(time.RFC3339Nano)},
|
||||
}
|
||||
applyRes, err := s.ApplyComponentPatchWithTx(ctx, tx, ApplyPatchCommand{
|
||||
EntityID: componentID,
|
||||
ChangeType: "COMPONENT_STATUS_SET",
|
||||
EffectiveAt: &failureAt,
|
||||
SourceType: "user",
|
||||
SourceRef: cmd.SourceRef,
|
||||
ActorType: "user",
|
||||
IdempotencyKey: cmd.IdempotencyKey,
|
||||
Patch: patch,
|
||||
})
|
||||
if err != nil {
|
||||
return RegisterManualComponentFailureResult{}, err
|
||||
}
|
||||
|
||||
if err := s.UpsertFailureProjectionWithTx(ctx, tx, FailureProjectionInput{
|
||||
Source: failureSource,
|
||||
ExternalID: failureExternal,
|
||||
PartID: componentID,
|
||||
MachineID: normalizeStringPtr(cmd.MachineID),
|
||||
FailureType: failureType,
|
||||
FailureTime: failureAt,
|
||||
Details: normalizeStringPtr(cmd.Description),
|
||||
}); err != nil {
|
||||
return RegisterManualComponentFailureResult{}, err
|
||||
}
|
||||
|
||||
var failureEventID string
|
||||
if err := tx.QueryRowContext(ctx,
|
||||
`SELECT id FROM failure_events WHERE source = ? AND external_id = ?`,
|
||||
failureSource, failureExternal,
|
||||
).Scan(&failureEventID); err != nil {
|
||||
return RegisterManualComponentFailureResult{}, err
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return RegisterManualComponentFailureResult{}, err
|
||||
}
|
||||
return RegisterManualComponentFailureResult{
|
||||
HistoryApply: applyRes,
|
||||
FailureEventID: failureEventID,
|
||||
FailureExternal: failureExternal,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func buildManualFailureExternalID(componentID string, failureAt time.Time, description *string) string {
|
||||
normDesc := ""
|
||||
if description != nil {
|
||||
normDesc = strings.TrimSpace(*description)
|
||||
}
|
||||
sum := sha1.Sum([]byte(strings.ToLower(componentID) + "|" + failureAt.UTC().Format(time.RFC3339Nano) + "|" + strings.ToLower(normDesc)))
|
||||
return "manual_ui:" + componentID + ":" + failureAt.UTC().Format(time.RFC3339Nano) + ":" + hex.EncodeToString(sum[:4])
|
||||
}
|
||||
Reference in New Issue
Block a user