jira-ruby 2.1.3

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 (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,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
@@ -0,0 +1,12 @@
1
+ module JIRA
2
+ module Resource
3
+ class CommentFactory < JIRA::BaseFactory # :nodoc:
4
+ end
5
+
6
+ class Comment < JIRA::Base
7
+ belongs_to :issue
8
+
9
+ nested_collections true
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,8 @@
1
+ module JIRA
2
+ module Resource
3
+ class ComponentFactory < JIRA::BaseFactory # :nodoc:
4
+ end
5
+
6
+ class Component < JIRA::Base; end
7
+ end
8
+ end
@@ -0,0 +1,44 @@
1
+ module JIRA
2
+ module Resource
3
+ class CreatemetaFactory < JIRA::BaseFactory # :nodoc:
4
+ end
5
+
6
+ class Createmeta < JIRA::Base
7
+ def self.endpoint_name
8
+ '/issue/createmeta'
9
+ end
10
+
11
+ def self.all(client, params = {})
12
+ if params.key?(:projectKeys)
13
+ values = Array(params[:projectKeys]).map { |i| (i.is_a?(JIRA::Resource::Project) ? i.key : i) }
14
+ params[:projectKeys] = values.join(',')
15
+ end
16
+
17
+ if params.key?(:projectIds)
18
+ values = Array(params[:projectIds]).map { |i| (i.is_a?(JIRA::Resource::Project) ? i.id : i) }
19
+ params[:projectIds] = values.join(',')
20
+ end
21
+
22
+ if params.key?(:issuetypeNames)
23
+ values = Array(params[:issuetypeNames]).map { |i| (i.is_a?(JIRA::Resource::Issuetype) ? i.name : i) }
24
+ params[:issuetypeNames] = values.join(',')
25
+ end
26
+
27
+ if params.key?(:issuetypeIds)
28
+ values = Array(params[:issuetypeIds]).map { |i| (i.is_a?(JIRA::Resource::Issuetype) ? i.id : i) }
29
+ params[:issuetypeIds] = values.join(',')
30
+ end
31
+
32
+ create_meta_url = client.options[:rest_base_path] + endpoint_name
33
+ params = hash_to_query_string(params)
34
+
35
+ response = params.empty? ? client.get(create_meta_url.to_s) : client.get("#{create_meta_url}?#{params}")
36
+
37
+ json = parse_json(response.body)
38
+ json['projects'].map do |attrs|
39
+ new(client, attrs: attrs)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,83 @@
1
+ module JIRA
2
+ module Resource
3
+ class FieldFactory < JIRA::BaseFactory # :nodoc:
4
+ delegate_to_target_class :map_fields, :name_to_id, :field_map
5
+ end
6
+
7
+ class Field < JIRA::Base
8
+ # translate a custom field description to a method-safe name
9
+ def self.safe_name(description)
10
+ description.gsub(/[^a-zA-Z0-9]/, '_')
11
+ end
12
+
13
+ # safe_name plus disambiguation if it fails it uses the original jira id (customfield_#####)
14
+ def self.safer_name(description, jira_id)
15
+ "#{safe_name(description)}_#{jira_id.split('_')[1]}"
16
+ rescue StandardError
17
+ jira_id
18
+ end
19
+
20
+ def self.map_fields(client)
21
+ field_map = {}
22
+ field_map_reverse = {}
23
+ fields = client.Field.all
24
+
25
+ # two pass approach, so that a custom field with the same name
26
+ # as a system field can't take precedence
27
+ fields.each do |f|
28
+ next if f.custom
29
+ name = safe_name(f.name)
30
+ field_map_reverse[f.id] = [f.name, name] # capture both the official name, and the mapped name
31
+ field_map[name] = f.id
32
+ end
33
+
34
+ fields.each do |f|
35
+ next unless f.custom
36
+ name = if field_map.key? f.name
37
+ renamed = safer_name(f.name, f.id)
38
+ warn "Duplicate Field name #{f.name} #{f.id} - renaming as #{renamed}"
39
+ renamed
40
+ else
41
+ safe_name(f.name)
42
+ end
43
+ field_map_reverse[f.id] = [f.name, name] # capture both the official name, and the mapped name
44
+ field_map[name] = f.id
45
+ end
46
+
47
+ client.cache.field_map_reverse = field_map_reverse # not sure where this will be used yet, but sure to be useful
48
+ client.cache.field_map = field_map
49
+ end
50
+
51
+ def self.field_map(client)
52
+ client.cache.field_map
53
+ end
54
+
55
+ def self.name_to_id(client, field_name)
56
+ field_name = field_name.to_s
57
+ return field_name unless client.cache.field_map && client.cache.field_map[field_name]
58
+ client.cache.field_map[field_name]
59
+ end
60
+
61
+ def respond_to?(method_name, _include_all = false)
62
+ if [method_name.to_s, client.Field.name_to_id(method_name)].any? { |k| attrs.key?(k) }
63
+ true
64
+ else
65
+ super(method_name)
66
+ end
67
+ end
68
+
69
+ def method_missing(method_name, *args, &block)
70
+ if attrs.key?(method_name.to_s)
71
+ attrs[method_name.to_s]
72
+ else
73
+ official_name = client.Field.name_to_id(method_name)
74
+ if attrs.key?(official_name)
75
+ attrs[official_name]
76
+ else
77
+ super(method_name, *args, &block)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,15 @@
1
+ module JIRA
2
+ module Resource
3
+ class FilterFactory < JIRA::BaseFactory # :nodoc:
4
+ end
5
+
6
+ class Filter < JIRA::Base
7
+ has_one :owner, class: JIRA::Resource::User
8
+
9
+ # Returns all the issues for this filter
10
+ def issues
11
+ Issue.jql(client, jql)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,141 @@
1
+ require 'cgi'
2
+ require 'json'
3
+
4
+ module JIRA
5
+ module Resource
6
+ class IssueFactory < JIRA::BaseFactory # :nodoc:
7
+ end
8
+
9
+ class Issue < JIRA::Base
10
+ has_one :reporter, class: JIRA::Resource::User,
11
+ nested_under: 'fields'
12
+ has_one :assignee, class: JIRA::Resource::User,
13
+ nested_under: 'fields'
14
+ has_one :project, nested_under: 'fields'
15
+
16
+ has_one :issuetype, nested_under: 'fields'
17
+
18
+ has_one :priority, nested_under: 'fields'
19
+
20
+ has_one :status, nested_under: 'fields'
21
+
22
+ has_many :transitions
23
+
24
+ has_many :components, nested_under: 'fields'
25
+
26
+ has_many :comments, nested_under: %w[fields comment]
27
+
28
+ has_many :attachments, nested_under: 'fields',
29
+ attribute_key: 'attachment'
30
+
31
+ has_many :versions, nested_under: 'fields'
32
+ has_many :fixVersions, class: JIRA::Resource::Version,
33
+ nested_under: 'fields'
34
+
35
+ has_many :worklogs, nested_under: %w[fields worklog]
36
+ has_one :sprint, class: JIRA::Resource::Sprint,
37
+ nested_under: 'fields'
38
+
39
+ has_many :closed_sprints, class: JIRA::Resource::Sprint,
40
+ nested_under: 'fields', attribute_key: 'closedSprints'
41
+
42
+ has_many :issuelinks, nested_under: 'fields'
43
+
44
+ has_many :remotelink, class: JIRA::Resource::Remotelink
45
+
46
+ has_many :watchers, attribute_key: 'watches',
47
+ nested_under: %w[fields watches]
48
+
49
+ def self.all(client)
50
+ start_at = 0
51
+ max_results = 1000
52
+ result = []
53
+ loop do
54
+ url = client.options[:rest_base_path] +
55
+ "/search?expand=transitions.fields&maxResults=#{max_results}&startAt=#{start_at}"
56
+ response = client.get(url)
57
+ json = parse_json(response.body)
58
+ json['issues'].map do |issue|
59
+ result.push(client.Issue.build(issue))
60
+ end
61
+ break if json['issues'].empty?
62
+ start_at += json['issues'].size
63
+ end
64
+ result
65
+ end
66
+
67
+ def self.jql(client, jql, options = { fields: nil, start_at: nil, max_results: nil, expand: nil, validate_query: true })
68
+ url = client.options[:rest_base_path] + "/search?jql=#{CGI.escape(jql)}"
69
+
70
+ url << "&fields=#{options[:fields].map { |value| CGI.escape(client.Field.name_to_id(value)) }.join(',')}" if options[:fields]
71
+ url << "&startAt=#{CGI.escape(options[:start_at].to_s)}" if options[:start_at]
72
+ url << "&maxResults=#{CGI.escape(options[:max_results].to_s)}" if options[:max_results]
73
+ url << '&validateQuery=false' if options[:validate_query] === false
74
+
75
+ if options[:expand]
76
+ options[:expand] = [options[:expand]] if options[:expand].is_a?(String)
77
+ url << "&expand=#{options[:expand].to_a.map { |value| CGI.escape(value.to_s) }.join(',')}"
78
+ end
79
+
80
+ response = client.get(url)
81
+ json = parse_json(response.body)
82
+ if options[:max_results] && (options[:max_results] == 0)
83
+ return json['total']
84
+ end
85
+ json['issues'].map do |issue|
86
+ client.Issue.build(issue)
87
+ end
88
+ end
89
+
90
+ # Fetches the attributes for the specified resource from JIRA unless
91
+ # the resource is already expanded and the optional force reload flag
92
+ # is not set
93
+ def fetch(reload = false, query_params = {})
94
+ return if expanded? && !reload
95
+ response = client.get(url_with_query_params(url, query_params))
96
+ set_attrs_from_response(response)
97
+ if @attrs && @attrs['fields'] && @attrs['fields']['worklog'] && (@attrs['fields']['worklog']['total'] > @attrs['fields']['worklog']['maxResults'])
98
+ worklog_url = client.options[:rest_base_path] + "/#{self.class.endpoint_name}/#{id}/worklog"
99
+ response = client.get(worklog_url)
100
+ unless response.body.nil? || (response.body.length < 2)
101
+ set_attrs({ 'fields' => { 'worklog' => self.class.parse_json(response.body) } }, false)
102
+ end
103
+ end
104
+ @expanded = true
105
+ end
106
+
107
+ def editmeta
108
+ editmeta_url = client.options[:rest_base_path] + "/#{self.class.endpoint_name}/#{key}/editmeta"
109
+
110
+ response = client.get(editmeta_url)
111
+ json = self.class.parse_json(response.body)
112
+ json['fields']
113
+ end
114
+
115
+ def respond_to?(method_name, _include_all = false)
116
+ if attrs.key?('fields') && [method_name.to_s, client.Field.name_to_id(method_name)].any? { |k| attrs['fields'].key?(k) }
117
+ true
118
+ else
119
+ super(method_name)
120
+ end
121
+ end
122
+
123
+ def method_missing(method_name, *args, &block)
124
+ if attrs.key?('fields')
125
+ if attrs['fields'].key?(method_name.to_s)
126
+ attrs['fields'][method_name.to_s]
127
+ else
128
+ official_name = client.Field.name_to_id(method_name)
129
+ if attrs['fields'].key?(official_name)
130
+ attrs['fields'][official_name]
131
+ else
132
+ super(method_name, *args, &block)
133
+ end
134
+ end
135
+ else
136
+ super(method_name, *args, &block)
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,20 @@
1
+ module JIRA
2
+ module Resource
3
+ class IssuelinkFactory < JIRA::BaseFactory # :nodoc:
4
+ end
5
+
6
+ # Because of circular dependency Issue->IssueLink->Issue
7
+ # we have to declare JIRA::Resource::Issue class.
8
+ class Issue < JIRA::Base; end
9
+
10
+ class Issuelink < JIRA::Base
11
+ has_one :type, class: JIRA::Resource::Issuelinktype
12
+ has_one :inwardIssue, class: JIRA::Resource::Issue
13
+ has_one :outwardIssue, class: JIRA::Resource::Issue
14
+
15
+ def self.endpoint_name
16
+ 'issueLink'
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ module JIRA
2
+ module Resource
3
+ class IssuelinktypeFactory < JIRA::BaseFactory # :nodoc:
4
+ end
5
+
6
+ class Issuelinktype < JIRA::Base
7
+ nested_collections true
8
+
9
+ def self.endpoint_name
10
+ 'issueLinkType'
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,8 @@
1
+ module JIRA
2
+ module Resource
3
+ class IssuetypeFactory < JIRA::BaseFactory # :nodoc:
4
+ end
5
+
6
+ class Issuetype < JIRA::Base; end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module JIRA
2
+ module Resource
3
+ class PriorityFactory < JIRA::BaseFactory # :nodoc:
4
+ end
5
+
6
+ class Priority < JIRA::Base; end
7
+ end
8
+ end
@@ -0,0 +1,41 @@
1
+ module JIRA
2
+ module Resource
3
+ class ProjectFactory < JIRA::BaseFactory # :nodoc:
4
+ end
5
+
6
+ class Project < JIRA::Base
7
+ has_one :lead, class: JIRA::Resource::User
8
+ has_many :components
9
+ has_many :issuetypes, attribute_key: 'issueTypes'
10
+ has_many :versions
11
+
12
+ def self.key_attribute
13
+ :key
14
+ end
15
+
16
+ # Returns all the issues for this project
17
+ def issues(options = {})
18
+ search_url = client.options[:rest_base_path] + '/search'
19
+ query_params = { jql: "project=\"#{key}\"" }
20
+ query_params.update Base.query_params_for_search(options)
21
+ response = client.get(url_with_query_params(search_url, query_params))
22
+ json = self.class.parse_json(response.body)
23
+ json['issues'].map do |issue|
24
+ client.Issue.build(issue)
25
+ end
26
+ end
27
+
28
+ def users(start_at: nil, max_results: nil)
29
+ users_url = client.options[:rest_base_path] + '/user/assignable/search'
30
+ query_params = { project: key_value }
31
+ query_params['startAt'] = start_at if start_at
32
+ query_params['maxResults'] = max_results if max_results
33
+ response = client.get(url_with_query_params(users_url, query_params))
34
+ json = self.class.parse_json(response.body)
35
+ json.map do |jira_user|
36
+ client.User.build(jira_user)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,67 @@
1
+ require 'cgi'
2
+
3
+ module JIRA
4
+ module Resource
5
+ class RapidViewFactory < JIRA::BaseFactory # :nodoc:
6
+ end
7
+
8
+ class RapidView < JIRA::Base
9
+ def self.all(client)
10
+ response = client.get(path_base(client) + '/rapidview')
11
+ json = parse_json(response.body)
12
+ json['views'].map do |view|
13
+ client.RapidView.build(view)
14
+ end
15
+ end
16
+
17
+ def self.find(client, key, _options = {})
18
+ response = client.get(path_base(client) + "/rapidview/#{key}")
19
+ json = parse_json(response.body)
20
+ client.RapidView.build(json)
21
+ end
22
+
23
+ def issues(options = {})
24
+ response = client.get(path_base(client) + "/xboard/plan/backlog/data?rapidViewId=#{id}")
25
+ json = self.class.parse_json(response.body)
26
+ # To get Issue objects with the same structure as for Issue.all
27
+ issue_ids = json['issues'].map { |issue| issue['id'] }
28
+
29
+ # First we have to get all IDs of parent and sub tasks
30
+ jql = "id IN(#{issue_ids.join(', ')})"
31
+
32
+ # Filtering options
33
+ jql << ' AND sprint IS NOT EMPTY' unless options[:include_backlog_items]
34
+
35
+ parent_issues = client.Issue.jql(jql)
36
+ subtask_ids = parent_issues.map { |t| t.subtasks.map { |sub| sub['id'] } }.flatten
37
+
38
+ parent_and_sub_ids = parent_issues.map(&:id) + subtask_ids
39
+ jql = "id IN(#{parent_and_sub_ids.join(', ')})"
40
+ jql << " and updated >= '#{options.delete(:updated)}'" if options[:updated]
41
+
42
+ client.Issue.jql(jql)
43
+ end
44
+
45
+ def sprints(options = {})
46
+ params = { includeHistoricSprints: options.fetch(:include_historic, false),
47
+ includeFutureSprints: options.fetch(:include_future, false) }
48
+ response = client.get(path_base(client) + "/sprintquery/#{id}?#{params.to_query}")
49
+ json = self.class.parse_json(response.body)
50
+ json['sprints'].map do |sprint|
51
+ sprint['rapidview_id'] = id
52
+ client.Sprint.build(sprint)
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def self.path_base(client)
59
+ client.options[:context_path] + '/rest/greenhopper/1.0'
60
+ end
61
+
62
+ def path_base(client)
63
+ self.class.path_base(client)
64
+ end
65
+ end
66
+ end
67
+ end