jira-ruby 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
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