Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions controllers/pr_check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package controllers

import (
"strconv"

"github.com/casbin/casbin-oa/object"
)

// GetPrChecks retrieves all PR check records for a specific PR.
func (c *ApiController) GetPrChecks() {
org := c.Input().Get("org")
repo := c.Input().Get("repo")
prNumberStr := c.Input().Get("prNumber")

if org == "" || repo == "" || prNumberStr == "" {
c.Data["json"] = map[string]interface{}{
"status": "error",
"msg": "Missing required parameters",
}
c.ServeJSON()
return
}

prNumber, err := strconv.Atoi(prNumberStr)
if err != nil {
c.Data["json"] = map[string]interface{}{
"status": "error",
"msg": "Invalid PR number",
}
c.ServeJSON()
return
}

prChecks := object.GetPrChecksByPR(org, repo, prNumber)
c.Data["json"] = prChecks
c.ServeJSON()
}

// GetPrCheck retrieves a specific PR check record.
func (c *ApiController) GetPrCheck() {
org := c.Input().Get("org")
repo := c.Input().Get("repo")
prNumberStr := c.Input().Get("prNumber")
checkName := c.Input().Get("checkName")

if org == "" || repo == "" || prNumberStr == "" || checkName == "" {
c.Data["json"] = map[string]interface{}{
"status": "error",
"msg": "Missing required parameters",
}
c.ServeJSON()
return
}

prNumber, err := strconv.Atoi(prNumberStr)
if err != nil {
c.Data["json"] = map[string]interface{}{
"status": "error",
"msg": "Invalid PR number",
}
c.ServeJSON()
return
}

prCheck := object.GetPrCheck(org, repo, prNumber, checkName)
c.Data["json"] = prCheck
c.ServeJSON()
}
103 changes: 102 additions & 1 deletion controllers/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package controllers
import (
"encoding/json"
"fmt"
"time"

"github.com/casbin/casbin-oa/object"
"github.com/casbin/casbin-oa/util"
Expand All @@ -26,9 +27,26 @@ import (
func (c *ApiController) WebhookOpen() {
var issueEvent github.IssuesEvent
var pullRequestEvent github.PullRequestEvent
var checkRunEvent github.CheckRunEvent

// Try to parse as different event types.
eventType := c.Ctx.Request.Header.Get("X-GitHub-Event")

result := false
switch eventType {
case "check_run":
err := json.Unmarshal(c.Ctx.Input.RequestBody, &checkRunEvent)
if err == nil && checkRunEvent.CheckRun != nil {
result = HandleCheckRunEvent(checkRunEvent)
c.Data["json"] = result
c.ServeJSON()
return
}
}

// Legacy handling for issues and pull requests.
json.Unmarshal(c.Ctx.Input.RequestBody, &pullRequestEvent)

var result bool
if pullRequestEvent.PullRequest != nil {
result = PullRequestOpen(pullRequestEvent)
} else {
Expand Down Expand Up @@ -106,6 +124,89 @@ func PullRequestOpen(pullRequestEvent github.PullRequestEvent) bool {
go util.Comment(commentStr, owner, repo, pullRequestEvent.GetNumber())
}
}
// Request automatic code review from copilot for human-created PRs.
go util.RequestCopilotReview(owner, repo, pullRequestEvent.GetNumber())
}
return true
}

// HandleCheckRunEvent handles check_run webhook events.
func HandleCheckRunEvent(event github.CheckRunEvent) bool {
if event.GetAction() != "completed" {
return false
}

checkRun := event.GetCheckRun()
if checkRun == nil {
return false
}

// Only process failed checks.
if checkRun.GetConclusion() != "failure" && checkRun.GetConclusion() != "cancelled" {
return false
}

// Get PR information.
prs := checkRun.PullRequests
if len(prs) == 0 {
return false
}

owner, repo := util.GetOwnerAndNameFromId(event.Repo.GetFullName())
//issueWebhook := object.GetIssueIfExist(owner, repo)
//if issueWebhook == nil {
// return false
//}

for _, pr := range prs {
prNumber := pr.GetNumber()
checkName := checkRun.GetName()

// Check if this is a linter check.
if !util.IsLinterCheck(checkName) {
continue
}

// Check if we should attempt to fix (max 3 attempts).
if !object.ShouldAttemptFix(owner, repo, prNumber, checkName) {
continue
}

// Get or create PR check record.
prCheck := object.GetPrCheck(owner, repo, prNumber, checkName)
if prCheck == nil {
// Create new record.
prCheck = &object.PrCheck{
Org: owner,
Repo: repo,
PrNumber: prNumber,
CheckRunId: checkRun.GetID(),
CheckName: checkName,
Status: checkRun.GetStatus(),
Conclusion: checkRun.GetConclusion(),
FailureReason: util.GetCheckFailureDetails(checkRun),
FixAttempts: 0,
LastAttemptAt: time.Now(),
IsFixed: false,
CreatedAt: time.Now(),
}
object.AddPrCheck(prCheck)
} else {
// Update existing record.
prCheck.CheckRunId = checkRun.GetID()
prCheck.Status = checkRun.GetStatus()
prCheck.Conclusion = checkRun.GetConclusion()
prCheck.FailureReason = util.GetCheckFailureDetails(checkRun)
object.UpdatePrCheck(prCheck.Id, prCheck)
}

// Increment fix attempts and get updated record.
prCheck = object.IncrementFixAttempts(owner, repo, prNumber, checkName)
if prCheck != nil {
// Comment on PR with failure details and tag copilot.
go util.CommentOnPRWithCopilotTag(owner, repo, prNumber, prCheck.FailureReason, prCheck.FixAttempts)
}
}

return true
}
5 changes: 5 additions & 0 deletions object/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,9 @@ func (a *Adapter) createTable() {
if err != nil {
panic(err)
}

err = a.Engine.Sync2(new(PrCheck))
if err != nil {
panic(err)
}
}
102 changes: 102 additions & 0 deletions object/pr_check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package object

import (
"time"

"github.com/casbin/casbin-oa/util"
)

type PrCheck struct {
Id int `xorm:"int notnull pk autoincr" json:"id"`
Org string `xorm:"varchar(100)" json:"org"`
Repo string `xorm:"varchar(100)" json:"repo"`
PrNumber int `xorm:"int" json:"prNumber"`
CheckRunId int64 `xorm:"bigint" json:"checkRunId"`
CheckName string `xorm:"varchar(200)" json:"checkName"`
Status string `xorm:"varchar(50)" json:"status"`
Conclusion string `xorm:"varchar(50)" json:"conclusion"`
FailureReason string `xorm:"text" json:"failureReason"`
FixAttempts int `xorm:"int" json:"fixAttempts"`
LastAttemptAt time.Time `xorm:"datetime" json:"lastAttemptAt"`
IsFixed bool `xorm:"bool" json:"isFixed"`
CreatedAt time.Time `xorm:"datetime" json:"createdAt"`
}

// GetPrCheck retrieves a PR check by org, repo, PR number and check name.
func GetPrCheck(org string, repo string, prNumber int, checkName string) *PrCheck {
prCheck := PrCheck{}
existed, err := adapter.Engine.Where("org = ? and repo = ? and pr_number = ? and check_name = ?",
org, repo, prNumber, checkName).Desc("id").Get(&prCheck)
if err != nil {
panic(err)
}
if existed {
return &prCheck
}
return nil
}

// GetPrChecksByPR retrieves all checks for a specific PR.
func GetPrChecksByPR(org string, repo string, prNumber int) []*PrCheck {
prChecks := []*PrCheck{}
err := adapter.Engine.Where("org = ? and repo = ? and pr_number = ?",
org, repo, prNumber).Find(&prChecks)
if err != nil {
panic(err)
}
return prChecks
}

// AddPrCheck adds a new PR check record.
func AddPrCheck(prCheck *PrCheck) bool {
affected, err := adapter.Engine.Insert(prCheck)
if err != nil {
panic(err)
}
return affected != 0
}

// UpdatePrCheck updates an existing PR check record.
func UpdatePrCheck(id int, prCheck *PrCheck) bool {
_, err := adapter.Engine.ID(id).AllCols().Update(prCheck)
if err != nil {
panic(err)
}
return true
}

// IncrementFixAttempts increments the fix attempts counter and returns the updated record.
func IncrementFixAttempts(org string, repo string, prNumber int, checkName string) *PrCheck {
prCheck := GetPrCheck(org, repo, prNumber, checkName)
if prCheck == nil {
return nil
}
prCheck.FixAttempts++
prCheck.LastAttemptAt = time.Now()
UpdatePrCheck(prCheck.Id, prCheck)
return prCheck
}

// ShouldAttemptFix checks if we should attempt to fix this check (max attempts configurable).
func ShouldAttemptFix(org string, repo string, prNumber int, checkName string) bool {
prCheck := GetPrCheck(org, repo, prNumber, checkName)
// First time, should attempt.
if prCheck == nil {
return true
}
return prCheck.FixAttempts < util.MaxFixAttempts && !prCheck.IsFixed
}
3 changes: 3 additions & 0 deletions routers/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ func initAPI() {
beego.Router("/api/webhook", &controllers.ApiController{}, "Post:WebhookOpen")
beego.Router("/api/is-mainland-ip", &controllers.ApiController{}, "GET:IsMainlandIp")

beego.Router("/api/get-pr-checks", &controllers.ApiController{}, "GET:GetPrChecks")
beego.Router("/api/get-pr-check", &controllers.ApiController{}, "GET:GetPrCheck")

beego.Router("/api/get-machines", &controllers.ApiController{}, "GET:GetMachines")
beego.Router("/api/get-machine", &controllers.ApiController{}, "GET:GetMachine")
beego.Router("/api/update-machine", &controllers.ApiController{}, "POST:UpdateMachine")
Expand Down
Loading
Loading