mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-01 20:59:45 +09:00
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>
This commit is contained in:
@@ -295,9 +295,8 @@ func GetRunByRepoAndID(ctx context.Context, repoID, runID int64) (*ActionRun, er
|
||||
return &run, nil
|
||||
}
|
||||
|
||||
func GetRunsByRepoAndID(ctx context.Context, repoID int64, runIDs []int64) ([]*ActionRun, error) {
|
||||
var runs []*ActionRun
|
||||
err := db.GetEngine(ctx).In("id", runIDs).Where("repo_id=?", repoID).Find(&runs)
|
||||
func GetRunsByRepoAndID(ctx context.Context, repoID int64, runIDs []int64) (runs []*ActionRun, err error) {
|
||||
err = db.GetEngine(ctx).In("id", runIDs).Where("repo_id=?", repoID).OrderBy("id").Find(&runs)
|
||||
return runs, err
|
||||
}
|
||||
|
||||
|
||||
@@ -642,7 +642,15 @@ func (data *actionRunListData) preparePartialRefreshRuns(ctx *context.Context) b
|
||||
ctx.ServerError("GetRunsByRepoAndID", err)
|
||||
return false
|
||||
}
|
||||
data.ActionRuns = runs
|
||||
runsMap := make(map[int64]*actions_model.ActionRun, len(runs))
|
||||
for _, run := range runs {
|
||||
runsMap[run.ID] = run
|
||||
}
|
||||
for _, id := range data.refreshRunIDs {
|
||||
if run, ok := runsMap[id]; ok {
|
||||
data.ActionRuns = append(data.ActionRuns, run)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/test"
|
||||
web_context "gitea.dev/services/context"
|
||||
"gitea.dev/services/contexttest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -74,3 +75,17 @@ func newWorkflowBadgeTestContext(t *testing.T) *web_context.Context {
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func TestActionRunListData(t *testing.T) {
|
||||
unittest.PrepareTestEnv(t)
|
||||
t.Run("preparePartialRefreshRuns", func(t *testing.T) {
|
||||
ctx, _ := contexttest.MockContext(t, "user5/repo4/actions")
|
||||
contexttest.LoadRepo(t, ctx, 4)
|
||||
d := &actionRunListData{refreshRunIDs: []int64{791, 792}}
|
||||
d.preparePartialRefreshRuns(ctx)
|
||||
assert.Equal(t, []int64{791, 792}, []int64{d.ActionRuns[0].ID, d.ActionRuns[1].ID})
|
||||
d = &actionRunListData{refreshRunIDs: []int64{792, 791}}
|
||||
d.preparePartialRefreshRuns(ctx)
|
||||
assert.Equal(t, []int64{792, 791}, []int64{d.ActionRuns[0].ID, d.ActionRuns[1].ID})
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user