Skip to content

Commit f365a6b

Browse files
fix(actions): keep runs order after auto refresh (#39479)
On a repository's Actions tab, runs are sorted newest first on initial page load. After the first auto refresh (added in #38329, every 3 seconds while runs are active and every 12 seconds otherwise), the same runs may appear in a different order and move again as their status changes. Example with four runs (Gitea 28.0.0, SQLite): ``` page load: #10 success, #9 failure, #8 success, #7 running after refresh: #8 success, #10 success, #9 failure, #7 running ``` To reproduce, open the Actions tab of a repository with runs in different statuses and wait for an auto refresh. On SQLite, the runs may be regrouped by status, with each group ordered oldest first. `preparePartialRefreshRuns` reloads the runs currently shown on the page using `GetRunsByRepoAndID`. That query has no `ORDER BY`, while the initial page load uses `FindRunOptions.ToOrders` and sorts by index descending. With SQLite, the query planner used the `(repo_id, status)` index, so the returned row order differed from the original page order. Since the query has no explicit ordering, this behavior is database-dependent. I have not tested MySQL or PostgreSQL. This change orders `GetRunsByRepoAndID` by index descending, the same order `FindRunOptions.ToOrders` uses for the initial page load. The refresh only reloads the runs already on the page, so they come back in the original order, with or without filters and on any page. The other caller of `GetRunsByRepoAndID`, run approval, does not depend on result ordering. Testing: - Added `TestPreparePartialRefreshRunsKeepsRequestedOrder`. Without the fix, runs 794, 793, 792, 791 are returned as 791, 792, 794, 793; with the fix, the test passes. - `go test` passes for `./routers/web/repo/actions/`, `./models/actions/` and `./services/actions/`. - `go vet` and `golangci-lint v2.13.2` pass for the changed packages. - Manually tested by building Gitea 28.0.0 with this patch and running it on our SQLite instance. The runs list keeps its newest-first order across auto refreshes. The official 28.0.0 binary reproduces the reordering. AI-assisted: drafted with Claude Code (claude-opus-5-5), reviewed by me. --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
1 parent e0095af commit f365a6b

3 files changed

Lines changed: 26 additions & 4 deletions

File tree

‎models/actions/run.go‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -295,9 +295,8 @@ func GetRunByRepoAndID(ctx context.Context, repoID, runID int64) (*ActionRun, er
295295
return &run, nil
296296
}
297297

298-
func GetRunsByRepoAndID(ctx context.Context, repoID int64, runIDs []int64) ([]*ActionRun, error) {
299-
var runs []*ActionRun
300-
err := db.GetEngine(ctx).In("id", runIDs).Where("repo_id=?", repoID).Find(&runs)
298+
func GetRunsByRepoAndID(ctx context.Context, repoID int64, runIDs []int64) (runs []*ActionRun, err error) {
299+
err = db.GetEngine(ctx).In("id", runIDs).Where("repo_id=?", repoID).OrderBy("id").Find(&runs)
301300
return runs, err
302301
}
303302

‎routers/web/repo/actions/actions.go‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,15 @@ func (data *actionRunListData) preparePartialRefreshRuns(ctx *context.Context) b
642642
ctx.ServerError("GetRunsByRepoAndID", err)
643643
return false
644644
}
645-
data.ActionRuns = runs
645+
runsMap := make(map[int64]*actions_model.ActionRun, len(runs))
646+
for _, run := range runs {
647+
runsMap[run.ID] = run
648+
}
649+
for _, id := range data.refreshRunIDs {
650+
if run, ok := runsMap[id]; ok {
651+
data.ActionRuns = append(data.ActionRuns, run)
652+
}
653+
}
646654
return true
647655
}
648656

‎routers/web/repo/actions/actions_test.go‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"gitea.dev/modules/setting"
1616
"gitea.dev/modules/test"
1717
web_context "gitea.dev/services/context"
18+
"gitea.dev/services/contexttest"
1819

1920
"github.com/stretchr/testify/assert"
2021
)
@@ -74,3 +75,17 @@ func newWorkflowBadgeTestContext(t *testing.T) *web_context.Context {
7475
}
7576
return ctx
7677
}
78+
79+
func TestActionRunListData(t *testing.T) {
80+
unittest.PrepareTestEnv(t)
81+
t.Run("preparePartialRefreshRuns", func(t *testing.T) {
82+
ctx, _ := contexttest.MockContext(t, "user5/repo4/actions")
83+
contexttest.LoadRepo(t, ctx, 4)
84+
d := &actionRunListData{refreshRunIDs: []int64{791, 792}}
85+
d.preparePartialRefreshRuns(ctx)
86+
assert.Equal(t, []int64{791, 792}, []int64{d.ActionRuns[0].ID, d.ActionRuns[1].ID})
87+
d = &actionRunListData{refreshRunIDs: []int64{792, 791}}
88+
d.preparePartialRefreshRuns(ctx)
89+
assert.Equal(t, []int64{792, 791}, []int64{d.ActionRuns[0].ID, d.ActionRuns[1].ID})
90+
})
91+
}

0 commit comments

Comments
 (0)