From d93bbcc0a6845bd40cf91e437678289f5c9bec3e Mon Sep 17 00:00:00 2001 From: Nicolas Date: Mon, 25 May 2026 16:41:36 +0200 Subject: [PATCH] feat(actions): List workflows that were executed once but got removed from the default branch (#37835) --- docs/guideline-backend.md | 5 ++++ models/actions/run_list.go | 12 ++++++++ models/actions/run_list_test.go | 24 +++++++++++++++ options/locale/locale_en-US.json | 2 ++ routers/web/repo/actions/actions.go | 35 ++++++++++++++++++++-- templates/admin/navbar.tmpl | 14 ++++----- templates/org/settings/navbar.tmpl | 2 +- templates/repo/actions/list.tmpl | 23 +++++++++++++-- templates/repo/settings/navbar.tmpl | 2 +- templates/user/settings/navbar.tmpl | 2 +- web_src/css/modules/menu.css | 1 + web_src/css/shared/settings.css | 46 ++++++++++++++++++++--------- 12 files changed, 138 insertions(+), 30 deletions(-) create mode 100644 models/actions/run_list_test.go diff --git a/docs/guideline-backend.md b/docs/guideline-backend.md index bc3e71113f2..29fa92288cd 100644 --- a/docs/guideline-backend.md +++ b/docs/guideline-backend.md @@ -56,3 +56,8 @@ Endpoints returning lists must - support pagination (`page` & `limit` options in query) - set `X-Total-Count` header via **SetTotalCountHeader** ([example](https://github.com/go-gitea/gitea/blob/7aae98cc5d4113f1e9918b7ee7dd09f67c189e3e/routers/api/v1/repo/issue.go#L444)) + +### Knowledge + +- Partially database table migration must use `SyncWithOptions(IgnoreDrop...)` +- Template variables with "camelCase" or "snake_case" are used for restoring the form values from a submitted form diff --git a/models/actions/run_list.go b/models/actions/run_list.go index bfa9bbfb169..7294991b0be 100644 --- a/models/actions/run_list.go +++ b/models/actions/run_list.go @@ -132,6 +132,18 @@ func GetStatusInfoList(ctx context.Context, lang translation.Locale) []StatusInf return statusInfoList } +// GetRunWorkflowIDs returns all distinct WorkflowIDs that have at least +// one ActionRun in the given repo. +func GetRunWorkflowIDs(ctx context.Context, repoID int64) ([]string, error) { + ids := make([]string, 0, 10) + return ids, db.GetEngine(ctx).Table("action_run"). + Where(builder.Eq{"repo_id": repoID}). + Distinct("workflow_id"). + Cols("workflow_id"). + Asc("workflow_id"). + Find(&ids) +} + // GetActors returns a slice of Actors func GetActors(ctx context.Context, repoID int64) ([]*user_model.User, error) { actors := make([]*user_model.User, 0, 10) diff --git a/models/actions/run_list_test.go b/models/actions/run_list_test.go new file mode 100644 index 00000000000..9ed004a3621 --- /dev/null +++ b/models/actions/run_list_test.go @@ -0,0 +1,24 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package actions + +import ( + "testing" + + "code.gitea.io/gitea/models/unittest" + + "github.com/stretchr/testify/assert" +) + +func TestGetRunWorkflowIDs(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + ids, err := GetRunWorkflowIDs(t.Context(), 4) + assert.NoError(t, err) + assert.Equal(t, []string{"artifact.yaml", "test.yaml"}, ids) + + ids, err = GetRunWorkflowIDs(t.Context(), 999999) + assert.NoError(t, err) + assert.Empty(t, ids) +} diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index 1ed99f724bc..de75131c191 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -3759,6 +3759,8 @@ "actions.runners.reset_registration_token_confirm": "Would you like to invalidate the current token and generate a new one?", "actions.runners.reset_registration_token_success": "Runner registration token reset successfully", "actions.runs.all_workflows": "All Workflows", + "actions.runs.other_workflows": "Other workflows", + "actions.runs.other_workflows_tooltip": "Workflows that were executed in this repository but do not exist on the default branch.", "actions.runs.workflow_run_count_1": "%d workflow run", "actions.runs.workflow_run_count_n": "%d workflow runs", "actions.runs.commit": "Commit", diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go index 1e6a839ee76..45d81fb7fe0 100644 --- a/routers/web/repo/actions/actions.go +++ b/routers/web/repo/actions/actions.go @@ -90,12 +90,16 @@ func List(ctx *context.Context) { if ctx.Written() { return } + otherWorkflows := prepareOtherWorkflows(ctx, workflows, curWorkflowID) + if ctx.Written() { + return + } prepareWorkflowDispatchTemplate(ctx, workflows, curWorkflowID) if ctx.Written() { return } - prepareWorkflowList(ctx, workflows) + prepareWorkflowList(ctx, workflows, otherWorkflows) if ctx.Written() { return } @@ -103,6 +107,31 @@ func List(ctx *context.Context) { ctx.HTML(http.StatusOK, tplListActions) } +// prepareOtherWorkflows surfaces historical runs whose workflow file no longer +// exists on the default branch (renamed, removed, or only on other branches). +func prepareOtherWorkflows(ctx *context.Context, workflows []WorkflowInfo, curWorkflowID string) []string { + listed := make(container.Set[string], len(workflows)) + for _, w := range workflows { + listed.Add(w.Entry.Name()) + } + + var other []string + if ctx.Repo.Repository.NumActionRuns > 0 { + ids, err := actions_model.GetRunWorkflowIDs(ctx, ctx.Repo.Repository.ID) + if err != nil { + ctx.ServerError("GetRunWorkflowIDs", err) + return nil + } + other = container.FilterSlice(ids, func(id string) (string, bool) { + return id, id != "" && !listed.Contains(id) + }) + } + + ctx.Data["OtherWorkflows"] = other + ctx.Data["CurWorkflowIsListed"] = curWorkflowID == "" || listed.Contains(curWorkflowID) + return other +} + func WorkflowDispatchInputs(ctx *context.Context) { ref := ctx.FormString("ref") if ref == "" { @@ -253,7 +282,7 @@ func prepareWorkflowDispatchTemplate(ctx *context.Context, workflowInfos []Workf ctx.Data["Tags"] = tags } -func prepareWorkflowList(ctx *context.Context, workflows []WorkflowInfo) { +func prepareWorkflowList(ctx *context.Context, workflows []WorkflowInfo, otherWorkflows []string) { actorID := ctx.FormInt64("actor") status := ctx.FormInt("status") workflowID := ctx.FormString("workflow") @@ -367,7 +396,7 @@ func prepareWorkflowList(ctx *context.Context, workflows []WorkflowInfo) { pager := context.NewPagination(total, opts.PageSize, opts.Page, 5) pager.AddParamFromRequest(ctx.Req) ctx.Data["Page"] = pager - ctx.Data["HasWorkflowsOrRuns"] = len(workflows) > 0 || len(runs) > 0 + ctx.Data["HasWorkflowsOrRuns"] = len(workflows) > 0 || len(otherWorkflows) > 0 || len(runs) > 0 ctx.Data["CanWriteRepoUnitActions"] = ctx.Repo.Permission.CanWrite(unit.TypeActions) } diff --git a/templates/admin/navbar.tmpl b/templates/admin/navbar.tmpl index ce3048ed9fa..b6c9f155e9c 100644 --- a/templates/admin/navbar.tmpl +++ b/templates/admin/navbar.tmpl @@ -2,7 +2,7 @@