jira-ruby 2.3.0 → 3.2.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 (138) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +20 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  4. data/.github/dependabot.yml +6 -0
  5. data/.github/workflows/CI.yml +30 -0
  6. data/.github/workflows/codeql.yml +96 -0
  7. data/.github/workflows/rubocop.yml +18 -0
  8. data/.gitignore +3 -1
  9. data/.rubocop.yml +123 -0
  10. data/.yardopts +4 -0
  11. data/Gemfile +11 -3
  12. data/Guardfile +2 -0
  13. data/README.md +94 -19
  14. data/Rakefile +3 -4
  15. data/jira-ruby.gemspec +12 -17
  16. data/lib/jira/atlassian/jwt.rb +76 -0
  17. data/lib/jira/base.rb +37 -36
  18. data/lib/jira/base_factory.rb +4 -1
  19. data/lib/jira/client.rb +132 -51
  20. data/lib/jira/has_many_proxy.rb +32 -28
  21. data/lib/jira/http_client.rb +80 -13
  22. data/lib/jira/http_error.rb +4 -0
  23. data/lib/jira/jwt_client.rb +19 -43
  24. data/lib/jira/oauth_client.rb +68 -3
  25. data/lib/jira/railtie.rb +2 -0
  26. data/lib/jira/request_client.rb +31 -2
  27. data/lib/jira/resource/agile.rb +7 -9
  28. data/lib/jira/resource/applinks.rb +5 -3
  29. data/lib/jira/resource/attachment.rb +144 -4
  30. data/lib/jira/resource/board.rb +5 -3
  31. data/lib/jira/resource/board_configuration.rb +2 -0
  32. data/lib/jira/resource/comment.rb +14 -0
  33. data/lib/jira/resource/component.rb +2 -0
  34. data/lib/jira/resource/createmeta.rb +7 -5
  35. data/lib/jira/resource/field.rb +13 -12
  36. data/lib/jira/resource/filter.rb +2 -0
  37. data/lib/jira/resource/issue.rb +148 -54
  38. data/lib/jira/resource/issue_picker_suggestions.rb +4 -1
  39. data/lib/jira/resource/issue_picker_suggestions_issue.rb +2 -0
  40. data/lib/jira/resource/issuelink.rb +6 -3
  41. data/lib/jira/resource/issuelinktype.rb +2 -0
  42. data/lib/jira/resource/issuetype.rb +2 -0
  43. data/lib/jira/resource/priority.rb +2 -0
  44. data/lib/jira/resource/project.rb +4 -2
  45. data/lib/jira/resource/properties.rb +56 -0
  46. data/lib/jira/resource/rapidview.rb +5 -3
  47. data/lib/jira/resource/remotelink.rb +2 -0
  48. data/lib/jira/resource/resolution.rb +2 -0
  49. data/lib/jira/resource/serverinfo.rb +2 -0
  50. data/lib/jira/resource/sprint.rb +14 -23
  51. data/lib/jira/resource/status.rb +7 -1
  52. data/lib/jira/resource/status_category.rb +10 -0
  53. data/lib/jira/resource/suggested_issue.rb +2 -0
  54. data/lib/jira/resource/transition.rb +2 -0
  55. data/lib/jira/resource/user.rb +3 -1
  56. data/lib/jira/resource/version.rb +2 -0
  57. data/lib/jira/resource/watcher.rb +3 -2
  58. data/lib/jira/resource/webhook.rb +9 -3
  59. data/lib/jira/resource/worklog.rb +15 -2
  60. data/lib/jira/version.rb +3 -1
  61. data/lib/jira-ruby.rb +6 -3
  62. data/lib/tasks/generate.rake +3 -1
  63. data/spec/data/files/jwt-signed-urls.json +317 -0
  64. data/spec/data/files/short.txt +1 -0
  65. data/spec/integration/attachment_spec.rb +3 -3
  66. data/spec/integration/comment_spec.rb +8 -8
  67. data/spec/integration/component_spec.rb +7 -7
  68. data/spec/integration/field_spec.rb +3 -3
  69. data/spec/integration/issue_spec.rb +21 -18
  70. data/spec/integration/issuelinktype_spec.rb +3 -3
  71. data/spec/integration/issuetype_spec.rb +3 -3
  72. data/spec/integration/priority_spec.rb +3 -3
  73. data/spec/integration/project_spec.rb +8 -8
  74. data/spec/integration/properties_spec.rb +45 -0
  75. data/spec/integration/rapidview_spec.rb +10 -10
  76. data/spec/integration/resolution_spec.rb +3 -3
  77. data/spec/integration/status_category_spec.rb +20 -0
  78. data/spec/integration/status_spec.rb +4 -8
  79. data/spec/integration/transition_spec.rb +5 -4
  80. data/spec/integration/user_spec.rb +34 -11
  81. data/spec/integration/version_spec.rb +7 -7
  82. data/spec/integration/watcher_spec.rb +21 -18
  83. data/spec/integration/webhook_spec.rb +35 -0
  84. data/spec/integration/worklog_spec.rb +8 -8
  85. data/spec/jira/atlassian/jwt_spec.rb +60 -0
  86. data/spec/jira/base_factory_spec.rb +13 -3
  87. data/spec/jira/base_spec.rb +135 -98
  88. data/spec/jira/client_spec.rb +72 -56
  89. data/spec/jira/has_many_proxy_spec.rb +3 -3
  90. data/spec/jira/http_client_spec.rb +94 -27
  91. data/spec/jira/http_error_spec.rb +2 -2
  92. data/spec/jira/oauth_client_spec.rb +14 -8
  93. data/spec/jira/request_client_spec.rb +4 -4
  94. data/spec/jira/resource/agile_spec.rb +32 -32
  95. data/spec/jira/resource/attachment_spec.rb +170 -57
  96. data/spec/jira/resource/board_spec.rb +24 -23
  97. data/spec/jira/resource/createmeta_spec.rb +48 -48
  98. data/spec/jira/resource/field_spec.rb +44 -27
  99. data/spec/jira/resource/filter_spec.rb +7 -6
  100. data/spec/jira/resource/issue_picker_suggestions_spec.rb +17 -17
  101. data/spec/jira/resource/issue_spec.rb +159 -93
  102. data/spec/jira/resource/jira_picker_suggestions_issue_spec.rb +3 -3
  103. data/spec/jira/resource/project_factory_spec.rb +3 -2
  104. data/spec/jira/resource/project_spec.rb +16 -16
  105. data/spec/jira/resource/sprint_spec.rb +88 -9
  106. data/spec/jira/resource/status_spec.rb +21 -0
  107. data/spec/jira/resource/user_factory_spec.rb +5 -5
  108. data/spec/jira/resource/worklog_spec.rb +4 -4
  109. data/spec/mock_responses/board/1_issues.json +2 -1
  110. data/spec/mock_responses/issue/10002/properties/foo.json +4 -0
  111. data/spec/mock_responses/issue/10002/properties/xyz.json +4 -0
  112. data/spec/mock_responses/issue/10002/properties/xyz.put.json +4 -0
  113. data/spec/mock_responses/issue/10002/properties.json +12 -0
  114. data/spec/mock_responses/issue.json +1 -0
  115. data/spec/mock_responses/rapidview/SAMPLEPROJECT.issues.full.json +2 -1
  116. data/spec/mock_responses/rapidview/SAMPLEPROJECT.issues.json +2 -1
  117. data/spec/mock_responses/sprint/1.json +13 -0
  118. data/spec/mock_responses/status/1.json +8 -1
  119. data/spec/mock_responses/status.json +40 -5
  120. data/spec/mock_responses/statuscategory/1.json +7 -0
  121. data/spec/mock_responses/statuscategory.json +30 -0
  122. data/spec/mock_responses/{user_username=admin.json → user_accountId=1234567890abcdef01234567.json} +2 -1
  123. data/spec/spec_helper.rb +1 -0
  124. data/spec/support/clients_helper.rb +5 -7
  125. data/spec/support/mock_client.rb +9 -0
  126. data/spec/support/mock_response.rb +8 -0
  127. data/spec/support/shared_examples/integration.rb +51 -58
  128. metadata +45 -257
  129. data/.travis.yml +0 -9
  130. data/example.rb +0 -232
  131. data/http-basic-example.rb +0 -113
  132. data/lib/jira/resource/sprint_report.rb +0 -8
  133. data/lib/jira/tasks.rb +0 -0
  134. data/spec/integration/webhook.rb +0 -25
  135. data/spec/jira/jwt_uri_builder_spec.rb +0 -59
  136. data/spec/mock_responses/jira/rest/webhooks/1.0/webhook.json +0 -11
  137. data/spec/mock_responses/webhook/webhook.json +0 -11
  138. /data/spec/mock_responses/{jira/rest/webhooks/1.0/webhook → webhook}/2.json +0 -0
data/lib/jira/base.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/core_ext/string'
2
4
  require 'active_support/inflector'
3
5
  require 'set'
@@ -81,8 +83,8 @@ module JIRA
81
83
  if options[relation]
82
84
  instance_variable_set("@#{relation}", options[relation])
83
85
  instance_variable_set("@#{relation}_id", options[relation].key_value)
84
- elsif options["#{relation}_id".to_sym]
85
- instance_variable_set("@#{relation}_id", options["#{relation}_id".to_sym])
86
+ elsif options[:"#{relation}_id"]
87
+ instance_variable_set("@#{relation}_id", options[:"#{relation}_id"])
86
88
  else
87
89
  raise ArgumentError, "Required option #{relation.inspect} missing" unless options[relation]
88
90
  end
@@ -96,7 +98,7 @@ module JIRA
96
98
  json = parse_json(response.body)
97
99
  json = json[endpoint_name.pluralize] if collection_attributes_are_nested
98
100
  json.map do |attrs|
99
- new(client, { attrs: attrs }.merge(options))
101
+ new(client, { attrs: }.merge(options))
100
102
  end
101
103
  end
102
104
 
@@ -111,7 +113,7 @@ module JIRA
111
113
  # Builds a new instance of the resource with the given attributes.
112
114
  # These attributes will be posted to the JIRA Api if save is called.
113
115
  def self.build(client, attrs)
114
- new(client, attrs: attrs)
116
+ new(client, attrs:)
115
117
  end
116
118
 
117
119
  # Returns the name of this resource for use in URL components.
@@ -141,7 +143,7 @@ module JIRA
141
143
  # JIRA::Resource::Comment.singular_path('456','/issue/123/')
142
144
  # # => /jira/rest/api/2/issue/123/comment/456
143
145
  def self.singular_path(client, key, prefix = '/')
144
- collection_path(client, prefix) + '/' + key
146
+ "#{collection_path(client, prefix)}/#{key}"
145
147
  end
146
148
 
147
149
  # Returns the attribute name of the attribute used for find.
@@ -193,10 +195,11 @@ module JIRA
193
195
  # # => Looks for {"foo":{"bar":{"baz":{"child":{}}}}}
194
196
  def self.has_one(resource, options = {})
195
197
  attribute_key = options[:attribute_key] || resource.to_s
196
- child_class = options[:class] || ('JIRA::Resource::' + resource.to_s.classify).constantize
198
+ child_class = options[:class] || "JIRA::Resource::#{resource.to_s.classify}".constantize
197
199
  define_method(resource) do
198
200
  attribute = maybe_nested_attribute(attribute_key, options[:nested_under])
199
201
  return nil unless attribute
202
+
200
203
  child_class.new(client, attrs: attribute)
201
204
  end
202
205
  end
@@ -244,7 +247,7 @@ module JIRA
244
247
  # # => Looks for {"foo":{"bar":{"baz":{"children":{}}}}}
245
248
  def self.has_many(collection, options = {})
246
249
  attribute_key = options[:attribute_key] || collection.to_s
247
- child_class = options[:class] || ('JIRA::Resource::' + collection.to_s.classify).constantize
250
+ child_class = options[:class] || "JIRA::Resource::#{collection.to_s.classify}".constantize
248
251
  self_class_basename = name.split('::').last.downcase.to_sym
249
252
  define_method(collection) do
250
253
  child_class_options = { self_class_basename => self }
@@ -288,11 +291,7 @@ module JIRA
288
291
  # and returns true when found, otherwise proxies the
289
292
  # call to the superclass.
290
293
  def respond_to?(method_name, _include_all = false)
291
- if attrs.key?(method_name.to_s)
292
- true
293
- else
294
- super(method_name)
295
- end
294
+ attrs.key?(method_name.to_s) || super(method_name)
296
295
  end
297
296
 
298
297
  # Overrides method_missing to check the attribute hash
@@ -322,7 +321,7 @@ module JIRA
322
321
  # issue it returns '/issue'
323
322
  def path_component
324
323
  path_component = "/#{self.class.endpoint_name}"
325
- path_component += '/' + key_value if key_value
324
+ path_component += "/#{key_value}" if key_value
326
325
  path_component
327
326
  end
328
327
 
@@ -331,6 +330,7 @@ module JIRA
331
330
  # is not set
332
331
  def fetch(reload = false, query_params = {})
333
332
  return if expanded? && !reload
333
+
334
334
  response = client.get(url_with_query_params(url, query_params))
335
335
  set_attrs_from_response(response)
336
336
  @expanded = true
@@ -359,14 +359,14 @@ module JIRA
359
359
  def save(attrs, path = url)
360
360
  begin
361
361
  save_status = save!(attrs, path)
362
- rescue JIRA::HTTPError => exception
362
+ rescue JIRA::HTTPError => e
363
363
  begin
364
- set_attrs_from_response(exception.response) # Merge error status generated by JIRA REST API
365
- rescue JSON::ParserError => parse_exception
364
+ set_attrs_from_response(e.response) # Merge error status generated by JIRA REST API
365
+ rescue JSON::ParserError
366
366
  set_attrs('exception' => {
367
- 'class' => exception.response.class.name,
368
- 'code' => exception.response.code,
369
- 'message' => exception.response.message
367
+ 'class' => e.response.class.name,
368
+ 'code' => e.response.code,
369
+ 'message' => e.response.message
370
370
  })
371
371
  end
372
372
  # raise exception
@@ -378,10 +378,10 @@ module JIRA
378
378
  # Sets the attributes hash from a HTTPResponse object from JIRA if it is
379
379
  # not nil or is not a json response.
380
380
  def set_attrs_from_response(response)
381
- unless response.body.nil? || (response.body.length < 2)
382
- json = self.class.parse_json(response.body)
383
- set_attrs(json)
384
- end
381
+ return if response.body.nil? || (response.body.length < 2)
382
+
383
+ json = self.class.parse_json(response.body)
384
+ set_attrs(json)
385
385
  end
386
386
 
387
387
  # Set the current attributes from a hash. If clobber is true, any existing
@@ -419,7 +419,7 @@ module JIRA
419
419
  prefix = '/'
420
420
  unless self.class.belongs_to_relationships.empty?
421
421
  prefix = self.class.belongs_to_relationships.inject(prefix) do |prefix_so_far, relationship|
422
- prefix_so_far.to_s + relationship.to_s + '/' + send("#{relationship}_id").to_s + '/'
422
+ "#{prefix_so_far}#{relationship}/#{send("#{relationship}_id")}/"
423
423
  end
424
424
  end
425
425
  if @attrs['self']
@@ -434,7 +434,8 @@ module JIRA
434
434
  end
435
435
 
436
436
  # This method fixes issue that there is no / prefix in url. It is happened when we call for instance
437
- # Looks like this issue is actual only in case if you use atlassian sdk your app path is not root (like /jira in example below)
437
+ # Looks like this issue is actual only in case if you use atlassian sdk your app pathis not root
438
+ # (like /jira in example below)
438
439
  # issue.save() for existing resource.
439
440
  # As a result we got error 400 from JIRA API:
440
441
  # [07/Jun/2015:15:32:19 +0400] "PUT jira/rest/api/2/issue/10111 HTTP/1.1" 400 -
@@ -443,6 +444,7 @@ module JIRA
443
444
  def patched_url
444
445
  result = url
445
446
  return result if result.start_with?('/', 'http')
447
+
446
448
  "/#{result}"
447
449
  end
448
450
 
@@ -476,15 +478,18 @@ module JIRA
476
478
 
477
479
  def self.maybe_nested_attribute(attributes, attribute_name, nested_under = nil)
478
480
  return attributes[attribute_name] if nested_under.nil?
481
+
479
482
  if nested_under.instance_of? Array
480
483
  final = nested_under.inject(attributes) do |parent, key|
481
484
  break if parent.nil?
485
+
482
486
  parent[key]
483
487
  end
484
488
  return nil if final.nil?
489
+
485
490
  final[attribute_name]
486
491
  else
487
- return attributes[nested_under][attribute_name]
492
+ attributes[nested_under][attribute_name]
488
493
  end
489
494
  end
490
495
 
@@ -493,10 +498,10 @@ module JIRA
493
498
  end
494
499
 
495
500
  def self.url_with_query_params(url, query_params)
496
- if !query_params.empty?
497
- "#{url}?#{hash_to_query_string query_params}"
498
- else
501
+ if query_params.empty?
499
502
  url
503
+ else
504
+ "#{url}?#{hash_to_query_string query_params}"
500
505
  end
501
506
  end
502
507
 
@@ -506,20 +511,16 @@ module JIRA
506
511
 
507
512
  def self.hash_to_query_string(query_params)
508
513
  query_params.map do |k, v|
509
- CGI.escape(k.to_s) + '=' + CGI.escape(v.to_s)
514
+ "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"
510
515
  end.join('&')
511
516
  end
512
517
 
513
518
  def self.query_params_for_single_fetch(options)
514
- Hash[options.select do |k, _v|
515
- QUERY_PARAMS_FOR_SINGLE_FETCH.include? k
516
- end]
519
+ options.slice(*QUERY_PARAMS_FOR_SINGLE_FETCH)
517
520
  end
518
521
 
519
522
  def self.query_params_for_search(options)
520
- Hash[options.select do |k, _v|
521
- QUERY_PARAMS_FOR_SEARCH.include? k
522
- end]
523
+ options.slice(*QUERY_PARAMS_FOR_SEARCH)
523
524
  end
524
525
  end
525
526
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JIRA
2
4
  # This is the base class for all the JIRA resource factory instances.
3
5
  class BaseFactory
@@ -36,7 +38,8 @@ module JIRA
36
38
  # The principle purpose of this class is to delegate methods to the corresponding
37
39
  # non-factory class and automatically prepend the client argument to the argument
38
40
  # list.
39
- delegate_to_target_class :all, :find, :collection_path, :singular_path, :jql, :get_backlog_issues, :get_board_issues, :get_sprints, :get_sprint_issues, :get_projects, :get_projects_full
41
+ delegate_to_target_class :all, :find, :collection_path, :singular_path, :jql, :jql_paged, :get_backlog_issues,
42
+ :get_board_issues, :get_sprints, :get_sprint_issues, :get_projects, :get_projects_full
40
43
 
41
44
  # This method needs special handling as it has a default argument value
42
45
  def build(attrs = {})
data/lib/jira/client.rb CHANGED
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
2
4
  require 'forwardable'
3
- require 'ostruct'
4
5
 
5
6
  module JIRA
6
7
  # This class is the main access point for all JIRA::Resource instances.
@@ -34,6 +35,7 @@ module JIRA
34
35
  # :default_headers => {},
35
36
  # :use_client_cert => false,
36
37
  # :read_timeout => nil,
38
+ # :max_retries => nil,
37
39
  # :http_debug => false,
38
40
  # :shared_secret => nil,
39
41
  # :cert_path => nil,
@@ -52,48 +54,50 @@ module JIRA
52
54
  #
53
55
  # The authenticated client instance returned by the respective client type
54
56
  # (Oauth, Basic)
55
- attr_accessor :consumer, :request_client, :http_debug, :cache
57
+ attr_accessor :consumer, :request_client, :http_debug, :field_map_cache
56
58
 
57
59
  # The configuration options for this client instance
58
60
  attr_reader :options
59
61
 
60
- def_delegators :@request_client, :init_access_token, :set_access_token, :set_request_token, :request_token, :access_token, :authenticated?
61
-
62
- DEFINED_OPTIONS = [
63
- :site,
64
- :context_path,
65
- :signature_method,
66
- :request_token_path,
67
- :authorize_path,
68
- :access_token_path,
69
- :private_key,
70
- :private_key_file,
71
- :rest_base_path,
72
- :consumer_key,
73
- :consumer_secret,
74
- :ssl_verify_mode,
75
- :ssl_version,
76
- :use_ssl,
77
- :username,
78
- :password,
79
- :auth_type,
80
- :proxy_address,
81
- :proxy_port,
82
- :proxy_username,
83
- :proxy_password,
84
- :use_cookies,
85
- :additional_cookies,
86
- :default_headers,
87
- :use_client_cert,
88
- :read_timeout,
89
- :http_debug,
90
- :issuer,
91
- :base_url,
92
- :shared_secret,
93
- :cert_path,
94
- :key_path,
95
- :ssl_client_cert,
96
- :ssl_client_key
62
+ def_delegators :@request_client, :init_access_token, :set_access_token, :set_request_token, :request_token,
63
+ :access_token, :authenticated?
64
+
65
+ DEFINED_OPTIONS = %i[
66
+ site
67
+ context_path
68
+ signature_method
69
+ request_token_path
70
+ authorize_path
71
+ access_token_path
72
+ private_key
73
+ private_key_file
74
+ rest_base_path
75
+ consumer_key
76
+ consumer_secret
77
+ ssl_verify_mode
78
+ ssl_version
79
+ use_ssl
80
+ username
81
+ password
82
+ auth_type
83
+ proxy_address
84
+ proxy_port
85
+ proxy_username
86
+ proxy_password
87
+ use_cookies
88
+ additional_cookies
89
+ default_headers
90
+ use_client_cert
91
+ read_timeout
92
+ max_retries
93
+ http_debug
94
+ issuer
95
+ base_url
96
+ shared_secret
97
+ cert_path
98
+ key_path
99
+ ssl_client_cert
100
+ ssl_client_key
97
101
  ].freeze
98
102
 
99
103
  DEFAULT_OPTIONS = {
@@ -108,6 +112,23 @@ module JIRA
108
112
  default_headers: {}
109
113
  }.freeze
110
114
 
115
+ # Creates a new JIRA::Client instance
116
+ # @param [Hash] options The configuration options for the client
117
+ # @option options [Symbol] :auth_type The authentication type to use, :basic, :oauth, :oauth_2legged, or :cookie.
118
+ # @option options [String] :site The base URL for the JIRA instance
119
+ # @option options [Boolean] :use_ssl Whether to use HTTPS for requests
120
+ # @option options [Integer] :ssl_verify_mode The SSL verification mode OpenSSL::SSL::VERIFY_PEER or OpenSSL::SSL::VERIFY_NONE
121
+ # @option options [Hash] :default_headers Additional headers to send with each request
122
+ # @option options [String] :cert_path The path to the SSL certificate file to verify the server with
123
+ # @option options [String] :use_client_cert Whether to use a client certificate for authentication
124
+ # @option options [String] :ssl_client_cert The client certificate to use
125
+ # @option options [String] :ssl_client_key The client certificate key to use
126
+ # @option options [String] :proxy_address The address of the proxy server to use
127
+ # @option options [Integer] :proxy_port The port of the proxy server to use
128
+ # @option options [String] :proxy_username The username for the proxy server
129
+ # @option options [String] :proxy_password The password for the proxy server
130
+ # @return [JIRA::Client] The client instance
131
+ # @raise [ArgumentError] If an unknown option is given
111
132
  def initialize(options = {})
112
133
  options = DEFAULT_OPTIONS.merge(options)
113
134
  @options = options
@@ -117,11 +138,20 @@ module JIRA
117
138
  raise ArgumentError, "Unknown option(s) given: #{unknown_options}" unless unknown_options.empty?
118
139
 
119
140
  if options[:use_client_cert]
120
- @options[:ssl_client_cert] = OpenSSL::X509::Certificate.new(File.read(@options[:cert_path])) if @options[:cert_path]
141
+ if @options[:cert_path]
142
+ @options[:ssl_client_cert] =
143
+ OpenSSL::X509::Certificate.new(File.read(@options[:cert_path]))
144
+ end
121
145
  @options[:ssl_client_key] = OpenSSL::PKey::RSA.new(File.read(@options[:key_path])) if @options[:key_path]
122
146
 
123
- raise ArgumentError, 'Options: :cert_path or :ssl_client_cert must be set when :use_client_cert is true' unless @options[:ssl_client_cert]
124
- raise ArgumentError, 'Options: :key_path or :ssl_client_key must be set when :use_client_cert is true' unless @options[:ssl_client_key]
147
+ unless @options[:ssl_client_cert]
148
+ raise ArgumentError,
149
+ 'Options: :cert_path or :ssl_client_cert must be set when :use_client_cert is true'
150
+ end
151
+ unless @options[:ssl_client_key]
152
+ raise ArgumentError,
153
+ 'Options: :key_path or :ssl_client_key must be set when :use_client_cert is true'
154
+ end
125
155
  end
126
156
 
127
157
  case options[:auth_type]
@@ -133,7 +163,11 @@ module JIRA
133
163
  when :basic
134
164
  @request_client = HttpClient.new(@options)
135
165
  when :cookie
136
- raise ArgumentError, 'Options: :use_cookies must be true for :cookie authorization type' if @options.key?(:use_cookies) && !@options[:use_cookies]
166
+ if @options.key?(:use_cookies) && !@options[:use_cookies]
167
+ raise ArgumentError,
168
+ 'Options: :use_cookies must be true for :cookie authorization type'
169
+ end
170
+
137
171
  @options[:use_cookies] = true
138
172
  @request_client = HttpClient.new(@options)
139
173
  @request_client.make_cookie_auth_request
@@ -146,8 +180,6 @@ module JIRA
146
180
  @http_debug = @options[:http_debug]
147
181
 
148
182
  @options.freeze
149
-
150
- @cache = OpenStruct.new
151
183
  end
152
184
 
153
185
  def Project # :nodoc:
@@ -182,6 +214,10 @@ module JIRA
182
214
  JIRA::Resource::StatusFactory.new(self)
183
215
  end
184
216
 
217
+ def StatusCategory # :nodoc:
218
+ JIRA::Resource::StatusCategoryFactory.new(self)
219
+ end
220
+
185
221
  def Resolution # :nodoc:
186
222
  JIRA::Resource::ResolutionFactory.new(self)
187
223
  end
@@ -226,10 +262,6 @@ module JIRA
226
262
  JIRA::Resource::SprintFactory.new(self)
227
263
  end
228
264
 
229
- def SprintReport
230
- JIRA::Resource::SprintReportFactory.new(self)
231
- end
232
-
233
265
  def ServerInfo
234
266
  JIRA::Resource::ServerInfoFactory.new(self)
235
267
  end
@@ -270,30 +302,70 @@ module JIRA
270
302
  JIRA::Resource::AgileFactory.new(self)
271
303
  end
272
304
 
305
+ def Properties
306
+ JIRA::Resource::PropertiesFactory.new(self)
307
+ end
308
+
273
309
  # HTTP methods without a body
310
+
311
+ # Make an HTTP DELETE request
312
+ # @param [String] path The path to request
313
+ # @param [Hash] headers The headers to send with the request
314
+ # @return [Net::HTTPResponse] The response object
315
+ # @raise [JIRA::HTTPError] If the response is not an HTTP success code
274
316
  def delete(path, headers = {})
275
317
  request(:delete, path, nil, merge_default_headers(headers))
276
318
  end
277
319
 
320
+ # Make an HTTP GET request
321
+ # @param [String] path The path to request
322
+ # @param [Hash] headers The headers to send with the request
323
+ # @return [Net::HTTPResponse] The response object
324
+ # @raise [JIRA::HTTPError] If the response is not an HTTP success code
278
325
  def get(path, headers = {})
279
326
  request(:get, path, nil, merge_default_headers(headers))
280
327
  end
281
328
 
329
+ # Make an HTTP HEAD request
330
+ # @param [String] path The path to request
331
+ # @param [Hash] headers The headers to send with the request
332
+ # @return [Net::HTTPResponse] The response object
333
+ # @raise [JIRA::HTTPError] If the response is not an HTTP success code
282
334
  def head(path, headers = {})
283
335
  request(:head, path, nil, merge_default_headers(headers))
284
336
  end
285
337
 
286
338
  # HTTP methods with a body
339
+
340
+ # Make an HTTP POST request
341
+ # @param [String] path The path to request
342
+ # @param [String] body The body of the request
343
+ # @param [Hash] headers The headers to send with the request
344
+ # @return [Net::HTTPResponse] The response object
287
345
  def post(path, body = '', headers = {})
288
346
  headers = { 'Content-Type' => 'application/json' }.merge(headers)
289
347
  request(:post, path, body, merge_default_headers(headers))
290
348
  end
291
349
 
350
+ # Make an HTTP POST request with a file upload
351
+ # @param [String] path The path to request
352
+ # @param [String] file The file to upload
353
+ # @param [Hash] headers The headers to send with the request
354
+ # @return [Net::HTTPResponse] The response object
355
+ # @raise [JIRA::HTTPError] If the response is not an HTTP success code
292
356
  def post_multipart(path, file, headers = {})
293
357
  puts "post multipart: #{path} - [#{file}]" if @http_debug
294
- @request_client.request_multipart(path, file, headers)
358
+ res = @request_client.request_multipart(path, file, merge_default_headers(headers))
359
+ puts "response: #{res}" if @http_debug
360
+ res
295
361
  end
296
362
 
363
+ # Make an HTTP PUT request
364
+ # @param [String] path The path to request
365
+ # @param [String] body The body of the request
366
+ # @param [Hash] headers The headers to send with the request
367
+ # @return [Net::HTTPResponse] The response object
368
+ # @raise [JIRA::HTTPError] If the response is not an HTTP success code
297
369
  def put(path, body = '', headers = {})
298
370
  headers = { 'Content-Type' => 'application/json' }.merge(headers)
299
371
  request(:put, path, body, merge_default_headers(headers))
@@ -301,11 +373,20 @@ module JIRA
301
373
 
302
374
  # Sends the specified HTTP request to the REST API through the
303
375
  # appropriate method (oauth, basic).
376
+ # @param [Symbol] http_method The HTTP method to use
377
+ # @param [String] path The path to request
378
+ # @param [String] body The body of the request
379
+ # @param [Hash] headers The headers to send with the request
380
+ # @return [Net::HTTPResponse] The response object
381
+ # @raise [JIRA::HTTPError] If the response is not an HTTP success code
304
382
  def request(http_method, path, body = '', headers = {})
305
383
  puts "#{http_method}: #{path} - [#{body}]" if @http_debug
306
- @request_client.request(http_method, path, body, headers)
384
+ res = @request_client.request(http_method, path, body, headers)
385
+ puts "response: #{res}" if @http_debug
386
+ res
307
387
  end
308
388
 
389
+ # @private
309
390
  # Stops sensitive client information from being displayed in logs
310
391
  def inspect
311
392
  "#<JIRA::Client:#{object_id}>"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # Whenever a collection from a has_many relationship is accessed, an instance
3
5
  # of this class is returned. This instance wraps the Array of instances in
@@ -6,37 +8,39 @@
6
8
  #
7
9
  # In practice, instances of this class behave exactly like an Array.
8
10
  #
9
- class JIRA::HasManyProxy
10
- attr_reader :target_class, :parent
11
- attr_accessor :collection
11
+ module JIRA
12
+ class HasManyProxy
13
+ attr_reader :target_class, :parent
14
+ attr_accessor :collection
12
15
 
13
- def initialize(parent, target_class, collection = [])
14
- @parent = parent
15
- @target_class = target_class
16
- @collection = collection
17
- end
16
+ def initialize(parent, target_class, collection = [])
17
+ @parent = parent
18
+ @target_class = target_class
19
+ @collection = collection
20
+ end
18
21
 
19
- # Builds an instance of this class with the correct parent.
20
- # For example, issue.comments.build(attrs) will initialize a
21
- # comment as follows:
22
- #
23
- # JIRA::Resource::Comment.new(issue.client,
24
- # :attrs => attrs,
25
- # :issue => issue)
26
- def build(attrs = {})
27
- resource = target_class.new(parent.client, :attrs => attrs, parent.to_sym => parent)
28
- collection << resource
29
- resource
30
- end
22
+ # Builds an instance of this class with the correct parent.
23
+ # For example, issue.comments.build(attrs) will initialize a
24
+ # comment as follows:
25
+ #
26
+ # JIRA::Resource::Comment.new(issue.client,
27
+ # :attrs => attrs,
28
+ # :issue => issue)
29
+ def build(attrs = {})
30
+ resource = target_class.new(parent.client, :attrs => attrs, parent.to_sym => parent)
31
+ collection << resource
32
+ resource
33
+ end
31
34
 
32
- # Forces an HTTP request to fetch all instances of the target class that
33
- # are associated with the parent
34
- def all
35
- target_class.all(parent.client, parent.to_sym => parent)
36
- end
35
+ # Forces an HTTP request to fetch all instances of the target class that
36
+ # are associated with the parent
37
+ def all
38
+ target_class.all(parent.client, parent.to_sym => parent)
39
+ end
37
40
 
38
- # Delegate any missing methods to the collection that this proxy wraps
39
- def method_missing(method_name, *args, &block)
40
- collection.send(method_name, *args, &block)
41
+ # Delegate any missing methods to the collection that this proxy wraps
42
+ def method_missing(method_name, ...)
43
+ collection.send(method_name, ...)
44
+ end
41
45
  end
42
46
  end