jira-ruby 3.0.0.beta1 → 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.
- checksums.yaml +4 -4
- data/.github/workflows/CI.yml +1 -0
- data/.github/workflows/codeql.yml +0 -4
- data/.gitignore +3 -1
- data/.rubocop.yml +1 -69
- data/.yardopts +4 -0
- data/lib/jira/base.rb +5 -13
- data/lib/jira/client.rb +59 -4
- data/lib/jira/has_many_proxy.rb +30 -28
- data/lib/jira/http_client.rb +64 -1
- data/lib/jira/oauth_client.rb +62 -0
- data/lib/jira/request_client.rb +26 -1
- data/lib/jira/resource/attachment.rb +88 -3
- data/lib/jira/resource/field.rb +4 -8
- data/lib/jira/resource/issue.rb +64 -4
- data/lib/jira/resource/issue_picker_suggestions.rb +1 -1
- data/lib/jira/resource/issuelink.rb +4 -3
- data/lib/jira/resource/watcher.rb +1 -1
- data/lib/jira/resource/webhook.rb +5 -1
- data/lib/jira/version.rb +1 -1
- data/lib/tasks/generate.rake +1 -1
- data/spec/integration/project_spec.rb +1 -1
- data/spec/integration/rapidview_spec.rb +1 -1
- data/spec/integration/user_spec.rb +12 -3
- data/spec/integration/watcher_spec.rb +6 -2
- data/spec/integration/{webhook.rb → webhook_spec.rb} +8 -1
- data/spec/jira/base_factory_spec.rb +11 -2
- data/spec/jira/base_spec.rb +80 -57
- data/spec/jira/client_spec.rb +20 -18
- data/spec/jira/http_client_spec.rb +2 -2
- data/spec/jira/oauth_client_spec.rb +8 -4
- data/spec/jira/resource/agile_spec.rb +2 -2
- data/spec/jira/resource/attachment_spec.rb +36 -13
- data/spec/jira/resource/board_spec.rb +5 -5
- data/spec/jira/resource/field_spec.rb +23 -24
- data/spec/jira/resource/issue_spec.rb +18 -18
- data/spec/jira/resource/project_spec.rb +6 -6
- data/spec/jira/resource/sprint_spec.rb +20 -8
- data/spec/jira/resource/status_spec.rb +1 -1
- data/spec/jira/resource/user_factory_spec.rb +2 -2
- data/spec/jira/resource/worklog_spec.rb +1 -1
- data/spec/support/clients_helper.rb +2 -2
- data/spec/support/mock_client.rb +9 -0
- data/spec/support/mock_response.rb +8 -0
- metadata +9 -10
data/lib/jira/oauth_client.rb
CHANGED
@@ -5,7 +5,16 @@ require 'json'
|
|
5
5
|
require 'forwardable'
|
6
6
|
|
7
7
|
module JIRA
|
8
|
+
# Client using OAuth 1.0
|
9
|
+
#
|
10
|
+
# @!attribute [rw] consumer
|
11
|
+
# @return [OAuth::Consumer] The oauth consumer object
|
12
|
+
# @!attribute [r] options
|
13
|
+
# @return [Hash] The oauth connection options
|
14
|
+
# @!attribute [r] access_token
|
15
|
+
# @return [OAuth::AccessToken] The oauth access token
|
8
16
|
class OauthClient < RequestClient
|
17
|
+
# @private
|
9
18
|
DEFAULT_OPTIONS = {
|
10
19
|
signature_method: 'RSA-SHA1',
|
11
20
|
request_token_path: '/plugins/servlet/oauth/request-token',
|
@@ -31,11 +40,29 @@ module JIRA
|
|
31
40
|
|
32
41
|
def_instance_delegators :@consumer, :key, :secret, :get_request_token
|
33
42
|
|
43
|
+
# Generally not used directly, but through JIRA::Client.
|
44
|
+
# @param [Hash] options Options as passed from JIRA::Client constructor.
|
45
|
+
# @option options [String] :signature_method The signature method to use (defaults to 'RSA-SHA1')
|
46
|
+
# @option options [String] :request_token_path The path to request a token (defaults to '/plugins/servlet/oauth/request-token')
|
47
|
+
# @option options [String] :authorize_path The path to authorize a token (defaults to '/plugins/servlet/oauth/authorize')
|
48
|
+
# @option options [String] :access_token_path The path to access a token (defaults to '/plugins/servlet/oauth/access-token')
|
49
|
+
# @option options [String] :private_key_file The path to the private key file
|
50
|
+
# @option options [String] :consumer_key The OAuth 1.0 consumer key
|
51
|
+
# @option options [String] :consumer_secret The OAuth 1.0 consumer secret
|
52
|
+
# @option options [Hash] :default_headers Additional headers for requests
|
53
|
+
# @option options [String] :proxy_uri Proxy URI
|
54
|
+
# @option options [String] :proxy_user Proxy user
|
55
|
+
# @option options [String] :proxy_password Proxy Password
|
34
56
|
def initialize(options)
|
35
57
|
@options = DEFAULT_OPTIONS.merge(options)
|
36
58
|
@consumer = init_oauth_consumer(@options)
|
37
59
|
end
|
38
60
|
|
61
|
+
# @private
|
62
|
+
# Initialises the OAuth consumer object.
|
63
|
+
# Generally you should not call this method directly, it is called by the constructor.
|
64
|
+
# @param [Hash] _options The options hash
|
65
|
+
# @return [OAuth::Consumer] The OAuth consumer object
|
39
66
|
def init_oauth_consumer(_options)
|
40
67
|
@options[:request_token_path] = @options[:context_path] + @options[:request_token_path]
|
41
68
|
@options[:authorize_path] = @options[:context_path] + @options[:authorize_path]
|
@@ -47,22 +74,31 @@ module JIRA
|
|
47
74
|
|
48
75
|
# Returns the current request token if it is set, else it creates
|
49
76
|
# and sets a new token.
|
77
|
+
# @param [Hash] options
|
50
78
|
def request_token(options = {}, ...)
|
51
79
|
@request_token ||= get_request_token(options, ...)
|
52
80
|
end
|
53
81
|
|
54
82
|
# Sets the request token from a given token and secret.
|
83
|
+
# @param [String] token The request token
|
84
|
+
# @param [String] secret The request token secret
|
85
|
+
# @return [OAuth::RequestToken] The request token object
|
55
86
|
def set_request_token(token, secret)
|
56
87
|
@request_token = OAuth::RequestToken.new(@consumer, token, secret)
|
57
88
|
end
|
58
89
|
|
59
90
|
# Initialises and returns a new access token from the params hash
|
60
91
|
# returned by the OAuth transaction.
|
92
|
+
# @param [Hash] params The params hash returned by the OAuth transaction
|
93
|
+
# @return [OAuth::AccessToken] The access token object
|
61
94
|
def init_access_token(params)
|
62
95
|
@access_token = request_token.get_access_token(params)
|
63
96
|
end
|
64
97
|
|
65
98
|
# Sets the access token from a preexisting token and secret.
|
99
|
+
# @param [String] token The access token
|
100
|
+
# @param [String] secret The access token secret
|
101
|
+
# @return [OAuth::AccessToken] The access token object
|
66
102
|
def set_access_token(token, secret)
|
67
103
|
@access_token = OAuth::AccessToken.new(@consumer, token, secret)
|
68
104
|
@authenticated = true
|
@@ -71,12 +107,25 @@ module JIRA
|
|
71
107
|
|
72
108
|
# Returns the current access token. Raises an
|
73
109
|
# JIRA::Client::UninitializedAccessTokenError exception if it is not set.
|
110
|
+
# @return [OAuth::AccessToken] The access token object
|
74
111
|
def access_token
|
75
112
|
raise UninitializedAccessTokenError unless @access_token
|
76
113
|
|
77
114
|
@access_token
|
78
115
|
end
|
79
116
|
|
117
|
+
# Makes a request to the JIRA server using the oauth gem.
|
118
|
+
#
|
119
|
+
# Generally you should not call this method directly, but use the helper methods in JIRA::Client.
|
120
|
+
#
|
121
|
+
# File uploads are not supported with this method. Use make_multipart_request instead.
|
122
|
+
#
|
123
|
+
# @param [Symbol] http_method The HTTP method to use
|
124
|
+
# @param [String] url The JIRA REST URL to call
|
125
|
+
# @param [String] body The body of the request
|
126
|
+
# @param [Hash] headers The headers to send with the request
|
127
|
+
# @return [Net::HTTPResponse] The response object
|
128
|
+
# @raise [JIRA::HTTPError] If the response is not an HTTP success code
|
80
129
|
def make_request(http_method, url, body = '', headers = {})
|
81
130
|
# When using oauth_2legged we need to add an empty oauth_token parameter to every request.
|
82
131
|
if @options[:auth_type] == :oauth_2legged
|
@@ -100,6 +149,17 @@ module JIRA
|
|
100
149
|
response
|
101
150
|
end
|
102
151
|
|
152
|
+
# Makes a multipart request to the JIRA server using the oauth gem.
|
153
|
+
#
|
154
|
+
# This is used for file uploads.
|
155
|
+
#
|
156
|
+
# Generally you should not call this method directly, but use the helper methods in JIRA::Client.
|
157
|
+
#
|
158
|
+
# @param [String] url The JIRA REST URL to call
|
159
|
+
# @param [Hash] data The Net::HTTP::Post::Multipart data to send with the request
|
160
|
+
# @param [Hash] headers The headers to send with the request
|
161
|
+
# @return [Net::HTTPResponse] The response object
|
162
|
+
# @raise [JIRA::HTTPError] If the response is not an HTTP success code
|
103
163
|
def make_multipart_request(url, data, headers = {})
|
104
164
|
request = Net::HTTP::Post::Multipart.new url, data, headers
|
105
165
|
|
@@ -110,6 +170,8 @@ module JIRA
|
|
110
170
|
response
|
111
171
|
end
|
112
172
|
|
173
|
+
# Returns true if the client is authenticated.
|
174
|
+
# @return [Boolean] True if the client is authenticated
|
113
175
|
def authenticated?
|
114
176
|
@authenticated
|
115
177
|
end
|
data/lib/jira/request_client.rb
CHANGED
@@ -5,11 +5,21 @@ require 'json'
|
|
5
5
|
require 'net/https'
|
6
6
|
|
7
7
|
module JIRA
|
8
|
+
# Base class for request clients specific to a particular authentication method.
|
8
9
|
class RequestClient
|
10
|
+
# Makes the JIRA REST API call.
|
11
|
+
#
|
9
12
|
# Returns the response if the request was successful (HTTP::2xx) and
|
10
13
|
# raises a JIRA::HTTPError if it was not successful, with the response
|
11
14
|
# attached.
|
12
|
-
|
15
|
+
#
|
16
|
+
# Generally you should not call this method directly, but use derived classes.
|
17
|
+
#
|
18
|
+
# File uploads are not supported with this method. Use request_multipart instead.
|
19
|
+
#
|
20
|
+
# @param [Array] args Arguments to pass to the request method
|
21
|
+
# @return [Net::HTTPResponse] The response from the server
|
22
|
+
# @raise [JIRA::HTTPError] if it was not successful
|
13
23
|
def request(*args)
|
14
24
|
response = make_request(*args)
|
15
25
|
raise HTTPError, response unless response.is_a?(Net::HTTPSuccess)
|
@@ -17,6 +27,15 @@ module JIRA
|
|
17
27
|
response
|
18
28
|
end
|
19
29
|
|
30
|
+
# Makes a multipart request to the JIRA server.
|
31
|
+
#
|
32
|
+
# This is used for file uploads.
|
33
|
+
#
|
34
|
+
# Generally you should not call this method directly, but use derived classes.
|
35
|
+
#
|
36
|
+
# @param [Array] args Arguments to pass to the request method
|
37
|
+
# @return [Net::HTTPResponse] The response from the server
|
38
|
+
# @raise [JIRA::HTTPError] if it was not successful
|
20
39
|
def request_multipart(*args)
|
21
40
|
response = make_multipart_request(*args)
|
22
41
|
raise HTTPError, response unless response.is_a?(Net::HTTPSuccess)
|
@@ -24,10 +43,16 @@ module JIRA
|
|
24
43
|
response
|
25
44
|
end
|
26
45
|
|
46
|
+
# Abstract method to make a request to the JIRA server.
|
47
|
+
# @abstract
|
48
|
+
# @param [Array] args Arguments to pass to the request method
|
27
49
|
def make_request(*args)
|
28
50
|
raise NotImplementedError
|
29
51
|
end
|
30
52
|
|
53
|
+
# Abstract method to make a request to the JIRA server with a file upload.
|
54
|
+
# @abstract
|
55
|
+
# @param [Array] args Arguments to pass to the request method
|
31
56
|
def make_multipart_request(*args)
|
32
57
|
raise NotImplementedError
|
33
58
|
end
|
@@ -9,14 +9,85 @@ module JIRA
|
|
9
9
|
delegate_to_target_class :meta
|
10
10
|
end
|
11
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
|
+
#
|
12
77
|
class Attachment < JIRA::Base
|
13
78
|
belongs_to :issue
|
14
79
|
has_one :author, class: JIRA::Resource::User
|
15
80
|
|
81
|
+
# @private
|
16
82
|
def self.endpoint_name
|
17
83
|
'attachments'
|
18
84
|
end
|
19
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.)
|
20
91
|
def self.meta(client)
|
21
92
|
response = client.get("#{client.options[:rest_base_path]}/attachment/meta")
|
22
93
|
parse_json(response.body)
|
@@ -42,23 +113,37 @@ module JIRA
|
|
42
113
|
# @param [Hash] headers Any additional headers to call Jira.
|
43
114
|
# @yield |file|
|
44
115
|
# @yieldparam [IO] file The IO object streaming the download.
|
45
|
-
def download_file(headers = {}, &
|
116
|
+
def download_file(headers = {}, &)
|
46
117
|
default_headers = client.options[:default_headers]
|
47
|
-
URI.parse(content).open(default_headers.merge(headers), &
|
118
|
+
URI.parse(content).open(default_headers.merge(headers), &)
|
48
119
|
end
|
49
120
|
|
50
121
|
# Downloads the file contents as a string object.
|
51
122
|
#
|
52
123
|
# Note that this reads the contents into a ruby string in memory.
|
53
|
-
# A file might be very large so it is
|
124
|
+
# A file might be very large so it is recommended to avoid this unless you are certain about doing so.
|
54
125
|
# Use the download_file method instead and avoid calling the read method without a limit.
|
55
126
|
#
|
127
|
+
# @example Save file contents to a string.
|
128
|
+
# content = download_contents
|
56
129
|
# @param [Hash] headers Any additional headers to call Jira.
|
57
130
|
# @return [String,NilClass] The file contents.
|
58
131
|
def download_contents(headers = {})
|
59
132
|
download_file(headers, &:read)
|
60
133
|
end
|
61
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
|
62
147
|
def save!(attrs, path = url)
|
63
148
|
file = attrs['file'] || attrs[:file] # Keep supporting 'file' parameter as a string for backward compatibility
|
64
149
|
mime_type = attrs[:mimeType] || 'application/binary'
|
data/lib/jira/resource/field.rb
CHANGED
@@ -21,7 +21,6 @@ module JIRA
|
|
21
21
|
|
22
22
|
def self.map_fields(client)
|
23
23
|
field_map = {}
|
24
|
-
field_map_reverse = {}
|
25
24
|
fields = client.Field.all
|
26
25
|
|
27
26
|
# two pass approach, so that a custom field with the same name
|
@@ -30,7 +29,6 @@ module JIRA
|
|
30
29
|
next if f.custom
|
31
30
|
|
32
31
|
name = safe_name(f.name)
|
33
|
-
field_map_reverse[f.id] = [f.name, name] # capture both the official name, and the mapped name
|
34
32
|
field_map[name] = f.id
|
35
33
|
end
|
36
34
|
|
@@ -44,23 +42,21 @@ module JIRA
|
|
44
42
|
else
|
45
43
|
safe_name(f.name)
|
46
44
|
end
|
47
|
-
field_map_reverse[f.id] = [f.name, name] # capture both the official name, and the mapped name
|
48
45
|
field_map[name] = f.id
|
49
46
|
end
|
50
47
|
|
51
|
-
client.
|
52
|
-
client.cache.field_map = field_map
|
48
|
+
client.field_map_cache = field_map
|
53
49
|
end
|
54
50
|
|
55
51
|
def self.field_map(client)
|
56
|
-
client.
|
52
|
+
client.field_map_cache
|
57
53
|
end
|
58
54
|
|
59
55
|
def self.name_to_id(client, field_name)
|
60
56
|
field_name = field_name.to_s
|
61
|
-
return field_name unless client.
|
57
|
+
return field_name unless client.field_map_cache && client.field_map_cache[field_name]
|
62
58
|
|
63
|
-
client.
|
59
|
+
client.field_map_cache[field_name]
|
64
60
|
end
|
65
61
|
|
66
62
|
def respond_to?(method_name, _include_all = false)
|
data/lib/jira/resource/issue.rb
CHANGED
@@ -8,6 +8,35 @@ module JIRA
|
|
8
8
|
class IssueFactory < JIRA::BaseFactory # :nodoc:
|
9
9
|
end
|
10
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
|
+
#
|
11
40
|
class Issue < JIRA::Base
|
12
41
|
has_one :reporter, class: JIRA::Resource::User, nested_under: 'fields'
|
13
42
|
has_one :assignee, class: JIRA::Resource::User, nested_under: 'fields'
|
@@ -29,6 +58,9 @@ module JIRA
|
|
29
58
|
has_many :remotelink, class: JIRA::Resource::Remotelink
|
30
59
|
has_many :watchers, attribute_key: 'watches', nested_under: %w[fields watches]
|
31
60
|
|
61
|
+
# Get collection of issues.
|
62
|
+
# @param client [JIRA::Client]
|
63
|
+
# @return [Array<JIRA::Resource::Issue>]
|
32
64
|
def self.all(client)
|
33
65
|
start_at = 0
|
34
66
|
max_results = 1000
|
@@ -48,8 +80,7 @@ module JIRA
|
|
48
80
|
result
|
49
81
|
end
|
50
82
|
|
51
|
-
def self.jql(client, jql, options = { fields: nil, start_at: nil, max_results: nil, expand: nil,
|
52
|
-
validate_query: true })
|
83
|
+
def self.jql(client, jql, options = { fields: nil, start_at: nil, max_results: nil, expand: nil, validate_query: true })
|
53
84
|
url = client.options[:rest_base_path] + "/search?jql=#{CGI.escape(jql)}"
|
54
85
|
|
55
86
|
if options[:fields]
|
@@ -59,7 +90,7 @@ validate_query: true })
|
|
59
90
|
end
|
60
91
|
url << "&startAt=#{CGI.escape(options[:start_at].to_s)}" if options[:start_at]
|
61
92
|
url << "&maxResults=#{CGI.escape(options[:max_results].to_s)}" if options[:max_results]
|
62
|
-
url << '&validateQuery=false' if options[:validate_query] === false
|
93
|
+
url << '&validateQuery=false' if options[:validate_query] === false # rubocop:disable Style/CaseEquality
|
63
94
|
|
64
95
|
if options[:expand]
|
65
96
|
options[:expand] = [options[:expand]] if options[:expand].is_a?(String)
|
@@ -68,7 +99,7 @@ validate_query: true })
|
|
68
99
|
|
69
100
|
response = client.get(url)
|
70
101
|
json = parse_json(response.body)
|
71
|
-
return json['total'] if options[:max_results]
|
102
|
+
return json['total'] if options[:max_results]&.zero?
|
72
103
|
|
73
104
|
json['issues'].map do |issue|
|
74
105
|
client.Issue.build(issue)
|
@@ -78,6 +109,13 @@ validate_query: true })
|
|
78
109
|
# Fetches the attributes for the specified resource from JIRA unless
|
79
110
|
# the resource is already expanded and the optional force reload flag
|
80
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]
|
81
119
|
def fetch(reload = false, query_params = {})
|
82
120
|
return if expanded? && !reload
|
83
121
|
|
@@ -101,6 +139,7 @@ validate_query: true })
|
|
101
139
|
json['fields']
|
102
140
|
end
|
103
141
|
|
142
|
+
# @private
|
104
143
|
def respond_to?(method_name, _include_all = false)
|
105
144
|
if attrs.key?('fields') && [method_name.to_s, client.Field.name_to_id(method_name)].any? do |k|
|
106
145
|
attrs['fields'].key?(k)
|
@@ -111,6 +150,7 @@ validate_query: true })
|
|
111
150
|
end
|
112
151
|
end
|
113
152
|
|
153
|
+
# @private
|
114
154
|
def method_missing(method_name, *args, &)
|
115
155
|
if attrs.key?('fields')
|
116
156
|
if attrs['fields'].key?(method_name.to_s)
|
@@ -127,6 +167,26 @@ validate_query: true })
|
|
127
167
|
super
|
128
168
|
end
|
129
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
|
+
# .
|
130
190
|
end
|
131
191
|
end
|
132
192
|
end
|
@@ -9,7 +9,7 @@ module JIRA
|
|
9
9
|
has_many :sections, class: JIRA::Resource::IssuePickerSuggestionsIssue
|
10
10
|
|
11
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 })
|
12
|
+
show_sub_tasks: nil, show_sub_tasks_parent: nil })
|
13
13
|
url = client.options[:rest_base_path] + "/issue/picker?query=#{CGI.escape(query)}"
|
14
14
|
|
15
15
|
url << "¤tJQL=#{CGI.escape(options[:current_jql])}" if options[:current_jql]
|
@@ -5,9 +5,10 @@ module JIRA
|
|
5
5
|
class IssuelinkFactory < JIRA::BaseFactory # :nodoc:
|
6
6
|
end
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
class Issue < JIRA::Base
|
9
|
+
# Because of circular dependency Issue->IssueLink->Issue
|
10
|
+
# we have to declare JIRA::Resource::Issue class.
|
11
|
+
end
|
11
12
|
|
12
13
|
class Issuelink < JIRA::Base
|
13
14
|
has_one :type, class: JIRA::Resource::Issuelinktype
|
@@ -16,8 +16,12 @@ module JIRA
|
|
16
16
|
client.options[:context_path] + REST_BASE_PATH
|
17
17
|
end
|
18
18
|
|
19
|
+
def self.singular_path(client, key, prefix = '/')
|
20
|
+
"#{full_url(client)}#{prefix}/#{endpoint_name}/#{key}"
|
21
|
+
end
|
22
|
+
|
19
23
|
def self.collection_path(client, prefix = '/')
|
20
|
-
full_url(client)
|
24
|
+
"#{full_url(client)}#{prefix}/#{endpoint_name}"
|
21
25
|
end
|
22
26
|
|
23
27
|
def self.all(client, options = {})
|
data/lib/jira/version.rb
CHANGED
data/lib/tasks/generate.rake
CHANGED
@@ -12,7 +12,7 @@ namespace :jira do
|
|
12
12
|
desc 'Run the system call to generate a RSA public certificate'
|
13
13
|
task :generate_public_cert do
|
14
14
|
puts "Executing 'openssl req -x509 -nodes -newkey rsa:1024 -sha1 -keyout rsakey.pem -out rsacert.pem'"
|
15
|
-
system('openssl req -x509 -subj "/C=US/ST=New York/L=New York/O=SUMO Heavy Industries/CN=www.sumoheavy.com" -nodes -newkey rsa:1024 -sha1 -keyout rsakey.pem -out rsacert.pem')
|
15
|
+
system('openssl req -x509 -subj "/C=US/ST=New York/L=New York/O=SUMO Heavy Industries/CN=www.sumoheavy.com" -nodes -newkey rsa:1024 -sha1 -keyout rsakey.pem -out rsacert.pem')
|
16
16
|
puts "Done. The RSA-SHA1 private keyfile is in the current directory: 'rsakey.pem'."
|
17
17
|
puts 'You will need to copy the following certificate into your application link configuration in Jira:'
|
18
18
|
system('cat rsacert.pem')
|
@@ -40,14 +40,23 @@ describe JIRA::Resource::User do
|
|
40
40
|
end
|
41
41
|
|
42
42
|
before do
|
43
|
+
user_factory = double('UserFactory')
|
44
|
+
|
43
45
|
allow(client).to receive(:get)
|
44
|
-
.with('/rest/api/2/users/search?username=_&maxResults=1000')
|
45
|
-
|
46
|
+
.with('/rest/api/2/users/search?username=_&maxResults=1000')
|
47
|
+
.and_return(double(body: '["User1"]'))
|
48
|
+
allow(client).to receive(:User).and_return(user_factory)
|
49
|
+
allow(user_factory).to receive(:build).with('users').and_return([])
|
46
50
|
end
|
47
51
|
|
48
52
|
it 'gets users with maxResults of 1000' do
|
53
|
+
user_factory = double('UserFactory')
|
54
|
+
|
49
55
|
expect(client).to receive(:get).with('/rest/api/2/users/search?username=_&maxResults=1000')
|
50
|
-
|
56
|
+
.and_return(double(body: '["User1"]'))
|
57
|
+
expect(client).to receive(:User).and_return(user_factory)
|
58
|
+
expect(user_factory).to receive(:build).with('User1')
|
59
|
+
|
51
60
|
described_class.all(client)
|
52
61
|
end
|
53
62
|
end
|
@@ -46,15 +46,19 @@ describe JIRA::Resource::Watcher do
|
|
46
46
|
|
47
47
|
it 'returnses all the watchers' do
|
48
48
|
issue = client.Issue.find('10002')
|
49
|
-
watchers = client.Watcher.all(
|
49
|
+
watchers = client.Watcher.all({ issue: })
|
50
50
|
expect(watchers.length).to eq(1)
|
51
51
|
end
|
52
52
|
|
53
53
|
it 'adds a watcher' do
|
54
54
|
issue = client.Issue.find('10002')
|
55
|
-
watcher = described_class.new(client, issue:)
|
55
|
+
watcher = described_class.new(client, issue: issue)
|
56
56
|
user_id = 'tester'
|
57
|
+
|
57
58
|
watcher.save!(user_id)
|
59
|
+
|
60
|
+
expect(WebMock).to have_requested(:post, "#{site_url}/jira/rest/api/2/issue/10002/watchers")
|
61
|
+
.with(body: '"tester"')
|
58
62
|
end
|
59
63
|
end
|
60
64
|
end
|
@@ -9,7 +9,7 @@ describe JIRA::Resource::Webhook do
|
|
9
9
|
|
10
10
|
let(:expected_attributes) do
|
11
11
|
{ 'name' => 'from API', 'url' => 'http://localhost:3000/webhooks/1', 'excludeBody' => false,
|
12
|
-
'filters' => { 'issue-related-events-section' => '' }, 'events' => [], 'enabled' => true, 'self' => 'http://localhost:2990/jira/rest/webhooks/1.0/webhook/2', 'lastUpdatedUser' => 'admin', 'lastUpdatedDisplayName' => 'admin', 'lastUpdated' => 1_453_306_520_188 }
|
12
|
+
'filters' => { 'issue-related-events-section' => '' }, 'events' => [], 'enabled' => true, 'self' => 'http://localhost:2990/jira/rest/webhooks/1.0/webhook/2', 'lastUpdatedUser' => 'admin', 'lastUpdatedDisplayName' => 'admin', 'lastUpdated' => 1_453_306_520_188 }
|
13
13
|
end
|
14
14
|
|
15
15
|
let(:expected_collection_length) { 1 }
|
@@ -21,6 +21,13 @@ describe JIRA::Resource::Webhook do
|
|
21
21
|
it 'returns a collection of components' do
|
22
22
|
stub_request(:get, site_url + described_class.singular_path(client, key))
|
23
23
|
.to_return(status: 200, body: get_mock_response('webhook/webhook.json'))
|
24
|
+
|
25
|
+
webhook = client.Webhook.find(key)
|
26
|
+
|
27
|
+
expect(webhook).to be_a described_class
|
28
|
+
expect(webhook.name).to eq 'from API'
|
29
|
+
expect(webhook.url).to eq '/jira/rest/webhooks/1.0/webhook/2'
|
30
|
+
expect(webhook.enabled).to be true
|
24
31
|
end
|
25
32
|
end
|
26
33
|
end
|
@@ -1,8 +1,17 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe JIRA::BaseFactory do
|
4
|
-
|
5
|
-
|
4
|
+
module JIRA
|
5
|
+
module Resource
|
6
|
+
class FooFactory < JIRA::BaseFactory; end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module JIRA
|
11
|
+
module Resource
|
12
|
+
class Foo; end
|
13
|
+
end
|
14
|
+
end
|
6
15
|
|
7
16
|
subject { JIRA::Resource::FooFactory.new(client) }
|
8
17
|
|