Skip to content

Commit 9711438

Browse files
committed
github-bots/sdk: add function to set comments on GitHub Issues
This also moves the main functionality of the existing setcomments for a PR, into a helper which is reused by both the set comments for PR and Issue. Issues don't always have the repository set on the main object, so also have a helper function to get the owner + repo name from the boject if set, else fall back to getting it from the repository URL. Added test cases for this too. Signed-off-by: rawlingsj <[email protected]>
1 parent 190ee73 commit 9711438

File tree

4 files changed

+112
-5
lines changed

4 files changed

+112
-5
lines changed

go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ require (
2626
github.com/sethvargo/go-envconfig v1.1.1
2727
github.com/shirou/gopsutil/v4 v4.25.2
2828
github.com/snabb/httpreaderat v1.0.1
29+
github.com/stretchr/testify v1.10.0
2930
go.opentelemetry.io/contrib/detectors/gcp v1.35.0
3031
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0
3132
go.opentelemetry.io/otel v1.35.0
@@ -64,6 +65,7 @@ require (
6465
github.com/cloudflare/circl v1.6.0 // indirect
6566
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect
6667
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
68+
github.com/davecgh/go-spew v1.1.1 // indirect
6769
github.com/ebitengine/purego v0.8.2 // indirect
6870
github.com/emirpasic/gods v1.18.1 // indirect
6971
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
@@ -104,6 +106,7 @@ require (
104106
github.com/pjbgf/sha1cd v0.3.2 // indirect
105107
github.com/pkg/errors v0.9.1 // indirect
106108
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
109+
github.com/pmezard/go-difflib v1.0.0 // indirect
107110
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
108111
github.com/prometheus/client_model v0.6.1 // indirect
109112
github.com/prometheus/common v0.62.0 // indirect
@@ -133,4 +136,5 @@ require (
133136
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
134137
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
135138
gopkg.in/warnings.v0 v0.1.2 // indirect
139+
gopkg.in/yaml.v3 v3.0.1 // indirect
136140
)

modules/github-bots/sdk/github.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -279,32 +279,48 @@ func (c GitHubClient) RemoveLabel(ctx context.Context, pr *github.PullRequest, l
279279
return nil
280280
}
281281

282-
// SetComment adds or replaces a bot comment on the given pull request.
283-
func (c GitHubClient) SetComment(ctx context.Context, pr *github.PullRequest, botName, content string) error {
284-
cs, _, err := c.inner.Issues.ListComments(ctx, *pr.Base.Repo.Owner.Login, *pr.Base.Repo.Name, *pr.Number, nil)
282+
// setCommentHelper is a helper function that adds or replaces a bot comment on a GitHub issue or PR.
283+
// It's used by both SetComment and SetIssueComment to avoid code duplication.
284+
func (c GitHubClient) setCommentHelper(ctx context.Context, owner, repo string, number int, botName, content string) error {
285+
cs, _, err := c.inner.Issues.ListComments(ctx, owner, repo, number, nil)
285286
if err != nil {
286287
return fmt.Errorf("listing comments: %w", err)
287288
}
288289
content = fmt.Sprintf("<!-- bot:%s -->\n\n%s", botName, content)
289290

290291
for _, com := range cs {
291292
if strings.Contains(*com.Body, fmt.Sprintf("<!-- bot:%s -->", botName)) {
292-
if _, resp, err := c.inner.Issues.EditComment(ctx, *pr.Base.Repo.Owner.Login, *pr.Base.Repo.Name, *com.ID, &github.IssueComment{
293+
if _, resp, err := c.inner.Issues.EditComment(ctx, owner, repo, *com.ID, &github.IssueComment{
293294
Body: &content,
294295
}); err != nil || resp.StatusCode != 200 {
295296
return validateResponse(ctx, err, resp, "editing comment")
296297
}
297298
return nil
298299
}
299300
}
300-
if _, resp, err := c.inner.Issues.CreateComment(ctx, *pr.Base.Repo.Owner.Login, *pr.Base.Repo.Name, *pr.Number, &github.IssueComment{
301+
if _, resp, err := c.inner.Issues.CreateComment(ctx, owner, repo, number, &github.IssueComment{
301302
Body: &content,
302303
}); err != nil || resp.StatusCode != 201 {
303304
return validateResponse(ctx, err, resp, "create comment")
304305
}
305306
return nil
306307
}
307308

309+
// SetComment adds or replaces a bot comment on the given pull request.
310+
func (c GitHubClient) SetComment(ctx context.Context, pr *github.PullRequest, botName, content string) error {
311+
return c.setCommentHelper(ctx, *pr.Base.Repo.Owner.Login, *pr.Base.Repo.Name, *pr.Number, botName, content)
312+
}
313+
314+
// SetIssueComment adds or replaces a bot comment on the given GitHub issue.
315+
func (c GitHubClient) SetIssueComment(ctx context.Context, issue *github.Issue, botName, content string) error {
316+
owner, repoName, err := getIssueRepoInfo(issue)
317+
if err != nil {
318+
return fmt.Errorf("getting repo info: %w", err)
319+
}
320+
321+
return c.setCommentHelper(ctx, owner, repoName, issue.GetNumber(), botName, content)
322+
}
323+
308324
// AddComment adds a new comment to the given pull request.
309325
func (c GitHubClient) AddComment(ctx context.Context, pr *github.PullRequest, botName, content string) error {
310326
content = fmt.Sprintf("<!-- bot:%s -->\n\n%s", botName, content)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package sdk
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/google/go-github/v68/github"
8+
)
9+
10+
// getIssueRepoInfo extracts owner and repository name from an issue
11+
// the repository object is not available on the issue, so we need to extract it from the URL
12+
func getIssueRepoInfo(issue *github.Issue) (owner, repoName string, err error) {
13+
14+
// If the issue has a repository object, we can get the owner and repo name from there
15+
if issue.Repository != nil {
16+
owner = issue.Repository.GetOwner().GetLogin()
17+
repoName = issue.Repository.GetName()
18+
return
19+
}
20+
21+
// If the repository object is not available, we need to extract it from the URL
22+
// Split the URL by "/"
23+
parts := strings.Split(issue.GetRepositoryURL(), "/")
24+
25+
// URLs should be in format https://api.github.com/repos/owner/repo
26+
// We need at least 2 parts after "repos" to get owner and repo
27+
if len(parts) >= 2 {
28+
// Get the last two parts
29+
repoName = parts[len(parts)-1]
30+
owner = parts[len(parts)-2]
31+
return
32+
}
33+
34+
// If we don't have at least 2 parts, return an error
35+
return "", "", fmt.Errorf("found %d parts in URL %s, expected at least 2", len(parts), issue.GetRepositoryURL())
36+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package sdk
2+
3+
import (
4+
"testing"
5+
6+
"github.com/google/go-github/v68/github"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func Test_RepoInfo(t *testing.T) {
11+
t.Run("extract from issue with URL", func(t *testing.T) {
12+
// Create an issue with a repository URL directly
13+
issue := &github.Issue{
14+
RepositoryURL: github.Ptr("https://api.github.com/repos/foo/bar"),
15+
}
16+
17+
org, repo, err := getIssueRepoInfo(issue)
18+
assert.Nil(t, err)
19+
assert.Equal(t, "foo", org)
20+
assert.Equal(t, "bar", repo)
21+
})
22+
23+
t.Run("extract from issue with Repository object", func(t *testing.T) {
24+
// Create an issue with Repository object
25+
owner := &github.User{Login: github.Ptr("test-owner")}
26+
repo := &github.Repository{
27+
Name: github.Ptr("test-repo"),
28+
Owner: owner,
29+
}
30+
issue := &github.Issue{
31+
Repository: repo,
32+
}
33+
34+
org, repoName, err := getIssueRepoInfo(issue)
35+
assert.Nil(t, err)
36+
assert.Equal(t, "test-owner", org)
37+
assert.Equal(t, "test-repo", repoName)
38+
})
39+
40+
t.Run("error case with malformed URL", func(t *testing.T) {
41+
// URL with fewer than 2 parts when split by "/"
42+
issue := &github.Issue{
43+
RepositoryURL: github.Ptr("no-slashes"),
44+
}
45+
46+
org, repo, err := getIssueRepoInfo(issue)
47+
assert.NotNil(t, err, "Expected an error but got nil")
48+
assert.Empty(t, org, "Expected empty owner string")
49+
assert.Empty(t, repo, "Expected empty repo string")
50+
})
51+
}

0 commit comments

Comments
 (0)