jira-ruby 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. checksums.yaml +5 -5
  2. data/Gemfile +7 -1
  3. data/Guardfile +1 -1
  4. data/Rakefile +4 -5
  5. data/http-basic-example.rb +13 -12
  6. data/jira-ruby.gemspec +9 -10
  7. data/lib/jira-ruby.rb +5 -2
  8. data/lib/jira/base.rb +49 -48
  9. data/lib/jira/base_factory.rb +1 -4
  10. data/lib/jira/client.rb +29 -20
  11. data/lib/jira/has_many_proxy.rb +0 -1
  12. data/lib/jira/http_client.rb +9 -10
  13. data/lib/jira/http_error.rb +3 -5
  14. data/lib/jira/oauth_client.rb +19 -20
  15. data/lib/jira/request_client.rb +3 -4
  16. data/lib/jira/resource/agile.rb +10 -8
  17. data/lib/jira/resource/applinks.rb +5 -8
  18. data/lib/jira/resource/attachment.rb +1 -2
  19. data/lib/jira/resource/board.rb +84 -0
  20. data/lib/jira/resource/comment.rb +0 -2
  21. data/lib/jira/resource/component.rb +1 -3
  22. data/lib/jira/resource/createmeta.rb +12 -14
  23. data/lib/jira/resource/field.rb +22 -22
  24. data/lib/jira/resource/filter.rb +2 -2
  25. data/lib/jira/resource/issue.rb +41 -39
  26. data/lib/jira/resource/issuelink.rb +3 -5
  27. data/lib/jira/resource/issuelinktype.rb +0 -1
  28. data/lib/jira/resource/issuetype.rb +1 -3
  29. data/lib/jira/resource/priority.rb +1 -3
  30. data/lib/jira/resource/project.rb +5 -7
  31. data/lib/jira/resource/rapidview.rb +28 -7
  32. data/lib/jira/resource/remotelink.rb +1 -4
  33. data/lib/jira/resource/resolution.rb +2 -4
  34. data/lib/jira/resource/serverinfo.rb +1 -2
  35. data/lib/jira/resource/sprint.rb +82 -18
  36. data/lib/jira/resource/sprint_report.rb +8 -0
  37. data/lib/jira/resource/status.rb +1 -3
  38. data/lib/jira/resource/transition.rb +2 -6
  39. data/lib/jira/resource/user.rb +12 -2
  40. data/lib/jira/resource/version.rb +1 -3
  41. data/lib/jira/resource/watcher.rb +1 -5
  42. data/lib/jira/resource/webhook.rb +3 -6
  43. data/lib/jira/resource/worklog.rb +3 -5
  44. data/lib/jira/version.rb +1 -1
  45. data/lib/tasks/generate.rake +4 -4
  46. data/spec/integration/attachment_spec.rb +15 -16
  47. data/spec/integration/comment_spec.rb +31 -34
  48. data/spec/integration/component_spec.rb +21 -24
  49. data/spec/integration/field_spec.rb +15 -18
  50. data/spec/integration/issue_spec.rb +44 -48
  51. data/spec/integration/issuelinktype_spec.rb +8 -11
  52. data/spec/integration/issuetype_spec.rb +5 -7
  53. data/spec/integration/priority_spec.rb +5 -8
  54. data/spec/integration/project_spec.rb +13 -20
  55. data/spec/integration/rapidview_spec.rb +17 -10
  56. data/spec/integration/resolution_spec.rb +7 -10
  57. data/spec/integration/status_spec.rb +5 -8
  58. data/spec/integration/transition_spec.rb +17 -20
  59. data/spec/integration/user_spec.rb +24 -8
  60. data/spec/integration/version_spec.rb +21 -25
  61. data/spec/integration/watcher_spec.rb +28 -34
  62. data/spec/integration/webhook.rb +8 -17
  63. data/spec/integration/worklog_spec.rb +30 -34
  64. data/spec/jira/base_factory_spec.rb +11 -12
  65. data/spec/jira/base_spec.rb +204 -228
  66. data/spec/jira/client_spec.rb +26 -28
  67. data/spec/jira/has_many_proxy_spec.rb +11 -12
  68. data/spec/jira/http_client_spec.rb +51 -52
  69. data/spec/jira/http_error_spec.rb +7 -9
  70. data/spec/jira/oauth_client_spec.rb +44 -46
  71. data/spec/jira/request_client_spec.rb +5 -5
  72. data/spec/jira/resource/agile_spec.rb +5 -7
  73. data/spec/jira/resource/attachment_spec.rb +25 -26
  74. data/spec/jira/resource/board_spec.rb +175 -0
  75. data/spec/jira/resource/createmeta_spec.rb +29 -32
  76. data/spec/jira/resource/field_spec.rb +42 -48
  77. data/spec/jira/resource/filter_spec.rb +40 -40
  78. data/spec/jira/resource/issue_spec.rb +87 -89
  79. data/spec/jira/resource/issuelink_spec.rb +1 -1
  80. data/spec/jira/resource/project_factory_spec.rb +2 -4
  81. data/spec/jira/resource/project_spec.rb +33 -33
  82. data/spec/jira/resource/sprint_spec.rb +78 -0
  83. data/spec/jira/resource/user_factory_spec.rb +6 -8
  84. data/spec/jira/resource/worklog_spec.rb +9 -11
  85. data/spec/spec_helper.rb +8 -9
  86. data/spec/support/clients_helper.rb +4 -4
  87. data/spec/support/shared_examples/integration.rb +60 -77
  88. metadata +59 -53
@@ -3,7 +3,6 @@ require 'forwardable'
3
3
  require 'ostruct'
4
4
 
5
5
  module JIRA
6
-
7
6
  # This class is the main access point for all JIRA::Resource instances.
8
7
  #
9
8
  # The client must be initialized with an options hash containing
@@ -32,7 +31,6 @@ module JIRA
32
31
  # objects.
33
32
 
34
33
  class Client
35
-
36
34
  extend Forwardable
37
35
 
38
36
  # The OAuth::Consumer instance returned by the OauthClient
@@ -47,17 +45,17 @@ module JIRA
47
45
  def_delegators :@request_client, :init_access_token, :set_access_token, :set_request_token, :request_token, :access_token, :authenticated?
48
46
 
49
47
  DEFAULT_OPTIONS = {
50
- :site => 'http://localhost:2990',
51
- :context_path => '/jira',
52
- :rest_base_path => "/rest/api/2",
53
- :ssl_verify_mode => OpenSSL::SSL::VERIFY_PEER,
54
- :use_ssl => true,
55
- :use_client_cert => false,
56
- :auth_type => :oauth,
57
- :http_debug => false
58
- }
59
-
60
- def initialize(options={})
48
+ site: 'http://localhost:2990',
49
+ context_path: '/jira',
50
+ rest_base_path: '/rest/api/2',
51
+ ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER,
52
+ use_ssl: true,
53
+ use_client_cert: false,
54
+ auth_type: :oauth,
55
+ http_debug: false
56
+ }.freeze
57
+
58
+ def initialize(options = {})
61
59
  options = DEFAULT_OPTIONS.merge(options)
62
60
  @options = options
63
61
  @options[:rest_base_path] = @options[:context_path] + @options[:rest_base_path]
@@ -153,10 +151,22 @@ module JIRA
153
151
  JIRA::Resource::FieldFactory.new(self)
154
152
  end
155
153
 
154
+ def Board
155
+ JIRA::Resource::BoardFactory.new(self)
156
+ end
157
+
156
158
  def RapidView
157
159
  JIRA::Resource::RapidViewFactory.new(self)
158
160
  end
159
161
 
162
+ def Sprint
163
+ JIRA::Resource::SprintFactory.new(self)
164
+ end
165
+
166
+ def SprintReport
167
+ JIRA::Resource::SprintReportFactory.new(self)
168
+ end
169
+
160
170
  def ServerInfo
161
171
  JIRA::Resource::ServerInfoFactory.new(self)
162
172
  end
@@ -212,27 +222,26 @@ module JIRA
212
222
 
213
223
  # HTTP methods with a body
214
224
  def post(path, body = '', headers = {})
215
- headers = {'Content-Type' => 'application/json'}.merge(headers)
225
+ headers = { 'Content-Type' => 'application/json' }.merge(headers)
216
226
  request(:post, path, body, merge_default_headers(headers))
217
227
  end
218
228
 
219
229
  def put(path, body = '', headers = {})
220
- headers = {'Content-Type' => 'application/json'}.merge(headers)
230
+ headers = { 'Content-Type' => 'application/json' }.merge(headers)
221
231
  request(:put, path, body, merge_default_headers(headers))
222
232
  end
223
233
 
224
234
  # Sends the specified HTTP request to the REST API through the
225
235
  # appropriate method (oauth, basic).
226
- def request(http_method, path, body = '', headers={})
236
+ def request(http_method, path, body = '', headers = {})
227
237
  puts "#{http_method}: #{path} - [#{body}]" if @http_debug
228
238
  @request_client.request(http_method, path, body, headers)
229
239
  end
230
240
 
231
241
  protected
232
242
 
233
- def merge_default_headers(headers)
234
- {'Accept' => 'application/json'}.merge(headers)
235
- end
236
-
243
+ def merge_default_headers(headers)
244
+ { 'Accept' => 'application/json' }.merge(headers)
245
+ end
237
246
  end
238
247
  end
@@ -7,7 +7,6 @@
7
7
  # In practice, instances of this class behave exactly like an Array.
8
8
  #
9
9
  class JIRA::HasManyProxy
10
-
11
10
  attr_reader :target_class, :parent
12
11
  attr_accessor :collection
13
12
 
@@ -5,11 +5,10 @@ require 'uri'
5
5
 
6
6
  module JIRA
7
7
  class HttpClient < RequestClient
8
-
9
8
  DEFAULT_OPTIONS = {
10
- :username => '',
11
- :password => ''
12
- }
9
+ username: '',
10
+ password: ''
11
+ }.freeze
13
12
 
14
13
  attr_reader :options
15
14
 
@@ -19,13 +18,13 @@ module JIRA
19
18
  end
20
19
 
21
20
  def make_cookie_auth_request
22
- body = { :username => @options[:username], :password => @options[:password] }.to_json
21
+ body = { username: @options[:username], password: @options[:password] }.to_json
23
22
  @options.delete(:username)
24
23
  @options.delete(:password)
25
- make_request(:post, @options[:context_path] + '/rest/auth/1/session', body, {'Content-Type' => 'application/json'})
24
+ make_request(:post, @options[:context_path] + '/rest/auth/1/session', body, 'Content-Type' => 'application/json')
26
25
  end
27
26
 
28
- def make_request(http_method, url, body='', headers={})
27
+ def make_request(http_method, url, body = '', headers = {})
29
28
  # When a proxy is enabled, Net::HTTP expects that the request path omits the domain name
30
29
  path = request_path(url)
31
30
  request = Net::HTTP.const_get(http_method.to_s.capitalize).new(path, headers)
@@ -44,9 +43,9 @@ module JIRA
44
43
 
45
44
  def http_conn(uri)
46
45
  if @options[:proxy_address]
47
- http_class = Net::HTTP::Proxy(@options[:proxy_address], @options[:proxy_port] ? @options[:proxy_port] : 80)
46
+ http_class = Net::HTTP::Proxy(@options[:proxy_address], @options[:proxy_port] || 80)
48
47
  else
49
- http_class = Net::HTTP
48
+ http_class = Net::HTTP
50
49
  end
51
50
  http_conn = http_class.new(uri.host, uri.port)
52
51
  http_conn.use_ssl = @options[:use_ssl]
@@ -90,7 +89,7 @@ module JIRA
90
89
 
91
90
  def add_cookies(request)
92
91
  cookie_array = @cookies.values.map { |cookie| "#{cookie.name}=#{cookie.value[0]}" }
93
- cookie_array += Array(@options[:additional_cookies]) if @options.key?(:additional_cookies)
92
+ cookie_array += Array(@options[:additional_cookies]) if @options.key?(:additional_cookies)
94
93
  request.add_field('Cookie', cookie_array.join('; ')) if cookie_array.any?
95
94
  request
96
95
  end
@@ -1,16 +1,14 @@
1
1
  require 'forwardable'
2
2
  module JIRA
3
-
4
3
  class HTTPError < StandardError
5
4
  extend Forwardable
6
5
 
7
- def_instance_delegators :@response, :message, :code
8
- attr_reader :response
6
+ def_instance_delegators :@response, :code
7
+ attr_reader :response, :message
9
8
 
10
9
  def initialize(response)
11
10
  @response = response
11
+ @message = response.try(:message) || response.try(:body)
12
12
  end
13
-
14
13
  end
15
-
16
14
  end
@@ -4,22 +4,21 @@ require 'forwardable'
4
4
 
5
5
  module JIRA
6
6
  class OauthClient < RequestClient
7
-
8
7
  DEFAULT_OPTIONS = {
9
- :signature_method => 'RSA-SHA1',
10
- :request_token_path => "/plugins/servlet/oauth/request-token",
11
- :authorize_path => "/plugins/servlet/oauth/authorize",
12
- :access_token_path => "/plugins/servlet/oauth/access-token",
13
- :private_key_file => "rsakey.pem",
14
- :consumer_key => nil,
15
- :consumer_secret => nil
16
- }
8
+ signature_method: 'RSA-SHA1',
9
+ request_token_path: '/plugins/servlet/oauth/request-token',
10
+ authorize_path: '/plugins/servlet/oauth/authorize',
11
+ access_token_path: '/plugins/servlet/oauth/access-token',
12
+ private_key_file: 'rsakey.pem',
13
+ consumer_key: nil,
14
+ consumer_secret: nil
15
+ }.freeze
17
16
 
18
17
  # This exception is thrown when the client is used before the OAuth access token
19
18
  # has been initialized.
20
19
  class UninitializedAccessTokenError < StandardError
21
20
  def message
22
- "init_access_token must be called before using the client"
21
+ 'init_access_token must be called before using the client'
23
22
  end
24
23
  end
25
24
 
@@ -35,11 +34,11 @@ module JIRA
35
34
  @consumer = init_oauth_consumer(@options)
36
35
  end
37
36
 
38
- def init_oauth_consumer(options)
37
+ def init_oauth_consumer(_options)
39
38
  @options[:request_token_path] = @options[:context_path] + @options[:request_token_path]
40
39
  @options[:authorize_path] = @options[:context_path] + @options[:authorize_path]
41
40
  @options[:access_token_path] = @options[:context_path] + @options[:access_token_path]
42
- OAuth::Consumer.new(@options[:consumer_key],@options[:consumer_secret],@options)
41
+ OAuth::Consumer.new(@options[:consumer_key], @options[:consumer_secret], @options)
43
42
  end
44
43
 
45
44
  # Returns the current request token if it is set, else it creates
@@ -69,20 +68,20 @@ module JIRA
69
68
  # Returns the current access token. Raises an
70
69
  # JIRA::Client::UninitializedAccessTokenError exception if it is not set.
71
70
  def access_token
72
- raise UninitializedAccessTokenError.new unless @access_token
71
+ raise UninitializedAccessTokenError unless @access_token
73
72
  @access_token
74
73
  end
75
74
 
76
- def make_request(http_method, path, body='', headers={})
75
+ def make_request(http_method, path, body = '', headers = {})
77
76
  # When using oauth_2legged we need to add an empty oauth_token parameter to every request.
78
77
  if @options[:auth_type] == :oauth_2legged
79
- oauth_params_str = "oauth_token="
78
+ oauth_params_str = 'oauth_token='
80
79
  uri = URI.parse(path)
81
- if uri.query.to_s == ""
82
- uri.query = oauth_params_str
83
- else
84
- uri.query = uri.query + "&" + oauth_params_str
85
- end
80
+ uri.query = if uri.query.to_s == ''
81
+ oauth_params_str
82
+ else
83
+ uri.query + '&' + oauth_params_str
84
+ end
86
85
  path = uri.to_s
87
86
  end
88
87
 
@@ -1,19 +1,18 @@
1
1
  require 'oauth'
2
2
  require 'json'
3
3
  require 'net/https'
4
- #require 'pry'
4
+ # require 'pry'
5
5
 
6
6
  module JIRA
7
7
  class RequestClient
8
-
9
8
  # Returns the response if the request was successful (HTTP::2xx) and
10
9
  # raises a JIRA::HTTPError if it was not successful, with the response
11
10
  # attached.
12
11
 
13
12
  def request(*args)
14
13
  response = make_request(*args)
15
- #binding.pry unless response.kind_of?(Net::HTTPSuccess)
16
- raise HTTPError.new(response) unless response.kind_of?(Net::HTTPSuccess)
14
+ # binding.pry unless response.kind_of?(Net::HTTPSuccess)
15
+ raise HTTPError, response unless response.is_a?(Net::HTTPSuccess)
17
16
  response
18
17
  end
19
18
  end
@@ -2,14 +2,16 @@ require 'cgi'
2
2
 
3
3
  module JIRA
4
4
  module Resource
5
-
6
5
  class AgileFactory < JIRA::BaseFactory # :nodoc:
7
6
  end
8
7
 
9
8
  class Agile < JIRA::Base
10
-
11
- def self.all(client)
12
- response = client.get(path_base(client) + '/board')
9
+ # @param client [JIRA::Client]
10
+ # @param options [Hash<Symbol, Object>]
11
+ # @return [Hash]
12
+ def self.all(client, options = {})
13
+ opts = options.empty? ? '' : "?#{hash_to_query_string(options)}"
14
+ response = client.get(path_base(client) + "/board#{opts}")
13
15
  parse_json(response.body)
14
16
  end
15
17
 
@@ -23,9 +25,10 @@ module JIRA
23
25
  response = client.get(path_base(client) + "/board/#{board_id}/issue?#{hash_to_query_string(options)}")
24
26
  json = parse_json(response.body)
25
27
  # To get Issue objects with the same structure as for Issue.all
26
- issue_ids = json['issues'].map { |issue|
28
+ return {} if json['issues'].size.zero?
29
+ issue_ids = json['issues'].map do |issue|
27
30
  issue['id']
28
- }
31
+ end
29
32
  client.Issue.jql("id IN(#{issue_ids.join(', ')})")
30
33
  end
31
34
 
@@ -41,7 +44,7 @@ module JIRA
41
44
  parse_json(response.body)
42
45
  end
43
46
 
44
- def self.get_projects_full(client, board_id, options = {})
47
+ def self.get_projects_full(client, board_id, _options = {})
45
48
  response = client.get(path_base(client) + "/board/#{board_id}/project/full")
46
49
  parse_json(response.body)
47
50
  end
@@ -72,6 +75,5 @@ module JIRA
72
75
  self.class.path_base(client)
73
76
  end
74
77
  end
75
-
76
78
  end
77
79
  end
@@ -1,13 +1,11 @@
1
1
  module JIRA
2
2
  module Resource
3
-
4
3
  class ApplicationLinkFactory < JIRA::BaseFactory # :nodoc:
5
4
  delegate_to_target_class :manifest
6
5
  end
7
6
 
8
7
  class ApplicationLink < JIRA::Base
9
-
10
- REST_BASE_PATH = '/rest/applinks/1.0'
8
+ REST_BASE_PATH = '/rest/applinks/1.0'.freeze
11
9
 
12
10
  def self.endpoint_name
13
11
  'listApplicationlinks'
@@ -18,7 +16,7 @@ module JIRA
18
16
  end
19
17
 
20
18
  def self.collection_path(client, prefix = '/')
21
- self.full_url(client) + prefix + self.endpoint_name
19
+ full_url(client) + prefix + endpoint_name
22
20
  end
23
21
 
24
22
  def self.all(client, options = {})
@@ -26,17 +24,16 @@ module JIRA
26
24
  json = parse_json(response.body)
27
25
  json = json['list']
28
26
  json.map do |attrs|
29
- self.new(client, {:attrs => attrs}.merge(options))
27
+ new(client, { attrs: attrs }.merge(options))
30
28
  end
31
29
  end
32
30
 
33
31
  def self.manifest(client)
34
- url = self.full_url(client) + '/manifest'
32
+ url = full_url(client) + '/manifest'
35
33
  response = client.get(url)
36
34
  json = parse_json(response.body)
37
- JIRA::Base.new(client, {:attrs => json})
35
+ JIRA::Base.new(client, attrs: json)
38
36
  end
39
-
40
37
  end
41
38
  end
42
39
  end
@@ -2,14 +2,13 @@ require 'net/http/post/multipart'
2
2
 
3
3
  module JIRA
4
4
  module Resource
5
-
6
5
  class AttachmentFactory < JIRA::BaseFactory # :nodoc:
7
6
  delegate_to_target_class :meta
8
7
  end
9
8
 
10
9
  class Attachment < JIRA::Base
11
10
  belongs_to :issue
12
- has_one :author, :class => JIRA::Resource::User
11
+ has_one :author, class: JIRA::Resource::User
13
12
 
14
13
  def self.endpoint_name
15
14
  'attachments'
@@ -0,0 +1,84 @@
1
+ require 'cgi'
2
+
3
+ module JIRA
4
+ module Resource
5
+ class BoardFactory < JIRA::BaseFactory # :nodoc:
6
+ end
7
+
8
+ class Board < JIRA::Base
9
+ def self.all(client)
10
+ path = path_base(client) + '/board'
11
+ response = client.get(path)
12
+ json = parse_json(response.body)
13
+ results = json['values']
14
+
15
+ until json['isLast']
16
+ params = { 'startAt' => (json['startAt'] + json['maxResults']).to_s }
17
+ response = client.get(url_with_query_params(path, params))
18
+ json = parse_json(response.body)
19
+ results += json['values']
20
+ end
21
+
22
+ results.map do |board|
23
+ client.Board.build(board)
24
+ end
25
+ end
26
+
27
+ def self.find(client, key, _options = {})
28
+ response = client.get(path_base(client) + "/board/#{key}")
29
+ json = parse_json(response.body)
30
+ client.Board.build(json)
31
+ end
32
+
33
+ def issues(params = {})
34
+ path = path_base(client) + "/board/#{id}/issue?"
35
+ response = client.get(url_with_query_params(path, params))
36
+ json = self.class.parse_json(response.body)
37
+ results = json['issues']
38
+
39
+ while (json['startAt'] + json['maxResults']) < json['total']
40
+ params['startAt'] = (json['startAt'] + json['maxResults'])
41
+ response = client.get(url_with_query_params(path, params))
42
+ json = self.class.parse_json(response.body)
43
+ results += json['issues']
44
+ end
45
+
46
+ results.map { |issue| client.Issue.build(issue) }
47
+ end
48
+
49
+ # options
50
+ # - state ~ future, active, closed, you can define multiple states separated by commas, e.g. state=active,closed
51
+ # - maxResults ~ default: 50 (JIRA API), 1000 (this library)
52
+ # - startAt ~ base index, starts at 0
53
+ def sprints(options = {})
54
+ # options.reverse_merge!(DEFAULT_OPTIONS)
55
+ response = client.get(path_base(client) + "/board/#{id}/sprint?#{options.to_query}")
56
+ json = self.class.parse_json(response.body)
57
+ json['values'].map do |sprint|
58
+ sprint['rapidview_id'] = id
59
+ client.Sprint.build(sprint)
60
+ end
61
+ end
62
+
63
+ def project
64
+ response = client.get(path_base(client) + "/board/#{id}/project")
65
+ json = self.class.parse_json(response.body)
66
+ json['values'][0]
67
+ end
68
+
69
+ def add_issue_to_backlog(issue)
70
+ client.post(path_base(client) + '/backlog/issue', { issues: [issue.id] }.to_json)
71
+ end
72
+
73
+ private
74
+
75
+ def self.path_base(client)
76
+ client.options[:context_path] + '/rest/agile/1.0'
77
+ end
78
+
79
+ def path_base(client)
80
+ self.class.path_base(client)
81
+ end
82
+ end
83
+ end
84
+ end