diff --git a/README.md b/README.md index d46077ff..662a4ded 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,7 @@ empty commit to the PR*. | `description` | No | `Concourse CI build failed` | The description status on the specified pull request. | | `description_file` | No | `my-output/description.txt` | Path to file containing the description status to add to the pull request | | `delete_previous_comments` | No | `true` | Boolean. Previous comments made on the pull request by this resource will be deleted before making the new comment. Useful for removing outdated information. | +| `delete_comment_tag` | No | `Summary` | String. A substring can be specified to match when deleting previous comments on a pull request, to avoid deleting all comments made by the same user. | Note that `comment`, `comment_file` and `target_url` will all expand environment variables, so in the examples above `$ATC_EXTERNAL_URL` will be replaced by the public URL of the Concourse ATCs. See https://concourse-ci.org/implementing-resource-types.html#resource-metadata for more details about metadata that is available via environment variables. diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index 523838c2..8cc9b7a8 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -611,6 +611,45 @@ func TestPutCommentsE2E(t *testing.T) { "new comment", }, }, + { + description: "do not delete old comments because tag doesn't match", + source: resource.Source{ + Repository: fmt.Sprintf("%s/%s", owner, repository), + V3Endpoint: "https://api.github.com/", + V4Endpoint: "https://api.github.com/graphql", + AccessToken: os.Getenv("GITHUB_ACCESS_TOKEN"), + }, + getParams: resource.GetParameters{}, + putParameters: resource.PutParameters{ + Comment: "new comment", + DeletePreviousComments: true, + DeleteCommentTag: "hello", + }, + previousComments: []string{"old comment"}, + expectedComments: []string{ + "old comment", + "new comment", + }, + }, + { + description: "delete previous comments because tag matches", + source: resource.Source{ + Repository: fmt.Sprintf("%s/%s", owner, repository), + V3Endpoint: "https://api.github.com/", + V4Endpoint: "https://api.github.com/graphql", + AccessToken: os.Getenv("GITHUB_ACCESS_TOKEN"), + }, + getParams: resource.GetParameters{}, + putParameters: resource.PutParameters{ + Comment: "new comment", + DeletePreviousComments: true, + DeleteCommentTag: "old", + }, + previousComments: []string{"old comment"}, + expectedComments: []string{ + "new comment", + }, + }, } for _, tc := range tests { diff --git a/fakes/fake_github.go b/fakes/fake_github.go index 1847478f..c344d05f 100644 --- a/fakes/fake_github.go +++ b/fakes/fake_github.go @@ -1,4 +1,4 @@ -// Code generated by counterfeiter. DO NOT EDIT. +gd// Code generated by counterfeiter. DO NOT EDIT. package fakes import ( @@ -9,10 +9,11 @@ import ( ) type FakeGithub struct { - DeletePreviousCommentsStub func(string) error + DeletePreviousCommentsStub func(string, string) error deletePreviousCommentsMutex sync.RWMutex deletePreviousCommentsArgsForCall []struct { arg1 string + arg2 string } deletePreviousCommentsReturns struct { result1 error @@ -106,16 +107,17 @@ type FakeGithub struct { invocationsMutex sync.RWMutex } -func (fake *FakeGithub) DeletePreviousComments(arg1 string) error { +func (fake *FakeGithub) DeletePreviousComments(arg1 string, arg2 string) error { fake.deletePreviousCommentsMutex.Lock() ret, specificReturn := fake.deletePreviousCommentsReturnsOnCall[len(fake.deletePreviousCommentsArgsForCall)] fake.deletePreviousCommentsArgsForCall = append(fake.deletePreviousCommentsArgsForCall, struct { arg1 string - }{arg1}) - fake.recordInvocation("DeletePreviousComments", []interface{}{arg1}) + arg2 string + }{arg1, arg2}) + fake.recordInvocation("DeletePreviousComments", []interface{}{arg1, arg2}) fake.deletePreviousCommentsMutex.Unlock() if fake.DeletePreviousCommentsStub != nil { - return fake.DeletePreviousCommentsStub(arg1) + return fake.DeletePreviousCommentsStub(arg1, arg2) } if specificReturn { return ret.result1 @@ -130,17 +132,17 @@ func (fake *FakeGithub) DeletePreviousCommentsCallCount() int { return len(fake.deletePreviousCommentsArgsForCall) } -func (fake *FakeGithub) DeletePreviousCommentsCalls(stub func(string) error) { +func (fake *FakeGithub) DeletePreviousCommentsCalls(stub func(string, string) error) { fake.deletePreviousCommentsMutex.Lock() defer fake.deletePreviousCommentsMutex.Unlock() fake.DeletePreviousCommentsStub = stub } -func (fake *FakeGithub) DeletePreviousCommentsArgsForCall(i int) string { +func (fake *FakeGithub) DeletePreviousCommentsArgsForCall(i int) (string, string) { fake.deletePreviousCommentsMutex.RLock() defer fake.deletePreviousCommentsMutex.RUnlock() argsForCall := fake.deletePreviousCommentsArgsForCall[i] - return argsForCall.arg1 + return argsForCall.arg1, argsForCall.arg2 } func (fake *FakeGithub) DeletePreviousCommentsReturns(result1 error) { diff --git a/github.go b/github.go index ab10cbdc..a4cee096 100644 --- a/github.go +++ b/github.go @@ -18,6 +18,7 @@ import ( ) // Github for testing purposes. +// //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -o fakes/fake_github.go . Github type Github interface { ListPullRequests([]githubv4.PullRequestState) ([]*PullRequest, error) @@ -26,7 +27,7 @@ type Github interface { GetPullRequest(string, string) (*PullRequest, error) GetChangedFiles(string, string) ([]ChangedFileObject, error) UpdateCommitStatus(string, string, string, string, string, string) error - DeletePreviousComments(string) error + DeletePreviousComments(string, string) error } // GithubClient for handling requests to the Github V3 and V4 APIs. @@ -356,7 +357,7 @@ func (m *GithubClient) UpdateCommitStatus(commitRef, baseContext, statusContext, return err } -func (m *GithubClient) DeletePreviousComments(prNumber string) error { +func (m *GithubClient) DeletePreviousComments(prNumber, commentTag string) error { pr, err := strconv.Atoi(prNumber) if err != nil { return fmt.Errorf("failed to convert pull request number to int: %s", err) @@ -376,6 +377,7 @@ func (m *GithubClient) DeletePreviousComments(prNumber string) error { Author struct { Login string } + BodyText string } } } `graphql:"comments(last:$commentsLast)"` @@ -396,9 +398,18 @@ func (m *GithubClient) DeletePreviousComments(prNumber string) error { for _, e := range getComments.Repository.PullRequest.Comments.Edges { if e.Node.Author.Login == getComments.Viewer.Login { - _, err := m.V3.Issues.DeleteComment(context.TODO(), m.Owner, m.Repository, e.Node.DatabaseId) - if err != nil { - return err + if commentTag != "" { + if strings.Contains(e.Node.BodyText, commentTag) { + _, err := m.V3.Issues.DeleteComment(context.TODO(), m.Owner, m.Repository, e.Node.DatabaseId) + if err != nil { + return err + } + } + } else { + _, err := m.V3.Issues.DeleteComment(context.TODO(), m.Owner, m.Repository, e.Node.DatabaseId) + if err != nil { + return err + } } } } diff --git a/out.go b/out.go index cd6245c9..0d979bbe 100644 --- a/out.go +++ b/out.go @@ -56,7 +56,7 @@ func Put(request PutRequest, manager Github, inputDir string) (*PutResponse, err // Delete previous comments if specified if request.Params.DeletePreviousComments { - err = manager.DeletePreviousComments(version.PR) + err = manager.DeletePreviousComments(version.PR, request.Params.DeleteCommentTag) if err != nil { return nil, fmt.Errorf("failed to delete previous comments: %s", err) } @@ -115,6 +115,7 @@ type PutParameters struct { CommentFile string `json:"comment_file"` Comment string `json:"comment"` DeletePreviousComments bool `json:"delete_previous_comments"` + DeleteCommentTag string `json:"delete_comment_tag"` } // Validate the put parameters. diff --git a/out_test.go b/out_test.go index 4d430c7b..a638d166 100644 --- a/out_test.go +++ b/out_test.go @@ -210,7 +210,7 @@ func TestPut(t *testing.T) { if tc.parameters.DeletePreviousComments { if assert.Equal(t, 1, github.DeletePreviousCommentsCallCount()) { - pr := github.DeletePreviousCommentsArgsForCall(0) + pr, _ := github.DeletePreviousCommentsArgsForCall(0) assert.Equal(t, tc.version.PR, pr) } }