jira-ruby 1.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
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