diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index f6e71cccb3e3e..e11dfc45b9b08 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -3833,6 +3833,11 @@ "actions.workflow.enable_success": "Workflow '%s' enabled successfully.", "actions.workflow.disabled": "Workflow is disabled.", "actions.workflow.run": "Run Workflow", + "actions.workflow.create_status_badge": "Create Status Badge", + "actions.workflow.status_badge": "Status Badge", + "actions.workflow.status_badge_url": "Badge URL", + "actions.workflow.status_badge_markdown": "Markdown", + "actions.workflow.status_badge_html": "HTML", "actions.workflow.not_found": "Workflow '%s' not found.", "actions.workflow.run_success": "Workflow '%s' run successfully.", "actions.workflow.from_ref": "Use workflow from", diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go index 9c5e1664de00e..484fd9f81bfeb 100644 --- a/routers/web/repo/actions/actions.go +++ b/routers/web/repo/actions/actions.go @@ -8,6 +8,7 @@ import ( stdCtx "context" "errors" "fmt" + "html" "net/http" "net/url" "slices" @@ -47,6 +48,12 @@ type WorkflowInfo struct { Workflow *act_model.Workflow } +type workflowBadge struct { + URL string + Markdown string + HTML 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 +410,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 +445,29 @@ 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, + Markdown: fmt.Sprintf("[![%s](%s)](%s)", escapeMarkdownImageAltText(displayName), badgeURL, workflowURL), + HTML: fmt.Sprintf(`%s`, html.EscapeString(workflowURL), html.EscapeString(badgeURL), html.EscapeString(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 { diff --git a/routers/web/repo/actions/actions_test.go b/routers/web/repo/actions/actions_test.go index 1f1040738bbad..a2059d8329502 100644 --- a/routers/web/repo/actions/actions_test.go +++ b/routers/web/repo/actions/actions_test.go @@ -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,45 @@ 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" `) + + 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", + Markdown: `[![CI \[prod\]\\build "fast" ](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)`, + HTML: `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 +} diff --git a/templates/repo/actions/list.tmpl b/templates/repo/actions/list.tmpl index bf2a1db0a7d67..633fdbb1e9618 100644 --- a/templates/repo/actions/list.tmpl +++ b/templates/repo/actions/list.tmpl @@ -106,6 +106,12 @@ + {{if .WorkflowBadge}} + + {{end}} + {{if and .AllowDisableOrEnableWorkflow .CurWorkflowIsListed $.CurWorkflow}} + + +
+ + + +
+
+ + + +
+ + + + {{end}} {{else}}