diff --git a/lib/jira/base_factory.rb b/lib/jira/base_factory.rb index 02ec7087..11fb53db 100644 --- a/lib/jira/base_factory.rb +++ b/lib/jira/base_factory.rb @@ -38,7 +38,7 @@ def self.delegate_to_target_class(*method_names) # The principle purpose of this class is to delegate methods to the corresponding # non-factory class and automatically prepend the client argument to the argument # list. - delegate_to_target_class :all, :find, :collection_path, :singular_path, :jql, :get_backlog_issues, + delegate_to_target_class :all, :find, :collection_path, :singular_path, :jql, :jql_paged, :get_backlog_issues, :get_board_issues, :get_sprints, :get_sprint_issues, :get_projects, :get_projects_full # This method needs special handling as it has a default argument value diff --git a/lib/jira/resource/issue.rb b/lib/jira/resource/issue.rb index 2a9ecaab..fe25fb53 100644 --- a/lib/jira/resource/issue.rb +++ b/lib/jira/resource/issue.rb @@ -80,7 +80,57 @@ def self.all(client) result end + # Get issues using JQL query. + # @param client [JIRA::Client] + # @param jql [String] the JQL query string to search with + # @param options [Hash] Jira API options for the search + # @return [Array] or [Integer] total count if max_results is 0 def self.jql(client, jql, options = { fields: nil, max_results: nil, expand: nil, reconcile_issues: nil }) + issues = [] + total = nil + next_page_token = nil + is_last = false + + until is_last + result = jql_paged(client, jql, options.merge(next_page_token:)) + + issues.concat(result[:issues]) + total = result[:total] + next_page_token = result[:next_page_token] + is_last = next_page_token.nil? + end + options[:max_results]&.zero? ? total : issues + end + + # Get paged issues using JQL query. + # @param jql [String] the JQL query string to search with + # @param options [Hash] Jira API options for the search, including next_page_token + # @return [Hash] with format { issues: [JIRA::Resource::Issue], next_page_token: [String], total: [Integer] } + def self.jql_paged(client, jql, options = { fields: nil, max_results: nil, expand: nil, reconcile_issues: nil, next_page_token: nil }) + url = jql_url(client, jql, options) + next_page_token = options[:next_page_token] + max_results = options[:max_results] + + issues = [] + + page_url = url.dup + page_url << "&nextPageToken=#{next_page_token}" if next_page_token + + response = client.get(page_url) + json = parse_json(response.body) + total = json['total'] + + unless max_results&.zero? + next_page_token = json['nextPageToken'] + json['issues'].map do |issue| + issues << client.Issue.build(issue) + end + end + + { issues:, next_page_token:, total: } + end + + def self.jql_url(client, jql, options) url = client.options[:rest_base_path] + "/search/jql?jql=#{CGI.escape(jql)}" if options[:fields] @@ -95,24 +145,7 @@ def self.jql(client, jql, options = { fields: nil, max_results: nil, expand: nil options[:expand] = [options[:expand]] if options[:expand].is_a?(String) url << "&expand=#{options[:expand].to_a.map { |value| CGI.escape(value.to_s) }.join(',')}" end - - issues = [] - next_page_token = nil - json = {} - while json['isLast'] != true - page_url = url.dup - page_url << "&nextPageToken=#{next_page_token}" if next_page_token - - response = client.get(page_url) - json = parse_json(response.body) - return json['total'] if options[:max_results]&.zero? - - next_page_token = json['nextPageToken'] - json['issues'].map do |issue| - issues << client.Issue.build(issue) - end - end - issues + url end # Fetches the attributes for the specified resource from JIRA unless diff --git a/spec/jira/resource/issue_spec.rb b/spec/jira/resource/issue_spec.rb index 5099c70b..596f1b5a 100644 --- a/spec/jira/resource/issue_spec.rb +++ b/spec/jira/resource/issue_spec.rb @@ -173,6 +173,40 @@ class JIRAResourceDelegation < SimpleDelegator # :nodoc: end end + describe '.jql_paged' do + let(:issue) { double } + let(:response) { double } + + before do + allow(response).to receive(:body).and_return(response_string) + allow(client).to receive(:Issue).and_return(issue) + allow(issue).to receive(:build).with({ 'key' => 'foo' }).and_return('1') + allow(issue).to receive(:build).with({ 'key' => 'bar' }).and_return('2') + allow(issue).to receive(:build).with({ 'key' => 'baz' }).and_return('3') + end + + context 'without next_page_token (first page)' do + subject { described_class.jql_paged(client, 'foo bar', page_size: 2) } + + before { expect(client).to receive(:get).with('/jira/rest/api/2/search/jql?jql=foo+bar').and_return(response) } + + let(:response) { double } + let(:response_string) { '{"issues": [{"key":"foo"},{"key":"bar"}], "isLast": false, "nextPageToken": "abc"}' } + + it { is_expected.to eq(issues: %w[1 2], next_page_token: 'abc', total: nil) } + end + + context 'with next_page_token' do + subject { described_class.jql_paged(client, 'foo bar', page_size: 2, next_page_token: 'abc') } + + let(:response_string) { '{"issues": [{"key":"baz"}], "isLast": true}' } + + before { expect(client).to receive(:get).with('/jira/rest/api/2/search/jql?jql=foo+bar&nextPageToken=abc').and_return(double(body: response_string)) } + + it { is_expected.to eq(issues: %w[3], next_page_token: nil, total: nil) } + end + end + it 'returns meta data available for editing an issue' do subject = described_class.new(client, attrs: { 'fields' => { 'key' => 'TST=123' } }) response = double