feat(actions): add workflow status badge modal (#38196)
- Add a Create Status Badge button for selected Actions workflows. - Show badge URL, Markdown, and HTML snippets backed by the existing workflow badge route. ## Screenshots <img width="553" height="470" alt="dyn-a5d565ab915b9ffb6c02ac68113494b0" src="https://github.com/user-attachments/assets/43b4ceb9-bbd1-4024-b058-d85ec8325e88" /> <img width="349" height="156" alt="grafik" src="https://github.com/user-attachments/assets/6eaec62d-ffb0-45c0-b63d-866a41a66005" /> Fixes https://github.com/go-gitea/gitea/issues/31462 --------- Signed-off-by: guanzi008 <245205080@qq.com> Co-authored-by: bircni <bircni@icloud.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
stdCtx "context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
@@ -47,6 +48,15 @@ type WorkflowInfo struct {
|
||||
Workflow *act_model.Workflow
|
||||
}
|
||||
|
||||
type workflowBadge struct {
|
||||
URL string
|
||||
WorkflowURL string
|
||||
Markdown string
|
||||
MarkdownAltText string
|
||||
HTML string
|
||||
HTMLAltText string
|
||||
}
|
||||
|
||||
// DisplayName returns the workflow name from the YAML file if present, otherwise the filename.
|
||||
func (w WorkflowInfo) DisplayName() string {
|
||||
if w.Workflow != nil && w.Workflow.Name != "" {
|
||||
@@ -403,10 +413,16 @@ func prepareWorkflowList(ctx *context.Context, workflows []WorkflowInfo, otherWo
|
||||
ctx.Data["Runs"] = runs
|
||||
|
||||
workflowNames := make(map[string]string, len(workflows))
|
||||
workflowDisplayName := workflowID
|
||||
for _, wf := range workflows {
|
||||
workflowNames[wf.Entry.Name()] = wf.DisplayName()
|
||||
displayName := wf.DisplayName()
|
||||
workflowNames[wf.Entry.Name()] = displayName
|
||||
if wf.Entry.Name() == workflowID {
|
||||
workflowDisplayName = displayName
|
||||
}
|
||||
}
|
||||
ctx.Data["WorkflowNames"] = workflowNames
|
||||
prepareWorkflowBadgeTemplate(ctx, workflowID, workflowDisplayName)
|
||||
|
||||
actors, err := actions_model.GetActors(ctx, ctx.Repo.Repository.ID)
|
||||
if err != nil {
|
||||
@@ -432,6 +448,32 @@ func prepareWorkflowList(ctx *context.Context, workflows []WorkflowInfo, otherWo
|
||||
ctx.Data["CanWriteRepoUnitActions"] = ctx.Repo.Permission.CanWrite(unit.TypeActions)
|
||||
}
|
||||
|
||||
func prepareWorkflowBadgeTemplate(ctx *context.Context, workflowID, displayName string) {
|
||||
if workflowID == "" {
|
||||
return
|
||||
}
|
||||
if displayName == "" {
|
||||
displayName = workflowID
|
||||
}
|
||||
|
||||
repoURL := ctx.Repo.Repository.HTMLURL(ctx)
|
||||
badgeURL := fmt.Sprintf("%s/actions/workflows/%s/badge.svg?branch=%s", repoURL, util.PathEscapeSegments(workflowID), url.QueryEscape(ctx.Repo.Repository.DefaultBranch))
|
||||
workflowURL := fmt.Sprintf("%s/actions?workflow=%s", repoURL, url.QueryEscape(workflowID))
|
||||
|
||||
ctx.Data["WorkflowBadge"] = workflowBadge{
|
||||
URL: badgeURL,
|
||||
WorkflowURL: workflowURL,
|
||||
Markdown: fmt.Sprintf("[](%s)", escapeMarkdownImageAltText(displayName), badgeURL, workflowURL),
|
||||
MarkdownAltText: escapeMarkdownImageAltText(displayName),
|
||||
HTML: fmt.Sprintf(`<a href="%s"><img src="%s" alt="%s"></a>`, html.EscapeString(workflowURL), html.EscapeString(badgeURL), html.EscapeString(displayName)),
|
||||
HTMLAltText: displayName,
|
||||
}
|
||||
}
|
||||
|
||||
func escapeMarkdownImageAltText(s string) string {
|
||||
return strings.NewReplacer(`\`, `\\`, `[`, `\[`, `]`, `\]`).Replace(s)
|
||||
}
|
||||
|
||||
// loadIsRefDeleted loads the IsRefDeleted field for each run in the list.
|
||||
// TODO: move this function to models/actions/run_list.go but now it will result in a circular import.
|
||||
func loadIsRefDeleted(ctx stdCtx.Context, repoID int64, runs actions_model.RunList) error {
|
||||
|
||||
@@ -4,12 +4,18 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
actions_model "gitea.dev/models/actions"
|
||||
"gitea.dev/models/db"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
unittest "gitea.dev/models/unittest"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/test"
|
||||
web_context "gitea.dev/services/context"
|
||||
|
||||
act_model "gitea.com/gitea/runner/act/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -176,3 +182,48 @@ func Test_loadIsRefDeleted(t *testing.T) {
|
||||
assert.True(t, run.IsRefDeleted)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareWorkflowBadgeTemplate(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.IsInTesting, true)()
|
||||
defer test.MockVariableValue(&setting.AppURL, "https://gitea.example.com/")()
|
||||
defer test.MockVariableValue(&setting.AppSubURL, "")()
|
||||
defer test.MockVariableValue(&setting.PublicURLDetection, setting.PublicURLNever)()
|
||||
|
||||
t.Run("no workflow selected", func(t *testing.T) {
|
||||
ctx := newWorkflowBadgeTestContext(t)
|
||||
|
||||
prepareWorkflowBadgeTemplate(ctx, "", "ignored")
|
||||
|
||||
assert.NotContains(t, ctx.Data, "WorkflowBadge")
|
||||
})
|
||||
|
||||
t.Run("selected workflow", func(t *testing.T) {
|
||||
ctx := newWorkflowBadgeTestContext(t)
|
||||
|
||||
prepareWorkflowBadgeTemplate(ctx, "build/test workflow.yml", `CI [prod]\build "fast" <ok>`)
|
||||
|
||||
assert.Equal(t, workflowBadge{
|
||||
URL: "https://gitea.example.com/user1/repo1/actions/workflows/build/test%20workflow.yml/badge.svg?branch=release%2F1.0+%26+hotfix",
|
||||
WorkflowURL: "https://gitea.example.com/user1/repo1/actions?workflow=build%2Ftest+workflow.yml",
|
||||
Markdown: `[![CI \[prod\]\\build "fast" <ok>](https://gitea.example.com/user1/repo1/actions/workflows/build/test%20workflow.yml/badge.svg?branch=release%2F1.0+%26+hotfix)]` +
|
||||
`(https://gitea.example.com/user1/repo1/actions?workflow=build%2Ftest+workflow.yml)`,
|
||||
MarkdownAltText: `CI \[prod\]\\build "fast" <ok>`,
|
||||
HTML: `<a href="https://gitea.example.com/user1/repo1/actions?workflow=build%2Ftest+workflow.yml"><img src="https://gitea.example.com/user1/repo1/actions/workflows/build/test%20workflow.yml/badge.svg?branch=release%2F1.0+%26+hotfix" alt="CI [prod]\build "fast" <ok>"></a>`,
|
||||
HTMLAltText: `CI [prod]\build "fast" <ok>`,
|
||||
}, ctx.Data["WorkflowBadge"])
|
||||
})
|
||||
}
|
||||
|
||||
func newWorkflowBadgeTestContext(t *testing.T) *web_context.Context {
|
||||
t.Helper()
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "https://gitea.example.com/user1/repo1/actions", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
ctx := web_context.NewWebContext(web_context.NewBaseContextForTest(resp, req), nil, nil)
|
||||
ctx.Repo.Repository = &repo_model.Repository{
|
||||
OwnerName: "user1",
|
||||
Name: "repo1",
|
||||
DefaultBranch: "release/1.0 & hotfix",
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user