-
-
Notifications
You must be signed in to change notification settings - Fork 511
Per-Workflow and Per-Workflow-Step badge generation #5977
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
base: main
Are you sure you want to change the base?
Conversation
|
Hi @qwerty287, if you can take a look any time, that would be perfect. If you by chance have an idea how to tackle the badge width issue and test issue, I'm open to any suggestions. Thanks in advance. |
qwerty287
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Looks quite good already, I added some comments to the code.
About the badge width, I also don't know… There are libraries to handle this, because you have to dynamically change the width. And this could be quite complex as you have to know the font etc… Not sure if there's a simple way in SVG to not set a fixed width. Maybe this is possible as well.
|
Thanks for your feedback. While keeping your feedback in mind, I prepared some changes. Additionally I tried to look up some sources to get an idea how the label width could be determined dynamically. Based on my research, I'm currently not sure if the possible result would justify the introduced complexity for simple badge generation because I not only have to calculate the width of the label based on the used font, but also have to keep the width in mind when modifying the coordinates in the SVG x-positions of the labels. Because on that I want to propose another solution. Instead of calculating the width of the badges dynamically, we just provide a second wider badge option that's only used if workflow and step is selected. They look like the current badges. I just doubled the width of the label area. The drawback remains the fixed width of the normal and wider badges that would enforce a label length limit, the user has to keep in mind to prevent unreadable badge labels. diff --git a/server/api/badge.go b/server/api/badge.go
index 72735ed7a..606b645b7 100644
--- a/server/api/badge.go
+++ b/server/api/badge.go
@@ -98,11 +98,12 @@ func GetBadge(c *gin.Context) {
if !errors.Is(err, types.RecordNotExist) {
log.Warn().Err(err).Msg("could not get last pipeline for badge")
}
- pipeline = nil
+ c.String(http.StatusOK, badges.Generate("pipeline", nil, false))
+ return
}
name := "pipeline"
- status := pipeline.Status
+ useLargeBadge := false
// we serve an SVG, so set content type appropriately.
c.Writer.Header().Set("Content-Type", "image/svg+xml")
@@ -111,32 +112,34 @@ func GetBadge(c *gin.Context) {
workflowName := c.Query("workflow")
if len(workflowName) != 0 {
name = workflowName
- status = model.StatusDeclined
-
workflows, err := _store.WorkflowGetTree(pipeline)
if err == nil {
for _, wf := range workflows {
if wf.Name == workflowName {
stepName := c.Query("step")
if len(stepName) == 0 {
- status = wf.State
- } else {
- // If step is explicitly requested
- name = name + ": " + stepName
- for _, s := range wf.Children {
- if s.Name == stepName {
- status = s.State
- break
- }
+ c.String(http.StatusOK, badges.Generate(name, &wf.State, useLargeBadge))
+ return
+ }
+ // If step is explicitly requested
+ name = name + ": " + stepName
+ useLargeBadge = true
+ for _, s := range wf.Children {
+ if s.Name == stepName {
+ c.String(http.StatusOK, badges.Generate(name, &s.State, useLargeBadge))
+ return
}
}
break
}
}
}
+ } else if pipeline != nil {
+ c.String(http.StatusOK, badges.Generate(name, &pipeline.Status, useLargeBadge))
+ return
}
- c.String(http.StatusOK, badges.Generate(name, status))
+ c.String(http.StatusOK, badges.Generate(name, nil, useLargeBadge))
}
// GetCC
diff --git a/server/badges/badges.go b/server/badges/badges.go
index f64b2592d..86efddc93 100644
--- a/server/badges/badges.go
+++ b/server/badges/badges.go
@@ -28,6 +28,12 @@ var (
badgeStarted = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="102" height="20" role="img" aria-label="pipeline: started"><title>pipeline: started</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="102" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="53" height="20" fill="#555"/><rect x="53" width="49" height="20" fill="#dfb317"/><rect width="102" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="275" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">pipeline</text><text x="275" y="140" transform="scale(.1)" fill="#fff" textLength="430">pipeline</text><text aria-hidden="true" x="765" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="390">started</text><text x="765" y="140" transform="scale(.1)" fill="#fff" textLength="390">started</text></g></svg>`
badgeError = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="20" role="img" aria-label="pipeline: error"><title>pipeline: error</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="90" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="53" height="20" fill="#555"/><rect x="53" width="37" height="20" fill="#9f9f9f"/><rect width="90" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="275" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">pipeline</text><text x="275" y="140" transform="scale(.1)" fill="#fff" textLength="430">pipeline</text><text aria-hidden="true" x="705" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">error</text><text x="705" y="140" transform="scale(.1)" fill="#fff" textLength="270">error</text></g></svg>`
badgeNone = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="20" role="img" aria-label="pipeline: none"><title>pipeline: none</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="90" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="53" height="20" fill="#555"/><rect x="53" width="37" height="20" fill="#9f9f9f"/><rect width="90" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="275" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">pipeline</text><text x="275" y="140" transform="scale(.1)" fill="#fff" textLength="430">pipeline</text><text aria-hidden="true" x="705" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">none</text><text x="705" y="140" transform="scale(.1)" fill="#fff" textLength="270">none</text></g></svg>`
+
+ largeBadgeSuccess = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="159" height="20" role="img" aria-label="pipeline: success"><title>pipeline: success</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="159" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="106" height="20" fill="#555"/><rect x="106" width="53" height="20" fill="#44cc11"/><rect width="212" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="560" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="860">pipeline</text><text x="560" y="140" transform="scale(.1)" fill="#fff" textLength="860">pipeline</text><text aria-hidden="true" x="1320" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">success</text><text x="1320" y="140" transform="scale(.1)" fill="#fff" textLength="430">success</text></g></svg>`
+ largeBadgeFailure = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="159" height="20" role="img" aria-label="pipeline: failure"><title>pipeline</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="159" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="106" height="20" fill="#555"/><rect x="106" width="53" height="20" fill="#e05d44"/><rect width="212" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="560" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="860">pipeline</text><text x="560" y="140" transform="scale(.1)" fill="#fff" textLength="860">pipeline</text><text aria-hidden="true" x="1320" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">failure</text><text x="1320" y="140" transform="scale(.1)" fill="#fff" textLength="350">failure</text></g></svg>`
+ largeBadgeStarted = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="159" height="20" role="img" aria-label="pipeline: started"><title>pipeline</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="159" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="106" height="20" fill="#555"/><rect x="106" width="53" height="20" fill="#dfb317"/><rect width="212" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="560" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="860">pipeline</text><text x="560" y="140" transform="scale(.1)" fill="#fff" textLength="860">pipeline</text><text aria-hidden="true" x="1320" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="390">started</text><text x="1320" y="140" transform="scale(.1)" fill="#fff" textLength="390">started</text></g></svg>`
+ largeBadgeError = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="143" height="20" role="img" aria-label="pipeline: error"><title>pipeline</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="143" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="106" height="20" fill="#555"/><rect x="106" width="37" height="20" fill="#9f9f9f"/><rect width="212" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="560" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="860">pipeline</text><text x="560" y="140" transform="scale(.1)" fill="#fff" textLength="860">pipeline</text><text aria-hidden="true" x="1250" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">error</text><text x="1250" y="140" transform="scale(.1)" fill="#fff" textLength="270">error</text></g></svg>`
+ largeBadgeNone = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="143" height="20" role="img" aria-label="pipeline: none"><title>pipeline</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="143" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="106" height="20" fill="#555"/><rect x="106" width="37" height="20" fill="#9f9f9f"/><rect width="212" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="560" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="860">pipeline</text><text x="560" y="140" transform="scale(.1)" fill="#fff" textLength="860">pipeline</text><text aria-hidden="true" x="1250" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">none</text><text x="1250" y="140" transform="scale(.1)" fill="#fff" textLength="270">none</text></g></svg>`
)
// Replace placeholder with specific name.
@@ -36,17 +42,40 @@ func SetBadgeName(badge, name string) string {
}
// Generate an SVG badge based on a pipeline.
-func Generate(name string, status model.StatusValue) string {
- switch status {
- case model.StatusSuccess:
- return SetBadgeName(badgeSuccess, name)
- case model.StatusFailure:
- return SetBadgeName(badgeFailure, name)
- case model.StatusError, model.StatusKilled:
- return SetBadgeName(badgeError, name)
- case model.StatusPending, model.StatusRunning:
- return SetBadgeName(badgeStarted, name)
- default:
- return SetBadgeName(badgeNone, name)
+func Generate(name string, status *model.StatusValue, useLargeBadge bool) string {
+ if useLargeBadge {
+ if status == nil {
+ return SetBadgeName(largeBadgeNone, name)
+ }
+
+ switch *status {
+ case model.StatusSuccess:
+ return SetBadgeName(largeBadgeSuccess, name)
+ case model.StatusFailure:
+ return SetBadgeName(largeBadgeFailure, name)
+ case model.StatusError, model.StatusKilled:
+ return SetBadgeName(largeBadgeError, name)
+ case model.StatusPending, model.StatusRunning:
+ return SetBadgeName(largeBadgeStarted, name)
+ default:
+ return SetBadgeName(largeBadgeNone, name)
+ }
+ } else {
+ if status == nil {
+ return SetBadgeName(badgeNone, name)
+ }
+
+ switch *status {
+ case model.StatusSuccess:
+ return SetBadgeName(badgeSuccess, name)
+ case model.StatusFailure:
+ return SetBadgeName(badgeFailure, name)
+ case model.StatusError, model.StatusKilled:
+ return SetBadgeName(badgeError, name)
+ case model.StatusPending, model.StatusRunning:
+ return SetBadgeName(badgeStarted, name)
+ default:
+ return SetBadgeName(badgeNone, name)
+ }
}
}Since Go doesn't support an equivalent of the ternary operator With the diff above, the badge generation would produce these outputs: I can also increase/decrease the label width area if requested. Would this be a good direction to follow or should I do more research into the dynamic label width calculation and work out a solution based on that? |
|
The width is still fixed in the large ones, right? Because then we might solve it for most labels, but as workflow and step names can be of any length, it's easy to have a bad one again. Dynamic width would be the best... |
|
Nice - without looking at the code this looks good already. I don't think we need a minimum at all for the width. Maybe you could also check out some lib like https://github.com/narqo/go-badge to generate this? So we don't have to reinvent the wheel… |
|
Maybe you got the notification for my last comment that I deleted. I was just dumb and messed it up. |
|
I applied the requested changes. If anything else comes to mind, please let me know and I try to provide the changes as quick as possible. |
|
Thanks! Looks good to me so far, haven't tested yet though. @woodpecker-ci/maintainers somebody else maybe should take a look as well |
|
Surge PR preview deployment succeeded. View it at https://woodpecker-ci-woodpecker-pr-5977.surge.sh |
|
@TumbleOwlee can you check out the image lib? It seems to have a security issue: https://ci.woodpecker-ci.org/repos/3780/pipeline/30874/22#L41 Maybe the badge library is not that nice, it's unmaintained for 3 years… :( And the linter fails as well: https://ci.woodpecker-ci.org/repos/3780/pipeline/30874/35 |
I can take a look later.
Maybe use https://github.com/essentialkaos/go-badge instead? It looks like it would require to add the Or I try to get in contact with the maintainer of
I will fix it later when I'm back home. I should have made sure to run it myself to prevent it... |
|
The implementation in github.com/narqo/go-badge is quite basic, I would vote to copy it into wp code. |
This commit copies the sources of `narqo/go-badge` (MIT) into woodpecker. Since the library was previously not updated for 3 years, additional minor changes had to be introduced to fullfill all linting requirements. This includes an update of the additional dependencies to prevent security issues.
for more information, see https://pre-commit.ci
|
@qwerty287 @xoxys As I had to update the dependencies to fix the security issues and you both voted for the copy of the
If I did anything wrong or if I should apply additional changes, please let me know. I already run |
|
@TumbleOwlee thanks for the effort. Can you check the remaining linter issued? |
Sure, I will take a look this evening. |
|
@xoxys I fixed the lint issues. The only issue remaining is https://ci.woodpecker-ci.org/repos/3780/pipeline/30889/23 and I'm not sure how to fix it correctly. I've never really worked with Is the dependency |
|
I will have to take another look at the spellcheck. Not sure why nothing was reported locally by running the same command as specified in the pipeline file, but now the same command fails in the pipleine... |
|
This securitycheck is not related I think. Just ignore it. But could you remove the unused stuff from the go-badge lib? And adding a license header to these files would be good as well, check that from the go-badge license. Similar to https://github.com/woodpecker-ci/woodpecker/blob/main/server/services/utils/hostmatcher/hostmatcher.go#L1 Sorry if I'm opening this again, but would it be possible to not embed the full font? And instead somehow only store the char widths? Because we shouldn't need most of the font itself - just the chars. (Wait with changing that till we finished discussion on the point) |
That's from the file name "Bitstream Vera License" |
Would it be something like and for the files with additional changes something like this? See: https://github.com/woodpecker-ci/woodpecker/blob/main/server/ccmenu/cc.go
Sure, I'll wait before tackling this. If the plan will be to extract the glyph advance width and just store the map of glyphs and their advance width, the issue with the filename is also automatically solved. I'll wait until hearing back from you. Thanks in advance for all the time you take. Edit: Just out of curiosity, I already looked into the freetype library to extract the necessary advance widths of all glyphs of any |
This PR introduces support of the query parameters
workflowandstepto be used to select a specific workflow and step when generating the badges.Closes #2373
Solution
The support of workflow and step selection is added to
GetBadge(..)by extracting the query parameters and usingWorkflowGetTree(pipeline)to select the workflow and thechildrenof the selected workflow to select the specified step. Afterwards the name of the workflow and step are used as the badge title. In case only the workflow is specified the name of the workflow is used. If a step is also given, the badge title is given as<workflow>: <step>.Additionally the UI is extended to include the input fields for the workflow and step.

Drawback
The solution requires the call to
_store.WorkflowGetTree(..)to retrieve the workflow and step information.Testing
I updated the test case of the badge generation and also deployed the patched woodpecker server on my personal server.
Remaining Issues