Skip to content

Commit 38cb0c4

Browse files
authored
fix(pull_request_reviews): Update pending pull request review lookup to work when the authenticated actor is a Copilot bot (#3355)
1 parent 85b0399 commit 38cb0c4

3 files changed

Lines changed: 278 additions & 289 deletions

File tree

‎pkg/github/granular_tools_test.go‎

Lines changed: 8 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1665,49 +1665,15 @@ func TestGranularUpdatePullRequestDraftState(t *testing.T) {
16651665

16661666
func TestGranularAddPullRequestReviewComment(t *testing.T) {
16671667
mockedClient := githubv4mock.NewMockedHTTPClient(
1668-
githubv4mock.NewQueryMatcher(
1669-
struct {
1670-
Viewer struct {
1671-
Login githubv4.String
1672-
}
1673-
}{},
1674-
nil,
1675-
githubv4mock.DataResponse(map[string]any{
1676-
"viewer": map[string]any{"login": "testuser"},
1677-
}),
1678-
),
1679-
githubv4mock.NewQueryMatcher(
1680-
struct {
1681-
Repository struct {
1682-
PullRequest struct {
1683-
Reviews struct {
1684-
Nodes []struct {
1685-
ID githubv4.ID
1686-
State githubv4.PullRequestReviewState
1687-
URL githubv4.URI
1688-
}
1689-
} `graphql:"reviews(first: 1, author: $author)"`
1690-
} `graphql:"pullRequest(number: $prNum)"`
1691-
} `graphql:"repository(owner: $owner, name: $name)"`
1692-
}{},
1693-
map[string]any{
1694-
"author": githubv4.String("testuser"),
1695-
"owner": githubv4.String("owner"),
1696-
"name": githubv4.String("repo"),
1697-
"prNum": githubv4.Int(1),
1668+
viewerIDQuery("U_testuser"),
1669+
getPendingReviewsQuery(getPendingReviewsQueryParams{
1670+
owner: "owner",
1671+
repo: "repo",
1672+
prNum: 1,
1673+
reviews: []pendingReviewQueryReview{
1674+
{id: "PRR_123", authorID: "U_testuser"},
16981675
},
1699-
githubv4mock.DataResponse(map[string]any{
1700-
"repository": map[string]any{
1701-
"pullRequest": map[string]any{
1702-
"reviews": map[string]any{
1703-
"nodes": []map[string]any{
1704-
{"id": "PRR_123", "state": "PENDING", "url": "https://github.com/owner/repo/pull/1#pullrequestreview-123"},
1705-
},
1706-
},
1707-
},
1708-
},
1709-
}),
1710-
),
1676+
}),
17111677
githubv4mock.NewMutationMatcher(
17121678
struct {
17131679
AddPullRequestReviewThread struct {

‎pkg/github/pullrequests.go‎

Lines changed: 106 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -1989,57 +1989,9 @@ func CreatePullRequestReview(ctx context.Context, client *githubv4.Client, param
19891989
}
19901990

19911991
func SubmitPendingPullRequestReview(ctx context.Context, client *githubv4.Client, params PullRequestReviewWriteParams) (*mcp.CallToolResult, error) {
1992-
// First we'll get the current user
1993-
var getViewerQuery struct {
1994-
Viewer struct {
1995-
Login githubv4.String
1996-
}
1997-
}
1998-
1999-
if err := client.Query(ctx, &getViewerQuery, nil); err != nil {
2000-
return ghErrors.NewGitHubGraphQLErrorResponse(ctx,
2001-
"failed to get current user",
2002-
err,
2003-
), nil
2004-
}
2005-
2006-
var getLatestReviewForViewerQuery struct {
2007-
Repository struct {
2008-
PullRequest struct {
2009-
Reviews struct {
2010-
Nodes []struct {
2011-
ID githubv4.ID
2012-
State githubv4.PullRequestReviewState
2013-
URL githubv4.URI
2014-
}
2015-
} `graphql:"reviews(first: 1, author: $author)"`
2016-
} `graphql:"pullRequest(number: $prNum)"`
2017-
} `graphql:"repository(owner: $owner, name: $name)"`
2018-
}
2019-
2020-
vars := map[string]any{
2021-
"author": githubv4.String(getViewerQuery.Viewer.Login),
2022-
"owner": githubv4.String(params.Owner),
2023-
"name": githubv4.String(params.Repo),
2024-
"prNum": githubv4.Int(params.PullNumber),
2025-
}
2026-
2027-
if err := client.Query(ctx, &getLatestReviewForViewerQuery, vars); err != nil {
2028-
return ghErrors.NewGitHubGraphQLErrorResponse(ctx,
2029-
"failed to get latest review for current user",
2030-
err,
2031-
), nil
2032-
}
2033-
2034-
// Validate there is one review and the state is pending
2035-
if len(getLatestReviewForViewerQuery.Repository.PullRequest.Reviews.Nodes) == 0 {
2036-
return utils.NewToolResultError("No pending review found for the viewer"), nil
2037-
}
2038-
2039-
review := getLatestReviewForViewerQuery.Repository.PullRequest.Reviews.Nodes[0]
2040-
if review.State != githubv4.PullRequestReviewStatePending {
2041-
errText := fmt.Sprintf("The latest review, found at %s is not pending", review.URL)
2042-
return utils.NewToolResultError(errText), nil
1992+
review, result := getPendingPullRequestReviewForViewer(ctx, client, params.Owner, params.Repo, params.PullNumber)
1993+
if result != nil {
1994+
return result, nil
20431995
}
20441996

20451997
// Prepare the mutation
@@ -2055,7 +2007,7 @@ func SubmitPendingPullRequestReview(ctx context.Context, client *githubv4.Client
20552007
ctx,
20562008
&submitPullRequestReviewMutation,
20572009
githubv4.SubmitPullRequestReviewInput{
2058-
PullRequestReviewID: &review.ID,
2010+
PullRequestReviewID: review,
20592011
Event: githubv4.PullRequestReviewEvent(params.Event),
20602012
Body: newGQLStringlikePtr[githubv4.String](&params.Body),
20612013
},
@@ -2074,57 +2026,9 @@ func SubmitPendingPullRequestReview(ctx context.Context, client *githubv4.Client
20742026
}
20752027

20762028
func DeletePendingPullRequestReview(ctx context.Context, client *githubv4.Client, params PullRequestReviewWriteParams) (*mcp.CallToolResult, error) {
2077-
// First we'll get the current user
2078-
var getViewerQuery struct {
2079-
Viewer struct {
2080-
Login githubv4.String
2081-
}
2082-
}
2083-
2084-
if err := client.Query(ctx, &getViewerQuery, nil); err != nil {
2085-
return ghErrors.NewGitHubGraphQLErrorResponse(ctx,
2086-
"failed to get current user",
2087-
err,
2088-
), nil
2089-
}
2090-
2091-
var getLatestReviewForViewerQuery struct {
2092-
Repository struct {
2093-
PullRequest struct {
2094-
Reviews struct {
2095-
Nodes []struct {
2096-
ID githubv4.ID
2097-
State githubv4.PullRequestReviewState
2098-
URL githubv4.URI
2099-
}
2100-
} `graphql:"reviews(first: 1, author: $author)"`
2101-
} `graphql:"pullRequest(number: $prNum)"`
2102-
} `graphql:"repository(owner: $owner, name: $name)"`
2103-
}
2104-
2105-
vars := map[string]any{
2106-
"author": githubv4.String(getViewerQuery.Viewer.Login),
2107-
"owner": githubv4.String(params.Owner),
2108-
"name": githubv4.String(params.Repo),
2109-
"prNum": githubv4.Int(params.PullNumber),
2110-
}
2111-
2112-
if err := client.Query(ctx, &getLatestReviewForViewerQuery, vars); err != nil {
2113-
return ghErrors.NewGitHubGraphQLErrorResponse(ctx,
2114-
"failed to get latest review for current user",
2115-
err,
2116-
), nil
2117-
}
2118-
2119-
// Validate there is one review and the state is pending
2120-
if len(getLatestReviewForViewerQuery.Repository.PullRequest.Reviews.Nodes) == 0 {
2121-
return utils.NewToolResultError("No pending review found for the viewer"), nil
2122-
}
2123-
2124-
review := getLatestReviewForViewerQuery.Repository.PullRequest.Reviews.Nodes[0]
2125-
if review.State != githubv4.PullRequestReviewStatePending {
2126-
errText := fmt.Sprintf("The latest review, found at %s is not pending", review.URL)
2127-
return utils.NewToolResultError(errText), nil
2029+
review, result := getPendingPullRequestReviewForViewer(ctx, client, params.Owner, params.Repo, params.PullNumber)
2030+
if result != nil {
2031+
return result, nil
21282032
}
21292033

21302034
// Prepare the mutation
@@ -2140,7 +2044,7 @@ func DeletePendingPullRequestReview(ctx context.Context, client *githubv4.Client
21402044
ctx,
21412045
&deletePullRequestReviewMutation,
21422046
githubv4.DeletePullRequestReviewInput{
2143-
PullRequestReviewID: &review.ID,
2047+
PullRequestReviewID: review,
21442048
},
21452049
nil,
21462050
); err != nil {
@@ -2235,57 +2139,9 @@ type AddCommentToPendingReviewParams struct {
22352139

22362140
// AddCommentToPendingReviewCall adds a review comment to the viewer's pending pull request review.
22372141
func AddCommentToPendingReviewCall(ctx context.Context, client *githubv4.Client, params AddCommentToPendingReviewParams) (*mcp.CallToolResult, error) {
2238-
// Get the current user
2239-
var getViewerQuery struct {
2240-
Viewer struct {
2241-
Login githubv4.String
2242-
}
2243-
}
2244-
2245-
if err := client.Query(ctx, &getViewerQuery, nil); err != nil {
2246-
return ghErrors.NewGitHubGraphQLErrorResponse(ctx,
2247-
"failed to get current user",
2248-
err,
2249-
), nil
2250-
}
2251-
2252-
var getLatestReviewForViewerQuery struct {
2253-
Repository struct {
2254-
PullRequest struct {
2255-
Reviews struct {
2256-
Nodes []struct {
2257-
ID githubv4.ID
2258-
State githubv4.PullRequestReviewState
2259-
URL githubv4.URI
2260-
}
2261-
} `graphql:"reviews(first: 1, author: $author)"`
2262-
} `graphql:"pullRequest(number: $prNum)"`
2263-
} `graphql:"repository(owner: $owner, name: $name)"`
2264-
}
2265-
2266-
vars := map[string]any{
2267-
"author": githubv4.String(getViewerQuery.Viewer.Login),
2268-
"owner": githubv4.String(params.Owner),
2269-
"name": githubv4.String(params.Repo),
2270-
"prNum": githubv4.Int(params.PullNumber),
2271-
}
2272-
2273-
if err := client.Query(ctx, &getLatestReviewForViewerQuery, vars); err != nil {
2274-
return ghErrors.NewGitHubGraphQLErrorResponse(ctx,
2275-
"failed to get latest review for current user",
2276-
err,
2277-
), nil
2278-
}
2279-
2280-
// Validate there is one review and the state is pending
2281-
if len(getLatestReviewForViewerQuery.Repository.PullRequest.Reviews.Nodes) == 0 {
2282-
return utils.NewToolResultError("No pending review found for the viewer"), nil
2283-
}
2284-
2285-
review := getLatestReviewForViewerQuery.Repository.PullRequest.Reviews.Nodes[0]
2286-
if review.State != githubv4.PullRequestReviewStatePending {
2287-
errText := fmt.Sprintf("The latest review, found at %s is not pending", review.URL)
2288-
return utils.NewToolResultError(errText), nil
2142+
review, result := getPendingPullRequestReviewForViewer(ctx, client, params.Owner, params.Repo, params.PullNumber)
2143+
if result != nil {
2144+
return result, nil
22892145
}
22902146

22912147
// Create a new review thread comment on the review.
@@ -2308,7 +2164,7 @@ func AddCommentToPendingReviewCall(ctx context.Context, client *githubv4.Client,
23082164
Side: newGQLStringlikePtr[githubv4.DiffSide](params.Side),
23092165
StartLine: newGQLIntPtr(params.StartLine),
23102166
StartSide: newGQLStringlikePtr[githubv4.DiffSide](params.StartSide),
2311-
PullRequestReviewID: &review.ID,
2167+
PullRequestReviewID: review,
23122168
},
23132169
nil,
23142170
); err != nil {
@@ -2326,6 +2182,100 @@ func AddCommentToPendingReviewCall(ctx context.Context, client *githubv4.Client,
23262182
return utils.NewToolResultText("pull request review comment successfully added to pending review"), nil
23272183
}
23282184

2185+
type pendingReviewAuthor struct {
2186+
Bot struct {
2187+
ID githubv4.ID `graphql:"botId: id"`
2188+
} `graphql:"... on Bot"`
2189+
EnterpriseUserAccount struct {
2190+
ID githubv4.ID `graphql:"enterpriseUserAccountId: id"`
2191+
} `graphql:"... on EnterpriseUserAccount"`
2192+
Mannequin struct {
2193+
ID githubv4.ID `graphql:"mannequinId: id"`
2194+
} `graphql:"... on Mannequin"`
2195+
Organization struct {
2196+
ID githubv4.ID `graphql:"organizationId: id"`
2197+
} `graphql:"... on Organization"`
2198+
User struct {
2199+
ID githubv4.ID `graphql:"userId: id"`
2200+
} `graphql:"... on User"`
2201+
}
2202+
2203+
func (a pendingReviewAuthor) id() githubv4.ID {
2204+
for _, id := range []githubv4.ID{
2205+
a.Bot.ID,
2206+
a.EnterpriseUserAccount.ID,
2207+
a.Mannequin.ID,
2208+
a.Organization.ID,
2209+
a.User.ID,
2210+
} {
2211+
if id != nil {
2212+
return id
2213+
}
2214+
}
2215+
return nil
2216+
}
2217+
2218+
func getPendingPullRequestReviewForViewer(ctx context.Context, client *githubv4.Client, owner, repo string, pullNumber int32) (*githubv4.ID, *mcp.CallToolResult) {
2219+
var getViewerQuery struct {
2220+
Viewer struct {
2221+
ID githubv4.ID
2222+
}
2223+
}
2224+
2225+
if err := client.Query(ctx, &getViewerQuery, nil); err != nil {
2226+
return nil, ghErrors.NewGitHubGraphQLErrorResponse(ctx,
2227+
"failed to get current user",
2228+
err,
2229+
)
2230+
}
2231+
2232+
vars := map[string]any{
2233+
"after": (*githubv4.String)(nil),
2234+
"owner": githubv4.String(owner),
2235+
"name": githubv4.String(repo),
2236+
"prNum": githubv4.Int(pullNumber),
2237+
"states": []githubv4.PullRequestReviewState{githubv4.PullRequestReviewStatePending},
2238+
}
2239+
2240+
for {
2241+
var getPendingReviewsQuery struct {
2242+
Repository struct {
2243+
PullRequest struct {
2244+
Reviews struct {
2245+
Nodes []struct {
2246+
ID githubv4.ID
2247+
Author pendingReviewAuthor
2248+
}
2249+
PageInfo struct {
2250+
HasNextPage githubv4.Boolean
2251+
EndCursor githubv4.String
2252+
}
2253+
} `graphql:"reviews(first: 100, after: $after, states: $states)"`
2254+
} `graphql:"pullRequest(number: $prNum)"`
2255+
} `graphql:"repository(owner: $owner, name: $name)"`
2256+
}
2257+
2258+
if err := client.Query(ctx, &getPendingReviewsQuery, vars); err != nil {
2259+
return nil, ghErrors.NewGitHubGraphQLErrorResponse(ctx,
2260+
"failed to get pending pull request reviews",
2261+
err,
2262+
)
2263+
}
2264+
2265+
for _, review := range getPendingReviewsQuery.Repository.PullRequest.Reviews.Nodes {
2266+
if review.Author.id() == getViewerQuery.Viewer.ID {
2267+
reviewID := review.ID
2268+
return &reviewID, nil
2269+
}
2270+
}
2271+
2272+
if !getPendingReviewsQuery.Repository.PullRequest.Reviews.PageInfo.HasNextPage {
2273+
return nil, utils.NewToolResultError("No pending review found for the viewer")
2274+
}
2275+
vars["after"] = githubv4.NewString(getPendingReviewsQuery.Repository.PullRequest.Reviews.PageInfo.EndCursor)
2276+
}
2277+
}
2278+
23292279
// AddCommentToPendingReview creates a tool to add a comment to a pull request review.
23302280
func AddCommentToPendingReview(t translations.TranslationHelperFunc) inventory.ServerTool {
23312281
schema := &jsonschema.Schema{

0 commit comments

Comments
 (0)