Handle maintainer timeout

This commit is contained in:
Zero King
2017-08-15 09:25:52 +00:00
parent 471987b446
commit dd73a99f18
10 changed files with 308 additions and 2 deletions

View File

@@ -0,0 +1,78 @@
package cron
import (
"log"
"strconv"
"time"
"github.com/macports/mpbot-github/pr/db"
"github.com/macports/mpbot-github/pr/githubapi"
)
type Manager struct {
DB db.DBHelper
Client githubapi.Client
}
func (manager *Manager) Start() {
manager.MaintainerTimeout()
for {
select {
case <-time.After(12 * time.Hour):
manager.MaintainerTimeout()
}
}
}
func (manager *Manager) MaintainerTimeout() {
//TODO: properly handle nil pointers
defer func() {
if r := recover(); r != nil {
log.Println(r)
}
}()
prs, err := manager.DB.GetTimeoutPRs()
if err != nil {
log.Println(err)
return
}
prLoop:
for _, pr := range prs {
log.Println("maintainer timeout of PR #" + strconv.Itoa(pr.Number) + " detected")
prStatus, err := manager.Client.GetPullRequest("macports", "macports-ports", pr.Number)
if err != nil {
log.Println("Failed to get status of PR #" + strconv.Itoa(pr.Number))
continue
}
if *prStatus.State == "closed" {
log.Println("PR #" + strconv.Itoa(pr.Number) + " closed, clear pending_review")
manager.DB.SetPRPendingReview(pr.Number, false)
continue
}
//TODO: de-hardcode
labels, err := manager.Client.ListLabels("macports", "macports-ports", pr.Number)
if err != nil {
continue
}
isApprovalRequired := false
for _, label := range labels {
if label == "maintainer: requires approval" {
isApprovalRequired = true
}
if label == "maintainer: timeout" {
manager.DB.SetPRPendingReview(pr.Number, false)
continue prLoop
}
}
if !isApprovalRequired {
manager.DB.SetPRPendingReview(pr.Number, false)
} else {
labels = append(labels, "maintainer: timeout")
err = manager.Client.ReplaceLabels("macports", "macports-ports", pr.Number, labels)
if err == nil {
manager.DB.SetPRPendingReview(pr.Number, false)
}
}
}
}

View File

@@ -6,6 +6,7 @@ import (
"log"
"os"
"strings"
"time"
// PostgreSQL driver
_ "github.com/lib/pq"
@@ -23,9 +24,21 @@ type PortMaintainer struct {
OpenMaintainer bool
}
type PullRequest struct {
Number int
Processed bool
PendingReview bool
Maintainers []string
}
type DBHelper interface {
GetGitHubHandle(email string) (string, error)
GetPortMaintainer(port string) (*PortMaintainer, error)
NewPR(number int, maintainers []string) error
GetPR(number int) (*PullRequest, error)
GetTimeoutPRs() ([]*PullRequest, error)
SetPRProcessed(number int, processed bool) error
SetPRPendingReview(number int, pendingReview bool) error
}
func NewDBHelper() (DBHelper, error) {
@@ -56,6 +69,17 @@ func NewDBHelper() (DBHelper, error) {
if err != nil {
return nil, err
}
_, err = prDB.Exec(`CREATE TABLE IF NOT EXISTS pull_requests
(
number INT PRIMARY KEY,
created TIMESTAMP NOT NULL,
processed BOOLEAN NOT NULL,
pending_review BOOLEAN NOT NULL,
maintainers TEXT NOT NULL
);`)
if err != nil {
return nil, err
}
return &sqlDBHelper{
tracDB: tracDB,
@@ -120,6 +144,7 @@ func (sqlDB *sqlDBHelper) GetPortMaintainer(port string) (*PortMaintainer, error
err = rows.Err()
if err != nil {
//TODO: log in caller
log.Println(err)
}
@@ -130,6 +155,63 @@ func (sqlDB *sqlDBHelper) GetPortMaintainer(port string) (*PortMaintainer, error
return maintainer, nil
}
func (sqlDB *sqlDBHelper) NewPR(number int, maintainers []string) error {
_, err := sqlDB.prDB.Exec("INSERT INTO pull_requests VALUES ($1, $2, $3, $4, $5)",
number, time.Now(), false, false, strings.Join(maintainers, " "))
return err
}
func (sqlDB *sqlDBHelper) GetPR(number int) (*PullRequest, error) {
pr := new(PullRequest)
var maintainerString string
err := sqlDB.prDB.QueryRow(
"SELECT number, processed, pending_review, maintainers FROM pull_requests WHERE number = $1", number).
Scan(&pr.Number, &pr.Processed, &pr.PendingReview, &maintainerString)
if err != nil {
return nil, err
}
pr.Maintainers = strings.Split(maintainerString, " ")
return pr, nil
}
func (sqlDB *sqlDBHelper) GetTimeoutPRs() ([]*PullRequest, error) {
var prs []*PullRequest
rows, err := sqlDB.wwwDB.Query("SELECT number, processed, pending_review, maintainers "+
"FROM pull_requests "+
"WHERE created <= $1 AND pending_review = true", time.Now().AddDate(0, 0, -3))
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
pr := new(PullRequest)
var maintainerString string
if err := rows.Scan(&pr.Number, &pr.Processed, &pr.PendingReview, &maintainerString); err != nil {
return nil, err
}
pr.Maintainers = strings.Split(maintainerString, " ")
prs = append(prs, pr)
}
err = rows.Err()
if err != nil {
log.Println(err)
}
return prs, nil
}
func (sqlDB *sqlDBHelper) SetPRProcessed(number int, processed bool) error {
_, err := sqlDB.prDB.Exec("UPDATE pull_requests SET processed = $1 WHERE number = $2", processed, number)
return err
}
func (sqlDB *sqlDBHelper) SetPRPendingReview(number int, pendingReview bool) error {
_, err := sqlDB.prDB.Exec("UPDATE pull_requests SET pending_review = $1 WHERE number = $2", pendingReview, number)
return err
}
func (sqlDB *sqlDBHelper) parseMaintainer(maintainerFullString string) *Maintainer {
maintainer := parseMaintainerString(maintainerFullString)
if maintainer.GithubHandle == "" && maintainer.Email != "" {

View File

@@ -8,6 +8,7 @@ import (
)
type Client interface {
GetPullRequest(owner, repo string, number int) (*github.PullRequest, error)
ListChangedPortsAndFiles(owner, repo string, number int) (ports []string, commitFiles []*github.CommitFile, err error)
CreateComment(owner, repo string, number int, body *string) error
ReplaceLabels(owner, repo string, number int, labels []string) error

View File

@@ -7,6 +7,11 @@ import (
"github.com/google/go-github/github"
)
func (client *githubClient) GetPullRequest(owner, repo string, number int) (*github.PullRequest, error) {
pr, _, err := client.PullRequests.Get(context.Background(), owner, repo, number)
return pr, err
}
func (client *githubClient) ListChangedPortsAndFiles(owner, repo string, number int) (ports []string, commitFiles []*github.CommitFile, err error) {
var allFiles []*github.CommitFile
opt := &github.ListOptions{PerPage: 30}

View File

@@ -5,7 +5,9 @@ import (
"log"
"os"
"github.com/macports/mpbot-github/pr/cron"
"github.com/macports/mpbot-github/pr/db"
"github.com/macports/mpbot-github/pr/githubapi"
"github.com/macports/mpbot-github/pr/webhook"
)
@@ -32,5 +34,11 @@ func main() {
log.Fatal(err)
}
cronManager := cron.Manager{
DB: dbHelper,
Client: githubapi.NewClient(botSecret),
}
go cronManager.Start()
webhook.NewReceiver(*webhookAddr, hookSecret, botSecret, prodFlag, dbHelper).Start()
}

View File

@@ -0,0 +1,50 @@
package webhook
import (
"encoding/json"
"log"
"strconv"
"github.com/google/go-github/github"
)
func (receiver *Receiver) handleIssueComment(body []byte) {
defer func() {
if r := recover(); r != nil {
log.Println(r)
}
}()
event := &github.IssueCommentEvent{}
err := json.Unmarshal(body, event)
if err != nil {
log.Println(err)
return
}
number := *event.Issue.Number
sender := *event.Sender.Login
// TODO: refactor, share with PullRequestReview
pr, err := receiver.dbHelper.GetPR(number)
if err != nil {
log.Println(err)
return
}
if !pr.Processed {
return
}
if !pr.PendingReview {
return
}
isOneMaintainer := false
for _, maintainer := range pr.Maintainers {
if maintainer == sender {
isOneMaintainer = true
}
}
if isOneMaintainer {
log.Println("Maintainer responded in PR #" + strconv.Itoa(pr.Number))
receiver.dbHelper.SetPRPendingReview(number, false)
}
}

View File

@@ -22,7 +22,6 @@ func (receiver *Receiver) handlePullRequest(body []byte) {
event := &github.PullRequestEvent{}
err := json.Unmarshal(body, event)
if err != nil {
// TODO: log
log.Println(err)
return
}
@@ -87,8 +86,18 @@ func (receiver *Receiver) handlePullRequest(body []byte) {
}
isMaintainer = isOneMaintainer && isMaintainer
maintainers := make([]string, len(handles))
{
i := 0
for k := range handles {
maintainers[i] = k
i++
}
}
switch *event.Action {
case "opened":
receiver.dbHelper.NewPR(number, maintainers)
// Notify maintainers
mentionSymbol := "@_"
if receiver.production {
@@ -125,6 +134,7 @@ func (receiver *Receiver) handlePullRequest(body []byte) {
} else if !isSubmission && !isMaintainer {
// TODO: store in DB
maintainerLabels = append(maintainerLabels, "maintainer: requires approval")
receiver.dbHelper.SetPRPendingReview(number, true)
}
// Collect existing labels (PR sender could add labels when creating a PR)
@@ -176,6 +186,8 @@ func (receiver *Receiver) handlePullRequest(body []byte) {
if err != nil {
log.Println(err)
}
receiver.dbHelper.SetPRProcessed(number, true)
// fallthrough
//case "synchronize":
}

View File

@@ -1,3 +1,50 @@
package webhook
func (receiver *Receiver) handlePullRequestReview(body []byte) {}
import (
"encoding/json"
"log"
"strconv"
"github.com/google/go-github/github"
)
func (receiver *Receiver) handlePullRequestReview(body []byte) {
defer func() {
if r := recover(); r != nil {
log.Println(r)
}
}()
event := &github.PullRequestReviewEvent{}
err := json.Unmarshal(body, event)
if err != nil {
log.Println(err)
return
}
number := *event.PullRequest.Number
sender := *event.Sender.Login
// TODO: refactor, share with IssueComment
pr, err := receiver.dbHelper.GetPR(number)
if err != nil {
log.Println(err)
return
}
if !pr.Processed {
return
}
if !pr.PendingReview {
return
}
isOneMaintainer := false
for _, maintainer := range pr.Maintainers {
if maintainer == sender {
isOneMaintainer = true
}
}
if isOneMaintainer {
log.Println("Maintainer responded in PR #" + strconv.Itoa(pr.Number))
receiver.dbHelper.SetPRPendingReview(number, false)
}
}

View File

@@ -92,6 +92,10 @@ type stubGitHubClient struct {
newLabels []string
}
func (stub *stubGitHubClient) GetPullRequest(owner, repo string, number int) (*github.PullRequest, error) {
return nil, errNotFound
}
func (stub *stubGitHubClient) ListChangedPortsAndFiles(owner, repo string, number int) (ports []string, commitFiles []*github.CommitFile, err error) {
if owner != "macports" || repo != "macports-ports" {
return nil, nil, errNotFound
@@ -182,6 +186,23 @@ func (stub *stubDBHelper) GetPortMaintainer(port string) (*db.PortMaintainer, er
return nil, errors.New("port not found")
}
func (stub *stubDBHelper) NewPR(number int, maintainers []string) error {
return nil
}
func (stub *stubDBHelper) GetPR(number int) (*db.PullRequest, error) {
return nil, nil
}
func (stub *stubDBHelper) GetTimeoutPRs() ([]*db.PullRequest, error) {
return nil, nil
}
func (stub *stubDBHelper) SetPRProcessed(number int, processed bool) error {
return nil
}
func (stub *stubDBHelper) SetPRPendingReview(number int, pendingReview bool) error {
return nil
}
func ptrOfStr(s string) *string {
return &s
}

View File

@@ -70,6 +70,8 @@ func (receiver *Receiver) Start() {
go receiver.handlePullRequest(body)
case "pull_request_review":
go receiver.handlePullRequestReview(body)
case "issue_comment":
go receiver.handleIssueComment(body)
}
w.WriteHeader(http.StatusNoContent)