From cc84f633c2347d22a7cfa5f12128452435177fb1 Mon Sep 17 00:00:00 2001 From: David May <1301201+wass3r@users.noreply.github.com> Date: Tue, 18 Feb 2020 16:59:07 +0000 Subject: [PATCH] enhancement(badge): add ability to target branch (#106) --- api/badge.go | 69 ++++++++----------------- database/build.go | 21 ++++++++ database/build_test.go | 71 ++++++++++++++++++++++++++ database/database.go | 3 ++ database/dml/dml_test.go | 2 + database/dml/postgres/build.go | 13 +++++ database/dml/postgres/build_test.go | 1 + database/dml/postgres/postgres_test.go | 1 + database/dml/postgres_test.go | 2 + database/dml/sqlite/build.go | 12 +++++ database/dml/sqlite/build_test.go | 1 + database/dml/sqlite/sqlite_test.go | 1 + database/dml/sqlite_test.go | 2 + go.mod | 2 +- go.sum | 2 + router/router.go | 2 +- 16 files changed, 154 insertions(+), 51 deletions(-) diff --git a/api/badge.go b/api/badge.go index da50fef57..2dc195727 100644 --- a/api/badge.go +++ b/api/badge.go @@ -5,8 +5,6 @@ package api import ( - "bytes" - "html/template" "net/http" "github.com/go-vela/server/database" @@ -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 = ` build build {{ .Title }} {{ .Title }} ` - tFallback = ` build build unknown unknown ` - ) - - 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() } diff --git a/database/build.go b/database/build.go index a2b6d41f9..db6f7c26f 100644 --- a/database/build.go +++ b/database/build.go @@ -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") diff --git a/database/build_test.go b/database/build_test.go index 0cea33e77..5b15bcf5a 100644 --- a/database/build_test.go +++ b/database/build_test.go @@ -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() diff --git a/database/database.go b/database/database.go index efc302222..210182533 100644 --- a/database/database.go +++ b/database/database.go @@ -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) diff --git a/database/dml/dml_test.go b/database/dml/dml_test.go index 2b544c4b5..19f6bd1af 100644 --- a/database/dml/dml_test.go +++ b/database/dml/dml_test.go @@ -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, @@ -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, diff --git a/database/dml/postgres/build.go b/database/dml/postgres/build.go index 902e9bb2f..97038ca63 100644 --- a/database/dml/postgres/build.go +++ b/database/dml/postgres/build.go @@ -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 @@ -109,6 +121,7 @@ func createBuildService() *Service { Select: map[string]string{ "repo": SelectRepoBuild, "last": SelectLastRepoBuild, + "lastByBranch": SelectLastRepoBuildByBranch, "count": SelectBuildsCount, "countByStatus": SelectBuildsCountByStatus, "countByRepo": SelectRepoBuildCount, diff --git a/database/dml/postgres/build_test.go b/database/dml/postgres/build_test.go index 048f07ae2..26d09456d 100644 --- a/database/dml/postgres/build_test.go +++ b/database/dml/postgres/build_test.go @@ -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, diff --git a/database/dml/postgres/postgres_test.go b/database/dml/postgres/postgres_test.go index ce8ab15ab..ff31aa126 100644 --- a/database/dml/postgres/postgres_test.go +++ b/database/dml/postgres/postgres_test.go @@ -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, diff --git a/database/dml/postgres_test.go b/database/dml/postgres_test.go index a88fcfa22..471452b49 100644 --- a/database/dml/postgres_test.go +++ b/database/dml/postgres_test.go @@ -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, @@ -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, diff --git a/database/dml/sqlite/build.go b/database/dml/sqlite/build.go index 2d7ccff98..f623a1af7 100644 --- a/database/dml/sqlite/build.go +++ b/database/dml/sqlite/build.go @@ -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 @@ -109,6 +120,7 @@ func createBuildService() *Service { Select: map[string]string{ "repo": SelectRepoBuild, "last": SelectLastRepoBuild, + "lastByBranch": SelectLastRepoBuildByBranch, "count": SelectBuildsCount, "countByStatus": SelectBuildsCountByStatus, "countByRepo": SelectRepoBuildCount, diff --git a/database/dml/sqlite/build_test.go b/database/dml/sqlite/build_test.go index 4e569ca86..ae6da8db1 100644 --- a/database/dml/sqlite/build_test.go +++ b/database/dml/sqlite/build_test.go @@ -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, diff --git a/database/dml/sqlite/sqlite_test.go b/database/dml/sqlite/sqlite_test.go index 82d8b8af2..d0bceb392 100644 --- a/database/dml/sqlite/sqlite_test.go +++ b/database/dml/sqlite/sqlite_test.go @@ -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, diff --git a/database/dml/sqlite_test.go b/database/dml/sqlite_test.go index da65815b7..34f7990e8 100644 --- a/database/dml/sqlite_test.go +++ b/database/dml/sqlite_test.go @@ -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, @@ -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, diff --git a/go.mod b/go.mod index 96fbcbdc6..8c476637d 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index cbd07c762..a945b253f 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/router/router.go b/router/router.go index 433f79f86..9156ef449 100644 --- a/router/router.go +++ b/router/router.go @@ -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)