Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions pkg/github/minimal_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,41 @@ func convertToMinimalCommit(commit *github.RepositoryCommit, includeDiffs bool)
return minimalCommit
}

// MinimalPageInfo contains pagination cursor information.
type MinimalPageInfo struct {
HasNextPage bool `json:"has_next_page"`
HasPreviousPage bool `json:"has_previous_page"`
StartCursor string `json:"start_cursor,omitempty"`
EndCursor string `json:"end_cursor,omitempty"`
}

// MinimalReviewComment is the trimmed output type for PR review comment objects.
type MinimalReviewComment struct {
Body string `json:"body,omitempty"`
Path string `json:"path"`
Line *int `json:"line,omitempty"`
Author string `json:"author,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
HTMLURL string `json:"html_url"`
}

// MinimalReviewThread is the trimmed output type for PR review thread objects.
type MinimalReviewThread struct {
IsResolved bool `json:"is_resolved"`
IsOutdated bool `json:"is_outdated"`
IsCollapsed bool `json:"is_collapsed"`
Comments []MinimalReviewComment `json:"comments"`
TotalCount int `json:"total_count"`
}

// MinimalReviewThreadsResponse is the trimmed output for a paginated list of PR review threads.
type MinimalReviewThreadsResponse struct {
ReviewThreads []MinimalReviewThread `json:"review_threads"`
TotalCount int `json:"total_count"`
PageInfo MinimalPageInfo `json:"page_info"`
}

func convertToMinimalPRFiles(files []*github.CommitFile) []MinimalPRFile {
result := make([]MinimalPRFile, 0, len(files))
for _, f := range files {
Expand All @@ -636,3 +671,61 @@ func convertToMinimalBranch(branch *github.Branch) MinimalBranch {
Protected: branch.GetProtected(),
}
}

func convertToMinimalReviewThreadsResponse(query reviewThreadsQuery) MinimalReviewThreadsResponse {
threads := query.Repository.PullRequest.ReviewThreads

minimalThreads := make([]MinimalReviewThread, 0, len(threads.Nodes))
for _, thread := range threads.Nodes {
minimalThreads = append(minimalThreads, convertToMinimalReviewThread(thread))
}

return MinimalReviewThreadsResponse{
ReviewThreads: minimalThreads,
TotalCount: int(threads.TotalCount),
PageInfo: MinimalPageInfo{
HasNextPage: bool(threads.PageInfo.HasNextPage),
HasPreviousPage: bool(threads.PageInfo.HasPreviousPage),
StartCursor: string(threads.PageInfo.StartCursor),
EndCursor: string(threads.PageInfo.EndCursor),
},
}
}

func convertToMinimalReviewThread(thread reviewThreadNode) MinimalReviewThread {
comments := make([]MinimalReviewComment, 0, len(thread.Comments.Nodes))
for _, c := range thread.Comments.Nodes {
comments = append(comments, convertToMinimalReviewComment(c))
}

return MinimalReviewThread{
IsResolved: bool(thread.IsResolved),
IsOutdated: bool(thread.IsOutdated),
IsCollapsed: bool(thread.IsCollapsed),
Comments: comments,
TotalCount: int(thread.Comments.TotalCount),
}
}

func convertToMinimalReviewComment(c reviewCommentNode) MinimalReviewComment {
m := MinimalReviewComment{
Body: string(c.Body),
Path: string(c.Path),
Author: string(c.Author.Login),
HTMLURL: c.URL.String(),
}

if c.Line != nil {
line := int(*c.Line)
m.Line = &line
}

if !c.CreatedAt.IsZero() {
m.CreatedAt = c.CreatedAt.Format(time.RFC3339)
}
if !c.UpdatedAt.IsZero() {
m.UpdatedAt = c.UpdatedAt.Format(time.RFC3339)
}

return m
}
19 changes: 1 addition & 18 deletions pkg/github/pullrequests.go
Original file line number Diff line number Diff line change
Expand Up @@ -406,24 +406,7 @@ func GetPullRequestReviewComments(ctx context.Context, gqlClient *githubv4.Clien
}
}

// Build response with review threads and pagination info
response := map[string]any{
"reviewThreads": query.Repository.PullRequest.ReviewThreads.Nodes,
"pageInfo": map[string]any{
"hasNextPage": query.Repository.PullRequest.ReviewThreads.PageInfo.HasNextPage,
"hasPreviousPage": query.Repository.PullRequest.ReviewThreads.PageInfo.HasPreviousPage,
"startCursor": string(query.Repository.PullRequest.ReviewThreads.PageInfo.StartCursor),
"endCursor": string(query.Repository.PullRequest.ReviewThreads.PageInfo.EndCursor),
},
"totalCount": int(query.Repository.PullRequest.ReviewThreads.TotalCount),
}

r, err := json.Marshal(response)
if err != nil {
return nil, fmt.Errorf("failed to marshal response: %w", err)
}

return utils.NewToolResultText(string(r)), nil
return MarshalledTextResult(convertToMinimalReviewThreadsResponse(query)), nil
}

func GetPullRequestReviews(ctx context.Context, client *github.Client, deps ToolDependencies, owner, repo string, pullNumber int) (*mcp.CallToolResult, error) {
Expand Down
63 changes: 24 additions & 39 deletions pkg/github/pullrequests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1619,45 +1619,35 @@ func Test_GetPullRequestComments(t *testing.T) {
},
expectError: false,
validateResult: func(t *testing.T, textContent string) {
var result map[string]any
var result MinimalReviewThreadsResponse
err := json.Unmarshal([]byte(textContent), &result)
require.NoError(t, err)

// Validate response structure
assert.Contains(t, result, "reviewThreads")
assert.Contains(t, result, "pageInfo")
assert.Contains(t, result, "totalCount")

// Validate review threads
threads := result["reviewThreads"].([]any)
assert.Len(t, threads, 1)
assert.Len(t, result.ReviewThreads, 1)

thread := threads[0].(map[string]any)
assert.Equal(t, "RT_kwDOA0xdyM4AX1Yz", thread["ID"])
assert.Equal(t, false, thread["IsResolved"])
assert.Equal(t, false, thread["IsOutdated"])
assert.Equal(t, false, thread["IsCollapsed"])
thread := result.ReviewThreads[0]
assert.Equal(t, false, thread.IsResolved)
assert.Equal(t, false, thread.IsOutdated)
assert.Equal(t, false, thread.IsCollapsed)

// Validate comments within thread
comments := thread["Comments"].(map[string]any)
commentNodes := comments["Nodes"].([]any)
assert.Len(t, commentNodes, 2)
assert.Len(t, thread.Comments, 2)

// Validate first comment
comment1 := commentNodes[0].(map[string]any)
assert.Equal(t, "PRRC_kwDOA0xdyM4AX1Y0", comment1["ID"])
assert.Equal(t, "This looks good", comment1["Body"])
assert.Equal(t, "file1.go", comment1["Path"])
comment1 := thread.Comments[0]
assert.Equal(t, "This looks good", comment1.Body)
assert.Equal(t, "file1.go", comment1.Path)
assert.Equal(t, "reviewer1", comment1.Author)

// Validate pagination info
pageInfo := result["pageInfo"].(map[string]any)
assert.Equal(t, false, pageInfo["hasNextPage"])
assert.Equal(t, false, pageInfo["hasPreviousPage"])
assert.Equal(t, "cursor1", pageInfo["startCursor"])
assert.Equal(t, "cursor2", pageInfo["endCursor"])
assert.Equal(t, false, result.PageInfo.HasNextPage)
assert.Equal(t, false, result.PageInfo.HasPreviousPage)
assert.Equal(t, "cursor1", result.PageInfo.StartCursor)
assert.Equal(t, "cursor2", result.PageInfo.EndCursor)

// Validate total count
assert.Equal(t, float64(1), result["totalCount"])
assert.Equal(t, 1, result.TotalCount)
},
},
{
Expand Down Expand Up @@ -1761,27 +1751,22 @@ func Test_GetPullRequestComments(t *testing.T) {
expectError: false,
lockdownEnabled: true,
validateResult: func(t *testing.T, textContent string) {
var result map[string]any
var result MinimalReviewThreadsResponse
err := json.Unmarshal([]byte(textContent), &result)
require.NoError(t, err)

// Validate that only maintainer comment is returned
threads := result["reviewThreads"].([]any)
assert.Len(t, threads, 1)
assert.Len(t, result.ReviewThreads, 1)

thread := threads[0].(map[string]any)
comments := thread["Comments"].(map[string]any)
thread := result.ReviewThreads[0]

// Should only have 1 comment (maintainer) after filtering
assert.Equal(t, float64(1), comments["TotalCount"])

commentNodes := comments["Nodes"].([]any)
assert.Len(t, commentNodes, 1)
assert.Equal(t, 1, thread.TotalCount)
assert.Len(t, thread.Comments, 1)

comment := commentNodes[0].(map[string]any)
author := comment["Author"].(map[string]any)
assert.Equal(t, "maintainer", author["Login"])
assert.Equal(t, "Maintainer review comment", comment["Body"])
comment := thread.Comments[0]
assert.Equal(t, "maintainer", comment.Author)
assert.Equal(t, "Maintainer review comment", comment.Body)
},
},
}
Expand Down