Skip to content
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6e35de7
Rework actions status icons
silverwind Apr 14, 2026
9e266a6
Use "In progress" for StatusRunning description
silverwind Apr 14, 2026
6716fca
Move action-status enrichment off the CommitStatus model
silverwind Apr 19, 2026
1489ee0
Merge branch 'main' into acticons
silverwind Apr 19, 2026
c140daf
Shorten doc comments on CommitStatusActionInfo
silverwind Apr 19, 2026
20c0ef8
Move commit_status template into icons/
silverwind Apr 19, 2026
7c310e8
Fix cancelled icon and align action status UI with GitHub
silverwind Apr 21, 2026
92e8974
Use maps.Copy for CommitStatusActionInfo merge
silverwind Apr 21, 2026
1f03083
Merge remote-tracking branch 'origin/main' into acticons
silverwind Apr 23, 2026
f67c18f
Keep CanRead/HideActionsURL blocks at callsites
silverwind Apr 23, 2026
264861b
Cover the commit-status tippy tooltip for in-flight action jobs
silverwind Apr 23, 2026
5b83e49
Move action-status enrichment into a template-side lookup
silverwind Apr 23, 2026
e70ecdb
Move enrichment helper into its natural home
silverwind Apr 23, 2026
b0a485b
Tighten statusinfo package and template hook
silverwind Apr 23, 2026
6cb3859
Move artifact-V4 download helpers into services/actions
silverwind Apr 23, 2026
d6cd40b
Collapse statusinfo subpackage into modules/actions
silverwind Apr 23, 2026
e9bb781
Merge remote-tracking branch 'origin/main' into acticons
silverwind Apr 24, 2026
c76ed0f
Simplify action-status lookup in pulls/status.tmpl
silverwind Apr 24, 2026
561e690
Use maps.Keys for jobIDs collection
silverwind Apr 24, 2026
59715a1
Address PR review feedback
silverwind Apr 27, 2026
bc99d63
Rename IconVariant to IconSuffix, drop ternary branching
silverwind Apr 27, 2026
266f9bf
Merge branch 'main' into acticons
silverwind Apr 27, 2026
e4dc0c4
Merge branch 'main' into acticons
silverwind Apr 30, 2026
95d29a7
Update templates/repo/icons/action_status.tmpl
wxiaoguang May 1, 2026
eddeb3f
Merge remote-tracking branch 'origin/main' into acticons
silverwind May 8, 2026
e83604f
Compute ActionInfo inside status_items.tmpl
silverwind May 8, 2026
887b405
Compute ActionInfo only in the merge box, trim comments
silverwind May 8, 2026
e18443f
Replace integration test with focused unit test
silverwind May 8, 2026
8d80fac
Pass icon-suffix to ActionRunStatus in attempt dropdown
silverwind May 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions modules/actions/commit_status_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package actions

import (
"context"
"maps"
"slices"

actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/log"
)

// CommitStatusInfo maps CommitStatus.ID to the live ActionRunJob status
// for Gitea Actions rows.
type CommitStatusInfo map[int64]actions_model.Status

// IconStatus returns the action status name to route the icon through
// repo/icons/action_status, or "" when the row isn't from Gitea Actions.
func (m CommitStatusInfo) IconStatus(s *git_model.CommitStatus) string {
if status, ok := m[s.ID]; ok {
return status.String()
}
return ""
}

// GetCommitStatusInfo resolves the live ActionRunJob.Status for every
// CommitStatus row backed by Gitea Actions. Rows from other sources (external
// CIs, API) are left untouched and rendered from their stored State.
//
// Side effect: fills in status.Repo for inputs whose Repo is nil, sharing one
// lookup across entries with the same RepoID — ParseGiteaActionsTargetURL
// needs Repo loaded and would otherwise lazy-load it per row.
func GetCommitStatusInfo(ctx context.Context, statuses []*git_model.CommitStatus) CommitStatusInfo {
if len(statuses) == 0 {
return nil
}
statusByJobID := make(map[int64]*git_model.CommitStatus)
repoByID := make(map[int64]*repo_model.Repository)
for _, status := range statuses {
if status == nil || status.TargetURL == "" {
continue
}
if status.Repo == nil {
status.Repo = repoByID[status.RepoID]
}
// ParseGiteaActionsTargetURL lazy-loads status.Repo on miss; cache the
// outcome so later entries with the same RepoID skip that load.
_, jobID, ok := status.ParseGiteaActionsTargetURL(ctx)
repoByID[status.RepoID] = status.Repo
if ok {
statusByJobID[jobID] = status
}
}
if len(statusByJobID) == 0 {
return nil
}
jobs := make(map[int64]*actions_model.ActionRunJob, len(statusByJobID))
if err := db.GetEngine(ctx).In("id", slices.Collect(maps.Keys(statusByJobID))).Cols("id", "status").Find(&jobs); err != nil {
log.Error("GetCommitStatusInfo: find action run jobs: %v", err)
return nil
}
info := make(CommitStatusInfo, len(jobs))
for jobID, status := range statusByJobID {
if job, ok := jobs[jobID]; ok && !job.Status.IsUnknown() {
info[status.ID] = job.Status
}
}
return info
}
25 changes: 25 additions & 0 deletions modules/templates/util_actions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package templates

import (
"context"

git_model "code.gitea.io/gitea/models/git"
actions_module "code.gitea.io/gitea/modules/actions"
)

// ActionsUtils groups template helpers for Gitea Actions data. Methods may
// issue DB queries.
type ActionsUtils struct {
ctx context.Context
}

func NewActionsUtils(ctx context.Context) *ActionsUtils {
return &ActionsUtils{ctx: ctx}
}

func (a *ActionsUtils) CommitStatusInfo(statuses []*git_model.CommitStatus) actions_module.CommitStatusInfo {
return actions_module.GetCommitStatusInfo(a.ctx, statuses)
}
2 changes: 1 addition & 1 deletion routers/api/actions/artifactsv4.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,14 @@ import (

actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/actions"
"code.gitea.io/gitea/services/context"

"google.golang.org/protobuf/encoding/protojson"
Expand Down
13 changes: 6 additions & 7 deletions routers/api/v1/repo/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
secret_model "code.gitea.io/gitea/models/secret"
"code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
Expand Down Expand Up @@ -1870,7 +1869,7 @@ func GetArtifact(ctx *context.APIContext) {
return
}

if actions.IsArtifactV4(art) {
if actions_service.IsArtifactV4(art) {
Comment thread
silverwind marked this conversation as resolved.
convertedArtifact, err := convert.ToActionArtifact(ctx.Repo.Repository, art)
if err != nil {
ctx.APIErrorInternal(err)
Expand Down Expand Up @@ -1919,7 +1918,7 @@ func DeleteArtifact(ctx *context.APIContext) {
return
}

if actions.IsArtifactV4(art) {
if actions_service.IsArtifactV4(art) {
if err := actions_model.SetArtifactNeedDeleteByID(ctx, art.ID); err != nil {
ctx.APIErrorInternal(err)
return
Expand Down Expand Up @@ -1992,10 +1991,10 @@ func DownloadArtifact(ctx *context.APIContext) {
return
}

if actions.IsArtifactV4(art) {
if actions_service.IsArtifactV4(art) {
// @actions/toolkit asserts that downloaded artifacts of a different runid return 302
// https://github.com/actions/toolkit/blob/44d43b5490b02998bd09b0c4ff369a4cc67876c2/packages/artifact/src/internal/download/download-artifact.ts#L203-L210
if actions.DownloadArtifactV4ServeDirect(ctx.Base, art) {
if actions_service.DownloadArtifactV4ServeDirect(ctx.Base, art) {
return
}

Expand Down Expand Up @@ -2047,8 +2046,8 @@ func DownloadArtifactRaw(ctx *context.APIContext) {
ctx.APIError(http.StatusNotFound, "Artifact has expired")
return
}
if actions.IsArtifactV4(art) {
err := actions.DownloadArtifactV4(ctx.Base, art)
if actions_service.IsArtifactV4(art) {
err := actions_service.DownloadArtifactV4(ctx.Base, art)
if err != nil {
ctx.APIErrorInternal(err)
return
Expand Down
4 changes: 2 additions & 2 deletions routers/web/repo/actions/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -972,8 +972,8 @@ func ArtifactsDownloadView(ctx *context_module.Context) {
// A v4 Artifact may only contain a single file
// Multiple files are uploaded as a single file archive
// All other cases fall back to the legacy v1–v3 zip handling below
if len(artifacts) == 1 && actions.IsArtifactV4(artifacts[0]) {
err := actions.DownloadArtifactV4(ctx.Base, artifacts[0])
if len(artifacts) == 1 && actions_service.IsArtifactV4(artifacts[0]) {
err := actions_service.DownloadArtifactV4(ctx.Base, artifacts[0])
if err != nil {
ctx.ServerError("DownloadArtifactV4", err)
return
Expand Down
File renamed without changes.
6 changes: 3 additions & 3 deletions services/actions/commit_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,11 @@ func toCommitStatusDescription(job *actions_model.ActionRunJob) string {
case actions_model.StatusFailure:
return fmt.Sprintf("Failing after %s", job.Duration())
case actions_model.StatusCancelled:
return "Has been cancelled"
return fmt.Sprintf("Cancelled after %s", job.Duration())
case actions_model.StatusSkipped:
return "Has been skipped"
return "Skipped"
case actions_model.StatusRunning:
return "Has started running"
return "In progress"
case actions_model.StatusWaiting:
return "Waiting to run"
case actions_model.StatusBlocked:
Expand Down
2 changes: 1 addition & 1 deletion services/actions/commit_status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func TestCreateCommitStatus_Dedupe(t *testing.T) {
require.Len(t, statuses, 2)
assert.Equal(t, "Waiting to run", statuses[0].Description)
assert.Equal(t, commitstatus.CommitStatusPending, statuses[1].State)
assert.Equal(t, "Has started running", statuses[1].Description)
assert.Equal(t, "In progress", statuses[1].Description)
assert.Equal(t, expectedTargetURL, statuses[1].TargetURL)

require.NoError(t, createCommitStatus(t.Context(), repo, "push", commit.ID.String(), run, job))
Expand Down
1 change: 1 addition & 0 deletions services/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ func NewTemplateContextForWeb(ctx reqctx.RequestContext, req *http.Request, loca
tmplCtx["AvatarUtils"] = templates.NewAvatarUtils(ctx)
tmplCtx["RenderUtils"] = templates.NewRenderUtils(ctx)
tmplCtx["MiscUtils"] = templates.NewMiscUtils(ctx)
tmplCtx["ActionsUtils"] = templates.NewActionsUtils(ctx)
tmplCtx["RootData"] = ctx.GetData()
tmplCtx["Consts"] = map[string]any{
"RepoUnitTypeCode": unit.TypeCode,
Expand Down
4 changes: 3 additions & 1 deletion templates/repo/actions/runs_list.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
{{range $run := .Runs}}
<div class="flex-item tw-items-center">
<div class="flex-item-leading">
{{template "repo/actions/status" (dict "status" $run.Status.String)}}
<span data-tooltip-content="{{ctx.Locale.Tr (printf "actions.status.%s" $run.Status.String)}}">
{{template "repo/icons/action_status" (dict "status" $run.Status.String "fill" true)}}
</span>
</div>
<div class="flex-item-main">
<span class="flex-item-title" title="{{$run.Title}}">
Expand Down
4 changes: 2 additions & 2 deletions templates/repo/commit_statuses.tmpl
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{{if .Statuses}}
{{if and (eq (len .Statuses) 1) .Status.TargetURL}}
<a class="flex-text-inline tw-no-underline {{.AdditionalClasses}}" data-global-init="initCommitStatuses" href="{{.Status.TargetURL}}">
{{template "repo/commit_status" .Status}}
{{template "repo/icons/commit_status" .Status}}
</a>
{{else}}
<span class="flex-text-inline {{.AdditionalClasses}}" data-global-init="initCommitStatuses" tabindex="0">
{{template "repo/commit_status" .Status}}
{{template "repo/icons/commit_status" .Status}}
</span>
{{end}}
<div class="tippy-target">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
<!-- This template should be kept the same as web_src/js/components/ActionRunStatus.vue
Please also update the vue file above if this template is modified.
action status accepted: success, skipped, waiting, blocked, running, failure, cancelled, unknown
-->
{{/* Template Attributes:
* status: one of success, skipped, waiting, blocked, running, failure, cancelled, unknown
* size: icon size in pixels (default 16)
* className: additional CSS classes
Comment thread
silverwind marked this conversation as resolved.
Outdated
* fill: use filled-circle icons for success/failure (default false: bare icons matching repo/commit_status)
Comment thread
silverwind marked this conversation as resolved.
Outdated

Keep this template in sync with web_src/js/components/ActionRunStatus.vue.
*/}}
{{- $size := Iif .size .size 16 -}}
{{- $className := Iif .className .className "" -}}
<span data-tooltip-content="{{ctx.Locale.Tr (printf "actions.status.%s" .status)}}">
{{if eq .status "success"}}
{{svg "octicon-check-circle-fill" $size (printf "tw-text-green %s" $className)}}
{{svg (Iif .fill "octicon-check-circle-fill" "octicon-check") $size (printf "tw-text-green %s" $className)}}
{{else if eq .status "skipped"}}
{{svg "octicon-skip" $size (printf "tw-text-text-light %s" $className)}}
{{else if eq .status "cancelled"}}
Expand All @@ -18,6 +21,5 @@
{{else if eq .status "running"}}
{{svg "gitea-running" $size (printf "tw-text-yellow rotate-clockwise %s" $className)}}
{{else}}{{/*failure, unknown*/}}
{{svg "octicon-x-circle-fill" $size (printf "tw-text-red %s" $className)}}
{{svg (Iif .fill "octicon-x-circle-fill" "octicon-x") $size (printf "tw-text-red %s" $className)}}
{{end}}
</span>
16 changes: 11 additions & 5 deletions templates/repo/pulls/status.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/}}
{{$statusCheckData := .StatusCheckData}}
{{if .CommitStatus}}
{{$actionInfo := ctx.ActionsUtils.CommitStatusInfo .CommitStatuses}}
Comment thread
silverwind marked this conversation as resolved.
Outdated
<div class="commit-status-panel">
<div class="ui top attached header commit-status-header">
{{$statusCheckData.CommitStatusCheckPrompt ctx.Locale}}
Expand Down Expand Up @@ -37,15 +38,20 @@
{{end}}

<div class="commit-status-list">
{{range .CommitStatuses}}
{{range $cs := .CommitStatuses}}
<div class="commit-status-item">
{{template "repo/commit_status" .}}
<div class="status-context gt-ellipsis">{{.Context}} <span class="tw-text-text-light-2">{{.Description}}</span></div>
{{$actionStatus := $actionInfo.IconStatus $cs}}
{{if $actionStatus}}
{{template "repo/icons/action_status" (dict "status" $actionStatus "size" 18 "className" "commit-status icon")}}
{{else}}
{{template "repo/icons/commit_status" $cs}}
{{end}}
<div class="status-context gt-ellipsis">{{$cs.Context}} <span class="tw-text-text-light-2">{{$cs.Description}}</span></div>
<div class="ui status-details">
{{if and $statusCheckData $statusCheckData.IsContextRequired}}
{{if (call $statusCheckData.IsContextRequired .Context)}}<div class="ui label">{{ctx.Locale.Tr "repo.pulls.status_checks_requested"}}</div>{{end}}
{{if (call $statusCheckData.IsContextRequired $cs.Context)}}<div class="ui label">{{ctx.Locale.Tr "repo.pulls.status_checks_requested"}}</div>{{end}}
{{end}}
<span>{{if .TargetURL}}<a href="{{.TargetURL}}">{{ctx.Locale.Tr "repo.pulls.status_checks_details"}}</a>{{end}}</span>
<span>{{if $cs.TargetURL}}<a href="{{$cs.TargetURL}}">{{ctx.Locale.Tr "repo.pulls.status_checks_details"}}</a>{{end}}</span>
</div>
</div>
{{end}}
Expand Down
Loading
Loading