add reviewer fields, endpoints

This commit is contained in:
Brad Rydzewski 2017-03-18 16:49:27 +08:00
parent b0c7f1f4eb
commit e319aaff15
16 changed files with 198 additions and 31 deletions

View file

@ -22,12 +22,15 @@ type Build struct {
Title string `json:"title" meddler:"build_title"` Title string `json:"title" meddler:"build_title"`
Message string `json:"message" meddler:"build_message"` Message string `json:"message" meddler:"build_message"`
Timestamp int64 `json:"timestamp" meddler:"build_timestamp"` Timestamp int64 `json:"timestamp" meddler:"build_timestamp"`
Sender string `json:"sender" meddler:"build_sender"`
Author string `json:"author" meddler:"build_author"` Author string `json:"author" meddler:"build_author"`
Avatar string `json:"author_avatar" meddler:"build_avatar"` Avatar string `json:"author_avatar" meddler:"build_avatar"`
Email string `json:"author_email" meddler:"build_email"` Email string `json:"author_email" meddler:"build_email"`
Link string `json:"link_url" meddler:"build_link"` Link string `json:"link_url" meddler:"build_link"`
Signed bool `json:"signed" meddler:"build_signed"` Signed bool `json:"signed" meddler:"build_signed"` // deprecate
Verified bool `json:"verified" meddler:"build_verified"` Verified bool `json:"verified" meddler:"build_verified"` // deprecate
Reviewer string `json:"reviewed_by" meddler:"build_reviewer"`
Reviewed int64 `json:"reviewed_at" meddler:"build_reviewed"`
Jobs []*Job `json:"jobs,omitempty" meddler:"-"` Jobs []*Job `json:"jobs,omitempty" meddler:"-"`
} }

View file

@ -8,13 +8,15 @@ const (
) )
const ( const (
StatusSkipped = "skipped" StatusSkipped = "skipped"
StatusPending = "pending" StatusPending = "pending"
StatusRunning = "running" StatusRunning = "running"
StatusSuccess = "success" StatusSuccess = "success"
StatusFailure = "failure" StatusFailure = "failure"
StatusKilled = "killed" StatusKilled = "killed"
StatusError = "error" StatusError = "error"
StatusBlocked = "blocked"
StatusDeclined = "declined"
) )
const ( const (

View file

@ -29,5 +29,6 @@ type Repo struct {
AllowPush bool `json:"allow_push" meddler:"repo_allow_push"` AllowPush bool `json:"allow_push" meddler:"repo_allow_push"`
AllowDeploy bool `json:"allow_deploys" meddler:"repo_allow_deploys"` AllowDeploy bool `json:"allow_deploys" meddler:"repo_allow_deploys"`
AllowTag bool `json:"allow_tags" meddler:"repo_allow_tags"` AllowTag bool `json:"allow_tags" meddler:"repo_allow_tags"`
Config string `json:"config_path" meddler:"repo_config_path"`
Hash string `json:"-" meddler:"repo_hash"` Hash string `json:"-" meddler:"repo_hash"`
} }

View file

@ -2,8 +2,8 @@ package bitbucket
import ( import (
"fmt" "fmt"
"regexp"
"net/url" "net/url"
"regexp"
"strings" "strings"
"github.com/drone/drone/model" "github.com/drone/drone/model"
@ -19,17 +19,19 @@ const (
) )
const ( const (
descPending = "this build is pending" descPending = "this build is pending"
descSuccess = "the build was successful" descSuccess = "the build was successful"
descFailure = "the build failed" descFailure = "the build failed"
descError = "oops, something went wrong" descBlocked = "the build requires approval"
descDeclined = "the build was rejected"
descError = "oops, something went wrong"
) )
// convertStatus is a helper function used to convert a Drone status to a // convertStatus is a helper function used to convert a Drone status to a
// Bitbucket commit status. // Bitbucket commit status.
func convertStatus(status string) string { func convertStatus(status string) string {
switch status { switch status {
case model.StatusPending, model.StatusRunning: case model.StatusPending, model.StatusRunning, model.StatusBlocked:
return statusPending return statusPending
case model.StatusSuccess: case model.StatusSuccess:
return statusSuccess return statusSuccess
@ -48,6 +50,10 @@ func convertDesc(status string) string {
return descSuccess return descSuccess
case model.StatusFailure: case model.StatusFailure:
return descFailure return descFailure
case model.StatusBlocked:
return descBlocked
case model.StatusDeclined:
return descDeclined
default: default:
return descError return descError
} }
@ -163,6 +169,7 @@ func convertPullHook(from *internal.PullRequestHook) *model.Build {
Message: from.PullRequest.Desc, Message: from.PullRequest.Desc,
Avatar: from.Actor.Links.Avatar.Href, Avatar: from.Actor.Links.Avatar.Href,
Author: from.Actor.Login, Author: from.Actor.Login,
Sender: from.Actor.Login,
Timestamp: from.PullRequest.Updated.UTC().Unix(), Timestamp: from.PullRequest.Updated.UTC().Unix(),
} }
} }
@ -177,6 +184,7 @@ func convertPushHook(hook *internal.PushHook, change *internal.Change) *model.Bu
Message: change.New.Target.Message, Message: change.New.Target.Message,
Avatar: hook.Actor.Links.Avatar.Href, Avatar: hook.Actor.Links.Avatar.Href,
Author: hook.Actor.Login, Author: hook.Actor.Login,
Sender: hook.Actor.Login,
Timestamp: change.New.Target.Date.UTC().Unix(), Timestamp: change.New.Target.Date.UTC().Unix(),
} }
switch change.New.Type { switch change.New.Type {
@ -198,9 +206,9 @@ var reGitMail = regexp.MustCompile("<(.*)>")
// extracts the email from a git commit author string // extracts the email from a git commit author string
func extractEmail(gitauthor string) (author string) { func extractEmail(gitauthor string) (author string) {
matches := reGitMail.FindAllStringSubmatch(gitauthor,-1) matches := reGitMail.FindAllStringSubmatch(gitauthor, -1)
if len(matches) == 1 { if len(matches) == 1 {
author = matches[0][1] author = matches[0][1]
} }
return return
} }

View file

@ -19,10 +19,12 @@ const (
) )
const ( const (
descPending = "this build is pending" descPending = "this build is pending"
descSuccess = "the build was successful" descSuccess = "the build was successful"
descFailure = "the build failed" descFailure = "the build failed"
descError = "oops, something went wrong" descBlocked = "the build requires approval"
descDeclined = "the build was rejected"
descError = "oops, something went wrong"
) )
const ( const (
@ -35,12 +37,12 @@ const (
// GitHub commit status. // GitHub commit status.
func convertStatus(status string) string { func convertStatus(status string) string {
switch status { switch status {
case model.StatusPending, model.StatusRunning: case model.StatusPending, model.StatusRunning, model.StatusBlocked:
return statusPending return statusPending
case model.StatusFailure, model.StatusDeclined:
return statusFailure
case model.StatusSuccess: case model.StatusSuccess:
return statusSuccess return statusSuccess
case model.StatusFailure:
return statusFailure
default: default:
return statusError return statusError
} }
@ -56,6 +58,10 @@ func convertDesc(status string) string {
return descSuccess return descSuccess
case model.StatusFailure: case model.StatusFailure:
return descFailure return descFailure
case model.StatusBlocked:
return descBlocked
case model.StatusDeclined:
return descDeclined
default: default:
return descError return descError
} }
@ -185,6 +191,7 @@ func convertPushHook(from *webhook) *model.Build {
Avatar: from.Sender.Avatar, Avatar: from.Sender.Avatar,
Author: from.Sender.Login, Author: from.Sender.Login,
Remote: from.Repo.CloneURL, Remote: from.Repo.CloneURL,
Sender: from.Sender.Login,
} }
if len(build.Author) == 0 { if len(build.Author) == 0 {
build.Author = from.Head.Author.Username build.Author = from.Head.Author.Username
@ -213,6 +220,7 @@ func convertDeployHook(from *webhook) *model.Build {
Ref: from.Deployment.Ref, Ref: from.Deployment.Ref,
Branch: from.Deployment.Ref, Branch: from.Deployment.Ref,
Deploy: from.Deployment.Env, Deploy: from.Deployment.Env,
Sender: from.Sender.Login,
} }
// if the ref is a sha or short sha we need to manuallyconstruct the ref. // if the ref is a sha or short sha we need to manuallyconstruct the ref.
if strings.HasPrefix(build.Commit, build.Ref) || build.Commit == build.Ref { if strings.HasPrefix(build.Commit, build.Ref) || build.Commit == build.Ref {
@ -242,6 +250,7 @@ func convertPullHook(from *webhook, merge bool) *model.Build {
Author: from.PullRequest.User.Login, Author: from.PullRequest.User.Login,
Avatar: from.PullRequest.User.Avatar, Avatar: from.PullRequest.User.Avatar,
Title: from.PullRequest.Title, Title: from.PullRequest.Title,
Sender: from.Sender.Login,
Remote: from.PullRequest.Head.Repo.CloneURL, Remote: from.PullRequest.Head.Repo.CloneURL,
Refspec: fmt.Sprintf(refspec, Refspec: fmt.Sprintf(refspec,
from.PullRequest.Head.Ref, from.PullRequest.Head.Ref,

View file

@ -181,6 +181,7 @@ func Test_helper(t *testing.T) {
from.PullRequest.Title = "Updated README.md" from.PullRequest.Title = "Updated README.md"
from.PullRequest.User.Login = "octocat" from.PullRequest.User.Login = "octocat"
from.PullRequest.User.Avatar = "https://avatars1.githubusercontent.com/u/583231" from.PullRequest.User.Avatar = "https://avatars1.githubusercontent.com/u/583231"
from.Sender.Login = "octocat"
build := convertPullHook(from, true) build := convertPullHook(from, true)
g.Assert(build.Event).Equal(model.EventPull) g.Assert(build.Event).Equal(model.EventPull)
@ -193,6 +194,7 @@ func Test_helper(t *testing.T) {
g.Assert(build.Title).Equal(from.PullRequest.Title) g.Assert(build.Title).Equal(from.PullRequest.Title)
g.Assert(build.Author).Equal(from.PullRequest.User.Login) g.Assert(build.Author).Equal(from.PullRequest.User.Login)
g.Assert(build.Avatar).Equal(from.PullRequest.User.Avatar) g.Assert(build.Avatar).Equal(from.PullRequest.User.Avatar)
g.Assert(build.Sender).Equal(from.Sender.Login)
}) })
g.It("should convert a deployment from webhook", func() { g.It("should convert a deployment from webhook", func() {

View file

@ -95,7 +95,7 @@ const HookPullRequest = `
"default_branch": "master" "default_branch": "master"
}, },
"sender": { "sender": {
"login": "baxterthehacker", "login": "octocat",
"avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3" "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3"
} }
} }

View file

@ -629,18 +629,20 @@ const (
) )
const ( const (
DescPending = "this build is pending" DescPending = "the build is pending"
DescRunning = "this buils is running" DescRunning = "the buils is running"
DescSuccess = "the build was successful" DescSuccess = "the build was successful"
DescFailure = "the build failed" DescFailure = "the build failed"
DescCanceled = "the build canceled" DescCanceled = "the build canceled"
DescBlocked = "the build is pending approval"
DescDeclined = "the build was rejected"
) )
// getStatus is a helper functin that converts a Drone // getStatus is a helper functin that converts a Drone
// status to a GitHub status. // status to a GitHub status.
func getStatus(status string) string { func getStatus(status string) string {
switch status { switch status {
case model.StatusPending: case model.StatusPending, model.StatusBlocked:
return StatusPending return StatusPending
case model.StatusRunning: case model.StatusRunning:
return StatusRunning return StatusRunning
@ -669,6 +671,10 @@ func getDesc(status string) string {
return DescFailure return DescFailure
case model.StatusKilled: case model.StatusKilled:
return DescCanceled return DescCanceled
case model.StatusBlocked:
return DescBlocked
case model.StatusDeclined:
return DescDeclined
default: default:
return DescFailure return DescFailure
} }

View file

@ -129,6 +129,7 @@ var HookPullRequest = `{
}, },
"sender": { "sender": {
"id": 1, "id": 1,
"login": "gordon",
"username": "gordon", "username": "gordon",
"full_name": "Gordon the Gopher", "full_name": "Gordon the Gopher",
"email": "gordon@golang.org", "email": "gordon@golang.org",

View file

@ -74,6 +74,10 @@ func buildFromPush(hook *pushHook) *model.Build {
if author == "" { if author == "" {
author = hook.Sender.Username author = hook.Sender.Username
} }
sender := hook.Sender.Username
if sender == "" {
sender = hook.Sender.Login
}
return &model.Build{ return &model.Build{
Event: model.EventPush, Event: model.EventPush,
@ -85,6 +89,7 @@ func buildFromPush(hook *pushHook) *model.Build {
Avatar: avatar, Avatar: avatar,
Author: author, Author: author,
Timestamp: time.Now().UTC().Unix(), Timestamp: time.Now().UTC().Unix(),
Sender: sender,
} }
} }
@ -98,6 +103,10 @@ func buildFromTag(hook *pushHook) *model.Build {
if author == "" { if author == "" {
author = hook.Sender.Username author = hook.Sender.Username
} }
sender := hook.Sender.Username
if sender == "" {
sender = hook.Sender.Login
}
return &model.Build{ return &model.Build{
Event: model.EventTag, Event: model.EventTag,
@ -108,6 +117,7 @@ func buildFromTag(hook *pushHook) *model.Build {
Message: fmt.Sprintf("created tag %s", hook.Ref), Message: fmt.Sprintf("created tag %s", hook.Ref),
Avatar: avatar, Avatar: avatar,
Author: author, Author: author,
Sender: sender,
Timestamp: time.Now().UTC().Unix(), Timestamp: time.Now().UTC().Unix(),
} }
} }
@ -118,6 +128,10 @@ func buildFromPullRequest(hook *pullRequestHook) *model.Build {
hook.Repo.URL, hook.Repo.URL,
fixMalformedAvatar(hook.PullRequest.User.Avatar), fixMalformedAvatar(hook.PullRequest.User.Avatar),
) )
sender := hook.Sender.Username
if sender == "" {
sender = hook.Sender.Login
}
build := &model.Build{ build := &model.Build{
Event: model.EventPull, Event: model.EventPull,
Commit: hook.PullRequest.Head.Sha, Commit: hook.PullRequest.Head.Sha,
@ -127,6 +141,7 @@ func buildFromPullRequest(hook *pullRequestHook) *model.Build {
Message: hook.PullRequest.Title, Message: hook.PullRequest.Title,
Author: hook.PullRequest.User.Username, Author: hook.PullRequest.User.Username,
Avatar: avatar, Avatar: avatar,
Sender: sender,
Title: hook.PullRequest.Title, Title: hook.PullRequest.Title,
Refspec: fmt.Sprintf("%s:%s", Refspec: fmt.Sprintf("%s:%s",
hook.PullRequest.Head.Ref, hook.PullRequest.Head.Ref,

View file

@ -116,6 +116,7 @@ type pullRequestHook struct {
} `json:"repository"` } `json:"repository"`
Sender struct { Sender struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Login string `json:"login"`
Username string `json:"username"` Username string `json:"username"`
Name string `json:"full_name"` Name string `json:"full_name"`
Email string `json:"email"` Email string `json:"email"`

View file

@ -108,6 +108,8 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
repo.POST("/chown", session.MustRepoAdmin(), server.ChownRepo) repo.POST("/chown", session.MustRepoAdmin(), server.ChownRepo)
repo.POST("/builds/:number", session.MustPush, server.PostBuild) repo.POST("/builds/:number", session.MustPush, server.PostBuild)
repo.POST("/builds/:number/approve", session.MustPush, server.PostApproval)
repo.POST("/builds/:number/decline", session.MustPush, server.PostDecline)
repo.DELETE("/builds/:number/:job", session.MustPush, server.DeleteBuild) repo.DELETE("/builds/:number/:job", session.MustPush, server.DeleteBuild)
} }
} }

View file

@ -156,6 +156,69 @@ func DeleteBuild(c *gin.Context) {
c.String(204, "") c.String(204, "")
} }
func PostApproval(c *gin.Context) {
var (
repo = session.Repo(c)
user = session.User(c)
num, _ = strconv.Atoi(
c.Params.ByName("number"),
)
)
build, err := store.GetBuildNumber(c, repo, num)
if err != nil {
c.AbortWithError(404, err)
return
}
if build.Status != model.StatusBlocked {
c.String(500, "cannot decline a build with status %s", build.Status)
return
}
build.Status = model.StatusPending
build.Reviewed = time.Now().Unix()
build.Reviewer = user.Login
if err := store.UpdateBuild(c, build); err != nil {
c.String(500, "error updating build. %s", err)
return
}
//
// TODO start build
//
c.JSON(200, build)
}
func PostDecline(c *gin.Context) {
var (
repo = session.Repo(c)
user = session.User(c)
num, _ = strconv.Atoi(
c.Params.ByName("number"),
)
)
build, err := store.GetBuildNumber(c, repo, num)
if err != nil {
c.AbortWithError(404, err)
return
}
if build.Status != model.StatusBlocked {
c.String(500, "cannot decline a build with status %s", build.Status)
return
}
build.Status = model.StatusDeclined
build.Reviewed = time.Now().Unix()
build.Reviewer = user.Login
if err := store.UpdateBuild(c, build); err != nil {
c.String(500, "error updating build. %s", err)
return
}
c.JSON(200, build)
}
func GetBuildQueue(c *gin.Context) { func GetBuildQueue(c *gin.Context) {
out, err := store.GetBuildQueue(c) out, err := store.GetBuildQueue(c)
if err != nil { if err != nil {

View file

@ -0,0 +1,18 @@
-- +migrate Up
ALTER TABLE repos ADD COLUMN repo_config_path VARCHAR(255);
ALTER TABLE builds ADD COLUMN build_sender VARCHAR(255);
ALTER TABLE builds ADD COLUMN build_reviewer VARCHAR(255);
ALTER TABLE builds ADD COLUMN build_reviewed INTEGER;
UPDATE repos SET repo_config_path = '';
UPDATE builds SET build_reviewer = '';
UPDATE builds SET build_reviewed = 0;
UPDATE builds SET build_sender = '';
-- +migrate Down
ALTER TABLE repos DROP COLUMN repo_config_path;
ALTER TABLE builds DROP COLUMN build_sender;
ALTER TABLE builds DROP COLUMN build_reviewer;
ALTER TABLE builds DROP COLUMN build_reviewed;

View file

@ -0,0 +1,18 @@
-- +migrate Up
ALTER TABLE repos ADD COLUMN repo_config_path VARCHAR(255);
ALTER TABLE builds ADD COLUMN build_reviewer VARCHAR(255);
ALTER TABLE builds ADD COLUMN build_reviewed INTEGER;
ALTER TABLE builds ADD COLUMN build_sender VARCHAR(255);
UPDATE repos SET repo_config_path = '';
UPDATE builds SET build_reviewer = '';
UPDATE builds SET build_reviewed = 0;
UPDATE builds SET build_sender = '';
-- +migrate Down
ALTER TABLE repos DROP COLUMN repo_config_path;
ALTER TABLE builds DROP COLUMN build_reviewer;
ALTER TABLE builds DROP COLUMN build_reviewed;
ALTER TABLE builds DROP COLUMN build_sender;

View file

@ -0,0 +1,18 @@
-- +migrate Up
ALTER TABLE repos ADD COLUMN repo_config_path TEXT;
ALTER TABLE builds ADD COLUMN build_reviewer TEXT;
ALTER TABLE builds ADD COLUMN build_reviewed INTEGER;
ALTER TABLE builds ADD COLUMN build_sender TEXT;
UPDATE repos SET repo_config_path = '';
UPDATE builds SET build_reviewer = '';
UPDATE builds SET build_reviewed = 0;
UPDATE builds SET build_sender = '';
-- +migrate Down
ALTER TABLE repos DROP COLUMN repo_config_path;
ALTER TABLE builds DROP COLUMN build_reviewer;
ALTER TABLE builds DROP COLUMN build_reviewed;
ALTER TABLE builds DROP COLUMN build_sender;