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.
Files changed (109) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +3 -0
  3. data/.travis.yml +5 -3
  4. data/Gemfile +7 -1
  5. data/Guardfile +1 -1
  6. data/README.md +452 -0
  7. data/Rakefile +6 -7
  8. data/example.rb +23 -1
  9. data/http-basic-example.rb +13 -12
  10. data/jira-ruby.gemspec +13 -13
  11. data/lib/jira/base.rb +53 -52
  12. data/lib/jira/base_factory.rb +3 -6
  13. data/lib/jira/client.rb +127 -30
  14. data/lib/jira/has_many_proxy.rb +0 -1
  15. data/lib/jira/http_client.rb +54 -16
  16. data/lib/jira/http_error.rb +3 -5
  17. data/lib/jira/jwt_client.rb +67 -0
  18. data/lib/jira/oauth_client.rb +47 -17
  19. data/lib/jira/request_client.rb +16 -5
  20. data/lib/jira/resource/agile.rb +34 -9
  21. data/lib/jira/resource/applinks.rb +5 -8
  22. data/lib/jira/resource/attachment.rb +41 -3
  23. data/lib/jira/resource/board.rb +91 -0
  24. data/lib/jira/resource/board_configuration.rb +9 -0
  25. data/lib/jira/resource/comment.rb +0 -2
  26. data/lib/jira/resource/component.rb +1 -3
  27. data/lib/jira/resource/createmeta.rb +12 -14
  28. data/lib/jira/resource/field.rb +22 -22
  29. data/lib/jira/resource/filter.rb +2 -2
  30. data/lib/jira/resource/issue.rb +69 -38
  31. data/lib/jira/resource/issue_picker_suggestions.rb +24 -0
  32. data/lib/jira/resource/issue_picker_suggestions_issue.rb +10 -0
  33. data/lib/jira/resource/issuelink.rb +3 -5
  34. data/lib/jira/resource/issuelinktype.rb +0 -1
  35. data/lib/jira/resource/issuetype.rb +1 -3
  36. data/lib/jira/resource/priority.rb +1 -3
  37. data/lib/jira/resource/project.rb +5 -7
  38. data/lib/jira/resource/rapidview.rb +28 -7
  39. data/lib/jira/resource/remotelink.rb +1 -4
  40. data/lib/jira/resource/resolution.rb +2 -4
  41. data/lib/jira/resource/serverinfo.rb +1 -2
  42. data/lib/jira/resource/sprint.rb +86 -17
  43. data/lib/jira/resource/sprint_report.rb +8 -0
  44. data/lib/jira/resource/status.rb +1 -3
  45. data/lib/jira/resource/suggested_issue.rb +9 -0
  46. data/lib/jira/resource/transition.rb +2 -6
  47. data/lib/jira/resource/user.rb +12 -2
  48. data/lib/jira/resource/version.rb +1 -3
  49. data/lib/jira/resource/watcher.rb +35 -0
  50. data/lib/jira/resource/webhook.rb +3 -6
  51. data/lib/jira/resource/worklog.rb +3 -5
  52. data/lib/jira/version.rb +1 -1
  53. data/lib/jira-ruby.rb +12 -2
  54. data/lib/tasks/generate.rake +4 -4
  55. data/spec/integration/attachment_spec.rb +17 -8
  56. data/spec/integration/comment_spec.rb +31 -34
  57. data/spec/integration/component_spec.rb +21 -24
  58. data/spec/integration/field_spec.rb +15 -18
  59. data/spec/integration/issue_spec.rb +45 -46
  60. data/spec/integration/issuelinktype_spec.rb +8 -11
  61. data/spec/integration/issuetype_spec.rb +5 -7
  62. data/spec/integration/priority_spec.rb +5 -8
  63. data/spec/integration/project_spec.rb +13 -20
  64. data/spec/integration/rapidview_spec.rb +17 -10
  65. data/spec/integration/resolution_spec.rb +7 -10
  66. data/spec/integration/status_spec.rb +5 -8
  67. data/spec/integration/transition_spec.rb +17 -20
  68. data/spec/integration/user_spec.rb +24 -8
  69. data/spec/integration/version_spec.rb +21 -25
  70. data/spec/integration/watcher_spec.rb +62 -0
  71. data/spec/integration/webhook.rb +8 -17
  72. data/spec/integration/worklog_spec.rb +30 -34
  73. data/spec/jira/base_factory_spec.rb +11 -12
  74. data/spec/jira/base_spec.rb +216 -229
  75. data/spec/jira/client_spec.rb +227 -159
  76. data/spec/jira/has_many_proxy_spec.rb +11 -12
  77. data/spec/jira/http_client_spec.rb +254 -31
  78. data/spec/jira/http_error_spec.rb +7 -9
  79. data/spec/jira/jwt_uri_builder_spec.rb +59 -0
  80. data/spec/jira/oauth_client_spec.rb +110 -39
  81. data/spec/jira/request_client_spec.rb +36 -9
  82. data/spec/jira/resource/agile_spec.rb +135 -0
  83. data/spec/jira/resource/attachment_spec.rb +127 -9
  84. data/spec/jira/resource/board_spec.rb +224 -0
  85. data/spec/jira/resource/createmeta_spec.rb +29 -32
  86. data/spec/jira/resource/field_spec.rb +42 -48
  87. data/spec/jira/resource/filter_spec.rb +40 -40
  88. data/spec/jira/resource/issue_picker_suggestions_spec.rb +79 -0
  89. data/spec/jira/resource/issue_spec.rb +88 -85
  90. data/spec/jira/resource/issuelink_spec.rb +1 -1
  91. data/spec/jira/resource/jira_picker_suggestions_issue_spec.rb +18 -0
  92. data/spec/jira/resource/project_factory_spec.rb +2 -4
  93. data/spec/jira/resource/project_spec.rb +33 -33
  94. data/spec/jira/resource/sprint_spec.rb +90 -0
  95. data/spec/jira/resource/user_factory_spec.rb +6 -8
  96. data/spec/jira/resource/worklog_spec.rb +9 -11
  97. data/spec/mock_responses/board/1.json +33 -0
  98. data/spec/mock_responses/board/1_issues.json +62 -0
  99. data/spec/mock_responses/empty_issues.json +8 -0
  100. data/spec/mock_responses/issue/10002/watchers.json +13 -0
  101. data/spec/mock_responses/issue.json +1 -1
  102. data/spec/mock_responses/sprint/1_issues.json +125 -0
  103. data/spec/spec_helper.rb +8 -9
  104. data/spec/support/clients_helper.rb +4 -4
  105. data/spec/support/shared_examples/integration.rb +60 -77
  106. metadata +115 -55
  107. data/.ruby-version +0 -1
  108. data/README.rdoc +0 -333
  109. /data/spec/mock_responses/{attachment → issue/10002/attachments}/10000.json +0 -0
@@ -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
- :username => '',
10
- :password => ''
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 = { :username => @options[:username], :password => @options[:password] }.to_json
22
- make_request(:post, '/rest/auth/1/session', body, {'Content-Type' => 'application/json'})
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, path, body='', headers={})
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
- add_cookies(request) if options[:use_cookies]
29
- request.basic_auth(@options[:username], @options[:password])
30
- response = basic_auth_http_conn.request(request)
31
- store_cookies(response) if options[:use_cookies]
32
- response
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
- http_class = Net::HTTP::Proxy(@options[:proxy_address], @options[:proxy_port] ? @options[:proxy_port] : 80)
49
+ http_class = Net::HTTP::Proxy(@options[:proxy_address], @options[:proxy_port] || 80, @options[:proxy_username], @options[:proxy_password])
42
50
  else
43
- http_class = Net::HTTP
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
- uri = URI.parse(@options[:site])
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.to_s }
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
@@ -1,16 +1,14 @@
1
1
  require 'forwardable'
2
2
  module JIRA
3
-
4
3
  class HTTPError < StandardError
5
4
  extend Forwardable
6
5
 
7
- def_instance_delegators :@response, :message, :code
8
- attr_reader :response
6
+ def_instance_delegators :@response, :code
7
+ attr_reader :response, :message
9
8
 
10
9
  def initialize(response)
11
10
  @response = response
11
+ @message = response.try(:message).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
@@ -4,22 +4,21 @@ require 'forwardable'
4
4
 
5
5
  module JIRA
6
6
  class OauthClient < RequestClient
7
-
8
7
  DEFAULT_OPTIONS = {
9
- :signature_method => 'RSA-SHA1',
10
- :request_token_path => "/plugins/servlet/oauth/request-token",
11
- :authorize_path => "/plugins/servlet/oauth/authorize",
12
- :access_token_path => "/plugins/servlet/oauth/access-token",
13
- :private_key_file => "rsakey.pem",
14
- :consumer_key => nil,
15
- :consumer_secret => nil
16
- }
8
+ signature_method: 'RSA-SHA1',
9
+ request_token_path: '/plugins/servlet/oauth/request-token',
10
+ authorize_path: '/plugins/servlet/oauth/authorize',
11
+ access_token_path: '/plugins/servlet/oauth/access-token',
12
+ private_key_file: 'rsakey.pem',
13
+ consumer_key: nil,
14
+ consumer_secret: nil
15
+ }.freeze
17
16
 
18
17
  # This exception is thrown when the client is used before the OAuth access token
19
18
  # has been initialized.
20
19
  class UninitializedAccessTokenError < StandardError
21
20
  def message
22
- "init_access_token must be called before using the client"
21
+ 'init_access_token must be called before using the client'
23
22
  end
24
23
  end
25
24
 
@@ -35,17 +34,19 @@ module JIRA
35
34
  @consumer = init_oauth_consumer(@options)
36
35
  end
37
36
 
38
- def init_oauth_consumer(options)
37
+ def init_oauth_consumer(_options)
39
38
  @options[:request_token_path] = @options[:context_path] + @options[:request_token_path]
40
39
  @options[:authorize_path] = @options[:context_path] + @options[:authorize_path]
41
40
  @options[:access_token_path] = @options[:context_path] + @options[:access_token_path]
42
- OAuth::Consumer.new(@options[:consumer_key],@options[:consumer_secret],@options)
41
+ # 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.new unless @access_token
73
+ raise UninitializedAccessTokenError unless @access_token
71
74
  @access_token
72
75
  end
73
76
 
74
- def make_request(http_method, path, body='', headers={})
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, path, headers
92
+ response = access_token.send http_method, url, headers
78
93
  when :post, :put
79
- response = access_token.send http_method, path, body, headers
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
@@ -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
- #binding.pry unless response.kind_of?(Net::HTTPSuccess)
16
- raise HTTPError.new(response) unless response.kind_of?(Net::HTTPSuccess)
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
@@ -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
- def self.all(client)
12
- response = client.get(path_base(client) + '/board')
9
+ # @param client [JIRA::Client]
10
+ # @param options [Hash<Symbol, Object>]
11
+ # @return [Hash]
12
+ def self.all(client, options = {})
13
+ opts = options.empty? ? '' : "?#{hash_to_query_string(options)}"
14
+ response = client.get(path_base(client) + "/board#{opts}")
13
15
  parse_json(response.body)
14
16
  end
15
17
 
16
18
  def self.get_backlog_issues(client, board_id, options = {})
17
19
  options[:maxResults] ||= 100
18
- response = client.get("/rest/agile/1.0/board/#{board_id}/backlog?maxResults=#{options[:maxResults]}")
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("/rest/agile/1.0/board/#{board_id}/sprint?maxResults=#{options[:maxResults]}")
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("/rest/agile/1.0/sprint/#{sprint_id}/issue?maxResults=#{options[:maxResults]}")
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
- self.full_url(client) + prefix + self.endpoint_name
19
+ full_url(client) + prefix + endpoint_name
22
20
  end
23
21
 
24
22
  def self.all(client, options = {})
@@ -26,17 +24,16 @@ module JIRA
26
24
  json = parse_json(response.body)
27
25
  json = json['list']
28
26
  json.map do |attrs|
29
- self.new(client, {:attrs => attrs}.merge(options))
27
+ new(client, { attrs: attrs }.merge(options))
30
28
  end
31
29
  end
32
30
 
33
31
  def self.manifest(client)
34
- url = self.full_url(client) + '/manifest'
32
+ url = full_url(client) + '/manifest'
35
33
  response = client.get(url)
36
34
  json = parse_json(response.body)
37
- JIRA::Base.new(client, {:attrs => json})
35
+ JIRA::Base.new(client, attrs: json)
38
36
  end
39
-
40
37
  end
41
38
  end
42
39
  end
@@ -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
- has_one :author, :class => JIRA::Resource::User
9
- end
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
@@ -0,0 +1,9 @@
1
+ module JIRA
2
+ module Resource
3
+ class BoardConfigurationFactory < JIRA::BaseFactory # :nodoc:
4
+ end
5
+
6
+ class BoardConfiguration < JIRA::Base
7
+ end
8
+ end
9
+ end
@@ -1,6 +1,5 @@
1
1
  module JIRA
2
2
  module Resource
3
-
4
3
  class CommentFactory < JIRA::BaseFactory # :nodoc:
5
4
  end
6
5
 
@@ -9,6 +8,5 @@ module JIRA
9
8
 
10
9
  nested_collections true
11
10
  end
12
-
13
11
  end
14
12
  end
@@ -1,10 +1,8 @@
1
1
  module JIRA
2
2
  module Resource
3
-
4
3
  class ComponentFactory < JIRA::BaseFactory # :nodoc:
5
4
  end
6
5
 
7
- class Component < JIRA::Base ; end
8
-
6
+ class Component < JIRA::Base; end
9
7
  end
10
8
  end