mirror of
https://github.com/go-gitea/gitea.git
synced 2026-07-31 22:58:18 +00:00
396ec646f6
Several small fixes to the Actions runner management UI.
### Runner task list links to the job, not the workflow run
Relabeled the first column from "Run" to "Job"; it now shows the job ID
and links to the specific job (`/actions/runs/{runID}/jobs/{jobID}`).
Renamed locale key `task_list.run` to `task_list.job`.
### Missing "Disabled" translation
The runner list rendered a grey label via `actions.runners.disabled`,
but that key
did not exist in `locale_en-US.json`, so the raw key string leaked into
the UI.
Replaced `"actions.runners.disabled"` with `"disabled"`.
### Status column sorting ignored active vs idle
Sorting by status ordered purely on `last_online`, but the displayed
status is
computed from both `last_online` (offline) and `last_active` (idle vs
active).
As a result idle runners were interleaved with active ones. Sorting now
ranks by
the computed status (active → idle → offline). Disabled runners sink to
the bottom
of their status group (`is_disabled` as a secondary key), with
`last_online`/`id`
as stable tiebreakers so pagination stays deterministic.
### Status label colors
Active and idle both rendered green. Idle is now yellow, active green,
and
offline/unknown grey; the separate grey "Disabled" badge is unchanged.
This keeps
connectivity visible even for disabled runners (e.g. a disabled runner
still shows
whether it is idle or offline).
<img width="715" height="406" alt="image"
src="https://github.com/user-attachments/assets/9ef06aa8-a870-4de5-9d94-603a58186908"
/>
---------
Co-authored-by: Zettat123 <zettat123@gmail.com>
131 lines
4.8 KiB
Go
131 lines
4.8 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package integration
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
actions_model "gitea.dev/models/actions"
|
|
"gitea.dev/models/db"
|
|
"gitea.dev/modules/timeutil"
|
|
"gitea.dev/tests"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestFindRunnersSortByStatus verifies that sorting by status ranks runners by
|
|
// their computed status (active, then idle, then offline) instead of interleaving
|
|
// idle runners with active ones by raw last_online, and that a disabled runner
|
|
// sinks to the bottom of its status group rather than mixing with enabled runners.
|
|
//
|
|
// It lives in tests/integration rather than a unit test because the status rank is
|
|
// a database-evaluated CASE expression; unit tests only run against SQLite, so the
|
|
// expression must be exercised against MySQL/PostgreSQL/MSSQL in CI to catch dialect
|
|
// differences.
|
|
func TestFindRunnersSortByStatus(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
ctx := t.Context()
|
|
|
|
const ownerID = 1001
|
|
now := time.Now()
|
|
// An idle runner that went online most recently would sort before an active
|
|
// runner when ordering by last_online alone; the status rank must override that.
|
|
insert := func(name string, lastOnline, lastActive time.Time, disabled bool) {
|
|
require.NoError(t, db.Insert(ctx, &actions_model.ActionRunner{
|
|
Name: name,
|
|
UUID: "STATUS-SORT-" + name,
|
|
TokenHash: "status-sort-token-" + name,
|
|
OwnerID: ownerID,
|
|
LastOnline: timeutil.TimeStamp(lastOnline.Unix()),
|
|
LastActive: timeutil.TimeStamp(lastActive.Unix()),
|
|
IsDisabled: disabled,
|
|
}))
|
|
}
|
|
// Each disabled runner has the most recent last_online within its status group,
|
|
// so it would sort first in that group if the disabled flag were ignored; the
|
|
// last_active value keeps it in the intended group (offline<idle<active).
|
|
insert("active", now.Add(-8*time.Second), now.Add(-5*time.Second), false)
|
|
insert("active-disabled", now, now.Add(-2*time.Second), true)
|
|
insert("idle", now.Add(-30*time.Second), now.Add(-20*time.Second), false)
|
|
insert("idle-disabled", now, now.Add(-20*time.Second), true)
|
|
insert("offline", now.Add(-3*time.Minute), now.Add(-3*time.Minute), false)
|
|
insert("offline-disabled", now.Add(-90*time.Second), now.Add(-90*time.Second), true)
|
|
|
|
names := func(runners []*actions_model.ActionRunner) []string {
|
|
out := make([]string, len(runners))
|
|
for i, r := range runners {
|
|
out[i] = r.Name
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Active group first, then idle, then offline; within each group enabled before disabled.
|
|
runners, err := db.Find[actions_model.ActionRunner](ctx, actions_model.FindRunnerOptions{OwnerID: ownerID, Sort: "online"})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []string{
|
|
"active", "active-disabled",
|
|
"idle", "idle-disabled",
|
|
"offline", "offline-disabled",
|
|
}, names(runners))
|
|
|
|
// The descending status sort reverses the group order but still keeps disabled
|
|
// runners at the bottom of their group.
|
|
runners, err = db.Find[actions_model.ActionRunner](ctx, actions_model.FindRunnerOptions{OwnerID: ownerID, Sort: "offline"})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []string{
|
|
"offline", "offline-disabled",
|
|
"idle", "idle-disabled",
|
|
"active", "active-disabled",
|
|
}, names(runners))
|
|
}
|
|
|
|
// TestFindRunnersPaginationNoDuplicates verifies that the unique id tiebreaker in
|
|
// FindRunnerOptions.ToOrders keeps pagination deterministic when the primary sort
|
|
// key (last_online) is tied for every runner. It is an integration test so the
|
|
// ordering is validated against every supported database, not only SQLite.
|
|
func TestFindRunnersPaginationNoDuplicates(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
ctx := t.Context()
|
|
|
|
// Create several runners that all share the same last_online value so the
|
|
// primary sort key (last_online) is tied for all of them.
|
|
const ownerID = 1000
|
|
const count = 6
|
|
for i := range count {
|
|
require.NoError(t, db.Insert(ctx, &actions_model.ActionRunner{
|
|
Name: "paginated-runner",
|
|
UUID: fmt.Sprintf("PAGINATE-TEST-0000-0000-00000000000%d", i),
|
|
TokenHash: fmt.Sprintf("paginate-test-token-hash-%d", i),
|
|
OwnerID: ownerID,
|
|
RepoID: 0,
|
|
LastOnline: 42,
|
|
}))
|
|
}
|
|
|
|
// Page through the runners and ensure every id is returned exactly once.
|
|
seen := make(map[int64]int)
|
|
const pageSize = 2
|
|
for page := 1; ; page++ {
|
|
runners, err := db.Find[actions_model.ActionRunner](ctx, actions_model.FindRunnerOptions{
|
|
ListOptions: db.ListOptions{Page: page, PageSize: pageSize},
|
|
OwnerID: ownerID,
|
|
})
|
|
require.NoError(t, err)
|
|
if len(runners) == 0 {
|
|
break
|
|
}
|
|
for _, r := range runners {
|
|
seen[r.ID]++
|
|
}
|
|
}
|
|
|
|
assert.Len(t, seen, count, "each runner should be returned exactly once across all pages")
|
|
for id, n := range seen {
|
|
assert.Equal(t, 1, n, "runner %d appeared on %d pages", id, n)
|
|
}
|
|
}
|