From d330708e8b0b0e219c79fdf30afb0eb3c8e56be7 Mon Sep 17 00:00:00 2001 From: Stephen Hosom Date: Tue, 15 Sep 2026 13:27:27 -0400 Subject: [PATCH] Use page info for team pagination Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 48d43b26-c466-49f6-ac97-aa304ac378fd --- lib/entitlements/backend/github_team/service.rb | 9 ++++++--- .../entitlements/backend/github_team/service_spec.rb | 10 +++++----- spec/unit/spec_helper.rb | 8 ++++++-- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/lib/entitlements/backend/github_team/service.rb b/lib/entitlements/backend/github_team/service.rb index dba731d..8e9c355 100644 --- a/lib/entitlements/backend/github_team/service.rb +++ b/lib/entitlements/backend/github_team/service.rb @@ -383,7 +383,9 @@ def graphql_team_data(team_slug) login } role - cursor + } + pageInfo { + endCursor } } } @@ -402,7 +404,8 @@ def graphql_team_data(team_slug) team_id = team.fetch("databaseId") parent_team_name = team.dig("parentTeam", "slug") - edges = team.fetch("members").fetch("edges") + members = team.fetch("members") + edges = members.fetch("edges") break unless edges.any? buffer = edges.map { |e| e.fetch("node").fetch("login").downcase } @@ -413,7 +416,7 @@ def graphql_team_data(team_slug) roles[e.fetch("node").fetch("login").downcase] = role end - cursor = edges.last.fetch("cursor") + cursor = members.dig("pageInfo", "endCursor") || edges.last["cursor"] next if cursor && buffer.size == max_graphql_results break diff --git a/spec/unit/entitlements/backend/github_team/service_spec.rb b/spec/unit/entitlements/backend/github_team/service_spec.rb index bc430af..dc2b813 100644 --- a/spec/unit/entitlements/backend/github_team/service_spec.rb +++ b/spec/unit/entitlements/backend/github_team/service_spec.rb @@ -80,7 +80,7 @@ graphql_response = '{"data":{"organization":{"team":null}}}' stub_request(:post, "https://github.fake/api/v3/graphql") .with( - body: "{\"query\":\"{\\norganization(login: \\\"kittensinc\\\") {\\nteam(slug: \\\"team-does-not-exist\\\") {\\ndatabaseId\\nparentTeam {\\nslug\\n}\\nmembers(first: 100, membership: IMMEDIATE) {\\nedges {\\nnode {\\nlogin\\n}\\nrole\\ncursor\\n}\\n}\\n}\\n}\\n}\"}" + body: "{\"query\":\"{\\norganization(login: \\\"kittensinc\\\") {\\nteam(slug: \\\"team-does-not-exist\\\") {\\ndatabaseId\\nparentTeam {\\nslug\\n}\\nmembers(first: 100, membership: IMMEDIATE) {\\nedges {\\nnode {\\nlogin\\n}\\nrole\\n}\\npageInfo {\\nendCursor\\n}\\n}\\n}\\n}\\n}\"}" ).to_return(status: 200, body: graphql_response) expect(logger).to receive(:debug).with("Setting up GitHub API connection to https://github.fake/api/v3/") @@ -93,7 +93,7 @@ it "returns a Entitlements::Backend::GitHubTeam::Models::Team object when the team exists" do stub_request(:post, "https://github.fake/api/v3/graphql") .with( - body: "{\"query\":\"{\\norganization(login: \\\"kittensinc\\\") {\\nteam(slug: \\\"cuddly-kittens\\\") {\\ndatabaseId\\nparentTeam {\\nslug\\n}\\nmembers(first: 100, membership: IMMEDIATE) {\\nedges {\\nnode {\\nlogin\\n}\\nrole\\ncursor\\n}\\n}\\n}\\n}\\n}\"}" + body: "{\"query\":\"{\\norganization(login: \\\"kittensinc\\\") {\\nteam(slug: \\\"cuddly-kittens\\\") {\\ndatabaseId\\nparentTeam {\\nslug\\n}\\nmembers(first: 100, membership: IMMEDIATE) {\\nedges {\\nnode {\\nlogin\\n}\\nrole\\n}\\npageInfo {\\nendCursor\\n}\\n}\\n}\\n}\\n}\"}" ).to_return(status: 200, body: graphql_response(cuddly_kittens, 0, 100)) expect(logger).to receive(:debug).with("Setting up GitHub API connection to https://github.fake/api/v3/") @@ -110,7 +110,7 @@ it "returns a Entitlements::Backend::GitHubTeam::Models::Team object with parent team when the team exists" do stub_request(:post, "https://github.fake/api/v3/graphql") .with( - body: "{\"query\":\"{\\norganization(login: \\\"kittensinc\\\") {\\nteam(slug: \\\"cuddly-kittens\\\") {\\ndatabaseId\\nparentTeam {\\nslug\\n}\\nmembers(first: 100, membership: IMMEDIATE) {\\nedges {\\nnode {\\nlogin\\n}\\nrole\\ncursor\\n}\\n}\\n}\\n}\\n}\"}" + body: "{\"query\":\"{\\norganization(login: \\\"kittensinc\\\") {\\nteam(slug: \\\"cuddly-kittens\\\") {\\ndatabaseId\\nparentTeam {\\nslug\\n}\\nmembers(first: 100, membership: IMMEDIATE) {\\nedges {\\nnode {\\nlogin\\n}\\nrole\\n}\\npageInfo {\\nendCursor\\n}\\n}\\n}\\n}\\n}\"}" ).to_return(status: 200, body: graphql_response(cuddly_kittens, 0, 100, parent_team: "parent-cats")) expect(logger).to receive(:debug).with("Setting up GitHub API connection to https://github.fake/api/v3/") @@ -129,7 +129,7 @@ it "returns a Entitlements::Backend::GitHubTeam::Models::Team object with parent team when the team exists but has empty entitlement metadata" do stub_request(:post, "https://github.fake/api/v3/graphql") .with( - body: "{\"query\":\"{\\norganization(login: \\\"kittensinc\\\") {\\nteam(slug: \\\"cuddly-kittens\\\") {\\ndatabaseId\\nparentTeam {\\nslug\\n}\\nmembers(first: 100, membership: IMMEDIATE) {\\nedges {\\nnode {\\nlogin\\n}\\nrole\\ncursor\\n}\\n}\\n}\\n}\\n}\"}" + body: "{\"query\":\"{\\norganization(login: \\\"kittensinc\\\") {\\nteam(slug: \\\"cuddly-kittens\\\") {\\ndatabaseId\\nparentTeam {\\nslug\\n}\\nmembers(first: 100, membership: IMMEDIATE) {\\nedges {\\nnode {\\nlogin\\n}\\nrole\\n}\\npageInfo {\\nendCursor\\n}\\n}\\n}\\n}\\n}\"}" ).to_return(status: 200, body: graphql_response(cuddly_kittens_no_metadata, 0, 100, parent_team: "parent-cats")) expect(logger).to receive(:debug).with("Setting up GitHub API connection to https://github.fake/api/v3/") @@ -718,7 +718,7 @@ it "parses team data from a single page of results" do stub_request(:post, "https://github.fake/api/v3/graphql") .with( - body: "{\"query\":\"{\\norganization(login: \\\"kittensinc\\\") {\\nteam(slug: \\\"grumpy-cat\\\") {\\ndatabaseId\\nparentTeam {\\nslug\\n}\\nmembers(first: 100, membership: IMMEDIATE) {\\nedges {\\nnode {\\nlogin\\n}\\nrole\\ncursor\\n}\\n}\\n}\\n}\\n}\"}", + body: "{\"query\":\"{\\norganization(login: \\\"kittensinc\\\") {\\nteam(slug: \\\"grumpy-cat\\\") {\\ndatabaseId\\nparentTeam {\\nslug\\n}\\nmembers(first: 100, membership: IMMEDIATE) {\\nedges {\\nnode {\\nlogin\\n}\\nrole\\n}\\npageInfo {\\nendCursor\\n}\\n}\\n}\\n}\\n}\"}", headers: { "Authorization" => "bearer GoPackGo", "Content-Type" => "application/json" diff --git a/spec/unit/spec_helper.rb b/spec/unit/spec_helper.rb index 2a4ee2a..2fc7d56 100644 --- a/spec/unit/spec_helper.rb +++ b/spec/unit/spec_helper.rb @@ -59,15 +59,19 @@ def default_filters def graphql_response(team, slice_start, slice_length, parent_team: nil) team_id = rand(1..10000) edges = team.member_strings.sort.to_a.slice(slice_start, slice_length).map do |m| - { "node" => { "login" => m }, "role" => "MEMBER", "cursor" => Base64.encode64(m) } + { "node" => { "login" => m }, "role" => "MEMBER" } end + end_cursor = edges.empty? ? nil : Base64.encode64(edges.last.fetch("node").fetch("login")) struct = { "data" => { "organization" => { "team" => { "databaseId" => team_id, "members" => { - "edges" => edges + "edges" => edges, + "pageInfo" => { + "endCursor" => end_cursor + } }, "parentTeam" => { "slug" => parent_team