Skip to content

Commit 27fd0f9

Browse files
committed
feat(query): add execution history and current state read queries
1 parent c461c4e commit 27fd0f9

File tree

3 files changed

+160
-0
lines changed

3 files changed

+160
-0
lines changed

internal/model/result.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package model
22

33
import (
4+
"fmt"
45
"time"
56

67
"github.com/pixel365/pulse/internal/config"
@@ -65,3 +66,61 @@ type CheckPolicy struct {
6566
FailureThreshold int
6667
SuccessThreshold int
6768
}
69+
70+
type CheckExecutionRecord struct {
71+
CreatedAt time.Time
72+
ID string
73+
CheckExecutionResult
74+
}
75+
76+
type CheckExecutionFilter struct {
77+
From *time.Time
78+
To *time.Time
79+
ServiceID string
80+
CheckID string
81+
Limit int
82+
}
83+
84+
func (f *CheckExecutionFilter) Apply(query string, fieldFn func(string) string) (string, []any) {
85+
var (
86+
conditions []string
87+
args []any
88+
)
89+
90+
if f.ServiceID != "" {
91+
args = append(args, f.ServiceID)
92+
conditions = append(conditions, fmt.Sprintf("%s = $%d", fieldFn("service_id"), len(args)))
93+
}
94+
95+
if f.CheckID != "" {
96+
args = append(args, f.CheckID)
97+
conditions = append(conditions, fmt.Sprintf("%s = $%d", fieldFn("check_id"), len(args)))
98+
}
99+
100+
if f.From != nil {
101+
args = append(args, *f.From)
102+
conditions = append(conditions, fmt.Sprintf("%s >= $%d", fieldFn("finished_at"), len(args)))
103+
}
104+
105+
if f.To != nil {
106+
args = append(args, *f.To)
107+
conditions = append(conditions, fmt.Sprintf("%s <= $%d", fieldFn("finished_at"), len(args)))
108+
}
109+
110+
if len(conditions) > 0 {
111+
query += "WHERE " + conditions[0]
112+
for i := 1; i < len(conditions); i++ {
113+
query += " AND " + conditions[i]
114+
}
115+
query += "\n"
116+
}
117+
118+
query += fmt.Sprintf("ORDER BY %s DESC\n", fieldFn("finished_at"))
119+
120+
if f.Limit > 0 {
121+
args = append(args, f.Limit)
122+
query += fmt.Sprintf("LIMIT $%d\n", len(args))
123+
}
124+
125+
return query, args
126+
}

internal/repository/check/execution.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package check
33
import (
44
"context"
55
"encoding/json"
6+
"time"
67

78
"github.com/pixel365/pulse/internal/repository"
89

@@ -68,6 +69,102 @@ INSERT INTO pulse.check_executions (
6869
return err
6970
}
7071

72+
func (e *ExecutionCheck) ListExecutions(
73+
ctx context.Context,
74+
filter model.CheckExecutionFilter,
75+
) ([]model.CheckExecutionRecord, error) {
76+
query := `
77+
SELECT
78+
execution_id,
79+
check_id,
80+
service_id,
81+
status,
82+
check_type,
83+
started_at,
84+
finished_at,
85+
duration,
86+
attempts_total,
87+
error_kind,
88+
error_message,
89+
details,
90+
created_at
91+
FROM pulse.check_executions
92+
`
93+
94+
query, args := filter.Apply(query, mustField)
95+
96+
rows, err := e.db.Query(ctx, query, args...)
97+
if err != nil {
98+
return nil, err
99+
}
100+
defer rows.Close()
101+
102+
var result []model.CheckExecutionRecord
103+
104+
for rows.Next() {
105+
var (
106+
row model.CheckExecutionRecord
107+
details []byte
108+
rawErrorKind *string
109+
rawErrorMessage *string
110+
rawDurationUs int64
111+
)
112+
113+
err = rows.Scan(
114+
&row.ExecutionID,
115+
&row.CheckID,
116+
&row.ServiceID,
117+
&row.Status,
118+
&row.CheckType,
119+
&row.StartedAt,
120+
&row.FinishedAt,
121+
&rawDurationUs,
122+
&row.AttemptsTotal,
123+
&rawErrorKind,
124+
&rawErrorMessage,
125+
&details,
126+
&row.CreatedAt,
127+
)
128+
if err != nil {
129+
return nil, err
130+
}
131+
132+
if rawErrorKind != nil {
133+
row.ErrorKind = e2.ErrorKind(*rawErrorKind)
134+
}
135+
136+
if rawErrorMessage != nil {
137+
row.ErrorMessage = *rawErrorMessage
138+
}
139+
140+
if details != nil {
141+
err = json.Unmarshal(details, &row.Details)
142+
if err != nil {
143+
return nil, err
144+
}
145+
}
146+
147+
row.Duration = time.Duration(rawDurationUs) * time.Microsecond
148+
149+
result = append(result, row)
150+
}
151+
152+
if err = rows.Err(); err != nil {
153+
return nil, err
154+
}
155+
156+
return result, nil
157+
}
158+
159+
func mustField(name string) string {
160+
switch name {
161+
case "service_id", "check_id", "finished_at":
162+
return name
163+
}
164+
165+
panic("unknown field " + name + " in check execution filter")
166+
}
167+
71168
func NewExecutionRepository(db repository.QueryExecutor) *ExecutionCheck {
72169
return &ExecutionCheck{db}
73170
}

internal/repository/check/interfaces.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,8 @@ type CheckStateRepository interface {
1414

1515
type CheckExecutionRepository interface {
1616
Add(context.Context, model.CheckExecutionResult) error
17+
ListExecutions(
18+
context.Context,
19+
model.CheckExecutionFilter,
20+
) ([]model.CheckExecutionRecord, error)
1721
}

0 commit comments

Comments
 (0)