Skip to content

Commit

Permalink
enhancement(badge): add ability to target branch (#106)
Browse files Browse the repository at this point in the history
  • Loading branch information
wass3r authored Feb 18, 2020
1 parent 4a5e9f1 commit cc84f63
Show file tree
Hide file tree
Showing 16 changed files with 154 additions and 51 deletions.
69 changes: 20 additions & 49 deletions api/badge.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
package api

import (
"bytes"
"html/template"
"net/http"

"github.com/go-vela/server/database"
Expand All @@ -17,71 +15,44 @@ import (
"github.com/gin-gonic/gin"
)

// Badge represents the API handler to
// GetBadge represents the API handler to
// return a build status badge.
func Badge(c *gin.Context) {
// TODO: allow getting lastbuild by branch and then allow query via `?branch=...`
func GetBadge(c *gin.Context) {
// capture middleware values
r := repo.Retrieve(c)
branch := c.DefaultQuery("branch", r.GetBranch())

logrus.Infof("Creating badge for latest build on %s", r.GetFullName())
logrus.Infof("Creating badge for latest build on %s for branch %s", r.GetFullName(), branch)

// set default badge
badge := buildBadge("unknown", "#9f9f9f")

// send API call to capture the last build for the repo
b, err := database.FromContext(c).GetLastBuild(r)
// send API call to capture the last build for the repo and branch
b, err := database.FromContext(c).GetLastBuildByBranch(r, branch)
if err != nil {
c.String(http.StatusOK, badge)
c.String(http.StatusOK, constants.BadgeUnknown)
return
}

badge := badgeForStatus(b.GetStatus())

// set headers to prevent caching
c.Header("Content-Type", "image/svg+xml")
c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
c.Header("Expires", "0") // passing invalid date sets resource as expired

switch b.GetStatus() {
c.String(http.StatusOK, badge)
}

// badgeForStatus is a helper to match the build status with a badge
func badgeForStatus(s string) string {
switch s {
case constants.StatusRunning, constants.StatusPending:
badge = buildBadge("running", "#dfb317")
return constants.BadgeRunning
case constants.StatusFailure, constants.StatusKilled:
badge = buildBadge("failed", "#e05d44")
return constants.BadgeFailed
case constants.StatusSuccess:
badge = buildBadge("success", "#44cc11")
return constants.BadgeSuccess
case constants.StatusError:
badge = buildBadge("error", "#fe7d37")
return constants.BadgeError
default:
c.String(http.StatusOK, badge)
return
}

c.String(http.StatusOK, badge)
}

// buildBadge is a helper that actually builds creates the SVG for the badge
func buildBadge(title, color string) string {
const (
t = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="88" height="20"> <linearGradient id="b" x2="0" y2="100%"> <stop offset="0" stop-color="#bbb" stop-opacity=".1"/> <stop offset="1" stop-opacity=".1"/> </linearGradient> <clipPath id="a"> <rect width="88" height="20" rx="3" fill="#fff"/> </clipPath> <g clip-path="url(#a)"> <path fill="#555" d="M0 0h37v20H0z"/> <path fill="{{ .Color }}" d="M37 0h51v20H37z"/> <path fill="url(#b)" d="M0 0h88v20H0z"/> </g> <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"> <text x="195" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">build</text> <text x="195" y="140" transform="scale(.1)" textLength="270">build</text> <text x="615" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="410">{{ .Title }}</text> <text x="615" y="140" transform="scale(.1)" textLength="410">{{ .Title }}</text> </g> </svg>`
tFallback = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="88" height="20"> <linearGradient id="b" x2="0" y2="100%"> <stop offset="0" stop-color="#bbb" stop-opacity=".1"/> <stop offset="1" stop-opacity=".1"/> </linearGradient> <clipPath id="a"> <rect width="88" height="20" rx="3" fill="#fff"/> </clipPath> <g clip-path="url(#a)"> <path fill="#555" d="M0 0h37v20H0z"/> <path fill="#9f9f9f" d="M37 0h51v20H37z"/> <path fill="url(#b)" d="M0 0h88v20H0z"/> </g> <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"> <text x="195" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">build</text> <text x="195" y="140" transform="scale(.1)" textLength="270">build</text> <text x="615" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="410">unknown</text> <text x="615" y="140" transform="scale(.1)" textLength="410">unknown</text> </g> </svg>`
)

tmpl, err := template.New("StatusBadge").Parse(t)
if err != nil {
return tFallback
return constants.BadgeUnknown
}

buffer := &bytes.Buffer{}

err = tmpl.Execute(buffer, struct {
Title string
Color string
}{
title,
color,
})
if err != nil {
return tFallback
}

return buffer.String()
}
21 changes: 21 additions & 0 deletions database/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,27 @@ func (c *client) GetLastBuild(r *library.Repo) (*library.Build, error) {
return b.ToLibrary(), err
}

// GetLastBuildByBranch gets the last build ran by repo ID and branch from the database.
func (c *client) GetLastBuildByBranch(r *library.Repo, branch string) (*library.Build, error) {
logrus.Tracef("Getting last build for repo %s from the database", r.GetFullName())

// variable to store query results
b := new(database.Build)

// send query to the database and store result in variable
err := c.Database.
Table(constants.TableBuild).
Raw(c.DML.BuildService.Select["lastByBranch"], r.GetID(), branch).
Scan(b).Error

// the record will not exist if it's a new repo
if gorm.IsRecordNotFoundError(err) {
return nil, nil
}

return b.ToLibrary(), err
}

// GetBuildCount gets the count of all builds from the database.
func (c *client) GetBuildCount() (int64, error) {
logrus.Trace("Count of builds from the database")
Expand Down
71 changes: 71 additions & 0 deletions database/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,77 @@ func TestDatabase_Client_GetLastBuild_NotFound(t *testing.T) {
}
}

func TestDatabase_Client_GetLastBuildByBranch(t *testing.T) {
// setup types
r := testRepo()
r.SetID(1)
r.SetOrg("foo")
r.SetName("bar")
r.SetFullName("foo/bar")

want := testBuild()
want.SetID(1)
want.SetRepoID(1)
want.SetNumber(1)
want.SetBranch("pr42")

b := testBuild()
b.SetID(2)
b.SetRepoID(1)
b.SetNumber(2)
b.SetBranch("master")

// setup database
database, _ := NewTest()

defer func() {
database.Database.Exec("delete from builds;")
database.Database.Close()
}()

_ = database.CreateBuild(want)
_ = database.CreateBuild(b)

// run test
got, err := database.GetLastBuildByBranch(r, "pr42")

if err != nil {
t.Errorf("GetLastBuild returned err: %v", err)
}

if !reflect.DeepEqual(got, want) {
t.Errorf("GetLastBuild is %v, want %v", got, want)
}
}

func TestDatabase_Client_GetLastBuildByBranch_NotFound(t *testing.T) {
// setup types
r := testRepo()
r.SetID(1)
r.SetOrg("foo")
r.SetName("bar")
r.SetFullName("foo/bar")

// setup database
database, _ := NewTest()

defer func() {
database.Database.Exec("delete from builds;")
database.Database.Close()
}()

// run test
got, err := database.GetLastBuildByBranch(r, "pr42")

if err != nil {
t.Errorf("GetLastBuild returned err: %v", err)
}

if got != nil {
t.Errorf("GetLastBuild is %v, want nil", got)
}
}

func TestDatabase_Client_GetBuildList(t *testing.T) {
// setup types
bOne := testBuild()
Expand Down
3 changes: 3 additions & 0 deletions database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ type Service interface {
// GetLastBuild defines a function that
// gets the last build ran by repo ID.
GetLastBuild(*library.Repo) (*library.Build, error)
// GetLastBuildByBranch defines a function that
// gets the last build ran by repo ID and branch.
GetLastBuildByBranch(*library.Repo, string) (*library.Build, error)
// GetBuildCount defines a function that
// gets the count of builds.
GetBuildCount() (int64, error)
Expand Down
2 changes: 2 additions & 0 deletions database/dml/dml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func TestDML_NewMap_Postgres(t *testing.T) {
Select: map[string]string{
"repo": postgres.SelectRepoBuild,
"last": postgres.SelectLastRepoBuild,
"lastByBranch": postgres.SelectLastRepoBuildByBranch,
"count": postgres.SelectBuildsCount,
"countByStatus": postgres.SelectBuildsCountByStatus,
"countByRepo": postgres.SelectRepoBuildCount,
Expand Down Expand Up @@ -148,6 +149,7 @@ func TestDML_NewMap_Sqlite(t *testing.T) {
Select: map[string]string{
"repo": sqlite.SelectRepoBuild,
"last": sqlite.SelectLastRepoBuild,
"lastByBranch": sqlite.SelectLastRepoBuildByBranch,
"count": sqlite.SelectBuildsCount,
"countByStatus": sqlite.SelectBuildsCountByStatus,
"countByRepo": sqlite.SelectRepoBuildCount,
Expand Down
13 changes: 13 additions & 0 deletions database/dml/postgres/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ FROM builds
WHERE repo_id = $1
ORDER BY number DESC
LIMIT 1;
`

// SelectLastRepoBuildByBranch represents a query to
// select the last build for a repo_id and branch name
// in the database
SelectLastRepoBuildByBranch = `
SELECT *
FROM builds
WHERE repo_id = $1
AND branch = $2
ORDER BY number DESC
LIMIT 1;
`

// SelectBuildsCount represents a query to select
Expand Down Expand Up @@ -109,6 +121,7 @@ func createBuildService() *Service {
Select: map[string]string{
"repo": SelectRepoBuild,
"last": SelectLastRepoBuild,
"lastByBranch": SelectLastRepoBuildByBranch,
"count": SelectBuildsCount,
"countByStatus": SelectBuildsCountByStatus,
"countByRepo": SelectRepoBuildCount,
Expand Down
1 change: 1 addition & 0 deletions database/dml/postgres/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func TestPostgres_createBuildService(t *testing.T) {
Select: map[string]string{
"repo": SelectRepoBuild,
"last": SelectLastRepoBuild,
"lastByBranch": SelectLastRepoBuildByBranch,
"count": SelectBuildsCount,
"countByStatus": SelectBuildsCountByStatus,
"countByRepo": SelectRepoBuildCount,
Expand Down
1 change: 1 addition & 0 deletions database/dml/postgres/postgres_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func TestPostgres_NewMap(t *testing.T) {
Select: map[string]string{
"repo": SelectRepoBuild,
"last": SelectLastRepoBuild,
"lastByBranch": SelectLastRepoBuildByBranch,
"count": SelectBuildsCount,
"countByStatus": SelectBuildsCountByStatus,
"countByRepo": SelectRepoBuildCount,
Expand Down
2 changes: 2 additions & 0 deletions database/dml/postgres_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func TestDML_mapFromPostgres(t *testing.T) {
Select: map[string]string{
"repo": postgres.SelectRepoBuild,
"last": postgres.SelectLastRepoBuild,
"lastByBranch": postgres.SelectLastRepoBuildByBranch,
"count": postgres.SelectBuildsCount,
"countByStatus": postgres.SelectBuildsCountByStatus,
"countByRepo": postgres.SelectRepoBuildCount,
Expand Down Expand Up @@ -141,6 +142,7 @@ func TestDML_serviceFromPostgres(t *testing.T) {
Select: map[string]string{
"repo": postgres.SelectRepoBuild,
"last": postgres.SelectLastRepoBuild,
"lastByBranch": postgres.SelectLastRepoBuildByBranch,
"count": postgres.SelectBuildsCount,
"countByStatus": postgres.SelectBuildsCountByStatus,
"countByRepo": postgres.SelectRepoBuildCount,
Expand Down
12 changes: 12 additions & 0 deletions database/dml/sqlite/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ FROM builds
WHERE repo_id = ?
ORDER BY number DESC
LIMIT 1;
`
// SelectLastRepoBuildByBranch represents a query to
// select the last build for a repo_id and branch name
// in the database
SelectLastRepoBuildByBranch = `
SELECT *
FROM builds
WHERE repo_id = ?
AND branch = ?
ORDER BY number DESC
LIMIT 1;
`

// SelectBuildsCount represents a query to select
Expand Down Expand Up @@ -109,6 +120,7 @@ func createBuildService() *Service {
Select: map[string]string{
"repo": SelectRepoBuild,
"last": SelectLastRepoBuild,
"lastByBranch": SelectLastRepoBuildByBranch,
"count": SelectBuildsCount,
"countByStatus": SelectBuildsCountByStatus,
"countByRepo": SelectRepoBuildCount,
Expand Down
1 change: 1 addition & 0 deletions database/dml/sqlite/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func TestSqlite_createBuildService(t *testing.T) {
Select: map[string]string{
"repo": SelectRepoBuild,
"last": SelectLastRepoBuild,
"lastByBranch": SelectLastRepoBuildByBranch,
"count": SelectBuildsCount,
"countByStatus": SelectBuildsCountByStatus,
"countByRepo": SelectRepoBuildCount,
Expand Down
1 change: 1 addition & 0 deletions database/dml/sqlite/sqlite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func TestSqlite_NewMap(t *testing.T) {
Select: map[string]string{
"repo": SelectRepoBuild,
"last": SelectLastRepoBuild,
"lastByBranch": SelectLastRepoBuildByBranch,
"count": SelectBuildsCount,
"countByStatus": SelectBuildsCountByStatus,
"countByRepo": SelectRepoBuildCount,
Expand Down
2 changes: 2 additions & 0 deletions database/dml/sqlite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func TestDML_mapFromSqlite(t *testing.T) {
Select: map[string]string{
"repo": sqlite.SelectRepoBuild,
"last": sqlite.SelectLastRepoBuild,
"lastByBranch": sqlite.SelectLastRepoBuildByBranch,
"count": sqlite.SelectBuildsCount,
"countByStatus": sqlite.SelectBuildsCountByStatus,
"countByRepo": sqlite.SelectRepoBuildCount,
Expand Down Expand Up @@ -141,6 +142,7 @@ func TestDML_serviceFromSqlite(t *testing.T) {
Select: map[string]string{
"repo": sqlite.SelectRepoBuild,
"last": sqlite.SelectLastRepoBuild,
"lastByBranch": sqlite.SelectLastRepoBuildByBranch,
"count": sqlite.SelectBuildsCount,
"countByStatus": sqlite.SelectBuildsCountByStatus,
"countByRepo": sqlite.SelectRepoBuildCount,
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ require (
github.com/gin-gonic/gin v1.5.0
github.com/go-redis/redis v6.15.6+incompatible
github.com/go-vela/compiler v0.3.0-rc2
github.com/go-vela/types v0.3.0-rc2
github.com/go-vela/types v0.3.0-rc3
github.com/google/go-github/v29 v29.0.3
github.com/google/uuid v1.1.1
github.com/hashicorp/go-hclog v0.10.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ github.com/go-vela/compiler v0.3.0-rc2 h1:S+KbcDshQabpwOhbeyol3B+WNml6l5TIPsLMXn
github.com/go-vela/compiler v0.3.0-rc2/go.mod h1:58jRH4hlIu3dKCImBzTIbpM+RLdLL1I0qsDYpinCMgQ=
github.com/go-vela/types v0.3.0-rc2 h1:FrDTX01K+2M6ertsv9GUAP76YWQANdcrA2N6oBIRX+E=
github.com/go-vela/types v0.3.0-rc2/go.mod h1:LNDrn7/nV38H0HmRfYv6YfPNb881R5NRE1YKXeVqpF4=
github.com/go-vela/types v0.3.0-rc3 h1:G7+eKZBjN1cCvAKC2LzoDIDkmooLFfUoQPwt/7fT9AM=
github.com/go-vela/types v0.3.0-rc3/go.mod h1:LNDrn7/nV38H0HmRfYv6YfPNb881R5NRE1YKXeVqpF4=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
Expand Down
2 changes: 1 addition & 1 deletion router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func Load(options ...gin.HandlerFunc) *gin.Engine {
r.Use(options...)

// Badge endpoint
r.GET("/badge/:org/:repo/status.svg", repo.Establish(), api.Badge)
r.GET("/badge/:org/:repo/status.svg", repo.Establish(), api.GetBadge)

// Health endpoint
r.GET("/health", api.Health)
Expand Down

0 comments on commit cc84f63

Please sign in to comment.