Use unfiltered totalCount for reviewer 'more results' display

Query aliased fields without search filter to get stable counts.
This commit is contained in:
Kynan Ware 2026-01-29 12:54:04 -07:00
parent 484526da77
commit c8b1409803
2 changed files with 53 additions and 22 deletions

View file

@ -973,6 +973,7 @@ func SuggestedReviewerActors(client *Client, repo ghrepo.Interface, prID string,
// Fetch 10 from each source to allow cascading quota to fill from available results.
// Use a single query that includes organization.teams - if the owner is not an org,
// we'll get a "Could not resolve to an Organization" error which we handle gracefully.
// We also fetch unfiltered total counts via aliases for the "X more" display.
type responseData struct {
Node struct {
PullRequest struct {
@ -996,20 +997,24 @@ func SuggestedReviewerActors(client *Client, repo ghrepo.Interface, prID string,
} `graphql:"node(id: $id)"`
Repository struct {
Collaborators struct {
TotalCount int
Nodes []struct {
Nodes []struct {
Login string
Name string
}
} `graphql:"collaborators(first: 10, query: $query)"`
CollaboratorsTotalCount struct {
TotalCount int
} `graphql:"collaboratorsTotalCount: collaborators(first: 0)"`
} `graphql:"repository(owner: $owner, name: $name)"`
Organization struct {
Teams struct {
TotalCount int
Nodes []struct {
Nodes []struct {
Slug string
}
} `graphql:"teams(first: 10, query: $query)"`
TeamsTotalCount struct {
TotalCount int
} `graphql:"teamsTotalCount: teams(first: 0)"`
} `graphql:"organization(login: $owner)"`
}
@ -1098,9 +1103,8 @@ func SuggestedReviewerActors(client *Client, repo ghrepo.Interface, prID string,
}
}
// MoreResults is the sum of collaborators and teams total counts
// (teams will be 0 for personal repos)
moreResults := result.Repository.Collaborators.TotalCount + result.Organization.Teams.TotalCount
// MoreResults uses unfiltered total counts (teams will be 0 for personal repos)
moreResults := result.Repository.CollaboratorsTotalCount.TotalCount + result.Organization.TeamsTotalCount.TotalCount
return candidates, moreResults, nil
}

View file

@ -141,7 +141,7 @@ func Test_Logins(t *testing.T) {
// mockReviewerResponse generates a GraphQL response for SuggestedReviewerActors tests.
// It creates suggestions (s1, s2...), collaborators (c1, c2...), and teams (team1, team2...).
// totalCollabs and totalTeams set the TotalCount fields (for "more results" calculation).
// totalCollabs and totalTeams set the unfiltered TotalCount fields (for "more results" calculation).
func mockReviewerResponse(suggestions, collabs, teams, totalCollabs, totalTeams int) string {
var suggestionNodes, collabNodes, teamNodes []string
@ -161,11 +161,17 @@ func mockReviewerResponse(suggestions, collabs, teams, totalCollabs, totalTeams
return fmt.Sprintf(`{
"data": {
"node": {"suggestedReviewerActors": {"nodes": [%s]}},
"repository": {"collaborators": {"totalCount": %d, "nodes": [%s]}},
"organization": {"teams": {"totalCount": %d, "nodes": [%s]}}
"repository": {
"collaborators": {"nodes": [%s]},
"collaboratorsTotalCount": {"totalCount": %d}
},
"organization": {
"teams": {"nodes": [%s]},
"teamsTotalCount": {"totalCount": %d}
}
}
}`, strings.Join(suggestionNodes, ","), totalCollabs, strings.Join(collabNodes, ","),
totalTeams, strings.Join(teamNodes, ","))
}`, strings.Join(suggestionNodes, ","), strings.Join(collabNodes, ","), totalCollabs,
strings.Join(teamNodes, ","), totalTeams)
}
func TestSuggestedReviewerActors(t *testing.T) {
@ -234,8 +240,14 @@ func TestSuggestedReviewerActors(t *testing.T) {
{"isAuthor": false, "reviewer": {"__typename": "User", "login": "s1", "name": "S1"}},
{"isAuthor": false, "reviewer": {"__typename": "User", "login": "s2", "name": "S2"}}
]}},
"repository": {"collaborators": {"totalCount": 5, "nodes": [{"login": "c1", "name": "C1"}]}},
"organization": {"teams": {"totalCount": 3, "nodes": [{"slug": "team1"}]}}
"repository": {
"collaborators": {"nodes": [{"login": "c1", "name": "C1"}]},
"collaboratorsTotalCount": {"totalCount": 5}
},
"organization": {
"teams": {"nodes": [{"slug": "team1"}]},
"teamsTotalCount": {"totalCount": 3}
}
}
}`))
},
@ -254,11 +266,17 @@ func TestSuggestedReviewerActors(t *testing.T) {
"node": {"suggestedReviewerActors": {"nodes": [
{"isAuthor": false, "reviewer": {"__typename": "User", "login": "shareduser", "name": "Shared"}}
]}},
"repository": {"collaborators": {"totalCount": 10, "nodes": [
{"login": "shareduser", "name": "Shared"},
{"login": "c1", "name": "C1"}
]}},
"organization": {"teams": {"totalCount": 5, "nodes": [{"slug": "team1"}]}}
"repository": {
"collaborators": {"nodes": [
{"login": "shareduser", "name": "Shared"},
{"login": "c1", "name": "C1"}
]},
"collaboratorsTotalCount": {"totalCount": 10}
},
"organization": {
"teams": {"nodes": [{"slug": "team1"}]},
"teamsTotalCount": {"totalCount": 5}
}
}
}`))
},
@ -276,7 +294,10 @@ func TestSuggestedReviewerActors(t *testing.T) {
"node": {"suggestedReviewerActors": {"nodes": [
{"isAuthor": false, "reviewer": {"__typename": "User", "login": "s1", "name": "S1"}}
]}},
"repository": {"collaborators": {"totalCount": 3, "nodes": [{"login": "c1", "name": "C1"}]}},
"repository": {
"collaborators": {"nodes": [{"login": "c1", "name": "C1"}]},
"collaboratorsTotalCount": {"totalCount": 3}
},
"organization": null
},
"errors": [{"message": "Could not resolve to an Organization with the login of 'OWNER'."}]
@ -297,8 +318,14 @@ func TestSuggestedReviewerActors(t *testing.T) {
{"isAuthor": false, "reviewer": {"__typename": "Bot", "login": "copilot-pull-request-reviewer"}},
{"isAuthor": false, "reviewer": {"__typename": "User", "login": "s1", "name": "S1"}}
]}},
"repository": {"collaborators": {"totalCount": 5, "nodes": []}},
"organization": {"teams": {"totalCount": 0, "nodes": []}}
"repository": {
"collaborators": {"nodes": []},
"collaboratorsTotalCount": {"totalCount": 5}
},
"organization": {
"teams": {"nodes": []},
"teamsTotalCount": {"totalCount": 0}
}
}
}`))
},