jira-ruby 1.2.0 → 2.3.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.
- checksums.yaml +5 -5
- data/.gitignore +3 -0
- data/.travis.yml +5 -3
- data/Gemfile +7 -1
- data/Guardfile +1 -1
- data/README.md +452 -0
- data/Rakefile +6 -7
- data/example.rb +23 -1
- data/http-basic-example.rb +13 -12
- data/jira-ruby.gemspec +13 -13
- data/lib/jira/base.rb +53 -52
- data/lib/jira/base_factory.rb +3 -6
- data/lib/jira/client.rb +127 -30
- data/lib/jira/has_many_proxy.rb +0 -1
- data/lib/jira/http_client.rb +54 -16
- data/lib/jira/http_error.rb +3 -5
- data/lib/jira/jwt_client.rb +67 -0
- data/lib/jira/oauth_client.rb +47 -17
- data/lib/jira/request_client.rb +16 -5
- data/lib/jira/resource/agile.rb +34 -9
- data/lib/jira/resource/applinks.rb +5 -8
- data/lib/jira/resource/attachment.rb +41 -3
- data/lib/jira/resource/board.rb +91 -0
- data/lib/jira/resource/board_configuration.rb +9 -0
- data/lib/jira/resource/comment.rb +0 -2
- data/lib/jira/resource/component.rb +1 -3
- data/lib/jira/resource/createmeta.rb +12 -14
- data/lib/jira/resource/field.rb +22 -22
- data/lib/jira/resource/filter.rb +2 -2
- data/lib/jira/resource/issue.rb +69 -38
- data/lib/jira/resource/issue_picker_suggestions.rb +24 -0
- data/lib/jira/resource/issue_picker_suggestions_issue.rb +10 -0
- data/lib/jira/resource/issuelink.rb +3 -5
- data/lib/jira/resource/issuelinktype.rb +0 -1
- data/lib/jira/resource/issuetype.rb +1 -3
- data/lib/jira/resource/priority.rb +1 -3
- data/lib/jira/resource/project.rb +5 -7
- data/lib/jira/resource/rapidview.rb +28 -7
- data/lib/jira/resource/remotelink.rb +1 -4
- data/lib/jira/resource/resolution.rb +2 -4
- data/lib/jira/resource/serverinfo.rb +1 -2
- data/lib/jira/resource/sprint.rb +86 -17
- data/lib/jira/resource/sprint_report.rb +8 -0
- data/lib/jira/resource/status.rb +1 -3
- data/lib/jira/resource/suggested_issue.rb +9 -0
- data/lib/jira/resource/transition.rb +2 -6
- data/lib/jira/resource/user.rb +12 -2
- data/lib/jira/resource/version.rb +1 -3
- data/lib/jira/resource/watcher.rb +35 -0
- data/lib/jira/resource/webhook.rb +3 -6
- data/lib/jira/resource/worklog.rb +3 -5
- data/lib/jira/version.rb +1 -1
- data/lib/jira-ruby.rb +12 -2
- data/lib/tasks/generate.rake +4 -4
- data/spec/integration/attachment_spec.rb +17 -8
- data/spec/integration/comment_spec.rb +31 -34
- data/spec/integration/component_spec.rb +21 -24
- data/spec/integration/field_spec.rb +15 -18
- data/spec/integration/issue_spec.rb +45 -46
- data/spec/integration/issuelinktype_spec.rb +8 -11
- data/spec/integration/issuetype_spec.rb +5 -7
- data/spec/integration/priority_spec.rb +5 -8
- data/spec/integration/project_spec.rb +13 -20
- data/spec/integration/rapidview_spec.rb +17 -10
- data/spec/integration/resolution_spec.rb +7 -10
- data/spec/integration/status_spec.rb +5 -8
- data/spec/integration/transition_spec.rb +17 -20
- data/spec/integration/user_spec.rb +24 -8
- data/spec/integration/version_spec.rb +21 -25
- data/spec/integration/watcher_spec.rb +62 -0
- data/spec/integration/webhook.rb +8 -17
- data/spec/integration/worklog_spec.rb +30 -34
- data/spec/jira/base_factory_spec.rb +11 -12
- data/spec/jira/base_spec.rb +216 -229
- data/spec/jira/client_spec.rb +227 -159
- data/spec/jira/has_many_proxy_spec.rb +11 -12
- data/spec/jira/http_client_spec.rb +254 -31
- data/spec/jira/http_error_spec.rb +7 -9
- data/spec/jira/jwt_uri_builder_spec.rb +59 -0
- data/spec/jira/oauth_client_spec.rb +110 -39
- data/spec/jira/request_client_spec.rb +36 -9
- data/spec/jira/resource/agile_spec.rb +135 -0
- data/spec/jira/resource/attachment_spec.rb +127 -9
- data/spec/jira/resource/board_spec.rb +224 -0
- data/spec/jira/resource/createmeta_spec.rb +29 -32
- data/spec/jira/resource/field_spec.rb +42 -48
- data/spec/jira/resource/filter_spec.rb +40 -40
- data/spec/jira/resource/issue_picker_suggestions_spec.rb +79 -0
- data/spec/jira/resource/issue_spec.rb +88 -85
- data/spec/jira/resource/issuelink_spec.rb +1 -1
- data/spec/jira/resource/jira_picker_suggestions_issue_spec.rb +18 -0
- data/spec/jira/resource/project_factory_spec.rb +2 -4
- data/spec/jira/resource/project_spec.rb +33 -33
- data/spec/jira/resource/sprint_spec.rb +90 -0
- data/spec/jira/resource/user_factory_spec.rb +6 -8
- data/spec/jira/resource/worklog_spec.rb +9 -11
- data/spec/mock_responses/board/1.json +33 -0
- data/spec/mock_responses/board/1_issues.json +62 -0
- data/spec/mock_responses/empty_issues.json +8 -0
- data/spec/mock_responses/issue/10002/watchers.json +13 -0
- data/spec/mock_responses/issue.json +1 -1
- data/spec/mock_responses/sprint/1_issues.json +125 -0
- data/spec/spec_helper.rb +8 -9
- data/spec/support/clients_helper.rb +4 -4
- data/spec/support/shared_examples/integration.rb +60 -77
- metadata +115 -55
- data/.ruby-version +0 -1
- data/README.rdoc +0 -333
- /data/spec/mock_responses/{attachment → issue/10002/attachments}/10000.json +0 -0
data/lib/jira/http_client.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'net/https'
|
3
3
|
require 'cgi/cookie'
|
4
|
+
require 'uri'
|
4
5
|
|
5
6
|
module JIRA
|
6
7
|
class HttpClient < RequestClient
|
7
|
-
|
8
8
|
DEFAULT_OPTIONS = {
|
9
|
-
:
|
10
|
-
:
|
11
|
-
}
|
9
|
+
username: nil,
|
10
|
+
password: nil
|
11
|
+
}.freeze
|
12
12
|
|
13
13
|
attr_reader :options
|
14
14
|
|
@@ -18,18 +18,26 @@ module JIRA
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def make_cookie_auth_request
|
21
|
-
body = { :
|
22
|
-
|
21
|
+
body = { username: @options[:username].to_s, password: @options[:password].to_s }.to_json
|
22
|
+
@options.delete(:username)
|
23
|
+
@options.delete(:password)
|
24
|
+
make_request(:post, @options[:context_path] + '/rest/auth/1/session', body, 'Content-Type' => 'application/json')
|
23
25
|
end
|
24
26
|
|
25
|
-
def make_request(http_method,
|
27
|
+
def make_request(http_method, url, body = '', headers = {})
|
28
|
+
# When a proxy is enabled, Net::HTTP expects that the request path omits the domain name
|
29
|
+
path = request_path(url)
|
26
30
|
request = Net::HTTP.const_get(http_method.to_s.capitalize).new(path, headers)
|
27
31
|
request.body = body unless body.nil?
|
28
|
-
|
29
|
-
request
|
30
|
-
|
31
|
-
|
32
|
-
|
32
|
+
|
33
|
+
execute_request(request)
|
34
|
+
end
|
35
|
+
|
36
|
+
def make_multipart_request(url, body, headers = {})
|
37
|
+
path = request_path(url)
|
38
|
+
request = Net::HTTP::Post::Multipart.new(path, body, headers)
|
39
|
+
|
40
|
+
execute_request(request)
|
33
41
|
end
|
34
42
|
|
35
43
|
def basic_auth_http_conn
|
@@ -38,23 +46,52 @@ module JIRA
|
|
38
46
|
|
39
47
|
def http_conn(uri)
|
40
48
|
if @options[:proxy_address]
|
41
|
-
|
49
|
+
http_class = Net::HTTP::Proxy(@options[:proxy_address], @options[:proxy_port] || 80, @options[:proxy_username], @options[:proxy_password])
|
42
50
|
else
|
43
|
-
|
51
|
+
http_class = Net::HTTP
|
44
52
|
end
|
45
53
|
http_conn = http_class.new(uri.host, uri.port)
|
46
54
|
http_conn.use_ssl = @options[:use_ssl]
|
55
|
+
if @options[:use_client_cert]
|
56
|
+
http_conn.cert = @options[:ssl_client_cert]
|
57
|
+
http_conn.key = @options[:ssl_client_key]
|
58
|
+
end
|
47
59
|
http_conn.verify_mode = @options[:ssl_verify_mode]
|
60
|
+
http_conn.ssl_version = @options[:ssl_version] if @options[:ssl_version]
|
48
61
|
http_conn.read_timeout = @options[:read_timeout]
|
62
|
+
http_conn.ca_file = @options[:ca_file] if @options[:ca_file]
|
49
63
|
http_conn
|
50
64
|
end
|
51
65
|
|
52
66
|
def uri
|
53
|
-
|
67
|
+
URI.parse(@options[:site])
|
68
|
+
end
|
69
|
+
|
70
|
+
def authenticated?
|
71
|
+
@authenticated
|
54
72
|
end
|
55
73
|
|
56
74
|
private
|
57
75
|
|
76
|
+
def execute_request(request)
|
77
|
+
add_cookies(request) if options[:use_cookies]
|
78
|
+
request.basic_auth(@options[:username], @options[:password]) if @options[:username] && @options[:password]
|
79
|
+
|
80
|
+
response = basic_auth_http_conn.request(request)
|
81
|
+
@authenticated = response.is_a? Net::HTTPOK
|
82
|
+
store_cookies(response) if options[:use_cookies]
|
83
|
+
|
84
|
+
response
|
85
|
+
end
|
86
|
+
|
87
|
+
def request_path(url)
|
88
|
+
parsed_uri = URI(url)
|
89
|
+
|
90
|
+
return url unless parsed_uri.is_a?(URI::HTTP)
|
91
|
+
|
92
|
+
parsed_uri.request_uri
|
93
|
+
end
|
94
|
+
|
58
95
|
def store_cookies(response)
|
59
96
|
cookies = response.get_fields('set-cookie')
|
60
97
|
if cookies
|
@@ -67,7 +104,8 @@ module JIRA
|
|
67
104
|
end
|
68
105
|
|
69
106
|
def add_cookies(request)
|
70
|
-
cookie_array = @cookies.values.map { |cookie| cookie.
|
107
|
+
cookie_array = @cookies.values.map { |cookie| "#{cookie.name}=#{cookie.value[0]}" }
|
108
|
+
cookie_array += Array(@options[:additional_cookies]) if @options.key?(:additional_cookies)
|
71
109
|
request.add_field('Cookie', cookie_array.join('; ')) if cookie_array.any?
|
72
110
|
request
|
73
111
|
end
|
data/lib/jira/http_error.rb
CHANGED
@@ -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, :
|
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).presence || response.try(:body)
|
12
12
|
end
|
13
|
-
|
14
13
|
end
|
15
|
-
|
16
14
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'atlassian/jwt'
|
2
|
+
|
3
|
+
module JIRA
|
4
|
+
class JwtClient < HttpClient
|
5
|
+
def make_request(http_method, url, body = '', headers = {})
|
6
|
+
@http_method = http_method
|
7
|
+
|
8
|
+
super(http_method, url, body, headers)
|
9
|
+
end
|
10
|
+
|
11
|
+
def make_multipart_request(url, data, headers = {})
|
12
|
+
@http_method = :post
|
13
|
+
|
14
|
+
super(url, data, headers)
|
15
|
+
end
|
16
|
+
|
17
|
+
class JwtUriBuilder
|
18
|
+
attr_reader :request_url, :http_method, :shared_secret, :site, :issuer
|
19
|
+
|
20
|
+
def initialize(request_url, http_method, shared_secret, site, issuer)
|
21
|
+
@request_url = request_url
|
22
|
+
@http_method = http_method
|
23
|
+
@shared_secret = shared_secret
|
24
|
+
@site = site
|
25
|
+
@issuer = issuer
|
26
|
+
end
|
27
|
+
|
28
|
+
def build
|
29
|
+
uri = URI.parse(request_url)
|
30
|
+
new_query = URI.decode_www_form(String(uri.query)) << ['jwt', jwt_header]
|
31
|
+
uri.query = URI.encode_www_form(new_query)
|
32
|
+
|
33
|
+
return uri.to_s unless uri.is_a?(URI::HTTP)
|
34
|
+
|
35
|
+
uri.request_uri
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def jwt_header
|
41
|
+
claim = Atlassian::Jwt.build_claims \
|
42
|
+
issuer,
|
43
|
+
request_url,
|
44
|
+
http_method.to_s,
|
45
|
+
site,
|
46
|
+
(Time.now - 60).to_i,
|
47
|
+
(Time.now + 86_400).to_i
|
48
|
+
|
49
|
+
JWT.encode claim, shared_secret
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
attr_reader :http_method
|
56
|
+
|
57
|
+
def request_path(url)
|
58
|
+
JwtUriBuilder.new(
|
59
|
+
url,
|
60
|
+
http_method.to_s,
|
61
|
+
@options[:shared_secret],
|
62
|
+
@options[:site],
|
63
|
+
@options[:issuer]
|
64
|
+
).build
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/jira/oauth_client.rb
CHANGED
@@ -4,22 +4,21 @@ require 'forwardable'
|
|
4
4
|
|
5
5
|
module JIRA
|
6
6
|
class OauthClient < RequestClient
|
7
|
-
|
8
7
|
DEFAULT_OPTIONS = {
|
9
|
-
:
|
10
|
-
:
|
11
|
-
:
|
12
|
-
:
|
13
|
-
:
|
14
|
-
:
|
15
|
-
:
|
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
|
-
|
21
|
+
'init_access_token must be called before using the client'
|
23
22
|
end
|
24
23
|
end
|
25
24
|
|
@@ -35,17 +34,19 @@ module JIRA
|
|
35
34
|
@consumer = init_oauth_consumer(@options)
|
36
35
|
end
|
37
36
|
|
38
|
-
def init_oauth_consumer(
|
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
|
-
|
41
|
+
# proxy_address does not exist in oauth's gem context but proxy does
|
42
|
+
@options[:proxy] = @options[:proxy_address] if @options[:proxy_address]
|
43
|
+
OAuth::Consumer.new(@options[:consumer_key], @options[:consumer_secret], @options)
|
43
44
|
end
|
44
45
|
|
45
46
|
# Returns the current request token if it is set, else it creates
|
46
47
|
# and sets a new token.
|
47
48
|
def request_token(options = {}, *arguments, &block)
|
48
|
-
@request_token ||= get_request_token(options, *arguments, block)
|
49
|
+
@request_token ||= get_request_token(options, *arguments, &block)
|
49
50
|
end
|
50
51
|
|
51
52
|
# Sets the request token from a given token and secret.
|
@@ -62,23 +63,52 @@ module JIRA
|
|
62
63
|
# Sets the access token from a preexisting token and secret.
|
63
64
|
def set_access_token(token, secret)
|
64
65
|
@access_token = OAuth::AccessToken.new(@consumer, token, secret)
|
66
|
+
@authenticated = true
|
67
|
+
@access_token
|
65
68
|
end
|
66
69
|
|
67
70
|
# Returns the current access token. Raises an
|
68
71
|
# JIRA::Client::UninitializedAccessTokenError exception if it is not set.
|
69
72
|
def access_token
|
70
|
-
raise UninitializedAccessTokenError
|
73
|
+
raise UninitializedAccessTokenError unless @access_token
|
71
74
|
@access_token
|
72
75
|
end
|
73
76
|
|
74
|
-
def make_request(http_method,
|
77
|
+
def make_request(http_method, url, body = '', headers = {})
|
78
|
+
# When using oauth_2legged we need to add an empty oauth_token parameter to every request.
|
79
|
+
if @options[:auth_type] == :oauth_2legged
|
80
|
+
oauth_params_str = 'oauth_token='
|
81
|
+
uri = URI.parse(url)
|
82
|
+
uri.query = if uri.query.to_s == ''
|
83
|
+
oauth_params_str
|
84
|
+
else
|
85
|
+
uri.query + '&' + oauth_params_str
|
86
|
+
end
|
87
|
+
url = uri.to_s
|
88
|
+
end
|
89
|
+
|
75
90
|
case http_method
|
76
91
|
when :delete, :get, :head
|
77
|
-
response = access_token.send http_method,
|
92
|
+
response = access_token.send http_method, url, headers
|
78
93
|
when :post, :put
|
79
|
-
response = access_token.send http_method,
|
94
|
+
response = access_token.send http_method, url, body, headers
|
80
95
|
end
|
96
|
+
@authenticated = true
|
81
97
|
response
|
82
98
|
end
|
99
|
+
|
100
|
+
def make_multipart_request(url, data, headers = {})
|
101
|
+
request = Net::HTTP::Post::Multipart.new url, data, headers
|
102
|
+
|
103
|
+
access_token.sign! request
|
104
|
+
|
105
|
+
response = consumer.http.request(request)
|
106
|
+
@authenticated = true
|
107
|
+
response
|
108
|
+
end
|
109
|
+
|
110
|
+
def authenticated?
|
111
|
+
@authenticated
|
112
|
+
end
|
83
113
|
end
|
84
114
|
end
|
data/lib/jira/request_client.rb
CHANGED
@@ -1,20 +1,31 @@
|
|
1
1
|
require 'oauth'
|
2
2
|
require 'json'
|
3
3
|
require 'net/https'
|
4
|
-
#require 'pry'
|
5
4
|
|
6
5
|
module JIRA
|
7
6
|
class RequestClient
|
8
|
-
|
9
7
|
# Returns the response if the request was successful (HTTP::2xx) and
|
10
8
|
# raises a JIRA::HTTPError if it was not successful, with the response
|
11
9
|
# attached.
|
12
10
|
|
13
11
|
def request(*args)
|
14
12
|
response = make_request(*args)
|
15
|
-
|
16
|
-
|
13
|
+
raise HTTPError, response unless response.is_a?(Net::HTTPSuccess)
|
14
|
+
response
|
15
|
+
end
|
16
|
+
|
17
|
+
def request_multipart(*args)
|
18
|
+
response = make_multipart_request(*args)
|
19
|
+
raise HTTPError, response unless response.is_a?(Net::HTTPSuccess)
|
17
20
|
response
|
18
21
|
end
|
22
|
+
|
23
|
+
def make_request(*args)
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
def make_multipart_request(*args)
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
19
30
|
end
|
20
|
-
end
|
31
|
+
end
|
data/lib/jira/resource/agile.rb
CHANGED
@@ -2,32 +2,59 @@ 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
|
-
|
12
|
-
|
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
|
|
16
18
|
def self.get_backlog_issues(client, board_id, options = {})
|
17
19
|
options[:maxResults] ||= 100
|
18
|
-
response = client.get("/
|
20
|
+
response = client.get(path_base(client) + "/board/#{board_id}/backlog?#{hash_to_query_string(options)}")
|
19
21
|
parse_json(response.body)
|
20
22
|
end
|
21
23
|
|
24
|
+
def self.get_board_issues(client, board_id, options = {})
|
25
|
+
response = client.get(path_base(client) + "/board/#{board_id}/issue?#{hash_to_query_string(options)}")
|
26
|
+
json = parse_json(response.body)
|
27
|
+
# To get Issue objects with the same structure as for Issue.all
|
28
|
+
return {} if json['issues'].size.zero?
|
29
|
+
issue_ids = json['issues'].map do |issue|
|
30
|
+
issue['id']
|
31
|
+
end
|
32
|
+
client.Issue.jql("id IN(#{issue_ids.join(', ')})")
|
33
|
+
end
|
34
|
+
|
22
35
|
def self.get_sprints(client, board_id, options = {})
|
23
36
|
options[:maxResults] ||= 100
|
24
|
-
response = client.get("/
|
37
|
+
response = client.get(path_base(client) + "/board/#{board_id}/sprint?#{hash_to_query_string(options)}")
|
25
38
|
parse_json(response.body)
|
26
39
|
end
|
27
40
|
|
28
41
|
def self.get_sprint_issues(client, sprint_id, options = {})
|
29
42
|
options[:maxResults] ||= 100
|
30
|
-
response = client.get("/
|
43
|
+
response = client.get(path_base(client) + "/sprint/#{sprint_id}/issue?#{hash_to_query_string(options)}")
|
44
|
+
parse_json(response.body)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.get_projects_full(client, board_id, _options = {})
|
48
|
+
response = client.get(path_base(client) + "/board/#{board_id}/project/full")
|
49
|
+
parse_json(response.body)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.get_projects(client, board_id, options = {})
|
53
|
+
options[:maxResults] ||= 100
|
54
|
+
create_meta_url = path_base(client) + "/board/#{board_id}/project"
|
55
|
+
params = hash_to_query_string(options)
|
56
|
+
|
57
|
+
response = client.get("#{create_meta_url}?#{params}")
|
31
58
|
parse_json(response.body)
|
32
59
|
end
|
33
60
|
|
@@ -47,8 +74,6 @@ module JIRA
|
|
47
74
|
def path_base(client)
|
48
75
|
self.class.path_base(client)
|
49
76
|
end
|
50
|
-
|
51
77
|
end
|
52
|
-
|
53
78
|
end
|
54
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
|
-
|
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
|
-
|
27
|
+
new(client, { attrs: attrs }.merge(options))
|
30
28
|
end
|
31
29
|
end
|
32
30
|
|
33
31
|
def self.manifest(client)
|
34
|
-
url =
|
32
|
+
url = full_url(client) + '/manifest'
|
35
33
|
response = client.get(url)
|
36
34
|
json = parse_json(response.body)
|
37
|
-
JIRA::Base.new(client,
|
35
|
+
JIRA::Base.new(client, attrs: json)
|
38
36
|
end
|
39
|
-
|
40
37
|
end
|
41
38
|
end
|
42
39
|
end
|
@@ -1,12 +1,50 @@
|
|
1
|
+
require 'net/http/post/multipart'
|
2
|
+
|
1
3
|
module JIRA
|
2
4
|
module Resource
|
3
|
-
|
4
5
|
class AttachmentFactory < JIRA::BaseFactory # :nodoc:
|
6
|
+
delegate_to_target_class :meta
|
5
7
|
end
|
6
8
|
|
7
9
|
class Attachment < JIRA::Base
|
8
|
-
|
9
|
-
|
10
|
+
belongs_to :issue
|
11
|
+
has_one :author, class: JIRA::Resource::User
|
12
|
+
|
13
|
+
def self.endpoint_name
|
14
|
+
'attachments'
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.meta(client)
|
18
|
+
response = client.get(client.options[:rest_base_path] + '/attachment/meta')
|
19
|
+
parse_json(response.body)
|
20
|
+
end
|
21
|
+
|
22
|
+
def save!(attrs, path = url)
|
23
|
+
file = attrs['file'] || attrs[:file] # Keep supporting 'file' parameter as a string for backward compatibility
|
24
|
+
mime_type = attrs[:mimeType] || 'application/binary'
|
25
|
+
|
26
|
+
headers = { 'X-Atlassian-Token' => 'nocheck' }
|
27
|
+
data = { 'file' => UploadIO.new(file, mime_type, file) }
|
10
28
|
|
29
|
+
response = client.post_multipart(path, data , headers)
|
30
|
+
|
31
|
+
set_attributes(attrs, response)
|
32
|
+
|
33
|
+
@expanded = false
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def set_attributes(attributes, response)
|
40
|
+
set_attrs(attributes, false)
|
41
|
+
return if response.body.nil? || response.body.length < 2
|
42
|
+
|
43
|
+
json = self.class.parse_json(response.body)
|
44
|
+
attachment = json[0]
|
45
|
+
|
46
|
+
set_attrs(attachment)
|
47
|
+
end
|
48
|
+
end
|
11
49
|
end
|
12
50
|
end
|
@@ -0,0 +1,91 @@
|
|
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
|
+
def configuration(params = {})
|
50
|
+
path = path_base(client) + "/board/#{id}/configuration"
|
51
|
+
response = client.get(url_with_query_params(path, params))
|
52
|
+
json = self.class.parse_json(response.body)
|
53
|
+
client.BoardConfiguration.build(json)
|
54
|
+
end
|
55
|
+
|
56
|
+
# options
|
57
|
+
# - state ~ future, active, closed, you can define multiple states separated by commas, e.g. state=active,closed
|
58
|
+
# - maxResults ~ default: 50 (JIRA API), 1000 (this library)
|
59
|
+
# - startAt ~ base index, starts at 0
|
60
|
+
def sprints(options = {})
|
61
|
+
# options.reverse_merge!(DEFAULT_OPTIONS)
|
62
|
+
response = client.get(path_base(client) + "/board/#{id}/sprint?#{options.to_query}")
|
63
|
+
json = self.class.parse_json(response.body)
|
64
|
+
json['values'].map do |sprint|
|
65
|
+
sprint['rapidview_id'] = id
|
66
|
+
client.Sprint.build(sprint)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def project
|
71
|
+
response = client.get(path_base(client) + "/board/#{id}/project")
|
72
|
+
json = self.class.parse_json(response.body)
|
73
|
+
json['values'][0]
|
74
|
+
end
|
75
|
+
|
76
|
+
def add_issue_to_backlog(issue)
|
77
|
+
client.post(path_base(client) + '/backlog/issue', { issues: [issue.id] }.to_json)
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def self.path_base(client)
|
83
|
+
client.options[:context_path] + '/rest/agile/1.0'
|
84
|
+
end
|
85
|
+
|
86
|
+
def path_base(client)
|
87
|
+
self.class.path_base(client)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|