-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(api/user): add endpoint for viewing builds sent by current user
- Loading branch information
Showing
12 changed files
with
608 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package build | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"strconv" | ||
"time" | ||
|
||
"github.com/gin-gonic/gin" | ||
"github.com/go-vela/server/api" | ||
"github.com/go-vela/server/database" | ||
"github.com/go-vela/server/router/middleware/user" | ||
"github.com/go-vela/server/util" | ||
"github.com/go-vela/types/constants" | ||
"github.com/go-vela/types/library" | ||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
// swagger:operation GET /api/v1/user/builds builds ListBuildsForSender | ||
// | ||
// Get builds from the configured backend | ||
// | ||
// --- | ||
// produces: | ||
// - application/json | ||
// parameters: | ||
// - in: query | ||
// name: event | ||
// description: Filter by build event | ||
// type: string | ||
// enum: | ||
// - comment | ||
// - deployment | ||
// - pull_request | ||
// - push | ||
// - schedule | ||
// - tag | ||
// - in: query | ||
// name: commit | ||
// description: Filter builds based on the commit hash | ||
// type: string | ||
// - in: query | ||
// name: branch | ||
// description: Filter builds by branch | ||
// type: string | ||
// - in: query | ||
// name: status | ||
// description: Filter by build status | ||
// type: string | ||
// enum: | ||
// - canceled | ||
// - error | ||
// - failure | ||
// - killed | ||
// - pending | ||
// - running | ||
// - success | ||
// - in: query | ||
// name: page | ||
// description: The page of results to retrieve | ||
// type: integer | ||
// default: 1 | ||
// - in: query | ||
// name: per_page | ||
// description: How many results per page to return | ||
// type: integer | ||
// maximum: 100 | ||
// default: 10 | ||
// - in: query | ||
// name: before | ||
// description: filter builds created before a certain time | ||
// type: integer | ||
// default: 1 | ||
// - in: query | ||
// name: after | ||
// description: filter builds created after a certain time | ||
// type: integer | ||
// default: 0 | ||
// security: | ||
// - ApiKeyAuth: [] | ||
// responses: | ||
// '200': | ||
// description: Successfully retrieved the builds | ||
// schema: | ||
// type: array | ||
// items: | ||
// "$ref": "#/definitions/Build" | ||
// headers: | ||
// X-Total-Count: | ||
// description: Total number of results | ||
// type: integer | ||
// Link: | ||
// description: see https://tools.ietf.org/html/rfc5988 | ||
// type: string | ||
// '400': | ||
// description: Unable to retrieve the list of builds | ||
// schema: | ||
// "$ref": "#/definitions/Error" | ||
// '500': | ||
// description: Unable to retrieve the list of builds | ||
// schema: | ||
// "$ref": "#/definitions/Error" | ||
|
||
// ListBuildsForSender represents the API handler to capture a | ||
// list of builds for a sender from the configured backend. | ||
func ListBuildsForSender(c *gin.Context) { | ||
// variables that will hold the build list, build list filters and total count | ||
var ( | ||
filters = map[string]interface{}{} | ||
b []*library.Build | ||
t int64 | ||
) | ||
|
||
// capture middleware values | ||
u := user.Retrieve(c) | ||
ctx := c.Request.Context() | ||
|
||
// update engine logger with API metadata | ||
// | ||
// https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields | ||
logrus.WithFields(logrus.Fields{ | ||
"user": u.GetName(), | ||
}).Infof("listing builds for sender %s", u.GetName()) | ||
|
||
// capture the branch name parameter | ||
branch := c.Query("branch") | ||
// capture the event type parameter | ||
event := c.Query("event") | ||
// capture the status type parameter | ||
status := c.Query("status") | ||
// capture the commit hash parameter | ||
commit := c.Query("commit") | ||
|
||
// check if branch filter was provided | ||
if len(branch) > 0 { | ||
// add branch to filters map | ||
filters["branch"] = branch | ||
} | ||
// check if event filter was provided | ||
if len(event) > 0 { | ||
// verify the event provided is a valid event type | ||
if event != constants.EventComment && event != constants.EventDeploy && | ||
event != constants.EventPush && event != constants.EventPull && | ||
event != constants.EventTag && event != constants.EventSchedule { | ||
retErr := fmt.Errorf("unable to process event %s: invalid event type provided", event) | ||
|
||
util.HandleError(c, http.StatusBadRequest, retErr) | ||
|
||
return | ||
} | ||
|
||
// add event to filters map | ||
filters["event"] = event | ||
} | ||
// check if status filter was provided | ||
if len(status) > 0 { | ||
// verify the status provided is a valid status type | ||
if status != constants.StatusCanceled && status != constants.StatusError && | ||
status != constants.StatusFailure && status != constants.StatusKilled && | ||
status != constants.StatusPending && status != constants.StatusRunning && | ||
status != constants.StatusSuccess { | ||
retErr := fmt.Errorf("unable to process status %s: invalid status type provided", status) | ||
|
||
util.HandleError(c, http.StatusBadRequest, retErr) | ||
|
||
return | ||
} | ||
|
||
// add status to filters map | ||
filters["status"] = status | ||
} | ||
|
||
// check if commit hash filter was provided | ||
if len(commit) > 0 { | ||
// add commit to filters map | ||
filters["commit"] = commit | ||
} | ||
|
||
// capture page query parameter if present | ||
page, err := strconv.Atoi(c.DefaultQuery("page", "1")) | ||
if err != nil { | ||
retErr := fmt.Errorf("unable to convert page query parameter for sender %s: %w", u.GetName(), err) | ||
|
||
util.HandleError(c, http.StatusBadRequest, retErr) | ||
|
||
return | ||
} | ||
|
||
// capture per_page query parameter if present | ||
perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10")) | ||
if err != nil { | ||
retErr := fmt.Errorf("unable to convert per_page query parameter for sender %s: %w", u.GetName(), err) | ||
|
||
util.HandleError(c, http.StatusBadRequest, retErr) | ||
|
||
return | ||
} | ||
|
||
// ensure per_page isn't above or below allowed values | ||
perPage = util.MaxInt(1, util.MinInt(100, perPage)) | ||
|
||
// capture before query parameter if present, default to now | ||
before, err := strconv.ParseInt(c.DefaultQuery("before", strconv.FormatInt(time.Now().UTC().Unix(), 10)), 10, 64) | ||
if err != nil { | ||
retErr := fmt.Errorf("unable to convert before query parameter for sender %s: %w", u.GetName(), err) | ||
|
||
util.HandleError(c, http.StatusBadRequest, retErr) | ||
|
||
return | ||
} | ||
|
||
// capture after query parameter if present, default to 0 | ||
after, err := strconv.ParseInt(c.DefaultQuery("after", "0"), 10, 64) | ||
if err != nil { | ||
retErr := fmt.Errorf("unable to convert after query parameter for sender %s: %w", u.GetName(), err) | ||
|
||
util.HandleError(c, http.StatusBadRequest, retErr) | ||
|
||
return | ||
} | ||
|
||
b, t, err = database.FromContext(c).ListBuildsForSender(ctx, u.GetName(), filters, before, after, page, perPage) | ||
if err != nil { | ||
retErr := fmt.Errorf("unable to list builds for sender %s: %w", u.GetName(), err) | ||
|
||
util.HandleError(c, http.StatusInternalServerError, retErr) | ||
|
||
return | ||
} | ||
|
||
// create pagination object | ||
pagination := api.Pagination{ | ||
Page: page, | ||
PerPage: perPage, | ||
Total: t, | ||
} | ||
// set pagination headers | ||
pagination.SetHeaderLink(c) | ||
|
||
c.JSON(http.StatusOK, b) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package build | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/go-vela/types/constants" | ||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
// CountBuildsForSender gets the count of builds by sender from the database. | ||
func (e *engine) CountBuildsForSender(ctx context.Context, sender string, filters map[string]interface{}) (int64, error) { | ||
e.logger.WithFields(logrus.Fields{ | ||
"sender": sender, | ||
}).Tracef("getting count of builds for sender %s from the database", sender) | ||
|
||
// variable to store query results | ||
var b int64 | ||
|
||
// send query to the database and store result in variable | ||
err := e.client. | ||
Table(constants.TableBuild). | ||
Where("sender = ?", sender). | ||
Where(filters). | ||
Count(&b). | ||
Error | ||
|
||
return b, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package build | ||
|
||
import ( | ||
"context" | ||
"reflect" | ||
"testing" | ||
|
||
"github.com/DATA-DOG/go-sqlmock" | ||
) | ||
|
||
func TestBuild_Engine_CountBuildsForSender(t *testing.T) { | ||
// setup types | ||
_buildOne := testBuild() | ||
_buildOne.SetID(1) | ||
_buildOne.SetRepoID(1) | ||
_buildOne.SetNumber(1) | ||
_buildOne.SetDeployPayload(nil) | ||
_buildOne.SetSender("octocat") | ||
|
||
_buildTwo := testBuild() | ||
_buildTwo.SetID(2) | ||
_buildTwo.SetRepoID(1) | ||
_buildTwo.SetNumber(2) | ||
_buildTwo.SetDeployPayload(nil) | ||
_buildTwo.SetSender("octokitty") | ||
|
||
_postgres, _mock := testPostgres(t) | ||
defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() | ||
|
||
// create expected result in mock | ||
_rows := sqlmock.NewRows([]string{"count"}).AddRow(1) | ||
|
||
// ensure the mock expects the query | ||
_mock.ExpectQuery(`SELECT count(*) FROM "builds" WHERE sender = $1`).WithArgs("octocat").WillReturnRows(_rows) | ||
|
||
_sqlite := testSqlite(t) | ||
defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() | ||
|
||
_, err := _sqlite.CreateBuild(context.TODO(), _buildOne) | ||
if err != nil { | ||
t.Errorf("unable to create test build for sqlite: %v", err) | ||
} | ||
|
||
_, err = _sqlite.CreateBuild(context.TODO(), _buildTwo) | ||
if err != nil { | ||
t.Errorf("unable to create test build for sqlite: %v", err) | ||
} | ||
|
||
// setup tests | ||
tests := []struct { | ||
failure bool | ||
name string | ||
database *engine | ||
want int64 | ||
}{ | ||
{ | ||
failure: false, | ||
name: "postgres", | ||
database: _postgres, | ||
want: 1, | ||
}, | ||
{ | ||
failure: false, | ||
name: "sqlite3", | ||
database: _sqlite, | ||
want: 1, | ||
}, | ||
} | ||
|
||
filters := map[string]interface{}{} | ||
|
||
// run tests | ||
for _, test := range tests { | ||
t.Run(test.name, func(t *testing.T) { | ||
got, err := test.database.CountBuildsForSender(context.TODO(), "octocat", filters) | ||
|
||
if test.failure { | ||
if err == nil { | ||
t.Errorf("CountBuildsForSender for %s should have returned err", test.name) | ||
} | ||
|
||
return | ||
} | ||
|
||
if err != nil { | ||
t.Errorf("CountBuildsForSender for %s returned err: %v", test.name, err) | ||
} | ||
|
||
if !reflect.DeepEqual(got, test.want) { | ||
t.Errorf("CountBuildsForSender for %s is %v, want %v", test.name, got, test.want) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.