From b99f5fb3c93022696e64cea9303e64539da1cdf6 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Tue, 13 Jan 2026 15:00:42 +0100 Subject: dashboard/app: add manual AI job triage Allow to set the Correct flag for completed AI jobs. --- dashboard/app/ai.go | 44 ++++++++++++++++++++++++++++++++-- dashboard/app/templates/ai_job.html | 15 ++++++++++++ dashboard/app/templates/templates.html | 2 ++ 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/dashboard/app/ai.go b/dashboard/app/ai.go index 9f620384f..4ea52bd7e 100644 --- a/dashboard/app/ai.go +++ b/dashboard/app/ai.go @@ -29,7 +29,9 @@ type uiAIJobsPage struct { } type uiAIJobPage struct { - Header *uiHeader + Header *uiHeader + Job *uiAIJob + // The slice contains the same single Job, just for HTML templates convenience. Jobs []*uiAIJob Results []*uiAIResult Trajectory []*uiAITrajectorySpan @@ -49,6 +51,7 @@ type uiAIJob struct { CodeRevision string CodeRevisionLink string Error string + Correct string } type uiAIResult struct { @@ -103,6 +106,22 @@ func handleAIJobPage(ctx context.Context, w http.ResponseWriter, r *http.Request if err != nil { return err } + if correct := r.FormValue("correct"); correct != "" { + if !job.Finished.Valid || job.Error != "" { + return fmt.Errorf("job is in wrong state to set correct status") + } + switch correct { + case aiCorrectnessCorrect: + job.Correct = spanner.NullBool{Bool: true, Valid: true} + case aiCorrectnessIncorrect: + job.Correct = spanner.NullBool{Bool: false, Valid: true} + default: + job.Correct = spanner.NullBool{} + } + if err := aidb.UpdateJob(ctx, job); err != nil { + return err + } + } trajectory, err := aidb.LoadTrajectory(ctx, job.ID) if err != nil { return err @@ -111,9 +130,11 @@ func handleAIJobPage(ctx context.Context, w http.ResponseWriter, r *http.Request if err != nil { return err } + uiJob := makeUIAIJob(job) page := &uiAIJobPage{ Header: hdr, - Jobs: []*uiAIJob{makeUIAIJob(job)}, + Job: uiJob, + Jobs: []*uiAIJob{uiJob}, Trajectory: makeUIAITrajectory(trajectory), } if m, ok := job.Results.Value.(map[string]any); ok && job.Results.Valid { @@ -131,6 +152,16 @@ func handleAIJobPage(ctx context.Context, w http.ResponseWriter, r *http.Request } func makeUIAIJob(job *aidb.Job) *uiAIJob { + correct := aiCorrectnessIncorrect + if !job.Finished.Valid { + correct = aiCorrectnessPending + } else if job.Error != "" { + correct = aiCorrectnessErrored + } else if !job.Correct.Valid { + correct = aiCorrectnessUnset + } else if job.Correct.Bool { + correct = aiCorrectnessCorrect + } return &uiAIJob{ ID: job.ID, Link: fmt.Sprintf("/ai_job?id=%v", job.ID), @@ -144,6 +175,7 @@ func makeUIAIJob(job *aidb.Job) *uiAIJob { CodeRevision: job.CodeRevision, CodeRevisionLink: vcs.LogLink(vcs.SyzkallerRepo, job.CodeRevision), Error: job.Error, + Correct: correct, } } @@ -447,6 +479,14 @@ func workflowsForBug(bug *Bug, manual bool) map[ai.WorkflowType]bool { return workflows } +const ( + aiCorrectnessCorrect = "✅" + aiCorrectnessIncorrect = "❌" + aiCorrectnessUnset = "❓" + aiCorrectnessPending = "⏳" + aiCorrectnessErrored = "💥" +) + func nullTime(v spanner.NullTime) time.Time { if !v.Valid { return time.Time{} diff --git a/dashboard/app/templates/ai_job.html b/dashboard/app/templates/ai_job.html index 3aaad429a..5a45654f8 100644 --- a/dashboard/app/templates/ai_job.html +++ b/dashboard/app/templates/ai_job.html @@ -15,6 +15,21 @@ Detailed info on a single AI job execution. {{template "header" .Header}} {{template "ai_job_list" .Jobs}} + {{if and (ne .Job.Correct "⏳") (ne .Job.Correct "💥")}} +
+
+ Result correct: + + + + + + + +
+
+ {{end}} + {{range $res := .Results}}
{{$res.Name}}:
{{$res.Value}}

diff --git a/dashboard/app/templates/templates.html b/dashboard/app/templates/templates.html index a50197664..15f50a287 100644 --- a/dashboard/app/templates/templates.html +++ b/dashboard/app/templates/templates.html @@ -683,6 +683,7 @@ Use of this source code is governed by Apache 2 LICENSE that can be found in the ID Workflow + Correct Description Created Started @@ -696,6 +697,7 @@ Use of this source code is governed by Apache 2 LICENSE that can be found in the {{link $job.Link $job.ID}} {{$job.Workflow}} + {{$job.Correct}} {{link $job.DescriptionLink $job.Description}} {{formatTime $job.Created}} {{formatTime $job.Started}} -- cgit mrf-deployment