jira-ruby 3.0.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/CI.yml +1 -0
- data/.github/workflows/rubocop.yml +1 -1
- data/README.md +0 -1
- data/jira-ruby.gemspec +2 -1
- data/lib/jira/atlassian/jwt.rb +76 -0
- data/lib/jira/base_factory.rb +1 -1
- data/lib/jira/client.rb +10 -2
- data/lib/jira/jwt_client.rb +2 -2
- data/lib/jira/resource/attachment.rb +17 -2
- data/lib/jira/resource/comment.rb +12 -0
- data/lib/jira/resource/createmeta.rb +4 -4
- data/lib/jira/resource/issue.rb +52 -18
- data/lib/jira/resource/properties.rb +56 -0
- data/lib/jira/resource/worklog.rb +12 -0
- data/lib/jira/version.rb +1 -1
- data/lib/jira-ruby.rb +1 -0
- data/spec/data/files/jwt-signed-urls.json +317 -0
- data/spec/integration/issue_spec.rb +1 -2
- data/spec/integration/properties_spec.rb +45 -0
- data/spec/integration/transition_spec.rb +3 -2
- data/spec/integration/webhook_spec.rb +4 -2
- data/spec/jira/atlassian/jwt_spec.rb +60 -0
- data/spec/jira/resource/issue_spec.rb +39 -1
- data/spec/mock_responses/issue/10002/properties/foo.json +4 -0
- data/spec/mock_responses/issue/10002/properties/xyz.json +4 -0
- data/spec/mock_responses/issue/10002/properties/xyz.put.json +4 -0
- data/spec/mock_responses/issue/10002/properties.json +12 -0
- data/spec/support/clients_helper.rb +3 -3
- data/spec/support/shared_examples/integration.rb +34 -38
- metadata +27 -6
- data/spec/mock_responses/jira/rest/webhooks/1.0/webhook.json +0 -11
- data/spec/mock_responses/webhook/webhook.json +0 -11
- /data/spec/mock_responses/{jira/rest/webhooks/1.0/webhook → webhook}/2.json +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4a043cafa57aad32acfda37495a8419b4511581786620049cb6264a05265aefb
|
|
4
|
+
data.tar.gz: 2c3b66dd69faaef351e043b10c62f3cfd7662e003905363db6bdbf458bfb10b1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 677a496c74235ea213ac92416b9dfb303e56bc04ea94b617134925ce744c25bd81583b2516a342c347a935959795c35813245499efcf2c058b075d308ef51454
|
|
7
|
+
data.tar.gz: ef42dd385a24ff73526e8e2d21fedb0af60832444f9d1e43c0c23a1cca58da61751a24901430abf0650cffacdd7b41ff28fa7620deb195ede7081af504eb4295
|
data/.github/workflows/CI.yml
CHANGED
data/README.md
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# JIRA API Gem
|
|
2
2
|
|
|
3
|
-
[](https://codeclimate.com/github/sumoheavy/jira-ruby)
|
|
4
3
|
[](https://github.com/sumoheavy/jira-ruby/actions/workflows/CI.yml)
|
|
5
4
|
|
|
6
5
|
This gem provides access to the Atlassian JIRA REST API.
|
data/jira-ruby.gemspec
CHANGED
|
@@ -23,7 +23,8 @@ Gem::Specification.new do |s|
|
|
|
23
23
|
s.require_paths = ['lib']
|
|
24
24
|
|
|
25
25
|
s.add_dependency 'activesupport'
|
|
26
|
-
s.add_dependency '
|
|
26
|
+
s.add_dependency 'cgi'
|
|
27
|
+
s.add_dependency 'jwt', '>= 2.1'
|
|
27
28
|
s.add_dependency 'multipart-post'
|
|
28
29
|
s.add_dependency 'oauth', '~> 1.0'
|
|
29
30
|
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright 2013 Atlassian Pty Ltd.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
require 'jwt'
|
|
18
|
+
require 'uri'
|
|
19
|
+
require 'cgi'
|
|
20
|
+
|
|
21
|
+
module JIRA
|
|
22
|
+
module Atlassian
|
|
23
|
+
module Jwt
|
|
24
|
+
class << self
|
|
25
|
+
CANONICAL_QUERY_SEPARATOR = '&'
|
|
26
|
+
ESCAPED_CANONICAL_QUERY_SEPARATOR = '%26'
|
|
27
|
+
|
|
28
|
+
def create_canonical_request(uri, http_method, base_uri)
|
|
29
|
+
uri = URI.parse(uri) unless uri.is_a? URI
|
|
30
|
+
base_uri = URI.parse(base_uri) unless base_uri.is_a? URI
|
|
31
|
+
|
|
32
|
+
[
|
|
33
|
+
http_method.upcase,
|
|
34
|
+
canonicalize_uri(uri, base_uri),
|
|
35
|
+
canonicalize_query_string(uri.query)
|
|
36
|
+
].join(CANONICAL_QUERY_SEPARATOR)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def build_claims(issuer, url, http_method, base_url = '', issued_at = nil, expires = nil, attributes = {}) # rubocop:disable Metrics/ParameterLists
|
|
40
|
+
issued_at ||= Time.now.to_i
|
|
41
|
+
expires ||= issued_at + 60
|
|
42
|
+
qsh = Digest::SHA256.hexdigest(create_canonical_request(url, http_method, base_url))
|
|
43
|
+
|
|
44
|
+
{
|
|
45
|
+
iss: issuer,
|
|
46
|
+
iat: issued_at,
|
|
47
|
+
exp: expires,
|
|
48
|
+
qsh: qsh
|
|
49
|
+
}.merge(attributes)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def canonicalize_uri(uri, base_uri)
|
|
53
|
+
path = uri.path.sub(/^#{base_uri.path}/, '')
|
|
54
|
+
path = '/' if path.nil? || path.empty?
|
|
55
|
+
path = "/#{path}" unless path.start_with? '/'
|
|
56
|
+
path.chomp!('/') if path.length > 1
|
|
57
|
+
path.gsub(CANONICAL_QUERY_SEPARATOR, ESCAPED_CANONICAL_QUERY_SEPARATOR)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def canonicalize_query_string(query)
|
|
61
|
+
return '' if query.nil? || query.empty?
|
|
62
|
+
|
|
63
|
+
query = CGI.parse(query)
|
|
64
|
+
query.delete('jwt')
|
|
65
|
+
query.each do |k, v|
|
|
66
|
+
query[k] = v.map { |a| CGI.escape a }.join(',') if v.is_a? Array
|
|
67
|
+
query[k].gsub!('+', '%20') # Use %20, not CGI.escape default of "+"
|
|
68
|
+
query[k].gsub!('%7E', '~') # Unescape "~" per JS tests
|
|
69
|
+
end
|
|
70
|
+
query = query.sort.to_h
|
|
71
|
+
query.map { |k, v| "#{CGI.escape k}=#{v}" }.join(CANONICAL_QUERY_SEPARATOR)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
data/lib/jira/base_factory.rb
CHANGED
|
@@ -38,7 +38,7 @@ module JIRA
|
|
|
38
38
|
# The principle purpose of this class is to delegate methods to the corresponding
|
|
39
39
|
# non-factory class and automatically prepend the client argument to the argument
|
|
40
40
|
# list.
|
|
41
|
-
delegate_to_target_class :all, :find, :collection_path, :singular_path, :jql, :get_backlog_issues,
|
|
41
|
+
delegate_to_target_class :all, :find, :collection_path, :singular_path, :jql, :jql_paged, :get_backlog_issues,
|
|
42
42
|
:get_board_issues, :get_sprints, :get_sprint_issues, :get_projects, :get_projects_full
|
|
43
43
|
|
|
44
44
|
# This method needs special handling as it has a default argument value
|
data/lib/jira/client.rb
CHANGED
|
@@ -302,6 +302,10 @@ module JIRA
|
|
|
302
302
|
JIRA::Resource::AgileFactory.new(self)
|
|
303
303
|
end
|
|
304
304
|
|
|
305
|
+
def Properties
|
|
306
|
+
JIRA::Resource::PropertiesFactory.new(self)
|
|
307
|
+
end
|
|
308
|
+
|
|
305
309
|
# HTTP methods without a body
|
|
306
310
|
|
|
307
311
|
# Make an HTTP DELETE request
|
|
@@ -351,7 +355,9 @@ module JIRA
|
|
|
351
355
|
# @raise [JIRA::HTTPError] If the response is not an HTTP success code
|
|
352
356
|
def post_multipart(path, file, headers = {})
|
|
353
357
|
puts "post multipart: #{path} - [#{file}]" if @http_debug
|
|
354
|
-
@request_client.request_multipart(path, file, merge_default_headers(headers))
|
|
358
|
+
res = @request_client.request_multipart(path, file, merge_default_headers(headers))
|
|
359
|
+
puts "response: #{res}" if @http_debug
|
|
360
|
+
res
|
|
355
361
|
end
|
|
356
362
|
|
|
357
363
|
# Make an HTTP PUT request
|
|
@@ -375,7 +381,9 @@ module JIRA
|
|
|
375
381
|
# @raise [JIRA::HTTPError] If the response is not an HTTP success code
|
|
376
382
|
def request(http_method, path, body = '', headers = {})
|
|
377
383
|
puts "#{http_method}: #{path} - [#{body}]" if @http_debug
|
|
378
|
-
@request_client.request(http_method, path, body, headers)
|
|
384
|
+
res = @request_client.request(http_method, path, body, headers)
|
|
385
|
+
puts "response: #{res}" if @http_debug
|
|
386
|
+
res
|
|
379
387
|
end
|
|
380
388
|
|
|
381
389
|
# @private
|
data/lib/jira/jwt_client.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'atlassian/jwt'
|
|
3
|
+
require 'jira/atlassian/jwt'
|
|
4
4
|
|
|
5
5
|
module JIRA
|
|
6
6
|
class JwtClient < HttpClient
|
|
@@ -29,7 +29,7 @@ module JIRA
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def build_jwt(url)
|
|
32
|
-
claim = Atlassian::Jwt.build_claims \
|
|
32
|
+
claim = JIRA::Atlassian::Jwt.build_claims \
|
|
33
33
|
@options[:issuer],
|
|
34
34
|
url,
|
|
35
35
|
http_method.to_s,
|
|
@@ -146,10 +146,14 @@ module JIRA
|
|
|
146
146
|
# @raise [JIRA::HTTPError] if failed
|
|
147
147
|
def save!(attrs, path = url)
|
|
148
148
|
file = attrs['file'] || attrs[:file] # Keep supporting 'file' parameter as a string for backward compatibility
|
|
149
|
-
|
|
149
|
+
# If :filename does not exist or is nil, that is fine as it will force
|
|
150
|
+
# Upload to determine the filename automatically from file.
|
|
151
|
+
# Breaking the filename out allows this to support any IO-based file parameter.
|
|
152
|
+
fname = attrs['filename'] || attrs[:filename]
|
|
153
|
+
mime_type = attrs['mimeType'] || attrs[:mimeType] || 'application/binary'
|
|
150
154
|
|
|
151
155
|
headers = { 'X-Atlassian-Token' => 'nocheck' }
|
|
152
|
-
data = { 'file' => Multipart::Post::UploadIO.new(file, mime_type,
|
|
156
|
+
data = { 'file' => Multipart::Post::UploadIO.new(file, mime_type, fname) }
|
|
153
157
|
|
|
154
158
|
response = client.post_multipart(path, data, headers)
|
|
155
159
|
|
|
@@ -159,6 +163,17 @@ module JIRA
|
|
|
159
163
|
true
|
|
160
164
|
end
|
|
161
165
|
|
|
166
|
+
def http_download
|
|
167
|
+
# Actually fetch the attachment
|
|
168
|
+
# Note: Jira handles attachment's weird!
|
|
169
|
+
# Typically, they respond with a redirect location that should not have the same authentication
|
|
170
|
+
client.get(attrs['content'])
|
|
171
|
+
rescue JIRA::HTTPError => e
|
|
172
|
+
raise e unless e.response.code =~ /\A3\d\d\z/ && e.response['location'].present?
|
|
173
|
+
|
|
174
|
+
Net::HTTP.get_response(URI(e.response['location']))
|
|
175
|
+
end
|
|
176
|
+
|
|
162
177
|
private
|
|
163
178
|
|
|
164
179
|
def set_attributes(attributes, response)
|
|
@@ -9,6 +9,18 @@ module JIRA
|
|
|
9
9
|
belongs_to :issue
|
|
10
10
|
|
|
11
11
|
nested_collections true
|
|
12
|
+
|
|
13
|
+
def self.all(client, options = {})
|
|
14
|
+
issue = options[:issue]
|
|
15
|
+
raise ArgumentError, 'parent issue is required' unless issue
|
|
16
|
+
|
|
17
|
+
response = client.get("#{issue.url}/#{endpoint_name}")
|
|
18
|
+
json = parse_json(response.body)
|
|
19
|
+
json = json[endpoint_name.pluralize]
|
|
20
|
+
json.map do |attrs|
|
|
21
|
+
new(client, { attrs: }.merge(options))
|
|
22
|
+
end
|
|
23
|
+
end
|
|
12
24
|
end
|
|
13
25
|
end
|
|
14
26
|
end
|
|
@@ -12,22 +12,22 @@ module JIRA
|
|
|
12
12
|
|
|
13
13
|
def self.all(client, params = {})
|
|
14
14
|
if params.key?(:projectKeys)
|
|
15
|
-
values = Array(params[:projectKeys]).map { |i|
|
|
15
|
+
values = Array(params[:projectKeys]).map { |i| i.is_a?(JIRA::Resource::Project) ? i.key : i }
|
|
16
16
|
params[:projectKeys] = values.join(',')
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
if params.key?(:projectIds)
|
|
20
|
-
values = Array(params[:projectIds]).map { |i|
|
|
20
|
+
values = Array(params[:projectIds]).map { |i| i.is_a?(JIRA::Resource::Project) ? i.id : i }
|
|
21
21
|
params[:projectIds] = values.join(',')
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
if params.key?(:issuetypeNames)
|
|
25
|
-
values = Array(params[:issuetypeNames]).map { |i|
|
|
25
|
+
values = Array(params[:issuetypeNames]).map { |i| i.is_a?(JIRA::Resource::Issuetype) ? i.name : i }
|
|
26
26
|
params[:issuetypeNames] = values.join(',')
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
if params.key?(:issuetypeIds)
|
|
30
|
-
values = Array(params[:issuetypeIds]).map { |i|
|
|
30
|
+
values = Array(params[:issuetypeIds]).map { |i| i.is_a?(JIRA::Resource::Issuetype) ? i.id : i }
|
|
31
31
|
params[:issuetypeIds] = values.join(',')
|
|
32
32
|
end
|
|
33
33
|
|
data/lib/jira/resource/issue.rb
CHANGED
|
@@ -57,6 +57,7 @@ module JIRA
|
|
|
57
57
|
has_many :issuelinks, nested_under: 'fields'
|
|
58
58
|
has_many :remotelink, class: JIRA::Resource::Remotelink
|
|
59
59
|
has_many :watchers, attribute_key: 'watches', nested_under: %w[fields watches]
|
|
60
|
+
has_many :properties, class: JIRA::Resource::Properties
|
|
60
61
|
|
|
61
62
|
# Get collection of issues.
|
|
62
63
|
# @param client [JIRA::Client]
|
|
@@ -80,7 +81,57 @@ module JIRA
|
|
|
80
81
|
result
|
|
81
82
|
end
|
|
82
83
|
|
|
84
|
+
# Get issues using JQL query.
|
|
85
|
+
# @param client [JIRA::Client]
|
|
86
|
+
# @param jql [String] the JQL query string to search with
|
|
87
|
+
# @param options [Hash] Jira API options for the search
|
|
88
|
+
# @return [Array<JIRA::Resource::Issue>] or [Integer] total count if max_results is 0
|
|
83
89
|
def self.jql(client, jql, options = { fields: nil, max_results: nil, expand: nil, reconcile_issues: nil })
|
|
90
|
+
issues = []
|
|
91
|
+
total = nil
|
|
92
|
+
next_page_token = nil
|
|
93
|
+
is_last = false
|
|
94
|
+
|
|
95
|
+
until is_last
|
|
96
|
+
result = jql_paged(client, jql, options.merge(next_page_token:))
|
|
97
|
+
|
|
98
|
+
issues.concat(result[:issues])
|
|
99
|
+
total = result[:total]
|
|
100
|
+
next_page_token = result[:next_page_token]
|
|
101
|
+
is_last = next_page_token.nil?
|
|
102
|
+
end
|
|
103
|
+
options[:max_results]&.zero? ? total : issues
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Get paged issues using JQL query.
|
|
107
|
+
# @param jql [String] the JQL query string to search with
|
|
108
|
+
# @param options [Hash] Jira API options for the search, including next_page_token
|
|
109
|
+
# @return [Hash] with format { issues: [JIRA::Resource::Issue], next_page_token: [String], total: [Integer] }
|
|
110
|
+
def self.jql_paged(client, jql, options = { fields: nil, max_results: nil, expand: nil, reconcile_issues: nil, next_page_token: nil })
|
|
111
|
+
url = jql_url(client, jql, options)
|
|
112
|
+
next_page_token = options[:next_page_token]
|
|
113
|
+
max_results = options[:max_results]
|
|
114
|
+
|
|
115
|
+
issues = []
|
|
116
|
+
|
|
117
|
+
page_url = url.dup
|
|
118
|
+
page_url << "&nextPageToken=#{next_page_token}" if next_page_token
|
|
119
|
+
|
|
120
|
+
response = client.get(page_url)
|
|
121
|
+
json = parse_json(response.body)
|
|
122
|
+
total = json['total']
|
|
123
|
+
|
|
124
|
+
unless max_results&.zero?
|
|
125
|
+
next_page_token = json['nextPageToken']
|
|
126
|
+
json['issues'].map do |issue|
|
|
127
|
+
issues << client.Issue.build(issue)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
{ issues:, next_page_token:, total: }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def self.jql_url(client, jql, options)
|
|
84
135
|
url = client.options[:rest_base_path] + "/search/jql?jql=#{CGI.escape(jql)}"
|
|
85
136
|
|
|
86
137
|
if options[:fields]
|
|
@@ -95,24 +146,7 @@ module JIRA
|
|
|
95
146
|
options[:expand] = [options[:expand]] if options[:expand].is_a?(String)
|
|
96
147
|
url << "&expand=#{options[:expand].to_a.map { |value| CGI.escape(value.to_s) }.join(',')}"
|
|
97
148
|
end
|
|
98
|
-
|
|
99
|
-
issues = []
|
|
100
|
-
next_page_token = nil
|
|
101
|
-
json = {}
|
|
102
|
-
while json['isLast'] != true
|
|
103
|
-
page_url = url.dup
|
|
104
|
-
page_url << "&nextPageToken=#{next_page_token}" if next_page_token
|
|
105
|
-
|
|
106
|
-
response = client.get(page_url)
|
|
107
|
-
json = parse_json(response.body)
|
|
108
|
-
return json['total'] if options[:max_results]&.zero?
|
|
109
|
-
|
|
110
|
-
next_page_token = json['nextPageToken']
|
|
111
|
-
json['issues'].map do |issue|
|
|
112
|
-
issues << client.Issue.build(issue)
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
issues
|
|
149
|
+
url
|
|
116
150
|
end
|
|
117
151
|
|
|
118
152
|
# Fetches the attributes for the specified resource from JIRA unless
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/inflector'
|
|
4
|
+
|
|
5
|
+
module JIRA
|
|
6
|
+
module Resource
|
|
7
|
+
class PropertiesFactory < JIRA::BaseFactory # :nodoc:
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class Properties < JIRA::Base
|
|
11
|
+
belongs_to :issue
|
|
12
|
+
|
|
13
|
+
def self.key_attribute
|
|
14
|
+
:key
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.all(client, options = {})
|
|
18
|
+
issue = options[:issue]
|
|
19
|
+
raise ArgumentError, 'parent issue is required' unless issue
|
|
20
|
+
|
|
21
|
+
response = client.get("#{issue.url}/#{endpoint_name}")
|
|
22
|
+
json = parse_json(response.body)
|
|
23
|
+
json[key_attribute.to_s.pluralize].map do |attrs|
|
|
24
|
+
## Net get the individual property
|
|
25
|
+
self_response = client.get(attrs['self'])
|
|
26
|
+
property = parse_json(self_response.body)
|
|
27
|
+
## Make sure to build the new resource via the issue.properties in order to support the has_many proxy
|
|
28
|
+
issue.properties.build(property)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
## Override save so we can handle the required attrs (and default 'value' when appropriate)
|
|
33
|
+
def save!(attrs = {}, path = nil)
|
|
34
|
+
if attrs.is_a?(Hash) && attrs.key?(self.class.key_attribute.to_s)
|
|
35
|
+
raise ArgumentError, "Use of 'value' is required when '#{self.class.key_attribute}' is provided" \
|
|
36
|
+
unless attrs.key?('value')
|
|
37
|
+
|
|
38
|
+
set_attrs(self.class.key_attribute.to_s => attrs[self.class.key_attribute.to_s])
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
raise ArgumentError, "'key' is required on a new record" if new_record?
|
|
42
|
+
|
|
43
|
+
path ||= patched_url
|
|
44
|
+
## We can take either the 'value' element from the hash, OR use the entire attrs as the value
|
|
45
|
+
value = attrs.is_a?(Hash) && attrs.key?('value') ? attrs['value'] : attrs
|
|
46
|
+
value = '' if value.nil?
|
|
47
|
+
## Note: this API endpoint requires a non-empty JSON body for the value of the property
|
|
48
|
+
## Note2: this API endpoint does not return a body, so no need to call set_attrs_from_response
|
|
49
|
+
client.send(:put, path, value.to_json)
|
|
50
|
+
set_attrs({ 'value' => value }, false)
|
|
51
|
+
@expanded = false
|
|
52
|
+
true
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -10,6 +10,18 @@ module JIRA
|
|
|
10
10
|
has_one :update_author, class: JIRA::Resource::User, attribute_key: 'updateAuthor'
|
|
11
11
|
belongs_to :issue
|
|
12
12
|
nested_collections true
|
|
13
|
+
|
|
14
|
+
def self.all(client, options = {})
|
|
15
|
+
issue = options[:issue]
|
|
16
|
+
raise ArgumentError, 'parent issue is required' unless issue
|
|
17
|
+
|
|
18
|
+
response = client.get("#{issue.url}/#{endpoint_name}")
|
|
19
|
+
json = parse_json(response.body)
|
|
20
|
+
json = json[endpoint_name.pluralize]
|
|
21
|
+
json.map do |attrs|
|
|
22
|
+
new(client, { attrs: }.merge(options))
|
|
23
|
+
end
|
|
24
|
+
end
|
|
13
25
|
end
|
|
14
26
|
end
|
|
15
27
|
end
|
data/lib/jira/version.rb
CHANGED
data/lib/jira-ruby.rb
CHANGED
|
@@ -23,6 +23,7 @@ require 'jira/resource/status'
|
|
|
23
23
|
require 'jira/resource/status_category'
|
|
24
24
|
require 'jira/resource/transition'
|
|
25
25
|
require 'jira/resource/project'
|
|
26
|
+
require 'jira/resource/properties'
|
|
26
27
|
require 'jira/resource/priority'
|
|
27
28
|
require 'jira/resource/comment'
|
|
28
29
|
require 'jira/resource/worklog'
|