Skip to content

Commit 98ac521

Browse files
retlehsclaude
andauthored
Track failed pipeline builds in admin dashboard (#2)
* Track failed pipeline builds in admin dashboard Previously, failed builds were invisible — the build row was only inserted after a successful build. Now the pipeline records a row with status "failed" and the error message when any step fails. The admin dashboard shows these with a red badge and error tooltip on hover. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix failed build ID collisions with successful builds Use the failure timestamp with a "-failed" suffix for the build ID instead of the pipeline start time, preventing PK collisions with successful builds created by repository.Build in the same pipeline run. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a509700 commit 98ac521

File tree

4 files changed

+48
-7
lines changed

4 files changed

+48
-7
lines changed

cmd/wpcomposer/cmd/pipeline.go

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package cmd
22

33
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
48
"github.com/spf13/cobra"
59
)
610

@@ -16,40 +20,69 @@ func runPipeline(cmd *cobra.Command, args []string) error {
1620
discoverSource, _ := cmd.Flags().GetString("discover-source")
1721

1822
ctx := cmd.Context()
23+
started := time.Now().UTC()
24+
25+
if err := executePipelineSteps(cmd, ctx, skipDiscover, skipDeploy, discoverSource); err != nil {
26+
recordFailedBuild(cmd, started, err)
27+
return err
28+
}
29+
30+
application.Logger.Info("pipeline: complete")
31+
return nil
32+
}
1933

34+
func executePipelineSteps(cmd *cobra.Command, ctx context.Context, skipDiscover, skipDeploy bool, discoverSource string) error {
2035
if !skipDiscover {
2136
application.Logger.Info("pipeline: running discover")
2237
discoverCmd.SetContext(ctx)
2338
_ = discoverCmd.Flags().Set("source", discoverSource)
2439
if err := runDiscover(discoverCmd, nil); err != nil {
25-
return err
40+
return fmt.Errorf("discover: %w", err)
2641
}
2742
}
2843

2944
application.Logger.Info("pipeline: running update")
3045
updateCmd.SetContext(ctx)
3146
if err := runUpdate(updateCmd, nil); err != nil {
32-
return err
47+
return fmt.Errorf("update: %w", err)
3348
}
3449

3550
application.Logger.Info("pipeline: running build")
3651
buildCmd.SetContext(ctx)
3752
if err := runBuild(buildCmd, nil); err != nil {
38-
return err
53+
return fmt.Errorf("build: %w", err)
3954
}
4055

4156
if !skipDeploy {
4257
application.Logger.Info("pipeline: running deploy")
4358
deployCmd.SetContext(ctx)
4459
if err := runDeploy(deployCmd, nil); err != nil {
45-
return err
60+
return fmt.Errorf("deploy: %w", err)
4661
}
4762
}
4863

49-
application.Logger.Info("pipeline: complete")
5064
return nil
5165
}
5266

67+
func recordFailedBuild(cmd *cobra.Command, started time.Time, pipelineErr error) {
68+
now := time.Now().UTC()
69+
buildID := now.Format("20060102-150405") + "-failed"
70+
_, dbErr := application.DB.ExecContext(cmd.Context(), `
71+
INSERT INTO builds (id, started_at, finished_at, duration_seconds,
72+
packages_total, packages_changed, packages_skipped,
73+
provider_groups, artifact_count, root_hash, sync_run_id, status, manifest_json, error_message)
74+
VALUES (?, ?, ?, ?, 0, 0, 0, 0, 0, '', NULL, 'failed', '{}', ?)`,
75+
buildID,
76+
started.Format(time.RFC3339),
77+
now.Format(time.RFC3339),
78+
int(now.Sub(started).Seconds()),
79+
pipelineErr.Error(),
80+
)
81+
if dbErr != nil {
82+
application.Logger.Warn("failed to record failed build in database", "error", dbErr)
83+
}
84+
}
85+
5386
func init() {
5487
appCommand(pipelineCmd)
5588
pipelineCmd.Flags().String("discover-source", "config", "discovery source (config or svn)")

internal/http/handlers.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -758,11 +758,13 @@ type buildRow struct {
758758
Status string
759759
IsCurrent bool
760760
R2SyncedAt string
761+
ErrorMessage string
761762
}
762763

763764
func queryBuilds(ctx context.Context, db *sql.DB) ([]buildRow, error) {
764765
rows, err := db.QueryContext(ctx, `SELECT id, started_at, packages_total, packages_changed,
765-
artifact_count, status, COALESCE(r2_synced_at, '') FROM builds ORDER BY started_at DESC LIMIT 50`)
766+
artifact_count, status, COALESCE(r2_synced_at, ''), COALESCE(error_message, '')
767+
FROM builds ORDER BY started_at DESC LIMIT 50`)
766768
if err != nil {
767769
return nil, err
768770
}
@@ -771,7 +773,7 @@ func queryBuilds(ctx context.Context, db *sql.DB) ([]buildRow, error) {
771773
var builds []buildRow
772774
for rows.Next() {
773775
var b buildRow
774-
_ = rows.Scan(&b.ID, &b.StartedAt, &b.PackagesTotal, &b.PackagesChanged, &b.ArtifactCount, &b.Status, &b.R2SyncedAt)
776+
_ = rows.Scan(&b.ID, &b.StartedAt, &b.PackagesTotal, &b.PackagesChanged, &b.ArtifactCount, &b.Status, &b.R2SyncedAt, &b.ErrorMessage)
775777
builds = append(builds, b)
776778
}
777779
return builds, rows.Err()

internal/http/templates/admin_builds.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ <h1 class="text-xl font-bold mb-6">Build History</h1>
2828
<td class="px-4 py-2 text-right">{{.ArtifactCount}}</td>
2929
<td class="px-4 py-2">
3030
{{if .IsCurrent}}<span class="text-xs bg-green-100 text-green-700 px-2 py-0.5 rounded-full">Current</span>
31+
{{else if eq .Status "failed"}}<span class="text-xs bg-red-100 text-red-700 px-2 py-0.5 rounded-full" title="{{.ErrorMessage}}">Failed</span>
3132
{{else}}<span class="text-xs bg-gray-100 text-gray-600 px-2 py-0.5 rounded-full">{{.Status}}</span>{{end}}
3233
</td>
3334
<td class="px-4 py-2">
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-- +goose Up
2+
ALTER TABLE builds ADD COLUMN error_message TEXT;
3+
4+
-- +goose Down
5+
ALTER TABLE builds DROP COLUMN error_message;

0 commit comments

Comments
 (0)