add flexible accepted keywords

This commit is contained in:
Thomas Stringer 2022-03-26 09:39:58 -04:00
parent a1f523c503
commit a55d8b93fb
4 changed files with 213 additions and 10 deletions

View file

@ -3,6 +3,7 @@ package main
import (
"context"
"fmt"
"regexp"
"strings"
"github.com/google/go-github/v43/github"
@ -47,8 +48,12 @@ URL: %s
Required approvers: %s
Respond '%s' to continue workflow or '%s' to cancel.
`, a.runURL(), a.approvers, approvalStatusApproved, approvalStatusDenied)
Respond '%s' to continue workflow or '%s' to cancel.`,
a.runURL(),
a.approvers,
formatAcceptedWords(approvedWords),
formatAcceptedWords(deniedWords),
)
var err error
a.approvalIssue, _, err = a.client.Issues.Create(ctx, a.repoOwner, a.repo, &github.IssueRequest{
Title: &issueTitle,
@ -59,7 +64,7 @@ Respond '%s' to continue workflow or '%s' to cancel.
return err
}
func approvalFromComments(comments []*github.IssueComment, approvers []string) approvalStatus {
func approvalFromComments(comments []*github.IssueComment, approvers []string) (approvalStatus, error) {
remainingApprovers := make([]string, len(approvers))
copy(remainingApprovers, approvers)
@ -71,19 +76,29 @@ func approvalFromComments(comments []*github.IssueComment, approvers []string) a
}
commentBody := comment.GetBody()
if commentBody == string(approvalStatusApproved) {
isApprovalComment, err := isApproved(commentBody)
if err != nil {
return approvalStatusPending, err
}
if isApprovalComment {
if len(remainingApprovers) == 1 {
return approvalStatusApproved
return approvalStatusApproved, nil
}
remainingApprovers[approverIdx] = remainingApprovers[len(remainingApprovers)-1]
remainingApprovers = remainingApprovers[:len(remainingApprovers)-1]
continue
} else if commentBody == string(approvalStatusDenied) {
return approvalStatusDenied
}
isDenialComment, err := isDenied(commentBody)
if err != nil {
return approvalStatusPending, err
}
if isDenialComment {
return approvalStatusDenied, nil
}
}
return approvalStatusPending
return approvalStatusPending, nil
}
func approversIndex(approvers []string, name string) int {
@ -94,3 +109,41 @@ func approversIndex(approvers []string, name string) int {
}
return -1
}
func isApproved(commentBody string) (bool, error) {
for _, approvedWord := range approvedWords {
matched, err := regexp.MatchString(fmt.Sprintf("(?i)^%s[.!]?$", approvedWord), commentBody)
if err != nil {
return false, err
}
if matched {
return true, nil
}
}
return false, nil
}
func isDenied(commentBody string) (bool, error) {
for _, deniedWord := range deniedWords {
matched, err := regexp.MatchString(fmt.Sprintf("(?i)^%s[.!]?$", deniedWord), commentBody)
if err != nil {
return false, err
}
if matched {
return true, nil
}
}
return false, nil
}
func formatAcceptedWords(words []string) string {
var quotedWords []string
for _, word := range words {
quotedWords = append(quotedWords, fmt.Sprintf("\"%s\"", word))
}
return strings.Join(quotedWords, ",")
}

View file

@ -116,10 +116,151 @@ func TestApprovalFromComments(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
actual := approvalFromComments(testCase.comments, testCase.approvers)
actual, err := approvalFromComments(testCase.comments, testCase.approvers)
if err != nil {
t.Fatalf("error getting approval from comments: %v", err)
}
if actual != testCase.expectedStatus {
t.Fatalf("actual %s, expected %s", actual, testCase.expectedStatus)
}
})
}
}
func TestApprovedCommentBody(t *testing.T) {
testCases := []struct {
name string
commentBody string
isSuccess bool
}{
{
name: "approved_lowercase_no_punctuation",
commentBody: "approved",
isSuccess: true,
},
{
name: "approve_lowercase_no_punctuation",
commentBody: "approve",
isSuccess: true,
},
{
name: "lgtm_lowercase_no_punctuation",
commentBody: "lgtm",
isSuccess: true,
},
{
name: "yes_lowercase_no_punctuation",
commentBody: "yes",
isSuccess: true,
},
{
name: "approve_uppercase_no_punctuation",
commentBody: "APPROVE",
isSuccess: true,
},
{
name: "approved_titlecase_period",
commentBody: "Approved.",
isSuccess: true,
},
{
name: "approved_titlecase_exclamation",
commentBody: "Approved!",
isSuccess: true,
},
{
name: "approved_titlecase_question",
commentBody: "Approved?",
isSuccess: false,
},
{
name: "sentence_with_keyword",
commentBody: "should i approve this",
isSuccess: false,
},
{
name: "sentence_without_keyword",
commentBody: "this is just some random comment",
isSuccess: false,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
actual, err := isApproved(testCase.commentBody)
if err != nil {
t.Fatalf("error getting approval: %v", err)
}
if actual != testCase.isSuccess {
t.Fatalf("expected %v but got %v", testCase.isSuccess, actual)
}
})
}
}
func TestDeniedCommentBody(t *testing.T) {
testCases := []struct {
name string
commentBody string
isSuccess bool
}{
{
name: "denied_lowercase_no_punctuation",
commentBody: "denied",
isSuccess: true,
},
{
name: "deny_lowercase_no_punctuation",
commentBody: "deny",
isSuccess: true,
},
{
name: "no_lowercase_no_punctuation",
commentBody: "no",
isSuccess: true,
},
{
name: "deny_uppercase_no_punctuation",
commentBody: "DENY",
isSuccess: true,
},
{
name: "denied_titlecase_period",
commentBody: "Denied.",
isSuccess: true,
},
{
name: "denied_titlecase_exclamation",
commentBody: "Denied!",
isSuccess: true,
},
{
name: "deny_titlecase_question",
commentBody: "Deny?",
isSuccess: false,
},
{
name: "sentence_with_keyword",
commentBody: "should i deny this",
isSuccess: false,
},
{
name: "sentence_without_keyword",
commentBody: "this is just some random comment",
isSuccess: false,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
actual, err := isDenied(testCase.commentBody)
if err != nil {
t.Fatalf("error getting approval: %v", err)
}
if actual != testCase.isSuccess {
t.Fatalf("expected %v but got %v", testCase.isSuccess, actual)
}
})
}
}

View file

@ -11,3 +11,8 @@ const (
envVarToken string = "INPUT_SECRET"
envVarApprovers string = "INPUT_APPROVERS"
)
var (
approvedWords = []string{"approved", "approve", "lgtm", "yes"}
deniedWords = []string{"denied", "deny", "no"}
)

View file

@ -57,7 +57,11 @@ commentLoop:
os.Exit(1)
}
approved := approvalFromComments(comments, approvers)
approved, err := approvalFromComments(comments, approvers)
if err != nil {
fmt.Printf("error getting approval from comments: %v\n", err)
os.Exit(1)
}
fmt.Printf("Workflow status: %s\n", approved)
switch approved {
case approvalStatusApproved: