Skip to content

Implement actions badge svgs #28102

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 36 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f510354
Provide actions badge svgs
lunny Sep 22, 2023
dab670a
draw badge
lng2020 Nov 16, 2023
7805652
change field
lng2020 Nov 17, 2023
41d0169
simpilfy code
lng2020 Nov 17, 2023
d705e21
lint
lng2020 Nov 17, 2023
40ccec9
delete file
lng2020 Nov 17, 2023
2319c96
apply camel case
lng2020 Nov 17, 2023
7c52b0b
fmt
lng2020 Nov 17, 2023
0139320
lint
lng2020 Nov 17, 2023
e7cc38b
lint
lng2020 Nov 17, 2023
772a59c
change sql
lng2020 Nov 24, 2023
fba2a8e
Merge remote-tracking branch 'upstream/main' into pr27187
lng2020 Feb 19, 2024
2073d50
Apply reviews
lng2020 Feb 21, 2024
58ee396
Merge remote-tracking branch 'upstream/main' into pr27187
lng2020 Feb 21, 2024
2e41661
improvement
lng2020 Feb 21, 2024
58c8fb7
fix lint
lng2020 Feb 21, 2024
78fda75
Merge remote-tracking branch 'upstream/main' into pr27187
lng2020 Feb 24, 2024
9c271a6
restructure file
lng2020 Feb 24, 2024
b58ec24
add doc
lng2020 Feb 24, 2024
3f4deef
Merge branch 'main' into pr27187
lng2020 Feb 24, 2024
e631800
lint
lng2020 Feb 25, 2024
eae95d3
lint md
lng2020 Feb 25, 2024
c1c897b
simplify font width
lng2020 Feb 25, 2024
9336d62
Merge branch 'main' into pr27187
lng2020 Feb 25, 2024
c5612a6
fix lint
lng2020 Feb 25, 2024
148fa0f
Merge branch 'main' into pr27187
GiteaBot Feb 27, 2024
aea7e85
Fix package import path in recent merged PR
lng2020 Feb 27, 2024
cc97325
Merge branch 'main' into pr27187
GiteaBot Feb 27, 2024
4e61919
Merge branch 'main' into pr27187
GiteaBot Feb 27, 2024
76e3516
Update docs/content/usage/badge.en-us.md
lng2020 Feb 27, 2024
e1cf32e
Merge remote-tracking branch 'upstream/main' into pr27187
lng2020 Feb 27, 2024
f1dfad8
Add comment to explain the badge layout
lng2020 Feb 27, 2024
815dee8
Update modules/badge/badge.go
lng2020 Feb 27, 2024
deda732
Merge branch 'main' into pr27187
GiteaBot Feb 27, 2024
93e0eeb
Merge branch 'main' into pr27187
GiteaBot Feb 27, 2024
bab5721
Merge branch 'main' into pr27187
GiteaBot Feb 27, 2024
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
37 changes: 37 additions & 0 deletions docs/content/usage/badge.en-us.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
date: "2023-02-25T00:00:00+00:00"
title: "Badge"
slug: "badge"
sidebar_position: 11
toc: false
draft: false
aliases:
- /en-us/badge
menu:
sidebar:
parent: "usage"
name: "Badge"
sidebar_position: 11
identifier: "Badge"
---

# Badge

Gitea has its builtin Badge system which allows you to display the status of your repository in other places. You can use the following badges:

## Workflow Badge

The Gitea Actions workflow badge is a badge that shows the status of the latest workflow run.
It is designed to be compatible with [GitHub Actions workflow badge](https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/adding-a-workflow-status-badge).

You can use the following URL to get the badge:

```
https://your-gitea-instance.com/{owner}/{repo}/actions/workflows/{workflow_file}?branch={branch}&event={event}
```

- `{owner}`: The owner of the repository.
- `{repo}`: The name of the repository.
- `{workflow_file}`: The name of the workflow file.
- `{branch}`: Optional. The branch of the workflow. Default to your repository's default branch.
- `{event}`: Optional. The event of the workflow. Default to none.
17 changes: 17 additions & 0 deletions models/actions/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,23 @@ func GetRunByIndex(ctx context.Context, repoID, index int64) (*ActionRun, error)
return run, nil
}

func GetWorkflowLatestRun(ctx context.Context, repoID int64, workflowFile, branch, event string) (*ActionRun, error) {
var run ActionRun
q := db.GetEngine(ctx).Where("repo_id=?", repoID).
And("ref = ?", branch).
And("workflow_id = ?", workflowFile)
if event != "" {
q.And("event = ?", event)
}
has, err := q.Desc("id").Get(&run)
if err != nil {
return nil, err
} else if !has {
return nil, util.NewNotExistErrorf("run with repo_id %d, ref %s, workflow_id %s", repoID, branch, workflowFile)
}
return &run, nil
}

// UpdateRun updates a run.
// It requires the inputted run has Version set.
// It will return error if the version is not matched (it means the run has been changed after loaded).
Expand Down
104 changes: 104 additions & 0 deletions modules/badge/badge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package badge

import (
actions_model "code.gitea.io/gitea/models/actions"
)

// The Badge layout: |offset|label|message|
// We use 10x scale to calculate more precisely
// Then scale down to normal size in tmpl file

type Label struct {
text string
width int
}

func (l Label) Text() string {
return l.text
}

func (l Label) Width() int {
return l.width
}

func (l Label) TextLength() int {
return int(float64(l.width-defaultOffset) * 9.5)
}

func (l Label) X() int {
return l.width*5 + 10
}

type Message struct {
text string
width int
x int
}

func (m Message) Text() string {
return m.text
}

func (m Message) Width() int {
return m.width
}

func (m Message) X() int {
return m.x
}

func (m Message) TextLength() int {
return int(float64(m.width-defaultOffset) * 9.5)
}

type Badge struct {
Color string
FontSize int
Label Label
Message Message
}

func (b Badge) Width() int {
return b.Label.width + b.Message.width
}

const (
defaultOffset = 9
defaultFontSize = 11
DefaultColor = "#9f9f9f" // Grey
defaultFontWidth = 7 // approximate speculation
)

var StatusColorMap = map[actions_model.Status]string{
actions_model.StatusSuccess: "#4c1", // Green
actions_model.StatusSkipped: "#dfb317", // Yellow
actions_model.StatusUnknown: "#97ca00", // Light Green
actions_model.StatusFailure: "#e05d44", // Red
actions_model.StatusCancelled: "#fe7d37", // Orange
actions_model.StatusWaiting: "#dfb317", // Yellow
actions_model.StatusRunning: "#dfb317", // Yellow
actions_model.StatusBlocked: "#dfb317", // Yellow
}

// GenerateBadge generates badge with given template
func GenerateBadge(label, message, color string) Badge {
lw := defaultFontWidth*len(label) + defaultOffset
mw := defaultFontWidth*len(message) + defaultOffset
x := lw*10 + mw*5 - 10
return Badge{
Label: Label{
text: label,
width: lw,
},
Message: Message{
text: message,
width: mw,
x: x,
},
FontSize: defaultFontSize * 10,
Color: color,
}
}
56 changes: 56 additions & 0 deletions routers/web/repo/actions/badge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package actions

import (
"errors"
"fmt"
"net/http"
"path/filepath"
"strings"

actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/modules/badge"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
)

func GetWorkflowBadge(ctx *context.Context) {
workflowFile := ctx.Params("workflow_name")
branch := ctx.Req.URL.Query().Get("branch")
if branch == "" {
branch = ctx.Repo.Repository.DefaultBranch
}
branchRef := fmt.Sprintf("refs/heads/%s", branch)
event := ctx.Req.URL.Query().Get("event")

badge, err := getWorkflowBadge(ctx, workflowFile, branchRef, event)
if err != nil {
ctx.ServerError("GetWorkflowBadge", err)
return
}

ctx.Data["Badge"] = badge
ctx.RespHeader().Set("Content-Type", "image/svg+xml")
ctx.HTML(http.StatusOK, "shared/actions/runner_badge")
}

func getWorkflowBadge(ctx *context.Context, workflowFile, branchName, event string) (badge.Badge, error) {
extension := filepath.Ext(workflowFile)
workflowName := strings.TrimSuffix(workflowFile, extension)

run, err := actions_model.GetWorkflowLatestRun(ctx, ctx.Repo.Repository.ID, workflowFile, branchName, event)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
return badge.GenerateBadge(workflowName, "no status", badge.DefaultColor), nil
}
return badge.Badge{}, err
}

color, ok := badge.StatusColorMap[run.Status]
if !ok {
return badge.GenerateBadge(workflowName, "unknown status", badge.DefaultColor), nil
}
return badge.GenerateBadge(workflowName, run.Status.String(), color), nil
}
3 changes: 3 additions & 0 deletions routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -1371,6 +1371,9 @@ func registerRoutes(m *web.Route) {
m.Delete("/artifacts/{artifact_name}", actions.ArtifactsDeleteView)
m.Post("/rerun", reqRepoActionsWriter, actions.Rerun)
})
m.Group("/workflows/{workflow_name}", func() {
m.Get("/badge.svg", actions.GetWorkflowBadge)
})
}, reqRepoActionsReader, actions.MustEnableActions)

m.Group("/wiki", func() {
Expand Down
25 changes: 25 additions & 0 deletions templates/shared/actions/runner_badge.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{{.Badge.Width}}" height="18"
role="img" aria-label="{{.Badge.Label.Text}}: {{.Badge.Message.Text}}">
<title>{{.Badge.Label.Text}}: {{.Badge.Message.Text}}</title>
<linearGradient id="s" x2="0" y2="100%">
<stop offset="0" stop-color="#fff" stop-opacity=".7" />
<stop offset=".1" stop-color="#aaa" stop-opacity=".1" />
<stop offset=".9" stop-color="#000" stop-opacity=".3" />
<stop offset="1" stop-color="#000" stop-opacity=".5" />
</linearGradient>
<clipPath id="r">
<rect width="{{.Badge.Width}}" height="18" rx="4" fill="#fff" />
</clipPath>
<g clip-path="url(#r)">
<rect width="{{.Badge.Label.Width}}" height="18" fill="#555" />
<rect x="{{.Badge.Label.Width}}" width="{{.Badge.Message.Width}}" height="18" fill="{{.Badge.Color}}" />
<rect width="{{.Badge.Width}}" height="18" fill="url(#s)" />
</g>
<g fill="#fff" text-anchor="middle" font-family="Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision"
font-size="{{.Badge.FontSize}}"><text aria-hidden="true" x="{{.Badge.Label.X}}" y="140" fill="#010101" fill-opacity=".3"
transform="scale(.1)" textLength="{{.Badge.Label.TextLength}}">{{.Badge.Label.Text}}</text><text x="{{.Badge.Label.X}}" y="130"
transform="scale(.1)" fill="#fff" textLength="{{.Badge.Label.TextLength}}">{{.Badge.Label.Text}}</text><text aria-hidden="true"
x="{{.Badge.Message.X}}" y="140" fill="#010101" fill-opacity=".3" transform="scale(.1)"
textLength="{{.Badge.Message.TextLength}}">{{.Badge.Message.Text}}</text><text x="{{.Badge.Message.X}}" y="130" transform="scale(.1)"
fill="#fff" textLength="{{.Badge.Message.TextLength}}">{{.Badge.Message.Text}}</text></g>
</svg>