You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pulsar.apache.org by lh...@apache.org on 2022/09/06 15:21:05 UTC
[pulsar-test-infra] branch master updated: Refactor docbot and add tests (#69)
This is an automated email from the ASF dual-hosted git repository.
lhotari pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-test-infra.git
The following commit(s) were added to refs/heads/master by this push:
new f7f525d Refactor docbot and add tests (#69)
f7f525d is described below
commit f7f525d26656c2a6dd23f281a8452262e273c3f2
Author: Zixuan Liu <no...@gmail.com>
AuthorDate: Tue Sep 6 23:21:01 2022 +0800
Refactor docbot and add tests (#69)
---
docbot/action.go | 316 +++++++++++++++++++++++++
docbot/action.yml | 2 +-
docbot/action_config.go | 123 ++++++++++
docbot/action_test.go | 320 ++++++++++++++++++++++++++
docbot/go.mod | 5 +-
docbot/go.sum | 15 +-
docbot/main.go | 600 +-----------------------------------------------
7 files changed, 780 insertions(+), 601 deletions(-)
diff --git a/docbot/action.go b/docbot/action.go
new file mode 100644
index 0000000..026a9f7
--- /dev/null
+++ b/docbot/action.go
@@ -0,0 +1,316 @@
+package main
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "regexp"
+ "strings"
+
+ "github.com/apache/pulsar-test-infra/docbot/pkg/logger"
+ "github.com/google/go-github/v45/github"
+ "golang.org/x/oauth2"
+)
+
+const (
+ MessageLabelMissing = `Please provide a correct documentation label for your PR.
+Instructions see [Pulsar Documentation Label Guide](https://docs.google.com/document/d/1Qw7LHQdXWBW9t2-r-A7QdFDBwmZh6ytB4guwMoXHqc0).`
+ MessageLabelMultiple = `Please select only one documentation label for your PR.
+Instructions see [Pulsar Documentation Label Guide](https://docs.google.com/document/d/1Qw7LHQdXWBW9t2-r-A7QdFDBwmZh6ytB4guwMoXHqc0).`
+)
+
+type Action struct {
+ config *ActionConfig
+
+ globalContext context.Context
+ client *github.Client
+
+ prNumber int
+}
+
+func NewAction(ac *ActionConfig) *Action {
+ ctx := context.Background()
+ ts := oauth2.StaticTokenSource(
+ &oauth2.Token{AccessToken: ac.GetToken()},
+ )
+
+ tc := oauth2.NewClient(ctx, ts)
+
+ return NewActionWithClient(ctx, ac, github.NewClient(tc))
+}
+
+func NewActionWithClient(ctx context.Context, ac *ActionConfig, client *github.Client) *Action {
+ return &Action{
+ config: ac,
+ globalContext: ctx,
+ client: client,
+ }
+}
+
+func (a *Action) Run(prNumber int, actionType string) error {
+ a.prNumber = prNumber
+
+ switch actionType {
+ case "opened", "edited", "labeled", "unlabeled":
+ return a.checkLabels()
+ }
+ return nil
+}
+
+func (a *Action) checkLabels() error {
+ pr, _, err := a.client.PullRequests.Get(a.globalContext, a.config.GetOwner(), a.config.GetRepo(), a.prNumber)
+ if err != nil {
+ return fmt.Errorf("get PR: %v", err)
+ }
+
+ var bodyLabels map[string]bool
+ if pr.Body != nil {
+ bodyLabels = a.extractLabels(*pr.Body)
+ }
+
+ logger.Infoln("@List repo labels")
+ repoLabels, err := a.getRepoLabels()
+ if err != nil {
+ return fmt.Errorf("list repo labels: %v", err)
+ }
+ logger.Infof("Repo labels: %v\n", repoLabels)
+
+ prLabels := a.labelsToMap(pr.Labels)
+ logger.Infof("PR labels: %v\n", prLabels)
+
+ // Get expected labels
+ // Only handle labels already exist in repo
+ expectedLabelsMap := make(map[string]bool)
+ checkedCount := 0
+ for label, checked := range bodyLabels {
+ if _, exist := repoLabels[label]; !exist {
+ logger.Infof("Found label %v not exist int repo\n", label)
+ continue
+ }
+ expectedLabelsMap[label] = checked
+ if checked {
+ checkedCount++
+ }
+ }
+ logger.Infof("Expected labels: %v\n", expectedLabelsMap)
+
+ labelsToRemove := make(map[string]struct{}, 0)
+ labelsToAdd := make(map[string]struct{}, 0)
+
+ if checkedCount == 0 {
+ logger.Infoln("Label missing")
+ for label := range a.config.labelWatchSet {
+ _, found := prLabels[label]
+ if found {
+ labelsToRemove[label] = struct{}{}
+ }
+ }
+ _, found := prLabels[a.config.GetLabelMissing()]
+ if !found {
+ labelsToAdd[a.config.GetLabelMissing()] = struct{}{}
+ } else {
+ logger.Infoln("Already added missing label.")
+ return errors.New(MessageLabelMissing)
+ }
+ } else {
+ if !a.config.GetEnableLabelMultiple() && checkedCount > 1 {
+ logger.Infoln("Multiple labels not enabled")
+ err = a.addAndCleanupHelpComment(pr.User.GetLogin(), MessageLabelMultiple)
+ if err != nil {
+ return err
+ }
+ return errors.New(MessageLabelMultiple)
+ }
+
+ _, found := prLabels[a.config.GetLabelMissing()]
+ if found {
+ labelsToRemove[a.config.GetLabelMissing()] = struct{}{}
+ }
+
+ for label, checked := range expectedLabelsMap {
+ _, found := prLabels[label]
+ if found {
+ continue
+ }
+ if checked {
+ labelsToAdd[label] = struct{}{}
+ }
+ }
+ }
+
+ if len(labelsToAdd) == 0 {
+ logger.Infoln("No labels to add.")
+ } else {
+ labels := a.labelsSetToString(labelsToAdd)
+ logger.Infof("Labels to add: %v\n", labels)
+ err = a.addLabels(labels)
+ if err != nil {
+ logger.Errorf("Failed add labels %v: %v\n", labelsToAdd, err)
+ return err
+ }
+ }
+
+ if len(labelsToRemove) == 0 {
+ logger.Infoln("No labels to remove.")
+ } else {
+ labels := a.labelsSetToString(labelsToRemove)
+ logger.Infof("Labels to remove: %v\n", labels)
+ for _, label := range labels {
+ err = a.removeLabel(label)
+ if err != nil {
+ logger.Errorf("Failed remove labels %v: %v\n", labelsToRemove, err)
+ return err
+ }
+ }
+ }
+
+ if checkedCount == 0 {
+ err := a.addAndCleanupHelpComment(pr.User.GetLogin(), MessageLabelMissing)
+ if err != nil {
+ return err
+ }
+ return errors.New(MessageLabelMissing)
+ }
+
+ return nil
+}
+
+func (a *Action) extractLabels(prBody string) map[string]bool {
+ r := regexp.MustCompile(a.config.GetLabelPattern())
+ targets := r.FindAllStringSubmatch(prBody, -1)
+
+ labels := make(map[string]bool)
+ for _, v := range targets {
+ checked := strings.ToLower(strings.TrimSpace(v[1])) == "x"
+ name := strings.TrimSpace(v[2])
+
+ // Filter uninterested labels
+ if _, exist := a.config.labelWatchSet[name]; !exist {
+ continue
+ }
+
+ labels[name] = checked
+ }
+
+ return labels
+}
+
+func (a *Action) getRepoLabels() (map[string]struct{}, error) {
+ ctx := context.Background()
+ listOptions := &github.ListOptions{PerPage: 100}
+ repoLabels := make(map[string]struct{}, 0)
+ for {
+ rLabels, resp, err := a.client.Issues.ListLabels(ctx, a.config.GetOwner(), a.config.GetRepo(), listOptions)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, label := range rLabels {
+ repoLabels[label.GetName()] = struct{}{}
+ }
+ if resp.NextPage == 0 {
+ break
+ }
+ listOptions.Page = resp.NextPage
+ }
+ return repoLabels, nil
+}
+
+func (a *Action) labelsToMap(labels []*github.Label) map[string]struct{} {
+ result := make(map[string]struct{}, 0)
+ for _, label := range labels {
+ result[label.GetName()] = struct{}{}
+ }
+ return result
+}
+
+func (a *Action) labelsSetToString(labels map[string]struct{}) []string {
+ result := []string{}
+ for label := range labels {
+ result = append(result, label)
+ }
+ return result
+}
+
+func (a *Action) getLabelInvalidCommentIDs(body string) ([]int64, error) {
+ ctx := context.Background()
+ listOptions := &github.IssueListCommentsOptions{}
+ listOptions.PerPage = 100
+ commentIDs := make([]int64, 0)
+ for {
+ comments, resp, err := a.client.Issues.ListComments(ctx, a.config.GetOwner(), a.config.GetRepo(),
+ a.prNumber, listOptions)
+ if err != nil {
+ return nil, err
+ }
+ for _, item := range comments {
+ if strings.Contains(*item.Body, body) {
+ commentIDs = append(commentIDs, *item.ID)
+ }
+ }
+
+ if resp.NextPage == 0 {
+ break
+ }
+ listOptions.Page = resp.NextPage
+ }
+
+ return commentIDs, nil
+}
+
+func (a *Action) createComment(body string) error {
+ _, _, err := a.client.Issues.CreateComment(a.globalContext, a.config.GetOwner(), a.config.GetRepo(),
+ a.prNumber, &github.IssueComment{Body: func(v string) *string { return &v }(body)})
+ return err
+}
+
+func (a *Action) deleteComment(commentID int64) error {
+ _, err := a.client.Issues.DeleteComment(a.globalContext, a.config.GetOwner(), a.config.GetRepo(),
+ commentID)
+ return err
+}
+
+func (a *Action) addLabels(labels []string) error {
+ _, _, err := a.client.Issues.AddLabelsToIssue(a.globalContext, a.config.GetOwner(), a.config.GetRepo(),
+ a.prNumber, labels)
+ return err
+}
+
+func (a *Action) removeLabel(label string) error {
+ _, err := a.client.Issues.RemoveLabelForIssue(a.globalContext, a.config.GetOwner(), a.config.GetRepo(),
+ a.prNumber, label)
+ return err
+}
+
+// addAndCleanupHelpComment adds a help comment when no help comment on the PR.
+func (a *Action) addAndCleanupHelpComment(login, body string) error {
+ commentIDs, err := a.getLabelInvalidCommentIDs(body)
+ if err != nil {
+ logger.Errorf("Failed to get the comment list: %v", err)
+ return err
+ }
+ if len(commentIDs) == 0 {
+ err = a.createComment(fmt.Sprintf("@%s %s", login, body))
+ if err != nil {
+ logger.Errorf("Failed to create %s comment: %v", body, err)
+ return err
+ }
+ return nil
+ } else {
+ // cleanup
+ if len(commentIDs) > 1 {
+ for index, id := range commentIDs {
+ if index == 0 {
+ continue
+ }
+ err := a.deleteComment(id)
+ if err != nil {
+ logger.Errorf("Failed to delete %v comment: %v", id, err)
+ return err
+ }
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/docbot/action.yml b/docbot/action.yml
index 9ecddf7..8a5dce6 100644
--- a/docbot/action.yml
+++ b/docbot/action.yml
@@ -17,6 +17,6 @@ runs:
with:
go-version: 1.18
- name: Execute
- run: go run main.go
+ run: go run .
shell: bash
working-directory: docbot
diff --git a/docbot/action_config.go b/docbot/action_config.go
new file mode 100644
index 0000000..34a810e
--- /dev/null
+++ b/docbot/action_config.go
@@ -0,0 +1,123 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "strings"
+)
+
+type ActionConfig struct {
+ token *string
+ repo *string
+ owner *string
+
+ labelPattern *string
+ labelWatchSet map[string]struct{}
+ labelMissing *string
+ enableLabelMissing *bool
+ enableLabelMultiple *bool
+}
+
+func NewActionConfig() (*ActionConfig, error) {
+ ownerRepoSlug := os.Getenv("GITHUB_REPOSITORY")
+ ownerRepo := strings.Split(ownerRepoSlug, "/")
+ if len(ownerRepo) != 2 {
+ return nil, fmt.Errorf("GITHUB_REPOSITORY is not found")
+ }
+ owner, repo := ownerRepo[0], ownerRepo[1]
+
+ token := os.Getenv("GITHUB_TOKEN")
+
+ labelPattern := os.Getenv("LABEL_PATTERN")
+ if len(labelPattern) == 0 {
+ labelPattern = "- \\[(.*?)\\] ?`(.+?)`"
+ }
+
+ labelWatchListSlug := os.Getenv("LABEL_WATCH_LIST")
+ labelWatchList := strings.Split(labelWatchListSlug, ",")
+ labelWatchSet := make(map[string]struct{})
+ for _, l := range labelWatchList {
+ key := strings.TrimSpace(l)
+ if key == "" {
+ continue
+ }
+ labelWatchSet[key] = struct{}{}
+ }
+
+ enableLabelMissingSlug := os.Getenv("ENABLE_LABEL_MISSING")
+ enableLabelMissing := true
+ if enableLabelMissingSlug == "false" {
+ enableLabelMissing = false
+ }
+
+ labelMissing := os.Getenv("LABEL_MISSING")
+ if len(labelMissing) == 0 {
+ labelMissing = "label-missing"
+ }
+
+ enableLabelMultipleSlug := os.Getenv("ENABLE_LABEL_MULTIPLE")
+ enableLabelMultiple := false
+ if enableLabelMultipleSlug == "true" {
+ enableLabelMultiple = true
+ }
+
+ return &ActionConfig{
+ token: &token,
+ repo: &repo,
+ owner: &owner,
+ labelPattern: &labelPattern,
+ labelWatchSet: labelWatchSet,
+ labelMissing: &labelMissing,
+ enableLabelMissing: &enableLabelMissing,
+ enableLabelMultiple: &enableLabelMultiple,
+ }, nil
+}
+
+func (ac *ActionConfig) GetToken() string {
+ if ac == nil || ac.token == nil {
+ return ""
+ }
+ return *ac.token
+}
+
+func (ac *ActionConfig) GetOwner() string {
+ if ac == nil || ac.owner == nil {
+ return ""
+ }
+ return *ac.owner
+}
+
+func (ac *ActionConfig) GetRepo() string {
+ if ac == nil || ac.repo == nil {
+ return ""
+ }
+ return *ac.repo
+}
+
+func (ac *ActionConfig) GetLabelPattern() string {
+ if ac == nil || ac.labelPattern == nil {
+ return ""
+ }
+ return *ac.labelPattern
+}
+
+func (ac *ActionConfig) GetLabelMissing() string {
+ if ac == nil || ac.labelMissing == nil {
+ return ""
+ }
+ return *ac.labelMissing
+}
+
+func (ac *ActionConfig) GetEnableLabelMissing() bool {
+ if ac == nil || ac.enableLabelMissing == nil {
+ return false
+ }
+ return *ac.enableLabelMissing
+}
+
+func (ac *ActionConfig) GetEnableLabelMultiple() bool {
+ if ac == nil || ac.enableLabelMultiple == nil {
+ return false
+ }
+ return *ac.enableLabelMultiple
+}
diff --git a/docbot/action_test.go b/docbot/action_test.go
new file mode 100644
index 0000000..a2af4f6
--- /dev/null
+++ b/docbot/action_test.go
@@ -0,0 +1,320 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "testing"
+
+ "github.com/google/go-github/v45/github"
+ "github.com/migueleliasweb/go-github-mock/src/mock"
+)
+
+func repoLabels() []*github.Label {
+ labels := []string{"doc-required", "doc-not-needed", "doc", "doc-complete", "doc-label-missing"}
+
+ result := make([]*github.Label, 0)
+ for _, label := range labels {
+ name := label
+ result = append(result, &github.Label{Name: &name})
+ }
+
+ return result
+}
+
+func mustNewActionConfig() *ActionConfig {
+ _ = os.Setenv("GITHUB_REPOSITORY", "apache/pulsar")
+ _ = os.Setenv("LABEL_WATCH_LIST", "doc,doc-required,doc-not-needed,doc-complete")
+ _ = os.Setenv("LABEL_MISSING", "doc-label-missing")
+
+ config, err := NewActionConfig()
+ if err != nil {
+ panic(err)
+ }
+
+ return config
+}
+
+func assertMessageLabel(t *testing.T, err error, message string) {
+ t.Helper()
+
+ if err == nil {
+ t.Fatal("Expect err not nil")
+ }
+
+ if err.Error() != message {
+ t.Fatal("Expect err equals " + message)
+ }
+}
+
+func TestSingleChecked(t *testing.T) {
+ id := int64(1)
+ body := fmt.Sprintf(`
+Check the box below or label this PR directly.
+
+Need to update docs?
+
+- [ ] %s
+(Your PR needs to update docs and you will update later)
+
+- [x] %s
+(Please explain why)
+
+- [ ] %s
+(Your PR contains doc changes)
+
+- [ ] %s
+(Docs have been already added)
+`, "`doc-required`", "`doc-not-needed`", "`doc`", "`doc-complete`")
+
+ mockedHTTPClient := mock.NewMockedHTTPClient(
+ mock.WithRequestMatch(
+ mock.GetReposPullsByOwnerByRepoByPullNumber,
+ github.PullRequest{
+ ID: &id,
+ Body: &body,
+ Labels: nil,
+ },
+ ), mock.WithRequestMatch(
+ mock.GetReposLabelsByOwnerByRepo,
+ repoLabels(),
+ ),
+ mock.WithRequestMatch(mock.PostReposIssuesLabelsByOwnerByRepoByIssueNumber, nil),
+ )
+
+ config := mustNewActionConfig()
+ action := NewActionWithClient(context.Background(), config, github.NewClient(mockedHTTPClient))
+
+ err := action.Run(1, "opened")
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestMultipleChecked(t *testing.T) {
+ id := int64(1)
+ body := fmt.Sprintf(`
+Check the box below or label this PR directly.
+
+Need to update docs?
+
+- [ ] %s
+(Your PR needs to update docs and you will update later)
+
+- [x] %s
+(Please explain why)
+
+- [x] %s
+(Your PR contains doc changes)
+
+- [ ] %s
+(Docs have been already added)
+`, "`doc-required`", "`doc-not-needed`", "`doc`", "`doc-complete`")
+
+ mockedHTTPClient := mock.NewMockedHTTPClient(
+ mock.WithRequestMatch(
+ mock.GetReposPullsByOwnerByRepoByPullNumber,
+ github.PullRequest{
+ ID: &id,
+ Body: &body,
+ Labels: nil,
+ },
+ ), mock.WithRequestMatch(
+ mock.GetReposLabelsByOwnerByRepo,
+ repoLabels(),
+ ),
+ mock.WithRequestMatch(mock.PostReposIssuesLabelsByOwnerByRepoByIssueNumber, nil),
+ )
+
+ const key = "ENABLE_LABEL_MULTIPLE"
+ value := os.Getenv(key)
+ defer func() {
+ // reset
+ _ = os.Setenv(key, value)
+ }()
+ _ = os.Setenv("ENABLE_LABEL_MULTIPLE", "true")
+
+ config := mustNewActionConfig()
+ action := NewActionWithClient(context.Background(), config, github.NewClient(mockedHTTPClient))
+
+ err := action.Run(1, "opened")
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestUnchecked(t *testing.T) {
+ id := int64(1)
+ body := fmt.Sprintf(`
+Check the box below or label this PR directly.
+
+Need to update docs?
+
+- [ ] %s
+(Your PR needs to update docs and you will update later)
+
+- [ ] %s
+(Please explain why)
+
+- [ ] %s
+(Your PR contains doc changes)
+
+- [ ] %s
+(Docs have been already added)
+`, "`doc-required`", "`doc-not-needed`", "`doc`", "`doc-complete`")
+
+ mockedHTTPClient := mock.NewMockedHTTPClient(
+ mock.WithRequestMatch(
+ mock.GetReposPullsByOwnerByRepoByPullNumber,
+ github.PullRequest{
+ ID: &id,
+ Body: &body,
+ Labels: nil,
+ },
+ ), mock.WithRequestMatch(
+ mock.GetReposLabelsByOwnerByRepo,
+ repoLabels(),
+ ),
+ mock.WithRequestMatch(mock.PostReposIssuesLabelsByOwnerByRepoByIssueNumber, nil),
+ mock.WithRequestMatch(mock.GetReposIssuesCommentsByOwnerByRepoByIssueNumber, nil),
+ mock.WithRequestMatch(mock.PostReposIssuesCommentsByOwnerByRepoByIssueNumber, nil),
+ )
+
+ config := mustNewActionConfig()
+ action := NewActionWithClient(context.Background(), config, github.NewClient(mockedHTTPClient))
+
+ err := action.Run(1, "opened")
+ assertMessageLabel(t, err, MessageLabelMissing)
+}
+
+func TestMultipleChecked_WhenMultipleLabelsNotEnabled(t *testing.T) {
+ id := int64(1)
+ body := fmt.Sprintf(`
+Check the box below or label this PR directly.
+
+Need to update docs?
+
+- [ ] %s
+(Your PR needs to update docs and you will update later)
+
+- [x] %s
+(Please explain why)
+
+- [x] %s
+(Your PR contains doc changes)
+
+- [ ] %s
+(Docs have been already added)
+`, "`doc-required`", "`doc-not-needed`", "`doc`", "`doc-complete`")
+
+ mockedHTTPClient := mock.NewMockedHTTPClient(
+ mock.WithRequestMatch(
+ mock.GetReposPullsByOwnerByRepoByPullNumber,
+ github.PullRequest{
+ ID: &id,
+ Body: &body,
+ Labels: nil,
+ },
+ ), mock.WithRequestMatch(
+ mock.GetReposLabelsByOwnerByRepo,
+ repoLabels(),
+ ),
+ mock.WithRequestMatch(mock.PostReposIssuesLabelsByOwnerByRepoByIssueNumber, nil),
+ mock.WithRequestMatch(mock.GetReposIssuesCommentsByOwnerByRepoByIssueNumber, nil),
+ mock.WithRequestMatch(mock.PostReposIssuesCommentsByOwnerByRepoByIssueNumber, nil),
+ )
+
+ config := mustNewActionConfig()
+ action := NewActionWithClient(context.Background(), config, github.NewClient(mockedHTTPClient))
+
+ err := action.Run(1, "opened")
+ assertMessageLabel(t, err, MessageLabelMultiple)
+}
+
+func TestSingleChecked_WhenLabelMissingExist(t *testing.T) {
+ id := int64(1)
+ body := fmt.Sprintf(`
+Check the box below or label this PR directly.
+
+Need to update docs?
+
+- [ ] %s
+(Your PR needs to update docs and you will update later)
+
+- [x] %s
+(Please explain why)
+
+- [ ] %s
+(Your PR contains doc changes)
+
+- [ ] %s
+(Docs have been already added)
+`, "`doc-required`", "`doc-not-needed`", "`doc`", "`doc-complete`")
+
+ labelMissing := "doc-label-missing"
+ mockedHTTPClient := mock.NewMockedHTTPClient(
+ mock.WithRequestMatch(
+ mock.GetReposPullsByOwnerByRepoByPullNumber,
+ github.PullRequest{
+ ID: &id,
+ Body: &body,
+ Labels: []*github.Label{{Name: &labelMissing}},
+ },
+ ), mock.WithRequestMatch(
+ mock.GetReposLabelsByOwnerByRepo,
+ repoLabels(),
+ ),
+ mock.WithRequestMatch(mock.PostReposIssuesLabelsByOwnerByRepoByIssueNumber, nil),
+ mock.WithRequestMatch(mock.DeleteReposIssuesLabelsByOwnerByRepoByIssueNumberByName, nil),
+ )
+
+ config := mustNewActionConfig()
+ action := NewActionWithClient(context.Background(), config, github.NewClient(mockedHTTPClient))
+
+ err := action.Run(1, "opened")
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestUnchecked_WhenLabelMissingExist(t *testing.T) {
+ id := int64(1)
+ body := fmt.Sprintf(`
+Check the box below or label this PR directly.
+
+Need to update docs?
+
+- [ ] %s
+(Your PR needs to update docs and you will update later)
+
+- [ ] %s
+(Please explain why)
+
+- [ ] %s
+(Your PR contains doc changes)
+
+- [ ] %s
+(Docs have been already added)
+`, "`doc-required`", "`doc-not-needed`", "`doc`", "`doc-complete`")
+
+ labelMissing := "doc-label-missing"
+ mockedHTTPClient := mock.NewMockedHTTPClient(
+ mock.WithRequestMatch(
+ mock.GetReposPullsByOwnerByRepoByPullNumber,
+ github.PullRequest{
+ ID: &id,
+ Body: &body,
+ Labels: []*github.Label{{Name: &labelMissing}},
+ },
+ ), mock.WithRequestMatch(
+ mock.GetReposLabelsByOwnerByRepo,
+ repoLabels(),
+ ),
+ )
+
+ config := mustNewActionConfig()
+ action := NewActionWithClient(context.Background(), config, github.NewClient(mockedHTTPClient))
+
+ err := action.Run(1, "opened")
+ assertMessageLabel(t, err, MessageLabelMissing)
+}
diff --git a/docbot/go.mod b/docbot/go.mod
index 273fc97..6fa874b 100644
--- a/docbot/go.mod
+++ b/docbot/go.mod
@@ -4,15 +4,18 @@ go 1.18
require (
github.com/google/go-github/v45 v45.0.0
+ github.com/migueleliasweb/go-github-mock v0.0.10
github.com/sethvargo/go-githubactions v1.0.0
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be
)
require (
github.com/golang/protobuf v1.3.2 // indirect
+ github.com/google/go-github/v41 v41.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
+ github.com/gorilla/mux v1.8.0 // indirect
github.com/sethvargo/go-envconfig v0.6.0 // indirect
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
- golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
+ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
google.golang.org/appengine v1.6.7 // indirect
)
diff --git a/docbot/go.sum b/docbot/go.sum
index cc47aa0..76374c4 100644
--- a/docbot/go.sum
+++ b/docbot/go.sum
@@ -1,12 +1,22 @@
+github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
+github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
+github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-github/v41 v41.0.0 h1:HseJrM2JFf2vfiZJ8anY2hqBjdfY1Vlj/K27ueww4gg=
+github.com/google/go-github/v41 v41.0.0/go.mod h1:XgmCA5H323A9rtgExdTcnDkcqp6S30AVACCBDOonIxg=
github.com/google/go-github/v45 v45.0.0 h1:LU0WBjYidxIVyx7PZeWb+FP4JZJ3Wh3FQgdumnGqiLs=
github.com/google/go-github/v45 v45.0.0/go.mod h1:FObaZJEDSTa/WGCzZ2Z3eoCDXWJKMenWWTrd8jrta28=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
+github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
+github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/migueleliasweb/go-github-mock v0.0.10 h1:VTaNa4eYzkRpnZ7Fqop7Jldgh7vsUsgnKWXDOnAvm3k=
+github.com/migueleliasweb/go-github-mock v0.0.10/go.mod h1:mD5w+9J3oBBMLr7uD6owEYlYBAL8tZd+BA7iGjI4EU8=
github.com/sethvargo/go-envconfig v0.6.0 h1:GxxdoeiNpWgGiVEphNFNObgMYRN/ZvI2dN7rBwadyss=
github.com/sethvargo/go-envconfig v0.6.0/go.mod h1:00S1FAhRUuTNJazWBWcJGvEHOM+NO6DhoRMAOX7FY5o=
github.com/sethvargo/go-githubactions v1.0.0 h1:5mYGPNxIwIXaS8MLj4uYGWM8QM8giUVqA4FuSYOZjXE=
@@ -15,12 +25,15 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
diff --git a/docbot/main.go b/docbot/main.go
index f98bfff..1a98d44 100644
--- a/docbot/main.go
+++ b/docbot/main.go
@@ -1,586 +1,10 @@
package main
import (
- "context"
- "fmt"
- "os"
- "regexp"
- "strings"
-
- "github.com/google/go-github/v45/github"
- "github.com/sethvargo/go-githubactions"
- "golang.org/x/oauth2"
-
"github.com/apache/pulsar-test-infra/docbot/pkg/logger"
+ "github.com/sethvargo/go-githubactions"
)
-const (
- MessageLabelMissing = `Please provide a correct documentation label for your PR.
-Instructions see [Pulsar Documentation Label Guide](https://docs.google.com/document/d/1Qw7LHQdXWBW9t2-r-A7QdFDBwmZh6ytB4guwMoXHqc0).`
- MessageLabelMultiple = `Please select only one documentation label for your PR.
-Instructions see [Pulsar Documentation Label Guide](https://docs.google.com/document/d/1Qw7LHQdXWBW9t2-r-A7QdFDBwmZh6ytB4guwMoXHqc0).`
-)
-
-type ActionConfig struct {
- token *string
- repo *string
- owner *string
- number *int
-
- labelPattern *string
- labelWatchSet map[string]struct{}
- labelMissing *string
- enableLabelMissing *bool
- enableLabelMultiple *bool
-
- // labels extracted from PR body
- labels map[string]bool
-}
-
-func NewActionConfig() (*ActionConfig, error) {
- ownerRepoSlug := os.Getenv("GITHUB_REPOSITORY")
- ownerRepo := strings.Split(ownerRepoSlug, "/")
- if len(ownerRepo) != 2 {
- return nil, fmt.Errorf("GITHUB_REPOSITORY is not found")
- }
- owner, repo := ownerRepo[0], ownerRepo[1]
-
- token := os.Getenv("GITHUB_TOKEN")
-
- labelPattern := os.Getenv("LABEL_PATTERN")
- if len(labelPattern) == 0 {
- labelPattern = "- \\[(.*?)\\] ?`(.+?)`"
- }
-
- labelWatchListSlug := os.Getenv("LABEL_WATCH_LIST")
- labelWatchList := strings.Split(strings.TrimSpace(labelWatchListSlug), ",")
- labelWatchSet := make(map[string]struct{})
- for _, l := range labelWatchList {
- labelWatchSet[l] = struct{}{}
- }
-
- enableLabelMissingSlug := os.Getenv("ENABLE_LABEL_MISSING")
- enableLabelMissing := true
- if enableLabelMissingSlug == "false" {
- enableLabelMissing = false
- }
-
- labelMissing := os.Getenv("LABEL_MISSING")
- if len(labelMissing) == 0 {
- labelMissing = "label-missing"
- }
-
- enableLabelMultipleSlug := os.Getenv("ENABLE_LABEL_MULTIPLE")
- enableLabelMultiple := false
- if enableLabelMultipleSlug == "true" {
- enableLabelMultiple = true
- }
-
- return &ActionConfig{
- token: &token,
- repo: &repo,
- owner: &owner,
- labelPattern: &labelPattern,
- labelWatchSet: labelWatchSet,
- labelMissing: &labelMissing,
- enableLabelMissing: &enableLabelMissing,
- enableLabelMultiple: &enableLabelMultiple,
- }, nil
-}
-
-func (ac *ActionConfig) GetToken() string {
- if ac == nil || ac.token == nil {
- return ""
- }
- return *ac.token
-}
-
-func (ac *ActionConfig) GetOwner() string {
- if ac == nil || ac.owner == nil {
- return ""
- }
- return *ac.owner
-}
-
-func (ac *ActionConfig) GetRepo() string {
- if ac == nil || ac.repo == nil {
- return ""
- }
- return *ac.repo
-}
-
-func (ac *ActionConfig) GetNumber() int {
- if ac == nil || ac.number == nil {
- return 0
- }
- return *ac.number
-}
-
-func (ac *ActionConfig) GetLabelPattern() string {
- if ac == nil || ac.labelPattern == nil {
- return ""
- }
- return *ac.labelPattern
-}
-
-func (ac *ActionConfig) GetLabelMissing() string {
- if ac == nil || ac.labelMissing == nil {
- return ""
- }
- return *ac.labelMissing
-}
-
-func (ac *ActionConfig) GetEnableLabelMissing() bool {
- if ac == nil || ac.enableLabelMissing == nil {
- return false
- }
- return *ac.enableLabelMissing
-}
-
-func (ac *ActionConfig) GetEnableLabelMultiple() bool {
- if ac == nil || ac.enableLabelMultiple == nil {
- return false
- }
- return *ac.enableLabelMultiple
-}
-
-type Action struct {
- config *ActionConfig
-
- globalContext context.Context
- client *github.Client
-
- // opened, edited, labeled, unlabeled
- event string
-}
-
-func NewAction(ac *ActionConfig) *Action {
- ctx := context.Background()
- ts := oauth2.StaticTokenSource(
- &oauth2.Token{AccessToken: ac.GetToken()},
- )
-
- tc := oauth2.NewClient(ctx, ts)
-
- return &Action{
- config: ac,
- globalContext: ctx,
- client: github.NewClient(tc),
- }
-}
-
-func (a *Action) Run(actionType string) error {
- a.event = actionType
- switch actionType {
- case "opened", "edited":
- return a.onPullRequestOpenedOrEdited()
- case "labeled", "unlabeled":
- return a.onPullRequestLabeledOrUnlabeled()
- }
- return nil
-}
-
-func (a *Action) onPullRequestOpenedOrEdited() error {
- pr, _, err := a.client.PullRequests.Get(a.globalContext, a.config.GetOwner(), a.config.GetRepo(), a.config.GetNumber())
- if err != nil {
- return fmt.Errorf("get PR: %v", err)
- }
-
- // Get repo labels
- logger.Infoln("@List repo labels")
- repoLabels, err := a.getRepoLabels()
- if err != nil {
- return fmt.Errorf("list repo labels: %v", err)
- }
- logger.Infof("Repo labels: %v\n", a.labelsToString(repoLabels))
-
- repoLabelsSet := make(map[string]struct{})
- for _, label := range repoLabels {
- repoLabelsSet[label.GetName()] = struct{}{}
- }
-
- // Get current labels on this PR
- logger.Infoln("@List issue labels")
- issueLabels, err := a.getIssueLabels()
- if err != nil {
- return fmt.Errorf("list current issue labels: %v", err)
- }
- logger.Infof("Issue labels: %v\n", a.labelsToString(issueLabels))
-
- // Get the intersection of issueLabels and labelWatchSet, including labelMissing
- logger.Infoln("@List current labels")
- currentLabelsSet := make(map[string]struct{})
- for _, label := range issueLabels {
- if _, exist := a.config.labelWatchSet[label.GetName()]; !exist && label.GetName() != a.config.GetLabelMissing() {
- continue
- }
- currentLabelsSet[label.GetName()] = struct{}{}
- }
- logger.Infof("Current labels: %v\n", a.labelsSetToString(currentLabelsSet))
-
- // Get expected labels
- // Only handle labels already exist in repo
- logger.Infoln("@List expected labels")
- expectedLabelsMap := make(map[string]bool)
- for label, checked := range a.config.labels {
- if _, exist := repoLabelsSet[label]; !exist {
- logger.Infof("Found label %v not exist int repo\n", label)
- continue
- }
- expectedLabelsMap[label] = checked
- }
- logger.Infof("Expected labels: %v\n", expectedLabelsMap)
-
- // Remove labels
- logger.Infoln("@Remove labels")
- labelsToRemove := make(map[string]struct{})
- if len(expectedLabelsMap) == 0 { // Remove current labels when PR body is empty
- for l := range a.config.labelWatchSet {
- if _, exist := currentLabelsSet[l]; exist {
- labelsToRemove[l] = struct{}{}
- }
- }
- } else {
- for label := range currentLabelsSet {
- if label == a.config.GetLabelMissing() {
- continue
- }
- if checked, exist := expectedLabelsMap[label]; exist && checked {
- continue
- }
- labelsToRemove[label] = struct{}{}
- }
- }
-
- // Remove missing label
- checkedCount := 0
- for _, checked := range expectedLabelsMap {
- if checked {
- checkedCount++
- }
- }
-
- if !a.config.GetEnableLabelMultiple() && checkedCount > 1 {
- logger.Infoln("Multiple labels detected")
- _, _, err = a.client.Issues.CreateComment(a.globalContext,
- a.config.GetOwner(), a.config.GetRepo(), a.config.GetNumber(),
- &github.IssueComment{
- Body: func(v string) *string { return &v }(fmt.Sprintf("@%s %s", pr.User.GetLogin(), MessageLabelMultiple))})
- if err != nil {
- return fmt.Errorf("create issue comment: %v", err)
- }
- return fmt.Errorf("%s", MessageLabelMultiple)
- }
-
- if _, exist := currentLabelsSet[a.config.GetLabelMissing()]; exist && checkedCount > 0 {
- labelsToRemove[a.config.GetLabelMissing()] = struct{}{}
- }
-
- logger.Infof("Labels to remove: %v\n", a.labelsSetToString(labelsToRemove))
-
- for label := range labelsToRemove {
- _, err := a.client.Issues.RemoveLabelForIssue(a.globalContext, a.config.GetOwner(), a.config.GetRepo(), a.config.GetNumber(), label)
- if err != nil {
- return fmt.Errorf("remove label %v: %v", label, err)
- }
- }
-
- // Add labels
- logger.Infoln("@Add labels")
-
- labelsToAdd := []string{}
- for label, checked := range expectedLabelsMap {
- if !checked {
- continue
- }
- if _, exist := currentLabelsSet[label]; !exist {
- labelsToAdd = append(labelsToAdd, label)
- }
- }
-
- if len(labelsToAdd) == 0 {
- logger.Infoln("No labels to add.")
- } else {
- logger.Infof("Labels to add: %v\n", labelsToAdd)
-
- _, _, err = a.client.Issues.AddLabelsToIssue(a.globalContext, a.config.GetOwner(), a.config.GetRepo(), a.config.GetNumber(), labelsToAdd)
- if err != nil {
- logger.Infof("Add labels %v: %v\n", labelsToAdd, err)
- }
- }
-
- // Add missing label
- if a.config.GetEnableLabelMissing() && checkedCount == 0 {
- if _, exist := currentLabelsSet[a.config.GetLabelMissing()]; exist {
- logger.Infoln("Already added missing label.")
- return fmt.Errorf("%s", MessageLabelMissing)
- }
-
- logger.Infoln("@Add missing label")
- _, _, err = a.client.Issues.AddLabelsToIssue(a.globalContext,
- a.config.GetOwner(), a.config.GetRepo(), a.config.GetNumber(),
- []string{a.config.GetLabelMissing()})
- if err != nil {
- return fmt.Errorf("add missing label %v: %v", a.config.GetLabelMissing(), err)
- }
-
- _, _, err = a.client.Issues.CreateComment(a.globalContext,
- a.config.GetOwner(), a.config.GetRepo(), a.config.GetNumber(),
- &github.IssueComment{
- Body: func(v string) *string { return &v }(fmt.Sprintf("@%s %s", pr.User.GetLogin(), MessageLabelMissing))})
- if err != nil {
- logger.Infof("Create issue comment: %v\n", err)
- }
-
- return fmt.Errorf("%s", MessageLabelMissing)
- }
-
- return nil
-}
-
-func (a *Action) onPullRequestLabeledOrUnlabeled() error {
- pr, _, err := a.client.PullRequests.Get(a.globalContext, a.config.GetOwner(), a.config.GetRepo(), a.config.GetNumber())
- if err != nil {
- return fmt.Errorf("get PR: %v", err)
- }
-
- // Get repo labels
- logger.Infoln("@List repo labels")
- repoLabels, err := a.getRepoLabels()
- if err != nil {
- return fmt.Errorf("list repo labels: %v", err)
- }
- logger.Infof("Repo labels: %v\n", a.labelsToString(repoLabels))
-
- repoLabelsSet := make(map[string]struct{})
- for _, label := range repoLabels {
- repoLabelsSet[label.GetName()] = struct{}{}
- }
-
- // Get current labels on this PR
- logger.Infoln("@List issue labels")
- issueLabels, err := a.getIssueLabels()
- if err != nil {
- return fmt.Errorf("list current issue labels: %v", err)
- }
- logger.Infof("Issue labels: %v\n", a.labelsToString(issueLabels))
-
- // Get the intersection of issueLabels and labelWatchSet, including labelMissing
- logger.Infoln("@List current labels")
- currentLabelsSet := make(map[string]struct{})
- for _, label := range issueLabels {
- if _, exist := a.config.labelWatchSet[label.GetName()]; !exist && label.GetName() != a.config.GetLabelMissing() {
- continue
- }
- currentLabelsSet[label.GetName()] = struct{}{}
- }
- logger.Infof("Current labels: %v\n", a.labelsSetToString(currentLabelsSet))
-
- // Get expected labels
- // Only handle labels already exist in repo
- logger.Infoln("@List expected labels")
- expectedLabelsMap := make(map[string]bool)
- for label, checked := range a.config.labels {
- if _, exist := repoLabelsSet[label]; !exist {
- logger.Infof("Found label %v not exist int repo\n", label)
- continue
- }
- expectedLabelsMap[label] = checked
- }
- logger.Infof("Expected labels: %v\n", expectedLabelsMap)
-
- // Remove missing label
- labelsToRemove := make(map[string]struct{})
- checkedCount := 0
- for label := range currentLabelsSet {
- if label != a.config.GetLabelMissing() {
- checkedCount++
- }
- }
-
- if !a.config.GetEnableLabelMultiple() && checkedCount > 1 {
- logger.Infoln("Multiple labels detected")
- _, _, err = a.client.Issues.CreateComment(a.globalContext,
- a.config.GetOwner(), a.config.GetRepo(), a.config.GetNumber(),
- &github.IssueComment{
- Body: func(v string) *string { return &v }(fmt.Sprintf("@%s %s", pr.User.GetLogin(), MessageLabelMultiple))})
- if err != nil {
- return fmt.Errorf("create issue comment: %v", err)
- }
- return fmt.Errorf("%s", MessageLabelMultiple)
- }
-
- if _, exist := currentLabelsSet[a.config.GetLabelMissing()]; exist && checkedCount > 0 {
- labelsToRemove[a.config.GetLabelMissing()] = struct{}{}
- }
-
- logger.Infof("Labels to remove: %v\n", labelsToRemove)
-
- for label := range labelsToRemove {
- _, err := a.client.Issues.RemoveLabelForIssue(a.globalContext, a.config.GetOwner(), a.config.GetRepo(), a.config.GetNumber(), label)
- if err != nil {
- return fmt.Errorf("remove label %v: %v", label, err)
- }
- }
-
- // Add missing label
- if a.config.GetEnableLabelMissing() && checkedCount == 0 {
- if _, exist := currentLabelsSet[a.config.GetLabelMissing()]; exist {
- logger.Infoln("Already added missing label.")
- return fmt.Errorf("%s", MessageLabelMissing)
- }
-
- logger.Infoln("@Add missing label")
- _, _, err = a.client.Issues.AddLabelsToIssue(a.globalContext,
- a.config.GetOwner(), a.config.GetRepo(), a.config.GetNumber(),
- []string{a.config.GetLabelMissing()})
- if err != nil {
- return fmt.Errorf("add missing label %v: %v", a.config.GetLabelMissing(), err)
- }
-
- _, _, err = a.client.Issues.CreateComment(a.globalContext,
- a.config.GetOwner(), a.config.GetRepo(), a.config.GetNumber(),
- &github.IssueComment{
- Body: func(v string) *string { return &v }(fmt.Sprintf("@%s %s", pr.User.GetLogin(), MessageLabelMissing))})
- if err != nil {
- logger.Infof("Create issue comment: %v\n", err)
- }
-
- return fmt.Errorf("%s", MessageLabelMissing)
- }
-
- // Update PR Body
- // Compare current labels and expected labels
- if a.event == "unlabeled" {
- return nil
- }
-
- changeList := make(map[string]bool)
- for label := range currentLabelsSet {
- if checked, exist := expectedLabelsMap[label]; exist && checked {
- continue
- }
-
- // If not exist, need to add
-
- // If exist but not checked, need to update
-
- changeList[label] = true
- }
-
- for label, checked := range expectedLabelsMap {
- if _, exist := currentLabelsSet[label]; !exist && checked {
- changeList[label] = false
- }
- }
-
- body := pr.GetBody()
- for label, checked := range changeList {
- src := fmt.Sprintf("- [ ] `%s`", label)
- dst := fmt.Sprintf("- [x] `%s`", label)
- if !checked {
- src = fmt.Sprintf("- [x] `%s`", label)
- dst = fmt.Sprintf("- [ ] `%s`", label)
- }
-
- if strings.Contains(body, src) { // Update the label
- body = strings.Replace(body, src, dst, 1)
- } else { // Add the label
- body = fmt.Sprintf("%s\r\n%s\r\n", body, dst)
- }
- }
-
- if len(changeList) > 0 {
- logger.Infoln("@Update PR body")
- logger.Infof("ChangeList: %v\n", changeList)
-
- _, _, err = a.client.PullRequests.Edit(a.globalContext, a.config.GetOwner(), a.config.GetRepo(), a.config.GetNumber(),
- &github.PullRequest{Body: &body})
- if err != nil {
- return fmt.Errorf("edit PR: %v", err)
- }
- }
-
- return nil
-}
-
-func (a *Action) extractLabels(prBody string) map[string]bool {
- r := regexp.MustCompile(a.config.GetLabelPattern())
- targets := r.FindAllStringSubmatch(prBody, -1)
- labels := make(map[string]bool)
-
- //// Init labels from watch list
- //for label := range a.config.labelWatchSet {
- // labels[label] = false
- //}
-
- for _, v := range targets {
- checked := strings.ToLower(strings.TrimSpace(v[1])) == "x"
- name := strings.TrimSpace(v[2])
-
- // Filter uninterested labels
- if _, exist := a.config.labelWatchSet[name]; !exist {
- continue
- }
-
- labels[name] = checked
- }
-
- return labels
-}
-
-func (a *Action) getRepoLabels() ([]*github.Label, error) {
- ctx := context.Background()
- listOptions := &github.ListOptions{PerPage: 100}
- repoLabels := make([]*github.Label, 0)
- for {
- rLabels, resp, err := a.client.Issues.ListLabels(ctx, a.config.GetOwner(), a.config.GetRepo(), listOptions)
- if err != nil {
- return nil, err
- }
- repoLabels = append(repoLabels, rLabels...)
- if resp.NextPage == 0 {
- break
- }
- listOptions.Page = resp.NextPage
- }
- return repoLabels, nil
-}
-
-func (a *Action) getIssueLabels() ([]*github.Label, error) {
- ctx := context.Background()
- listOptions := &github.ListOptions{PerPage: 100}
- issueLabels := make([]*github.Label, 0)
- for {
- iLabels, resp, err := a.client.Issues.ListLabelsByIssue(ctx, a.config.GetOwner(), a.config.GetRepo(), a.config.GetNumber(), listOptions)
- if err != nil {
- return nil, err
- }
- issueLabels = append(issueLabels, iLabels...)
- if resp.NextPage == 0 {
- break
- }
- listOptions.Page = resp.NextPage
- }
- return issueLabels, nil
-}
-
-func (a *Action) labelsToString(labels []*github.Label) []string {
- result := []string{}
- for _, label := range labels {
- result = append(result, label.GetName())
- }
- return result
-}
-
-func (a *Action) labelsSetToString(labels map[string]struct{}) []string {
- result := []string{}
- for label := range labels {
- result = append(result, label)
- }
- return result
-}
-
func main() {
logger.Infoln("@Start docbot")
@@ -597,8 +21,6 @@ func main() {
}
switch githubContext.EventName {
- case "issues":
- logger.Infoln("@EventName is issues")
case "pull_request", "pull_request_target":
logger.Infoln("@EventName is PR")
@@ -607,26 +29,8 @@ func main() {
logger.Fatalln("Action type is not string")
}
- pr := githubContext.Event["pull_request"]
- pullRequest, ok := pr.(map[string]interface{})
- if !ok {
- logger.Fatalln("PR event is not map")
- }
-
number := int(githubContext.Event["number"].(float64))
-
- prBody, ok := pullRequest["body"].(string)
- if !ok {
- logger.Fatalln("PR body is not string")
- }
-
- // Get expected labels
- labels := action.extractLabels(prBody)
-
- actionConfig.number = &number
- actionConfig.labels = labels
-
- if err := action.Run(actionType); err != nil {
+ if err := action.Run(number, actionType); err != nil {
logger.Fatalln(err)
}
}