feat: persist run history

This commit is contained in:
Vincent Koc 2026-04-26 23:28:04 -07:00
parent 9da0b82cc1
commit bf3013019c
No known key found for this signature in database
2 changed files with 136 additions and 0 deletions

90
internal/store/runs.go Normal file
View File

@ -0,0 +1,90 @@
package store
import (
"context"
"database/sql"
"fmt"
)
type RunRecord struct {
ID int64 `json:"id"`
RepoID int64 `json:"repo_id"`
Kind string `json:"kind"`
Scope string `json:"scope"`
Status string `json:"status"`
StartedAt string `json:"started_at"`
FinishedAt string `json:"finished_at,omitempty"`
StatsJSON string `json:"stats_json,omitempty"`
ErrorText string `json:"error_text,omitempty"`
}
func (s *Store) RecordRun(ctx context.Context, run RunRecord) (int64, error) {
table, err := runTable(run.Kind)
if err != nil {
return 0, err
}
var id int64
err = s.db.QueryRowContext(ctx, `
insert into `+table+`(repo_id, scope, status, started_at, finished_at, stats_json, error_text)
values(?, ?, ?, ?, ?, ?, ?)
returning id
`, run.RepoID, run.Scope, run.Status, run.StartedAt, nullString(run.FinishedAt), nullString(run.StatsJSON), nullString(run.ErrorText)).Scan(&id)
if err != nil {
return 0, fmt.Errorf("record %s run: %w", run.Kind, err)
}
return id, nil
}
func (s *Store) ListRuns(ctx context.Context, repoID int64, kind string, limit int) ([]RunRecord, error) {
table, err := runTable(kind)
if err != nil {
return nil, err
}
if limit <= 0 {
limit = 20
}
rows, err := s.db.QueryContext(ctx, `
select id, repo_id, scope, status, started_at, finished_at, stats_json, error_text
from `+table+`
where repo_id = ?
order by id desc
limit ?
`, repoID, limit)
if err != nil {
return nil, fmt.Errorf("list %s runs: %w", kind, err)
}
defer rows.Close()
var out []RunRecord
for rows.Next() {
var run RunRecord
var finishedAt, statsJSON, errorText sql.NullString
if err := rows.Scan(&run.ID, &run.RepoID, &run.Scope, &run.Status, &run.StartedAt, &finishedAt, &statsJSON, &errorText); err != nil {
return nil, fmt.Errorf("scan %s run: %w", kind, err)
}
run.Kind = kind
run.FinishedAt = finishedAt.String
run.StatsJSON = statsJSON.String
run.ErrorText = errorText.String
out = append(out, run)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate %s runs: %w", kind, err)
}
return out, nil
}
func runTable(kind string) (string, error) {
switch kind {
case "sync":
return "sync_runs", nil
case "summary":
return "summary_runs", nil
case "embedding":
return "embedding_runs", nil
case "cluster":
return "cluster_runs", nil
default:
return "", fmt.Errorf("unsupported run kind %q", kind)
}
}

View File

@ -0,0 +1,46 @@
package store
import (
"context"
"path/filepath"
"testing"
)
func TestRecordAndListRuns(t *testing.T) {
ctx := context.Background()
st, err := Open(ctx, filepath.Join(t.TempDir(), "gitcrawl.db"))
if err != nil {
t.Fatalf("open store: %v", err)
}
defer st.Close()
repoID, err := st.UpsertRepository(ctx, Repository{
Owner: "openclaw",
Name: "gitcrawl",
FullName: "openclaw/gitcrawl",
RawJSON: "{}",
UpdatedAt: "2026-04-26T00:00:00Z",
})
if err != nil {
t.Fatalf("repo: %v", err)
}
if _, err := st.RecordRun(ctx, RunRecord{
RepoID: repoID,
Kind: "sync",
Scope: "open",
Status: "success",
StartedAt: "2026-04-26T00:00:00Z",
FinishedAt: "2026-04-26T00:00:01Z",
StatsJSON: `{"threads_synced":1}`,
}); err != nil {
t.Fatalf("record run: %v", err)
}
runs, err := st.ListRuns(ctx, repoID, "sync", 10)
if err != nil {
t.Fatalf("list runs: %v", err)
}
if len(runs) != 1 || runs[0].Kind != "sync" || runs[0].Status != "success" {
t.Fatalf("unexpected runs: %#v", runs)
}
}