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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ _None_

### Bug Fixes

_None_
- Fix GitHub API search queries to support fine-grained tokens in addition to classic tokens. Fine-grained tokens require explicit `is:issue` or `is:pull-request` qualifiers in search queries. [#663]

### Internal Changes

Expand Down
34 changes: 31 additions & 3 deletions lib/fastlane/plugin/wpmreleasetoolkit/helper/github_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,42 @@ def get_milestone(repository, release)
# @param [String] repository The repository name, including the organization (e.g. `wordpress-mobile/wordpress-ios`)
# @param [Sawyer::Resource, String] milestone The milestone object, or title of the milestone, we want to fetch the list of PRs for (e.g.: `16.9`)
# @param [Boolean] include_closed If set to true, will include both opened and closed PRs. Otherwise, will only include opened PRs.
# @return [Array<Sawyer::Resource>] A list of the PRs for the given milestone, sorted by number
# @return [Array<Sawyer::Resource>] A list of the PRs and issues for the given milestone, sorted by number
#
def get_prs_and_issues_for_milestone(repository:, milestone:, include_closed: false)
# While the `/search` API used with a classic tokens returns both issues and PRs, using a fine-grained tokens always require either 'is:issue' or 'is:pull-request',
# therefore we need to make two separate calls to cover both cases
issues = search_milestone_items(repository: repository, milestone: milestone, type: :issue, include_closed: include_closed)
prs = search_milestone_items(repository: repository, milestone: milestone, type: :pr, include_closed: include_closed)

(issues + prs).sort_by(&:number)
end

# Search for issues or PRs for a given milestone
#
# @param [String] repository The repository name, including the organization (e.g. `wordpress-mobile/wordpress-ios`)
# @param [Sawyer::Resource, String] milestone The milestone object, or title of the milestone
# @param [Symbol] type The type of items to search for (:issue or :pr/:pull_request)
# @param [Boolean] include_closed If set to true, will include both opened and closed items. Otherwise, will only include opened items.
# @return [Array<Sawyer::Resource>] A list of issues or PRs for the given milestone
#
def search_milestone_items(repository:, milestone:, type:, include_closed: false)
milestone_title = milestone.is_a?(Sawyer::Resource) ? milestone.title : milestone
query = %(repo:#{repository} milestone:"#{milestone_title}")

# Map type symbol to GitHub search qualifier
type_qualifier = case type
when :issue
'is:issue'
when :pr, :pull_request
'is:pull-request'
else
raise ArgumentError, "Invalid type: #{type}. Must be :issue or :pr"
end

query = %(repo:#{repository} milestone:"#{milestone_title}" #{type_qualifier})
query += ' is:open' unless include_closed

client.search_issues(query)[:items].sort_by(&:number)
client.search_issues(query)[:items]
end

# Set/Update the milestone assigned to a given PR or issue
Expand Down
42 changes: 30 additions & 12 deletions spec/github_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -220,33 +220,51 @@ def get_milestone(milestone_name:)
end

it 'returns only opened PRs for a given milestone by default' do
search_results = [101, 103].map { |num| sawyer_resource_stub(number: num) }
issue_results = [101].map { |num| sawyer_resource_stub(number: num) }
pr_results = [103].map { |num| sawyer_resource_stub(number: num) }

allow(client).to receive(:search_issues)
.with(%(repo:#{test_repo} milestone:"12.3 New Version" is:issue is:open))
.and_return({ items: issue_results })

allow(client).to receive(:search_issues)
.with(%(repo:#{test_repo} milestone:"12.3 New Version" is:open))
.and_return({ items: search_results })
.with(%(repo:#{test_repo} milestone:"12.3 New Version" is:pull-request is:open))
.and_return({ items: pr_results })

result = helper.get_prs_and_issues_for_milestone(repository: test_repo, milestone: '12.3 New Version')
expect(result).to eq(search_results)
expect(result.map(&:number)).to eq([101, 103])
end

it 'returns only opened PRs for a given milestone if include_closed is false' do
search_results = [101, 103].map { |num| sawyer_resource_stub(number: num) }
issue_results = [101].map { |num| sawyer_resource_stub(number: num) }
pr_results = [103].map { |num| sawyer_resource_stub(number: num) }

allow(client).to receive(:search_issues)
.with(%(repo:#{test_repo} milestone:"12.3 New Version" is:open))
.and_return({ items: search_results })
.with(%(repo:#{test_repo} milestone:"12.3 New Version" is:issue is:open))
.and_return({ items: issue_results })

allow(client).to receive(:search_issues)
.with(%(repo:#{test_repo} milestone:"12.3 New Version" is:pull-request is:open))
.and_return({ items: pr_results })

result = helper.get_prs_and_issues_for_milestone(repository: test_repo, milestone: '12.3 New Version', include_closed: false)
expect(result).to eq(search_results)
expect(result.map(&:number)).to eq([101, 103])
end

it 'returns both opened and closed PRs of a milestone if include_closed is true' do
search_results = [101, 102, 103, 104].map { |num| sawyer_resource_stub(number: num) }
issue_results = [101, 102].map { |num| sawyer_resource_stub(number: num) }
pr_results = [103, 104].map { |num| sawyer_resource_stub(number: num) }

allow(client).to receive(:search_issues)
.with(%(repo:#{test_repo} milestone:"12.3 New Version" is:issue))
.and_return({ items: issue_results })

allow(client).to receive(:search_issues)
.with(%(repo:#{test_repo} milestone:"12.3 New Version"))
.and_return({ items: search_results })
.with(%(repo:#{test_repo} milestone:"12.3 New Version" is:pull-request))
.and_return({ items: pr_results })

result = helper.get_prs_and_issues_for_milestone(repository: test_repo, milestone: '12.3 New Version', include_closed: true)
expect(result).to eq(search_results)
expect(result.map(&:number)).to eq([101, 102, 103, 104])
end
end

Expand Down
25 changes: 19 additions & 6 deletions spec/update_assigned_milestone_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,12 @@ def mock_pr(number)
context 'when providing a source milestone' do
it 'updates the milestone of all matching and still-opened PRs' do
allow(client).to receive(:search_issues)
.with(%(repo:#{test_repo} milestone:"#{mock_milestone(12.2)[:title]}" is:open))
.and_return({ items: [101, 103].map { |n| mock_pr(n) } })
.with(%(repo:#{test_repo} milestone:"#{mock_milestone(12.2)[:title]}" is:issue is:open))
.and_return({ items: [101].map { |n| mock_pr(n) } })

allow(client).to receive(:search_issues)
.with(%(repo:#{test_repo} milestone:"#{mock_milestone(12.2)[:title]}" is:pull-request is:open))
.and_return({ items: [103].map { |n| mock_pr(n) } })

expect(client).to receive(:update_issue).with(test_repo, 101, { milestone: 123 })
expect(client).to receive(:update_issue).with(test_repo, 103, { milestone: 123 })
Expand All @@ -91,8 +95,13 @@ def mock_pr(number)
it 'adds a PR comment if one is provided' do
comment = 'Updated milestone from `12.2` to `12.3`'
allow(client).to receive(:search_issues)
.with(%(repo:#{test_repo} milestone:"#{mock_milestone(12.2)[:title]}" is:open))
.and_return({ items: [101, 103].map { |n| mock_pr(n) } })
.with(%(repo:#{test_repo} milestone:"#{mock_milestone(12.2)[:title]}" is:issue is:open))
.and_return({ items: [101].map { |n| mock_pr(n) } })

allow(client).to receive(:search_issues)
.with(%(repo:#{test_repo} milestone:"#{mock_milestone(12.2)[:title]}" is:pull-request is:open))
.and_return({ items: [103].map { |n| mock_pr(n) } })

allow(client).to receive(:issue_comments).and_return([])

expect(client).to receive(:update_issue).with(test_repo, 101, { milestone: 123 })
Expand All @@ -113,8 +122,12 @@ def mock_pr(number)

it 'does not add a PR comment if comment is empty' do
allow(client).to receive(:search_issues)
.with(%(repo:#{test_repo} milestone:"#{mock_milestone(12.2)[:title]}" is:open))
.and_return({ items: [101, 103].map { |n| mock_pr(n) } })
.with(%(repo:#{test_repo} milestone:"#{mock_milestone(12.2)[:title]}" is:issue is:open))
.and_return({ items: [101].map { |n| mock_pr(n) } })

allow(client).to receive(:search_issues)
.with(%(repo:#{test_repo} milestone:"#{mock_milestone(12.2)[:title]}" is:pull-request is:open))
.and_return({ items: [103].map { |n| mock_pr(n) } })

expect(client).to receive(:update_issue).with(test_repo, 101, { milestone: 123 })
expect(client).to receive(:update_issue).with(test_repo, 103, { milestone: 123 })
Expand Down