jira-ruby 2.3.0 → 3.0.0.beta2

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 (122) 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 +29 -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 +120 -0
  10. data/.yardopts +4 -0
  11. data/Gemfile +11 -3
  12. data/Guardfile +2 -0
  13. data/README.md +94 -18
  14. data/Rakefile +3 -4
  15. data/jira-ruby.gemspec +11 -17
  16. data/lib/jira/base.rb +37 -36
  17. data/lib/jira/base_factory.rb +4 -1
  18. data/lib/jira/client.rb +123 -50
  19. data/lib/jira/has_many_proxy.rb +32 -28
  20. data/lib/jira/http_client.rb +80 -13
  21. data/lib/jira/http_error.rb +4 -0
  22. data/lib/jira/jwt_client.rb +18 -42
  23. data/lib/jira/oauth_client.rb +68 -3
  24. data/lib/jira/railtie.rb +2 -0
  25. data/lib/jira/request_client.rb +31 -2
  26. data/lib/jira/resource/agile.rb +7 -9
  27. data/lib/jira/resource/applinks.rb +5 -3
  28. data/lib/jira/resource/attachment.rb +128 -3
  29. data/lib/jira/resource/board.rb +5 -3
  30. data/lib/jira/resource/board_configuration.rb +2 -0
  31. data/lib/jira/resource/comment.rb +2 -0
  32. data/lib/jira/resource/component.rb +2 -0
  33. data/lib/jira/resource/createmeta.rb +3 -1
  34. data/lib/jira/resource/field.rb +13 -12
  35. data/lib/jira/resource/filter.rb +2 -0
  36. data/lib/jira/resource/issue.rb +95 -44
  37. data/lib/jira/resource/issue_picker_suggestions.rb +4 -1
  38. data/lib/jira/resource/issue_picker_suggestions_issue.rb +2 -0
  39. data/lib/jira/resource/issuelink.rb +6 -3
  40. data/lib/jira/resource/issuelinktype.rb +2 -0
  41. data/lib/jira/resource/issuetype.rb +2 -0
  42. data/lib/jira/resource/priority.rb +2 -0
  43. data/lib/jira/resource/project.rb +4 -2
  44. data/lib/jira/resource/rapidview.rb +5 -3
  45. data/lib/jira/resource/remotelink.rb +2 -0
  46. data/lib/jira/resource/resolution.rb +2 -0
  47. data/lib/jira/resource/serverinfo.rb +2 -0
  48. data/lib/jira/resource/sprint.rb +14 -23
  49. data/lib/jira/resource/status.rb +7 -1
  50. data/lib/jira/resource/status_category.rb +10 -0
  51. data/lib/jira/resource/suggested_issue.rb +2 -0
  52. data/lib/jira/resource/transition.rb +2 -0
  53. data/lib/jira/resource/user.rb +3 -1
  54. data/lib/jira/resource/version.rb +2 -0
  55. data/lib/jira/resource/watcher.rb +3 -2
  56. data/lib/jira/resource/webhook.rb +9 -3
  57. data/lib/jira/resource/worklog.rb +3 -2
  58. data/lib/jira/version.rb +3 -1
  59. data/lib/jira-ruby.rb +5 -3
  60. data/lib/tasks/generate.rake +3 -1
  61. data/spec/data/files/short.txt +1 -0
  62. data/spec/integration/attachment_spec.rb +3 -3
  63. data/spec/integration/comment_spec.rb +8 -8
  64. data/spec/integration/component_spec.rb +7 -7
  65. data/spec/integration/field_spec.rb +3 -3
  66. data/spec/integration/issue_spec.rb +20 -16
  67. data/spec/integration/issuelinktype_spec.rb +3 -3
  68. data/spec/integration/issuetype_spec.rb +3 -3
  69. data/spec/integration/priority_spec.rb +3 -3
  70. data/spec/integration/project_spec.rb +8 -8
  71. data/spec/integration/rapidview_spec.rb +10 -10
  72. data/spec/integration/resolution_spec.rb +3 -3
  73. data/spec/integration/status_category_spec.rb +20 -0
  74. data/spec/integration/status_spec.rb +4 -8
  75. data/spec/integration/transition_spec.rb +2 -2
  76. data/spec/integration/user_spec.rb +34 -11
  77. data/spec/integration/version_spec.rb +7 -7
  78. data/spec/integration/watcher_spec.rb +21 -18
  79. data/spec/integration/webhook_spec.rb +33 -0
  80. data/spec/integration/worklog_spec.rb +8 -8
  81. data/spec/jira/base_factory_spec.rb +13 -3
  82. data/spec/jira/base_spec.rb +135 -98
  83. data/spec/jira/client_spec.rb +63 -47
  84. data/spec/jira/has_many_proxy_spec.rb +3 -3
  85. data/spec/jira/http_client_spec.rb +94 -27
  86. data/spec/jira/http_error_spec.rb +2 -2
  87. data/spec/jira/oauth_client_spec.rb +14 -8
  88. data/spec/jira/request_client_spec.rb +4 -4
  89. data/spec/jira/resource/agile_spec.rb +30 -30
  90. data/spec/jira/resource/attachment_spec.rb +170 -57
  91. data/spec/jira/resource/board_spec.rb +24 -23
  92. data/spec/jira/resource/createmeta_spec.rb +48 -48
  93. data/spec/jira/resource/field_spec.rb +44 -27
  94. data/spec/jira/resource/filter_spec.rb +4 -4
  95. data/spec/jira/resource/issue_picker_suggestions_spec.rb +17 -17
  96. data/spec/jira/resource/issue_spec.rb +49 -43
  97. data/spec/jira/resource/jira_picker_suggestions_issue_spec.rb +3 -3
  98. data/spec/jira/resource/project_factory_spec.rb +3 -2
  99. data/spec/jira/resource/project_spec.rb +14 -14
  100. data/spec/jira/resource/sprint_spec.rb +88 -9
  101. data/spec/jira/resource/status_spec.rb +21 -0
  102. data/spec/jira/resource/user_factory_spec.rb +5 -5
  103. data/spec/jira/resource/worklog_spec.rb +4 -4
  104. data/spec/mock_responses/sprint/1.json +13 -0
  105. data/spec/mock_responses/status/1.json +8 -1
  106. data/spec/mock_responses/status.json +40 -5
  107. data/spec/mock_responses/statuscategory/1.json +7 -0
  108. data/spec/mock_responses/statuscategory.json +30 -0
  109. data/spec/mock_responses/{user_username=admin.json → user_accountId=1234567890abcdef01234567.json} +2 -1
  110. data/spec/spec_helper.rb +1 -0
  111. data/spec/support/clients_helper.rb +3 -5
  112. data/spec/support/mock_client.rb +9 -0
  113. data/spec/support/mock_response.rb +8 -0
  114. data/spec/support/shared_examples/integration.rb +25 -28
  115. metadata +27 -260
  116. data/.travis.yml +0 -9
  117. data/example.rb +0 -232
  118. data/http-basic-example.rb +0 -113
  119. data/lib/jira/resource/sprint_report.rb +0 -8
  120. data/lib/jira/tasks.rb +0 -0
  121. data/spec/integration/webhook.rb +0 -25
  122. data/spec/jira/jwt_uri_builder_spec.rb +0 -59
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'net/http/post/multipart'
4
+ require 'open-uri'
2
5
 
3
6
  module JIRA
4
7
  module Resource
@@ -6,27 +9,149 @@ module JIRA
6
9
  delegate_to_target_class :meta
7
10
  end
8
11
 
12
+ # This class provides the Attachment object <-> REST mapping for JIRA::Resource::Attachment derived class,
13
+ # i.e. the Create, Retrieve, Update, Delete lifecycle methods.
14
+ #
15
+ # == Lifecycle methods
16
+ #
17
+ # === Retrieving a single attachment by Issue and attachment id
18
+ #
19
+ # # Find attachment with id 30076 on issue SUP-3000.
20
+ # issue = JIRA::Resource::Issue.find(client, 'SUP-3000', { fields: 'attachment' } )
21
+ # attachment = issue.attachments.find do |attachment_curr|
22
+ # 30076 == attachment_curr.id.to_i
23
+ # end
24
+ #
25
+ # === Retrieving meta information for an attachments
26
+ #
27
+ # attachment.meta
28
+ #
29
+ # === Retrieving file contents of attachment
30
+ #
31
+ # content = URI.open(attachment.content).read
32
+ # content = attachment.download_contents
33
+ # content = attachment.download_file { |file| file.read }
34
+ #
35
+ # === Adding an attachment to an issue
36
+ #
37
+ # Dir.mktmpdir do |dir|
38
+ # path = File.join(dir, filename)
39
+ # IO.copy_stream(file.path, path)
40
+ #
41
+ # issue = JIRA::Resource::Issue.find(client, 'SUP-3000', { fields: 'attachment' } )
42
+ # attachment = issue.attachments.build
43
+ # attachment.save!( { file: path, mimeType: content_type } )
44
+ # end
45
+ #
46
+ # === Deleting an attachment
47
+ #
48
+ # attachment.delete
49
+ #
50
+ # @!method save(attrs, path = url)
51
+ # Uploads a file as an attachment to its issue.
52
+ #
53
+ # Filename used will be the basename of the given file.
54
+ #
55
+ # @param [Hash] attrs the options to create a message with.
56
+ # @option attrs [IO,String] :file The file to upload, either a file object or a file path to find the file.
57
+ # @option attrs [String] :mimeType The MIME type of the file.
58
+ # @return [Boolean] True if successful, false if failed.
59
+ #
60
+ # @!attribute [r] self
61
+ # @return [String] URL to JSON of this attachment
62
+ # @!attribute [r] filename
63
+ # @return [String] the filename
64
+ # @!attribute [r] author
65
+ # @return [JIRA::Resource::User] the user who created the attachment
66
+ # @!attribute [r] created
67
+ # @return [String] timestamp when the attachment was created
68
+ # @!attribute [r] size
69
+ # @return [Integer] the file size
70
+ # @!attribute [r] mimeType
71
+ # @return [String] MIME of the content type
72
+ # @!attribute [r] content
73
+ # @return [String] URL (not the contents) to download the contents of the attachment
74
+ # @!attribute [r] thumbnail
75
+ # @return [String] URL to download the thumbnail of the attachment
76
+ #
9
77
  class Attachment < JIRA::Base
10
78
  belongs_to :issue
11
79
  has_one :author, class: JIRA::Resource::User
12
80
 
81
+ # @private
13
82
  def self.endpoint_name
14
83
  'attachments'
15
84
  end
16
85
 
86
+ # Gets metadata about attachments on the server.
87
+ # @example Return metadata
88
+ # Attachment.meta(client)
89
+ # => { "enabled" => true, "uploadLimit" => 1000000 }
90
+ # @return [Hash] The metadata for attachments. (See example.)
17
91
  def self.meta(client)
18
- response = client.get(client.options[:rest_base_path] + '/attachment/meta')
92
+ response = client.get("#{client.options[:rest_base_path]}/attachment/meta")
19
93
  parse_json(response.body)
20
94
  end
21
95
 
96
+ # Opens a file streaming the download of the attachment.
97
+ # @example Write file contents to a file.
98
+ # File.open('some-filename', 'wb') do |output|
99
+ # download_file do |file|
100
+ # IO.copy_stream(file, output)
101
+ # end
102
+ # end
103
+ # @example Stream file contents for an HTTP response.
104
+ # response.headers[ "Content-Type" ] = "application/octet-stream"
105
+ # download_file do |file|
106
+ # chunk = file.read(8000)
107
+ # while chunk.present? do
108
+ # response.stream.write(chunk)
109
+ # chunk = file.read(8000)
110
+ # end
111
+ # end
112
+ # response.stream.close
113
+ # @param [Hash] headers Any additional headers to call Jira.
114
+ # @yield |file|
115
+ # @yieldparam [IO] file The IO object streaming the download.
116
+ def download_file(headers = {}, &)
117
+ default_headers = client.options[:default_headers]
118
+ URI.parse(content).open(default_headers.merge(headers), &)
119
+ end
120
+
121
+ # Downloads the file contents as a string object.
122
+ #
123
+ # Note that this reads the contents into a ruby string in memory.
124
+ # A file might be very large so it is recommended to avoid this unless you are certain about doing so.
125
+ # Use the download_file method instead and avoid calling the read method without a limit.
126
+ #
127
+ # @example Save file contents to a string.
128
+ # content = download_contents
129
+ # @param [Hash] headers Any additional headers to call Jira.
130
+ # @return [String,NilClass] The file contents.
131
+ def download_contents(headers = {})
132
+ download_file(headers, &:read)
133
+ end
134
+
135
+ # Uploads a file as an attachment to its issue.
136
+ #
137
+ # Filename used will be the basename of the given file.
138
+ #
139
+ # @example Save a file as an attachment
140
+ # issue = JIRA::Resource::Issue.find(client, 'SUP-3000', { fields: 'attachment' } )
141
+ # attachment = issue.attachments.build
142
+ # attachment.save!( { file: path, mimeType: 'text/plain' } )
143
+ # @param [Hash] attrs the options to create a message with.
144
+ # @option attrs [IO,String] :file The file to upload, either a file object or a file path to find the file.
145
+ # @option attrs [String] :mimeType The MIME type of the file.
146
+ # @raise [JIRA::HTTPError] if failed
22
147
  def save!(attrs, path = url)
23
148
  file = attrs['file'] || attrs[:file] # Keep supporting 'file' parameter as a string for backward compatibility
24
149
  mime_type = attrs[:mimeType] || 'application/binary'
25
150
 
26
151
  headers = { 'X-Atlassian-Token' => 'nocheck' }
27
- data = { 'file' => UploadIO.new(file, mime_type, file) }
152
+ data = { 'file' => Multipart::Post::UploadIO.new(file, mime_type, file) }
28
153
 
29
- response = client.post_multipart(path, data , headers)
154
+ response = client.post_multipart(path, data, headers)
30
155
 
31
156
  set_attributes(attrs, response)
32
157
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'cgi'
2
4
 
3
5
  module JIRA
@@ -7,7 +9,7 @@ module JIRA
7
9
 
8
10
  class Board < JIRA::Base
9
11
  def self.all(client)
10
- path = path_base(client) + '/board'
12
+ path = "#{path_base(client)}/board"
11
13
  response = client.get(path)
12
14
  json = parse_json(response.body)
13
15
  results = json['values']
@@ -74,13 +76,13 @@ module JIRA
74
76
  end
75
77
 
76
78
  def add_issue_to_backlog(issue)
77
- client.post(path_base(client) + '/backlog/issue', { issues: [issue.id] }.to_json)
79
+ client.post("#{path_base(client)}/backlog/issue", { issues: [issue.id] }.to_json)
78
80
  end
79
81
 
80
82
  private
81
83
 
82
84
  def self.path_base(client)
83
- client.options[:context_path] + '/rest/agile/1.0'
85
+ "#{client.options[:context_path]}/rest/agile/1.0"
84
86
  end
85
87
 
86
88
  def path_base(client)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JIRA
2
4
  module Resource
3
5
  class BoardConfigurationFactory < JIRA::BaseFactory # :nodoc:
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JIRA
2
4
  module Resource
3
5
  class CommentFactory < JIRA::BaseFactory # :nodoc:
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JIRA
2
4
  module Resource
3
5
  class ComponentFactory < JIRA::BaseFactory # :nodoc:
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JIRA
2
4
  module Resource
3
5
  class CreatemetaFactory < JIRA::BaseFactory # :nodoc:
@@ -36,7 +38,7 @@ module JIRA
36
38
 
37
39
  json = parse_json(response.body)
38
40
  json['projects'].map do |attrs|
39
- new(client, attrs: attrs)
41
+ new(client, attrs:)
40
42
  end
41
43
  end
42
44
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JIRA
2
4
  module Resource
3
5
  class FieldFactory < JIRA::BaseFactory # :nodoc:
@@ -19,43 +21,42 @@ module JIRA
19
21
 
20
22
  def self.map_fields(client)
21
23
  field_map = {}
22
- field_map_reverse = {}
23
24
  fields = client.Field.all
24
25
 
25
26
  # two pass approach, so that a custom field with the same name
26
27
  # as a system field can't take precedence
27
28
  fields.each do |f|
28
29
  next if f.custom
30
+
29
31
  name = safe_name(f.name)
30
- field_map_reverse[f.id] = [f.name, name] # capture both the official name, and the mapped name
31
32
  field_map[name] = f.id
32
33
  end
33
34
 
34
- fields.each do |f|
35
+ fields.each do |f| # rubocop:disable Style/CombinableLoops
35
36
  next unless f.custom
37
+
36
38
  name = if field_map.key? f.name
37
39
  renamed = safer_name(f.name, f.id)
38
40
  warn "Duplicate Field name #{f.name} #{f.id} - renaming as #{renamed}"
39
41
  renamed
40
42
  else
41
43
  safe_name(f.name)
42
- end
43
- field_map_reverse[f.id] = [f.name, name] # capture both the official name, and the mapped name
44
+ end
44
45
  field_map[name] = f.id
45
46
  end
46
47
 
47
- client.cache.field_map_reverse = field_map_reverse # not sure where this will be used yet, but sure to be useful
48
- client.cache.field_map = field_map
48
+ client.field_map_cache = field_map
49
49
  end
50
50
 
51
51
  def self.field_map(client)
52
- client.cache.field_map
52
+ client.field_map_cache
53
53
  end
54
54
 
55
55
  def self.name_to_id(client, field_name)
56
56
  field_name = field_name.to_s
57
- return field_name unless client.cache.field_map && client.cache.field_map[field_name]
58
- client.cache.field_map[field_name]
57
+ return field_name unless client.field_map_cache && client.field_map_cache[field_name]
58
+
59
+ client.field_map_cache[field_name]
59
60
  end
60
61
 
61
62
  def respond_to?(method_name, _include_all = false)
@@ -66,7 +67,7 @@ module JIRA
66
67
  end
67
68
  end
68
69
 
69
- def method_missing(method_name, *args, &block)
70
+ def method_missing(method_name, *args, &)
70
71
  if attrs.key?(method_name.to_s)
71
72
  attrs[method_name.to_s]
72
73
  else
@@ -74,7 +75,7 @@ module JIRA
74
75
  if attrs.key?(official_name)
75
76
  attrs[official_name]
76
77
  else
77
- super(method_name, *args, &block)
78
+ super
78
79
  end
79
80
  end
80
81
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JIRA
2
4
  module Resource
3
5
  class FilterFactory < JIRA::BaseFactory # :nodoc:
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'cgi'
2
4
  require 'json'
3
5
 
@@ -6,46 +8,59 @@ module JIRA
6
8
  class IssueFactory < JIRA::BaseFactory # :nodoc:
7
9
  end
8
10
 
11
+ # This class provides the Issue object <-> REST mapping for JIRA::Resource::Issue derived class,
12
+ # i.e. the Create, Retrieve, Update, Delete lifecycle methods.
13
+ #
14
+ # == Lifecycle methods
15
+ #
16
+ # === Retrieving all issues
17
+ #
18
+ # client.Issue.all
19
+ #
20
+ # === Retrieving a single issue
21
+ #
22
+ # options = { expand: 'editmeta' }
23
+ # issue = client.Issue.find("SUP-3000", options)
24
+ #
25
+ # === Creating a new issue
26
+ #
27
+ # issue = client.Issue.build(fields: { summary: 'New issue', project: { key: 'SUP' }, issuetype: { name: 'Bug' } })
28
+ # issue.save
29
+ #
30
+ # === Updating an issue
31
+ #
32
+ # issue = client.Issue.find("SUP-3000")
33
+ # issue.save(fields: { summary: 'Updated issue' })
34
+ #
35
+ # === Deleting an issue
36
+ #
37
+ # issue = client.Issue.find("SUP-3000")
38
+ # issue.delete
39
+ #
9
40
  class Issue < JIRA::Base
10
- has_one :reporter, class: JIRA::Resource::User,
11
- nested_under: 'fields'
12
- has_one :assignee, class: JIRA::Resource::User,
13
- nested_under: 'fields'
14
- has_one :project, nested_under: 'fields'
15
-
41
+ has_one :reporter, class: JIRA::Resource::User, nested_under: 'fields'
42
+ has_one :assignee, class: JIRA::Resource::User, nested_under: 'fields'
43
+ has_one :project, nested_under: 'fields'
16
44
  has_one :issuetype, nested_under: 'fields'
17
-
18
- has_one :priority, nested_under: 'fields'
19
-
20
- has_one :status, nested_under: 'fields'
21
-
45
+ has_one :priority, nested_under: 'fields'
46
+ has_one :status, nested_under: 'fields'
47
+ has_one :resolution, nested_under: 'fields'
22
48
  has_many :transitions
23
-
24
49
  has_many :components, nested_under: 'fields'
25
-
26
50
  has_many :comments, nested_under: %w[fields comment]
27
-
28
- has_many :attachments, nested_under: 'fields',
29
- attribute_key: 'attachment'
30
-
31
- has_many :versions, nested_under: 'fields'
32
- has_many :fixVersions, class: JIRA::Resource::Version,
33
- nested_under: 'fields'
34
-
51
+ has_many :attachments, nested_under: 'fields', attribute_key: 'attachment'
52
+ has_many :versions, nested_under: 'fields'
53
+ has_many :fixVersions, class: JIRA::Resource::Version, nested_under: 'fields'
35
54
  has_many :worklogs, nested_under: %w[fields worklog]
36
- has_one :sprint, class: JIRA::Resource::Sprint,
37
- nested_under: 'fields'
38
-
39
- has_many :closed_sprints, class: JIRA::Resource::Sprint,
40
- nested_under: 'fields', attribute_key: 'closedSprints'
41
-
55
+ has_one :sprint, class: JIRA::Resource::Sprint, nested_under: 'fields'
56
+ has_many :closed_sprints, class: JIRA::Resource::Sprint, nested_under: 'fields', attribute_key: 'closedSprints'
42
57
  has_many :issuelinks, nested_under: 'fields'
43
-
44
58
  has_many :remotelink, class: JIRA::Resource::Remotelink
59
+ has_many :watchers, attribute_key: 'watches', nested_under: %w[fields watches]
45
60
 
46
- has_many :watchers, attribute_key: 'watches',
47
- nested_under: %w[fields watches]
48
-
61
+ # Get collection of issues.
62
+ # @param client [JIRA::Client]
63
+ # @return [Array<JIRA::Resource::Issue>]
49
64
  def self.all(client)
50
65
  start_at = 0
51
66
  max_results = 1000
@@ -59,6 +74,7 @@ module JIRA
59
74
  result.push(client.Issue.build(issue))
60
75
  end
61
76
  break if json['issues'].empty?
77
+
62
78
  start_at += json['issues'].size
63
79
  end
64
80
  result
@@ -67,10 +83,14 @@ module JIRA
67
83
  def self.jql(client, jql, options = { fields: nil, start_at: nil, max_results: nil, expand: nil, validate_query: true })
68
84
  url = client.options[:rest_base_path] + "/search?jql=#{CGI.escape(jql)}"
69
85
 
70
- url << "&fields=#{options[:fields].map { |value| CGI.escape(client.Field.name_to_id(value)) }.join(',')}" if options[:fields]
86
+ if options[:fields]
87
+ url << "&fields=#{options[:fields].map do |value|
88
+ CGI.escape(client.Field.name_to_id(value))
89
+ end.join(',')}"
90
+ end
71
91
  url << "&startAt=#{CGI.escape(options[:start_at].to_s)}" if options[:start_at]
72
92
  url << "&maxResults=#{CGI.escape(options[:max_results].to_s)}" if options[:max_results]
73
- url << '&validateQuery=false' if options[:validate_query] === false
93
+ url << '&validateQuery=false' if options[:validate_query] === false # rubocop:disable Style/CaseEquality
74
94
 
75
95
  if options[:expand]
76
96
  options[:expand] = [options[:expand]] if options[:expand].is_a?(String)
@@ -79,9 +99,8 @@ module JIRA
79
99
 
80
100
  response = client.get(url)
81
101
  json = parse_json(response.body)
82
- if options[:max_results] && (options[:max_results] == 0)
83
- return json['total']
84
- end
102
+ return json['total'] if options[:max_results]&.zero?
103
+
85
104
  json['issues'].map do |issue|
86
105
  client.Issue.build(issue)
87
106
  end
@@ -90,16 +109,24 @@ module JIRA
90
109
  # Fetches the attributes for the specified resource from JIRA unless
91
110
  # the resource is already expanded and the optional force reload flag
92
111
  # is not set
112
+ # @param [Boolean] reload
113
+ # @param [Hash] query_params
114
+ # @option query_params [String] :fields
115
+ # @option query_params [String] :expand
116
+ # @option query_params [Integer] :startAt
117
+ # @option query_params [Integer] :maxResults
118
+ # @return [void]
93
119
  def fetch(reload = false, query_params = {})
94
120
  return if expanded? && !reload
121
+
95
122
  response = client.get(url_with_query_params(url, query_params))
96
123
  set_attrs_from_response(response)
97
- if @attrs && @attrs['fields'] && @attrs['fields']['worklog'] && (@attrs['fields']['worklog']['total'] > @attrs['fields']['worklog']['maxResults'])
124
+ if @attrs && @attrs['fields'] &&
125
+ @attrs['fields']['worklog'] &&
126
+ (@attrs['fields']['worklog']['total'] > @attrs['fields']['worklog']['maxResults'])
98
127
  worklog_url = client.options[:rest_base_path] + "/#{self.class.endpoint_name}/#{id}/worklog"
99
128
  response = client.get(worklog_url)
100
- unless response.body.nil? || (response.body.length < 2)
101
- set_attrs({ 'fields' => { 'worklog' => self.class.parse_json(response.body) } }, false)
102
- end
129
+ set_attrs({ 'fields' => { 'worklog' => self.class.parse_json(response.body) } }, false) unless response.body.nil? || (response.body.length < 2)
103
130
  end
104
131
  @expanded = true
105
132
  end
@@ -112,15 +139,19 @@ module JIRA
112
139
  json['fields']
113
140
  end
114
141
 
142
+ # @private
115
143
  def respond_to?(method_name, _include_all = false)
116
- if attrs.key?('fields') && [method_name.to_s, client.Field.name_to_id(method_name)].any? { |k| attrs['fields'].key?(k) }
144
+ if attrs.key?('fields') && [method_name.to_s, client.Field.name_to_id(method_name)].any? do |k|
145
+ attrs['fields'].key?(k)
146
+ end
117
147
  true
118
148
  else
119
149
  super(method_name)
120
150
  end
121
151
  end
122
152
 
123
- def method_missing(method_name, *args, &block)
153
+ # @private
154
+ def method_missing(method_name, *args, &)
124
155
  if attrs.key?('fields')
125
156
  if attrs['fields'].key?(method_name.to_s)
126
157
  attrs['fields'][method_name.to_s]
@@ -129,13 +160,33 @@ module JIRA
129
160
  if attrs['fields'].key?(official_name)
130
161
  attrs['fields'][official_name]
131
162
  else
132
- super(method_name, *args, &block)
163
+ super
133
164
  end
134
165
  end
135
166
  else
136
- super(method_name, *args, &block)
167
+ super
137
168
  end
138
169
  end
170
+
171
+ # @!method self.find(client, key, options = {})
172
+ # Gets/fetches an issue from JIRA.
173
+ #
174
+ # Note: attachments are not fetched by default.
175
+ #
176
+ # @param [JIRA::Client] client
177
+ # @param [String] key the key of the issue to find
178
+ # @param [Hash] options the options to find the issue with
179
+ # @option options [String] :fields the fields to include in the response
180
+ # @return [JIRA::Resource::Issue] the found issue
181
+ # @example Find an issue
182
+ # JIRA::Resource::Issue.find(client, "SUP-3000", { fields: %w[summary description attachment created ] } )
183
+ #
184
+ # @!method self.build(attrs = {})
185
+ # Constructs a new issue object.
186
+ # @param [Hash] attrs the attributes to initialize the issue with
187
+ # @return [JIRA::Resource::Issue] the new issue
188
+ #
189
+ # .
139
190
  end
140
191
  end
141
192
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JIRA
2
4
  module Resource
3
5
  class IssuePickerSuggestionsFactory < JIRA::BaseFactory # :nodoc:
@@ -6,7 +8,8 @@ module JIRA
6
8
  class IssuePickerSuggestions < JIRA::Base
7
9
  has_many :sections, class: JIRA::Resource::IssuePickerSuggestionsIssue
8
10
 
9
- def self.all(client, query = '', options = { current_jql: nil, current_issue_key: nil, current_project_id: nil, show_sub_tasks: nil, show_sub_tasks_parent: nil })
11
+ def self.all(client, query = '', options = { current_jql: nil, current_issue_key: nil, current_project_id: nil,
12
+ show_sub_tasks: nil, show_sub_tasks_parent: nil })
10
13
  url = client.options[:rest_base_path] + "/issue/picker?query=#{CGI.escape(query)}"
11
14
 
12
15
  url << "&currentJQL=#{CGI.escape(options[:current_jql])}" if options[:current_jql]
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JIRA
2
4
  module Resource
3
5
  class IssuePickerSuggestionsIssueFactory < JIRA::BaseFactory # :nodoc:
@@ -1,11 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JIRA
2
4
  module Resource
3
5
  class IssuelinkFactory < JIRA::BaseFactory # :nodoc:
4
6
  end
5
7
 
6
- # Because of circular dependency Issue->IssueLink->Issue
7
- # we have to declare JIRA::Resource::Issue class.
8
- class Issue < JIRA::Base; end
8
+ class Issue < JIRA::Base
9
+ # Because of circular dependency Issue->IssueLink->Issue
10
+ # we have to declare JIRA::Resource::Issue class.
11
+ end
9
12
 
10
13
  class Issuelink < JIRA::Base
11
14
  has_one :type, class: JIRA::Resource::Issuelinktype
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JIRA
2
4
  module Resource
3
5
  class IssuelinktypeFactory < JIRA::BaseFactory # :nodoc:
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JIRA
2
4
  module Resource
3
5
  class IssuetypeFactory < JIRA::BaseFactory # :nodoc:
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JIRA
2
4
  module Resource
3
5
  class PriorityFactory < JIRA::BaseFactory # :nodoc:
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JIRA
2
4
  module Resource
3
5
  class ProjectFactory < JIRA::BaseFactory # :nodoc:
@@ -15,7 +17,7 @@ module JIRA
15
17
 
16
18
  # Returns all the issues for this project
17
19
  def issues(options = {})
18
- search_url = client.options[:rest_base_path] + '/search'
20
+ search_url = "#{client.options[:rest_base_path]}/search"
19
21
  query_params = { jql: "project=\"#{key}\"" }
20
22
  query_params.update Base.query_params_for_search(options)
21
23
  response = client.get(url_with_query_params(search_url, query_params))
@@ -26,7 +28,7 @@ module JIRA
26
28
  end
27
29
 
28
30
  def users(start_at: nil, max_results: nil)
29
- users_url = client.options[:rest_base_path] + '/user/assignable/search'
31
+ users_url = "#{client.options[:rest_base_path]}/user/assignable/search"
30
32
  query_params = { project: key_value }
31
33
  query_params['startAt'] = start_at if start_at
32
34
  query_params['maxResults'] = max_results if max_results
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'cgi'
2
4
 
3
5
  module JIRA
@@ -7,7 +9,7 @@ module JIRA
7
9
 
8
10
  class RapidView < JIRA::Base
9
11
  def self.all(client)
10
- response = client.get(path_base(client) + '/rapidview')
12
+ response = client.get("#{path_base(client)}/rapidview")
11
13
  json = parse_json(response.body)
12
14
  json['views'].map do |view|
13
15
  client.RapidView.build(view)
@@ -44,7 +46,7 @@ module JIRA
44
46
 
45
47
  def sprints(options = {})
46
48
  params = { includeHistoricSprints: options.fetch(:include_historic, false),
47
- includeFutureSprints: options.fetch(:include_future, false) }
49
+ includeFutureSprints: options.fetch(:include_future, false) }
48
50
  response = client.get(path_base(client) + "/sprintquery/#{id}?#{params.to_query}")
49
51
  json = self.class.parse_json(response.body)
50
52
  json['sprints'].map do |sprint|
@@ -56,7 +58,7 @@ module JIRA
56
58
  private
57
59
 
58
60
  def self.path_base(client)
59
- client.options[:context_path] + '/rest/greenhopper/1.0'
61
+ "#{client.options[:context_path]}/rest/greenhopper/1.0"
60
62
  end
61
63
 
62
64
  def path_base(client)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JIRA
2
4
  module Resource
3
5
  class RemotelinkFactory < JIRA::BaseFactory # :nodoc:
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JIRA
2
4
  module Resource
3
5
  class ResolutionFactory < JIRA::BaseFactory # :nodoc:
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JIRA
2
4
  module Resource
3
5
  class ServerInfoFactory < JIRA::BaseFactory # :nodoc: