Skip to content

Commit afcd11c

Browse files
bircnimdave0905
andauthored
BUG: Fix workflow run jobs API returning null steps (#36603)
## Problem `GET /api/v1/repos/{owner}/{repo}/actions/runs/{runId}/jobs` was always returning `steps: null` for each job. ## Cause In `convert.ToActionWorkflowJob`, when the job had a `TaskID` we loaded the task with `db.GetByID` but never loaded `task.Steps`. `ActionTask.Steps` is not stored in the task row (`xorm:"-"`); it comes from `action_task_step` and is only filled by `task.LoadAttributes()` / `GetTaskStepsByTaskID()`. So the conversion loop over `task.Steps` always saw nil and produced no steps in the API response. ## Solution After resolving the task (by ID when the caller passes `nil`), we now load its steps with `GetTaskStepsByTaskID(ctx, task.ID)` and set `task.Steps` before building the API steps slice. No other behavior is changed. ## Testing - New integration test `TestAPIListWorkflowRunJobsReturnsSteps`: calls the runs/{runId}/jobs endpoint, inserts a task step for a fixture job, and asserts that the response includes non-null, non-empty `steps` with the expected step data. - `make test-sqlite#TestAPIListWorkflowRunJobsReturnsSteps` passes with this fix. --------- Co-authored-by: Manav <mdave0905@gmail.com>
1 parent 0d8bd77 commit afcd11c

File tree

2 files changed

+68
-24
lines changed

2 files changed

+68
-24
lines changed

services/convert/convert.go

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -349,20 +349,29 @@ func ToActionWorkflowJob(ctx context.Context, repo *repo_model.Repository, task
349349
}
350350
}
351351

352-
runnerID = task.RunnerID
353-
if runner, ok, _ := db.GetByID[actions_model.ActionRunner](ctx, runnerID); ok {
354-
runnerName = runner.Name
355-
}
356-
for i, step := range task.Steps {
357-
stepStatus, stepConclusion := ToActionsStatus(job.Status)
358-
steps = append(steps, &api.ActionWorkflowStep{
359-
Name: step.Name,
360-
Number: int64(i),
361-
Status: stepStatus,
362-
Conclusion: stepConclusion,
363-
StartedAt: step.Started.AsTime().UTC(),
364-
CompletedAt: step.Stopped.AsTime().UTC(),
365-
})
352+
if task != nil {
353+
if task.Steps == nil {
354+
task.Steps, err = actions_model.GetTaskStepsByTaskID(ctx, task.ID)
355+
if err != nil {
356+
return nil, err
357+
}
358+
task.Steps = util.SliceNilAsEmpty(task.Steps)
359+
}
360+
runnerID = task.RunnerID
361+
if runner, ok, _ := db.GetByID[actions_model.ActionRunner](ctx, runnerID); ok {
362+
runnerName = runner.Name
363+
}
364+
for i, step := range task.Steps {
365+
stepStatus, stepConclusion := ToActionsStatus(job.Status)
366+
steps = append(steps, &api.ActionWorkflowStep{
367+
Name: step.Name,
368+
Number: int64(i),
369+
Status: stepStatus,
370+
Conclusion: stepConclusion,
371+
StartedAt: step.Started.AsTime().UTC(),
372+
CompletedAt: step.Stopped.AsTime().UTC(),
373+
})
374+
}
366375
}
367376
}
368377

@@ -383,7 +392,7 @@ func ToActionWorkflowJob(ctx context.Context, repo *repo_model.Repository, task
383392
Conclusion: conclusion,
384393
RunnerID: runnerID,
385394
RunnerName: runnerName,
386-
Steps: steps,
395+
Steps: util.SliceNilAsEmpty(steps),
387396
CreatedAt: job.Created.AsTime().UTC(),
388397
StartedAt: job.Started.AsTime().UTC(),
389398
CompletedAt: job.Stopped.AsTime().UTC(),

tests/integration/api_actions_run_test.go

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,21 @@ package integration
66
import (
77
"fmt"
88
"net/http"
9+
"slices"
910
"testing"
1011

12+
actions_model "code.gitea.io/gitea/models/actions"
1113
auth_model "code.gitea.io/gitea/models/auth"
14+
"code.gitea.io/gitea/models/db"
1215
repo_model "code.gitea.io/gitea/models/repo"
1316
"code.gitea.io/gitea/models/unittest"
1417
user_model "code.gitea.io/gitea/models/user"
1518
"code.gitea.io/gitea/modules/json"
1619
api "code.gitea.io/gitea/modules/structs"
20+
"code.gitea.io/gitea/modules/timeutil"
1721

1822
"github.com/stretchr/testify/assert"
23+
"github.com/stretchr/testify/require"
1924
)
2025

2126
func TestAPIActionsGetWorkflowRun(t *testing.T) {
@@ -26,15 +31,45 @@ func TestAPIActionsGetWorkflowRun(t *testing.T) {
2631
session := loginUser(t, user.Name)
2732
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
2833

29-
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs/802802", repo.FullName())).
30-
AddTokenAuth(token)
31-
MakeRequest(t, req, http.StatusNotFound)
32-
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs/802", repo.FullName())).
33-
AddTokenAuth(token)
34-
MakeRequest(t, req, http.StatusNotFound)
35-
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs/803", repo.FullName())).
36-
AddTokenAuth(token)
37-
MakeRequest(t, req, http.StatusOK)
34+
t.Run("GetRun", func(t *testing.T) {
35+
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs/802802", repo.FullName())).
36+
AddTokenAuth(token)
37+
MakeRequest(t, req, http.StatusNotFound)
38+
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs/802", repo.FullName())).
39+
AddTokenAuth(token)
40+
MakeRequest(t, req, http.StatusNotFound)
41+
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs/803", repo.FullName())).
42+
AddTokenAuth(token)
43+
MakeRequest(t, req, http.StatusOK)
44+
})
45+
46+
t.Run("GetJobSteps", func(t *testing.T) {
47+
// Insert task steps for task_id 53 (job 198) so the API can return them once the backend loads them
48+
_, err := db.GetEngine(t.Context()).Insert(&actions_model.ActionTaskStep{
49+
Name: "main",
50+
TaskID: 53,
51+
Index: 0,
52+
RepoID: repo.ID,
53+
Status: actions_model.StatusSuccess,
54+
Started: timeutil.TimeStamp(1683636528),
55+
Stopped: timeutil.TimeStamp(1683636626),
56+
})
57+
require.NoError(t, err)
58+
59+
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795/jobs", repo.FullName())).
60+
AddTokenAuth(token)
61+
resp := MakeRequest(t, req, http.StatusOK)
62+
63+
var jobList api.ActionWorkflowJobsResponse
64+
err = json.Unmarshal(resp.Body.Bytes(), &jobList)
65+
require.NoError(t, err)
66+
67+
job198Idx := slices.IndexFunc(jobList.Entries, func(job *api.ActionWorkflowJob) bool { return job.ID == 198 })
68+
require.NotEqual(t, -1, job198Idx, "expected to find job 198 in run 795 jobs list")
69+
job198 := jobList.Entries[job198Idx]
70+
require.NotEmpty(t, job198.Steps, "job must return at least one step when task has steps")
71+
assert.Equal(t, "main", job198.Steps[0].Name, "first step name")
72+
})
3873
}
3974

4075
func TestAPIActionsGetWorkflowJob(t *testing.T) {

0 commit comments

Comments
 (0)