jira-ruby 2.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (154) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.travis.yml +9 -0
  4. data/Gemfile +14 -0
  5. data/Guardfile +14 -0
  6. data/LICENSE +19 -0
  7. data/README.md +427 -0
  8. data/Rakefile +31 -0
  9. data/example.rb +224 -0
  10. data/http-basic-example.rb +113 -0
  11. data/jira-ruby.gemspec +35 -0
  12. data/lib/jira-ruby.rb +49 -0
  13. data/lib/jira/base.rb +525 -0
  14. data/lib/jira/base_factory.rb +46 -0
  15. data/lib/jira/client.rb +308 -0
  16. data/lib/jira/has_many_proxy.rb +42 -0
  17. data/lib/jira/http_client.rb +112 -0
  18. data/lib/jira/http_error.rb +14 -0
  19. data/lib/jira/jwt_client.rb +67 -0
  20. data/lib/jira/oauth_client.rb +114 -0
  21. data/lib/jira/railtie.rb +10 -0
  22. data/lib/jira/request_client.rb +31 -0
  23. data/lib/jira/resource/agile.rb +79 -0
  24. data/lib/jira/resource/applinks.rb +39 -0
  25. data/lib/jira/resource/attachment.rb +50 -0
  26. data/lib/jira/resource/board.rb +91 -0
  27. data/lib/jira/resource/board_configuration.rb +9 -0
  28. data/lib/jira/resource/comment.rb +12 -0
  29. data/lib/jira/resource/component.rb +8 -0
  30. data/lib/jira/resource/createmeta.rb +44 -0
  31. data/lib/jira/resource/field.rb +83 -0
  32. data/lib/jira/resource/filter.rb +15 -0
  33. data/lib/jira/resource/issue.rb +141 -0
  34. data/lib/jira/resource/issuelink.rb +20 -0
  35. data/lib/jira/resource/issuelinktype.rb +14 -0
  36. data/lib/jira/resource/issuetype.rb +8 -0
  37. data/lib/jira/resource/priority.rb +8 -0
  38. data/lib/jira/resource/project.rb +41 -0
  39. data/lib/jira/resource/rapidview.rb +67 -0
  40. data/lib/jira/resource/remotelink.rb +26 -0
  41. data/lib/jira/resource/resolution.rb +8 -0
  42. data/lib/jira/resource/serverinfo.rb +18 -0
  43. data/lib/jira/resource/sprint.rb +105 -0
  44. data/lib/jira/resource/sprint_report.rb +8 -0
  45. data/lib/jira/resource/status.rb +8 -0
  46. data/lib/jira/resource/transition.rb +29 -0
  47. data/lib/jira/resource/user.rb +30 -0
  48. data/lib/jira/resource/version.rb +8 -0
  49. data/lib/jira/resource/watcher.rb +35 -0
  50. data/lib/jira/resource/webhook.rb +37 -0
  51. data/lib/jira/resource/worklog.rb +14 -0
  52. data/lib/jira/tasks.rb +0 -0
  53. data/lib/jira/version.rb +3 -0
  54. data/lib/tasks/generate.rake +18 -0
  55. data/spec/integration/attachment_spec.rb +32 -0
  56. data/spec/integration/comment_spec.rb +52 -0
  57. data/spec/integration/component_spec.rb +39 -0
  58. data/spec/integration/field_spec.rb +32 -0
  59. data/spec/integration/issue_spec.rb +93 -0
  60. data/spec/integration/issuelinktype_spec.rb +26 -0
  61. data/spec/integration/issuetype_spec.rb +24 -0
  62. data/spec/integration/priority_spec.rb +24 -0
  63. data/spec/integration/project_spec.rb +49 -0
  64. data/spec/integration/rapidview_spec.rb +74 -0
  65. data/spec/integration/resolution_spec.rb +26 -0
  66. data/spec/integration/status_spec.rb +24 -0
  67. data/spec/integration/transition_spec.rb +49 -0
  68. data/spec/integration/user_spec.rb +41 -0
  69. data/spec/integration/version_spec.rb +39 -0
  70. data/spec/integration/watcher_spec.rb +62 -0
  71. data/spec/integration/webhook.rb +25 -0
  72. data/spec/integration/worklog_spec.rb +51 -0
  73. data/spec/jira/base_factory_spec.rb +45 -0
  74. data/spec/jira/base_spec.rb +598 -0
  75. data/spec/jira/client_spec.rb +291 -0
  76. data/spec/jira/has_many_proxy_spec.rb +46 -0
  77. data/spec/jira/http_client_spec.rb +328 -0
  78. data/spec/jira/http_error_spec.rb +24 -0
  79. data/spec/jira/jwt_uri_builder_spec.rb +59 -0
  80. data/spec/jira/oauth_client_spec.rb +162 -0
  81. data/spec/jira/request_client_spec.rb +41 -0
  82. data/spec/jira/resource/agile_spec.rb +135 -0
  83. data/spec/jira/resource/attachment_spec.rb +138 -0
  84. data/spec/jira/resource/board_spec.rb +224 -0
  85. data/spec/jira/resource/createmeta_spec.rb +258 -0
  86. data/spec/jira/resource/field_spec.rb +85 -0
  87. data/spec/jira/resource/filter_spec.rb +97 -0
  88. data/spec/jira/resource/issue_spec.rb +227 -0
  89. data/spec/jira/resource/issuelink_spec.rb +14 -0
  90. data/spec/jira/resource/project_factory_spec.rb +11 -0
  91. data/spec/jira/resource/project_spec.rb +123 -0
  92. data/spec/jira/resource/sprint_spec.rb +90 -0
  93. data/spec/jira/resource/user_factory_spec.rb +31 -0
  94. data/spec/jira/resource/worklog_spec.rb +22 -0
  95. data/spec/mock_responses/board/1.json +33 -0
  96. data/spec/mock_responses/board/1_issues.json +62 -0
  97. data/spec/mock_responses/component.post.json +28 -0
  98. data/spec/mock_responses/component/10000.invalid.put.json +5 -0
  99. data/spec/mock_responses/component/10000.json +39 -0
  100. data/spec/mock_responses/component/10000.put.json +39 -0
  101. data/spec/mock_responses/empty_issues.json +8 -0
  102. data/spec/mock_responses/field.json +32 -0
  103. data/spec/mock_responses/field/1.json +15 -0
  104. data/spec/mock_responses/issue.json +1108 -0
  105. data/spec/mock_responses/issue.post.json +5 -0
  106. data/spec/mock_responses/issue/10002.invalid.put.json +6 -0
  107. data/spec/mock_responses/issue/10002.json +126 -0
  108. data/spec/mock_responses/issue/10002.put.missing_field_update.json +6 -0
  109. data/spec/mock_responses/issue/10002/attachments/10000.json +20 -0
  110. data/spec/mock_responses/issue/10002/comment.json +65 -0
  111. data/spec/mock_responses/issue/10002/comment.post.json +29 -0
  112. data/spec/mock_responses/issue/10002/comment/10000.json +29 -0
  113. data/spec/mock_responses/issue/10002/comment/10000.put.json +29 -0
  114. data/spec/mock_responses/issue/10002/transitions.json +49 -0
  115. data/spec/mock_responses/issue/10002/transitions.post.json +1 -0
  116. data/spec/mock_responses/issue/10002/watchers.json +13 -0
  117. data/spec/mock_responses/issue/10002/worklog.json +98 -0
  118. data/spec/mock_responses/issue/10002/worklog.post.json +30 -0
  119. data/spec/mock_responses/issue/10002/worklog/10000.json +31 -0
  120. data/spec/mock_responses/issue/10002/worklog/10000.put.json +30 -0
  121. data/spec/mock_responses/issueLinkType.json +25 -0
  122. data/spec/mock_responses/issueLinkType/10000.json +7 -0
  123. data/spec/mock_responses/issuetype.json +42 -0
  124. data/spec/mock_responses/issuetype/5.json +8 -0
  125. data/spec/mock_responses/jira/rest/webhooks/1.0/webhook.json +11 -0
  126. data/spec/mock_responses/jira/rest/webhooks/1.0/webhook/2.json +11 -0
  127. data/spec/mock_responses/priority.json +42 -0
  128. data/spec/mock_responses/priority/1.json +8 -0
  129. data/spec/mock_responses/project.json +12 -0
  130. data/spec/mock_responses/project/SAMPLEPROJECT.issues.json +1108 -0
  131. data/spec/mock_responses/project/SAMPLEPROJECT.json +84 -0
  132. data/spec/mock_responses/rapidview.json +10 -0
  133. data/spec/mock_responses/rapidview/SAMPLEPROJECT.issues.full.json +276 -0
  134. data/spec/mock_responses/rapidview/SAMPLEPROJECT.issues.json +111 -0
  135. data/spec/mock_responses/rapidview/SAMPLEPROJECT.json +6 -0
  136. data/spec/mock_responses/resolution.json +15 -0
  137. data/spec/mock_responses/resolution/1.json +7 -0
  138. data/spec/mock_responses/sprint/1_issues.json +125 -0
  139. data/spec/mock_responses/status.json +37 -0
  140. data/spec/mock_responses/status/1.json +7 -0
  141. data/spec/mock_responses/user_username=admin.json +17 -0
  142. data/spec/mock_responses/version.post.json +7 -0
  143. data/spec/mock_responses/version/10000.invalid.put.json +5 -0
  144. data/spec/mock_responses/version/10000.json +11 -0
  145. data/spec/mock_responses/version/10000.put.json +7 -0
  146. data/spec/mock_responses/webhook.json +11 -0
  147. data/spec/mock_responses/webhook/webhook.json +11 -0
  148. data/spec/spec_helper.rb +21 -0
  149. data/spec/support/clients_helper.rb +16 -0
  150. data/spec/support/matchers/have_attributes.rb +11 -0
  151. data/spec/support/matchers/have_many.rb +9 -0
  152. data/spec/support/matchers/have_one.rb +5 -0
  153. data/spec/support/shared_examples/integration.rb +177 -0
  154. metadata +491 -0
@@ -0,0 +1,14 @@
1
+ require 'forwardable'
2
+ module JIRA
3
+ class HTTPError < StandardError
4
+ extend Forwardable
5
+
6
+ def_instance_delegators :@response, :code
7
+ attr_reader :response, :message
8
+
9
+ def initialize(response)
10
+ @response = response
11
+ @message = response.try(:message).presence || response.try(:body)
12
+ end
13
+ end
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
@@ -0,0 +1,114 @@
1
+ require 'oauth'
2
+ require 'json'
3
+ require 'forwardable'
4
+
5
+ module JIRA
6
+ class OauthClient < RequestClient
7
+ DEFAULT_OPTIONS = {
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
16
+
17
+ # This exception is thrown when the client is used before the OAuth access token
18
+ # has been initialized.
19
+ class UninitializedAccessTokenError < StandardError
20
+ def message
21
+ 'init_access_token must be called before using the client'
22
+ end
23
+ end
24
+
25
+ extend Forwardable
26
+
27
+ attr_accessor :consumer
28
+ attr_reader :options
29
+
30
+ def_instance_delegators :@consumer, :key, :secret, :get_request_token
31
+
32
+ def initialize(options)
33
+ @options = DEFAULT_OPTIONS.merge(options)
34
+ @consumer = init_oauth_consumer(@options)
35
+ end
36
+
37
+ def init_oauth_consumer(_options)
38
+ @options[:request_token_path] = @options[:context_path] + @options[:request_token_path]
39
+ @options[:authorize_path] = @options[:context_path] + @options[:authorize_path]
40
+ @options[:access_token_path] = @options[:context_path] + @options[:access_token_path]
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)
44
+ end
45
+
46
+ # Returns the current request token if it is set, else it creates
47
+ # and sets a new token.
48
+ def request_token(options = {}, *arguments, &block)
49
+ @request_token ||= get_request_token(options, *arguments, block)
50
+ end
51
+
52
+ # Sets the request token from a given token and secret.
53
+ def set_request_token(token, secret)
54
+ @request_token = OAuth::RequestToken.new(@consumer, token, secret)
55
+ end
56
+
57
+ # Initialises and returns a new access token from the params hash
58
+ # returned by the OAuth transaction.
59
+ def init_access_token(params)
60
+ @access_token = request_token.get_access_token(params)
61
+ end
62
+
63
+ # Sets the access token from a preexisting token and secret.
64
+ def set_access_token(token, secret)
65
+ @access_token = OAuth::AccessToken.new(@consumer, token, secret)
66
+ @authenticated = true
67
+ @access_token
68
+ end
69
+
70
+ # Returns the current access token. Raises an
71
+ # JIRA::Client::UninitializedAccessTokenError exception if it is not set.
72
+ def access_token
73
+ raise UninitializedAccessTokenError unless @access_token
74
+ @access_token
75
+ end
76
+
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
+
90
+ case http_method
91
+ when :delete, :get, :head
92
+ response = access_token.send http_method, url, headers
93
+ when :post, :put
94
+ response = access_token.send http_method, url, body, headers
95
+ end
96
+ @authenticated = true
97
+ response
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
113
+ end
114
+ end
@@ -0,0 +1,10 @@
1
+ require 'jira-ruby'
2
+ require 'rails'
3
+
4
+ module JIRA
5
+ class Railtie < Rails::Railtie
6
+ rake_tasks do
7
+ load 'tasks/generate.rake'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,31 @@
1
+ require 'oauth'
2
+ require 'json'
3
+ require 'net/https'
4
+
5
+ module JIRA
6
+ class RequestClient
7
+ # Returns the response if the request was successful (HTTP::2xx) and
8
+ # raises a JIRA::HTTPError if it was not successful, with the response
9
+ # attached.
10
+
11
+ def request(*args)
12
+ response = make_request(*args)
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)
20
+ response
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
30
+ end
31
+ end
@@ -0,0 +1,79 @@
1
+ require 'cgi'
2
+
3
+ module JIRA
4
+ module Resource
5
+ class AgileFactory < JIRA::BaseFactory # :nodoc:
6
+ end
7
+
8
+ class Agile < JIRA::Base
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}")
15
+ parse_json(response.body)
16
+ end
17
+
18
+ def self.get_backlog_issues(client, board_id, options = {})
19
+ options[:maxResults] ||= 100
20
+ response = client.get(path_base(client) + "/board/#{board_id}/backlog?#{hash_to_query_string(options)}")
21
+ parse_json(response.body)
22
+ end
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
+
35
+ def self.get_sprints(client, board_id, options = {})
36
+ options[:maxResults] ||= 100
37
+ response = client.get(path_base(client) + "/board/#{board_id}/sprint?#{hash_to_query_string(options)}")
38
+ parse_json(response.body)
39
+ end
40
+
41
+ def self.get_sprint_issues(client, sprint_id, options = {})
42
+ options[:maxResults] ||= 100
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}")
58
+ parse_json(response.body)
59
+ end
60
+
61
+ # def self.find(client, key, options = {})
62
+ # options[:maxResults] ||= 100
63
+ # fields = options[:fields].join(',') unless options[:fields].nil?
64
+ # response = client.get("/rest/api/latest/search?jql=sprint=#{key}&fields=#{fields}&maxResults=#{options[:maxResults]}")
65
+ # parse_json(response.body)
66
+ # end
67
+
68
+ private
69
+
70
+ def self.path_base(client)
71
+ client.options[:context_path] + '/rest/agile/1.0'
72
+ end
73
+
74
+ def path_base(client)
75
+ self.class.path_base(client)
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,39 @@
1
+ module JIRA
2
+ module Resource
3
+ class ApplicationLinkFactory < JIRA::BaseFactory # :nodoc:
4
+ delegate_to_target_class :manifest
5
+ end
6
+
7
+ class ApplicationLink < JIRA::Base
8
+ REST_BASE_PATH = '/rest/applinks/1.0'.freeze
9
+
10
+ def self.endpoint_name
11
+ 'listApplicationlinks'
12
+ end
13
+
14
+ def self.full_url(client)
15
+ client.options[:context_path] + REST_BASE_PATH
16
+ end
17
+
18
+ def self.collection_path(client, prefix = '/')
19
+ full_url(client) + prefix + endpoint_name
20
+ end
21
+
22
+ def self.all(client, options = {})
23
+ response = client.get(collection_path(client))
24
+ json = parse_json(response.body)
25
+ json = json['list']
26
+ json.map do |attrs|
27
+ new(client, { attrs: attrs }.merge(options))
28
+ end
29
+ end
30
+
31
+ def self.manifest(client)
32
+ url = full_url(client) + '/manifest'
33
+ response = client.get(url)
34
+ json = parse_json(response.body)
35
+ JIRA::Base.new(client, attrs: json)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,50 @@
1
+ require 'net/http/post/multipart'
2
+
3
+ module JIRA
4
+ module Resource
5
+ class AttachmentFactory < JIRA::BaseFactory # :nodoc:
6
+ delegate_to_target_class :meta
7
+ end
8
+
9
+ class Attachment < JIRA::Base
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) }
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
49
+ end
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