ruby-jira 0.1.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 +7 -0
- data/CHANGELOG.md +14 -0
- data/LICENSE.txt +24 -0
- data/README.md +273 -0
- data/lib/jira/api.rb +22 -0
- data/lib/jira/client/issues.rb +35 -0
- data/lib/jira/client/project_permission_schemes.rb +37 -0
- data/lib/jira/client/projects.rb +33 -0
- data/lib/jira/client.rb +43 -0
- data/lib/jira/configuration.rb +101 -0
- data/lib/jira/error.rb +211 -0
- data/lib/jira/objectified_hash.rb +66 -0
- data/lib/jira/pagination/cursor_paginated_response.rb +92 -0
- data/lib/jira/pagination/paginated_response.rb +96 -0
- data/lib/jira/request/authentication.rb +180 -0
- data/lib/jira/request/paginated_response.rb +4 -0
- data/lib/jira/request/rate_limiting.rb +126 -0
- data/lib/jira/request/request_building.rb +78 -0
- data/lib/jira/request/response_parsing.rb +45 -0
- data/lib/jira/request.rb +153 -0
- data/lib/jira/version.rb +5 -0
- data/lib/jira.rb +65 -0
- metadata +80 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jira
|
|
4
|
+
class Request
|
|
5
|
+
# Implements Jira Cloud rate-limit retry policy.
|
|
6
|
+
#
|
|
7
|
+
# Follows the official Atlassian guidance:
|
|
8
|
+
# https://developer.atlassian.com/cloud/jira/platform/rate-limiting/
|
|
9
|
+
#
|
|
10
|
+
# Supported response headers (enforced by Jira Cloud):
|
|
11
|
+
# Retry-After — seconds to wait before retrying (429 and some 503)
|
|
12
|
+
# X-RateLimit-Reset — ISO 8601 timestamp when the window resets (429 only)
|
|
13
|
+
# X-RateLimit-Limit — max request rate for the current scope
|
|
14
|
+
# X-RateLimit-Remaining — remaining capacity in the current window
|
|
15
|
+
# X-RateLimit-NearLimit — "true" when < 20% capacity remains
|
|
16
|
+
# RateLimit-Reason — which limit was exceeded (burst/quota/per-issue)
|
|
17
|
+
class RetryPolicy
|
|
18
|
+
IDEMPOTENT_HTTP_METHODS = %w[get head put delete options].freeze
|
|
19
|
+
|
|
20
|
+
# Unix timestamp threshold: values above this are epoch seconds, not second-counts.
|
|
21
|
+
UNIX_TIMESTAMP_THRESHOLD = 1_000_000_000
|
|
22
|
+
|
|
23
|
+
# Jitter range recommended by Atlassian docs: multiply backoff by factor in [0.7, 1.3].
|
|
24
|
+
JITTER_RANGE = (0.7..1.3)
|
|
25
|
+
|
|
26
|
+
def initialize(request:, rand_proc: method(:rand))
|
|
27
|
+
@request = request
|
|
28
|
+
@rand_proc = rand_proc
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def sleep_before_retry(response:, retries_left:)
|
|
32
|
+
sleep(wait_seconds(response: response, retries_left: retries_left))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def retryable?(error:, method:, response:, retries_left:)
|
|
36
|
+
return false if retries_left <= 1
|
|
37
|
+
return false unless IDEMPOTENT_HTTP_METHODS.include?(method.to_s)
|
|
38
|
+
return true if error.is_a?(Jira::Error::TooManyRequests)
|
|
39
|
+
|
|
40
|
+
response_has_rate_limit_hint?(response)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def wait_seconds(response:, retries_left:)
|
|
44
|
+
retry_after = parse_retry_after(response)
|
|
45
|
+
return retry_after if retry_after
|
|
46
|
+
|
|
47
|
+
reset_delay = parse_rate_limit_reset(response)
|
|
48
|
+
return reset_delay if reset_delay
|
|
49
|
+
|
|
50
|
+
exponential_backoff_wait(retries_left)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
# Retry-After: integer seconds (e.g. "5").
|
|
56
|
+
def parse_retry_after(response)
|
|
57
|
+
value = response&.headers&.[]("Retry-After")
|
|
58
|
+
return nil unless value
|
|
59
|
+
|
|
60
|
+
parse_seconds_or_timestamp(value)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# X-RateLimit-Reset: ISO 8601 timestamp (e.g. "2024-01-15T10:30:00.000Z").
|
|
64
|
+
# Also accepts RateLimit-Reset as fallback.
|
|
65
|
+
def parse_rate_limit_reset(response)
|
|
66
|
+
value = response&.headers&.[]("X-RateLimit-Reset") || response&.headers&.[]("RateLimit-Reset")
|
|
67
|
+
return nil unless value
|
|
68
|
+
|
|
69
|
+
seconds = parse_seconds_or_timestamp(value)
|
|
70
|
+
return nil if seconds.nil?
|
|
71
|
+
|
|
72
|
+
[seconds, 0.0].max
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Parses a header value that can be:
|
|
76
|
+
# - integer seconds: "5"
|
|
77
|
+
# - Unix epoch timestamp: "1705314600"
|
|
78
|
+
# - ISO 8601 datetime: "2024-01-15T10:30:00.000Z" (Jira Cloud standard)
|
|
79
|
+
# - HTTP date: "Mon, 15 Jan 2024 10:30:00 GMT"
|
|
80
|
+
def parse_seconds_or_timestamp(value)
|
|
81
|
+
numeric = Float(value)
|
|
82
|
+
return numeric if numeric < UNIX_TIMESTAMP_THRESHOLD
|
|
83
|
+
|
|
84
|
+
[numeric - Time.now.to_f, 0.0].max
|
|
85
|
+
rescue ArgumentError, TypeError
|
|
86
|
+
parse_datetime_string(value.to_s)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def parse_datetime_string(value)
|
|
90
|
+
[Time.iso8601(value).to_f - Time.now.to_f, 0.0].max
|
|
91
|
+
rescue ArgumentError
|
|
92
|
+
begin
|
|
93
|
+
[Time.httpdate(value).to_f - Time.now.to_f, 0.0].max
|
|
94
|
+
rescue ArgumentError
|
|
95
|
+
nil
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Exponential backoff with proportional jitter, per Atlassian recommendations:
|
|
100
|
+
# base_delay * 2^attempt, capped at max_delay, multiplied by rand(0.7..1.3).
|
|
101
|
+
def exponential_backoff_wait(retries_left)
|
|
102
|
+
retry_attempt = ratelimit_retries - retries_left
|
|
103
|
+
backoff = [ratelimit_base_delay * (2**retry_attempt), ratelimit_max_delay].min
|
|
104
|
+
jitter_factor = @rand_proc.call(JITTER_RANGE)
|
|
105
|
+
[backoff * jitter_factor, ratelimit_max_delay].min
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def response_has_rate_limit_hint?(response)
|
|
109
|
+
headers = response&.headers || {}
|
|
110
|
+
headers.key?("Retry-After") || headers.key?("X-RateLimit-Reset") || headers.key?("RateLimit-Reset")
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def ratelimit_retries
|
|
114
|
+
@request.ratelimit_retries || Configuration::DEFAULT_RATELIMIT_RETRIES
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def ratelimit_base_delay
|
|
118
|
+
@request.ratelimit_base_delay || Configuration::DEFAULT_RATELIMIT_BASE_DELAY
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def ratelimit_max_delay
|
|
122
|
+
@request.ratelimit_max_delay || Configuration::DEFAULT_RATELIMIT_MAX_DELAY
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jira
|
|
4
|
+
class Request
|
|
5
|
+
class UrlBuilder
|
|
6
|
+
OAUTH_API_BASE = "https://api.atlassian.com/ex/jira"
|
|
7
|
+
PLATFORM_API_PATH = "/rest/api/3"
|
|
8
|
+
|
|
9
|
+
def initialize(request:, authenticator:)
|
|
10
|
+
@request = request
|
|
11
|
+
@authenticator = authenticator
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def build(path)
|
|
15
|
+
"#{api_base_url}#{normalize_path(path)}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def api_request_path
|
|
19
|
+
URI.parse(api_base_url).request_uri
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def api_base_url
|
|
25
|
+
case @authenticator.auth_type
|
|
26
|
+
when :basic
|
|
27
|
+
"#{normalized_endpoint}#{PLATFORM_API_PATH}"
|
|
28
|
+
when :oauth2
|
|
29
|
+
"#{OAUTH_API_BASE}/#{@request.cloud_id}#{PLATFORM_API_PATH}"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def normalized_endpoint
|
|
34
|
+
@request.endpoint.to_s.delete_suffix("/")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def normalize_path(path)
|
|
38
|
+
string_path = path.to_s
|
|
39
|
+
string_path.start_with?("/") ? string_path : "/#{string_path}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class ParamsBuilder
|
|
44
|
+
def initialize(request:, authenticator:)
|
|
45
|
+
@request = request
|
|
46
|
+
@authenticator = authenticator
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def build(options)
|
|
50
|
+
params = options.dup
|
|
51
|
+
merge_httparty_config!(params)
|
|
52
|
+
add_authorization_header!(params) unless params[:unauthenticated]
|
|
53
|
+
serialize_json_body!(params)
|
|
54
|
+
params
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def merge_httparty_config!(params)
|
|
60
|
+
params.merge!(@request.httparty) if @request.httparty
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def add_authorization_header!(params)
|
|
64
|
+
params[:headers] ||= {}
|
|
65
|
+
params[:headers].merge!(@authenticator.authorization_header)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def serialize_json_body!(params)
|
|
69
|
+
return unless params[:body].is_a?(Hash)
|
|
70
|
+
return if params[:multipart] == true
|
|
71
|
+
|
|
72
|
+
params[:headers] ||= {}
|
|
73
|
+
params[:headers]["Content-Type"] ||= "application/json"
|
|
74
|
+
params[:body] = params[:body].to_json
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jira
|
|
4
|
+
class Request
|
|
5
|
+
class ResponseParser
|
|
6
|
+
class << self
|
|
7
|
+
def parse(body) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
8
|
+
decoded = decode(body)
|
|
9
|
+
return PaginatedResponse.new(decoded) if offset_paginated?(decoded)
|
|
10
|
+
return CursorPaginatedResponse.new(decoded) if cursor_paginated?(decoded)
|
|
11
|
+
return ObjectifiedHash.new(decoded) if decoded.is_a?(Hash)
|
|
12
|
+
return decoded.map { |item| item.is_a?(Hash) ? ObjectifiedHash.new(item) : item } if decoded.is_a?(Array)
|
|
13
|
+
return true if decoded
|
|
14
|
+
return false unless decoded
|
|
15
|
+
|
|
16
|
+
raise Error::Parsing, "Couldn't parse a response body"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def decode(response)
|
|
20
|
+
return {} if response.nil? || response.empty?
|
|
21
|
+
|
|
22
|
+
JSON.parse(response, symbolize_names: true)
|
|
23
|
+
rescue JSON::ParserError
|
|
24
|
+
raise Error::Parsing, "The response is not a valid JSON '#{response}'"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
# Offset-based pagination: GET /project/search, GET /workflow/search, etc.
|
|
30
|
+
# Requires :values and at least one offset-pagination hint.
|
|
31
|
+
def offset_paginated?(body)
|
|
32
|
+
body.is_a?(Hash) &&
|
|
33
|
+
body.key?(:values) &&
|
|
34
|
+
(body.key?(:isLast) || body.key?(:nextPage) || body.key?(:startAt))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Cursor-based pagination: POST /search/jql, etc.
|
|
38
|
+
# The token drives the next request; items live under a variable key.
|
|
39
|
+
def cursor_paginated?(body)
|
|
40
|
+
body.is_a?(Hash) && body.key?(:nextPageToken)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
data/lib/jira/request.rb
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
require "httparty"
|
|
5
|
+
require "json"
|
|
6
|
+
require "time"
|
|
7
|
+
require "uri"
|
|
8
|
+
|
|
9
|
+
require_relative "request/authentication"
|
|
10
|
+
require_relative "request/rate_limiting"
|
|
11
|
+
require_relative "request/request_building"
|
|
12
|
+
require_relative "request/response_parsing"
|
|
13
|
+
|
|
14
|
+
module Jira
|
|
15
|
+
# @private
|
|
16
|
+
class Request
|
|
17
|
+
include HTTParty
|
|
18
|
+
|
|
19
|
+
OAUTH_MISSING_CREDENTIALS_MESSAGE = Authenticator::OAUTH_MISSING_CREDENTIALS_MESSAGE
|
|
20
|
+
|
|
21
|
+
format :json
|
|
22
|
+
maintain_method_across_redirects true
|
|
23
|
+
headers "Accept" => "application/json", "Content-Type" => "application/json"
|
|
24
|
+
parser(proc { |body, _| parse(body) })
|
|
25
|
+
|
|
26
|
+
attr_accessor :endpoint,
|
|
27
|
+
:auth_type,
|
|
28
|
+
:email,
|
|
29
|
+
:api_token,
|
|
30
|
+
:oauth_access_token,
|
|
31
|
+
:oauth_client_id,
|
|
32
|
+
:oauth_client_secret,
|
|
33
|
+
:oauth_refresh_token,
|
|
34
|
+
:oauth_grant_type,
|
|
35
|
+
:oauth_token_endpoint,
|
|
36
|
+
:oauth_access_token_expires_at,
|
|
37
|
+
:cloud_id,
|
|
38
|
+
:httparty,
|
|
39
|
+
:ratelimit_retries,
|
|
40
|
+
:ratelimit_base_delay,
|
|
41
|
+
:ratelimit_max_delay
|
|
42
|
+
|
|
43
|
+
class << self
|
|
44
|
+
def parse(body)
|
|
45
|
+
ResponseParser.parse(body)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def decode(response)
|
|
49
|
+
ResponseParser.decode(response)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
%w[get post put patch delete].each do |method|
|
|
54
|
+
define_method(method) do |path, options = {}|
|
|
55
|
+
execute_request(method, path, options)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def validate(response)
|
|
60
|
+
error_klass = Error.klass(response)
|
|
61
|
+
raise error_klass, response if error_klass
|
|
62
|
+
|
|
63
|
+
parsed = response.parsed_response
|
|
64
|
+
parsed.client = self if parsed.respond_to?(:client=)
|
|
65
|
+
parsed
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def request_defaults
|
|
69
|
+
validate_endpoint!
|
|
70
|
+
authenticator.validate!
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def api_request_path
|
|
74
|
+
url_builder.api_request_path
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def execute_request(method, path, options)
|
|
80
|
+
params = params_builder.build(options)
|
|
81
|
+
retries_left = retries_left_for(params)
|
|
82
|
+
result = perform_request_with_retry(method, path, params, retries_left)
|
|
83
|
+
setup_cursor_fetcher!(result, method, path, options) if result.is_a?(CursorPaginatedResponse)
|
|
84
|
+
result
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def perform_request_with_retry(method, path, params, retries_left)
|
|
88
|
+
response = perform_request(method, path, params)
|
|
89
|
+
validate(response)
|
|
90
|
+
rescue Jira::Error::TooManyRequests, Jira::Error::ServiceUnavailable => e
|
|
91
|
+
raise e unless should_retry?(e, method, response, retries_left)
|
|
92
|
+
|
|
93
|
+
retry_policy.sleep_before_retry(response: response, retries_left: retries_left - 1)
|
|
94
|
+
retries_left -= 1
|
|
95
|
+
retry
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def setup_cursor_fetcher!(result, method, path, options)
|
|
99
|
+
result.next_page_fetcher = lambda do |token|
|
|
100
|
+
merged = options.dup
|
|
101
|
+
if method.to_s == "get"
|
|
102
|
+
merged[:query] = (merged.fetch(:query, nil) || {}).merge(nextPageToken: token)
|
|
103
|
+
else
|
|
104
|
+
body = merged[:body].is_a?(Hash) ? merged[:body].dup : {}
|
|
105
|
+
merged[:body] = body.merge(nextPageToken: token)
|
|
106
|
+
end
|
|
107
|
+
send(method, path, merged)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def perform_request(method, path, params)
|
|
112
|
+
self.class.send(method, build_url(path), params)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def retries_left_for(params)
|
|
116
|
+
params.delete(:ratelimit_retries) || ratelimit_retries || Configuration::DEFAULT_RATELIMIT_RETRIES
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def build_url(path)
|
|
120
|
+
url_builder.build(path)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def authorization_header
|
|
124
|
+
authenticator.authorization_header
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def should_retry?(error, method, response, retries_left)
|
|
128
|
+
retry_policy.retryable?(error: error, method: method, response: response, retries_left: retries_left)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def validate_endpoint!
|
|
132
|
+
return unless endpoint.to_s.strip.empty?
|
|
133
|
+
|
|
134
|
+
raise Error::MissingCredentials, "Please set an endpoint to API"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def authenticator
|
|
138
|
+
@authenticator ||= Authenticator.new(request: self)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def retry_policy
|
|
142
|
+
@retry_policy ||= RetryPolicy.new(request: self)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def params_builder
|
|
146
|
+
@params_builder ||= ParamsBuilder.new(request: self, authenticator: authenticator)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def url_builder
|
|
150
|
+
@url_builder ||= UrlBuilder.new(request: self, authenticator: authenticator)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
data/lib/jira/version.rb
ADDED
data/lib/jira.rb
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "jira/version"
|
|
4
|
+
require_relative "jira/configuration"
|
|
5
|
+
require_relative "jira/error"
|
|
6
|
+
require_relative "jira/objectified_hash"
|
|
7
|
+
require_relative "jira/pagination/paginated_response"
|
|
8
|
+
require_relative "jira/pagination/cursor_paginated_response"
|
|
9
|
+
require_relative "jira/request"
|
|
10
|
+
require_relative "jira/api"
|
|
11
|
+
require_relative "jira/client"
|
|
12
|
+
|
|
13
|
+
module Jira
|
|
14
|
+
extend Configuration
|
|
15
|
+
|
|
16
|
+
# Alias for Jira::Client.new
|
|
17
|
+
#
|
|
18
|
+
# @return [Jira::Client]
|
|
19
|
+
def self.client(options = {})
|
|
20
|
+
Jira::Client.new(options)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.method_missing(method, ...)
|
|
24
|
+
return super unless client.respond_to?(method)
|
|
25
|
+
|
|
26
|
+
client.send(method, ...)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.respond_to_missing?(method_name, include_private = false)
|
|
30
|
+
client.respond_to?(method_name) || super
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Delegate to HTTParty.http_proxy
|
|
34
|
+
def self.http_proxy(address = nil, port = nil, username = nil, password = nil) # rubocop:disable Metrics/ParameterLists
|
|
35
|
+
Jira::Request.http_proxy(address, port, username, password)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Returns an unsorted array of available client methods.
|
|
39
|
+
#
|
|
40
|
+
# @return [Array<Symbol>]
|
|
41
|
+
def self.actions # rubocop:disable Metrics/MethodLength
|
|
42
|
+
hidden = Regexp.union(
|
|
43
|
+
/endpoint/,
|
|
44
|
+
/auth_type/,
|
|
45
|
+
/email/,
|
|
46
|
+
/api_token/,
|
|
47
|
+
/oauth_access_token/,
|
|
48
|
+
/oauth_client_id/,
|
|
49
|
+
/oauth_client_secret/,
|
|
50
|
+
/oauth_refresh_token/,
|
|
51
|
+
/oauth_grant_type/,
|
|
52
|
+
/oauth_token_endpoint/,
|
|
53
|
+
/cloud_id/,
|
|
54
|
+
/user_agent/,
|
|
55
|
+
/get/,
|
|
56
|
+
/post/,
|
|
57
|
+
/put/,
|
|
58
|
+
/patch/,
|
|
59
|
+
/\Adelete\z/,
|
|
60
|
+
/validate\z/,
|
|
61
|
+
/httparty/
|
|
62
|
+
)
|
|
63
|
+
(Jira::Client.instance_methods - Object.methods).reject { |method_name| method_name[hidden] }
|
|
64
|
+
end
|
|
65
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ruby-jira
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Maciej Kozak
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: httparty
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.24'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.24'
|
|
26
|
+
description: A Ruby wrapper for Jira Cloud API
|
|
27
|
+
email:
|
|
28
|
+
- maciej.kozak@gmail.com
|
|
29
|
+
executables: []
|
|
30
|
+
extensions: []
|
|
31
|
+
extra_rdoc_files: []
|
|
32
|
+
files:
|
|
33
|
+
- CHANGELOG.md
|
|
34
|
+
- LICENSE.txt
|
|
35
|
+
- README.md
|
|
36
|
+
- lib/jira.rb
|
|
37
|
+
- lib/jira/api.rb
|
|
38
|
+
- lib/jira/client.rb
|
|
39
|
+
- lib/jira/client/issues.rb
|
|
40
|
+
- lib/jira/client/project_permission_schemes.rb
|
|
41
|
+
- lib/jira/client/projects.rb
|
|
42
|
+
- lib/jira/configuration.rb
|
|
43
|
+
- lib/jira/error.rb
|
|
44
|
+
- lib/jira/objectified_hash.rb
|
|
45
|
+
- lib/jira/pagination/cursor_paginated_response.rb
|
|
46
|
+
- lib/jira/pagination/paginated_response.rb
|
|
47
|
+
- lib/jira/request.rb
|
|
48
|
+
- lib/jira/request/authentication.rb
|
|
49
|
+
- lib/jira/request/paginated_response.rb
|
|
50
|
+
- lib/jira/request/rate_limiting.rb
|
|
51
|
+
- lib/jira/request/request_building.rb
|
|
52
|
+
- lib/jira/request/response_parsing.rb
|
|
53
|
+
- lib/jira/version.rb
|
|
54
|
+
homepage: https://github.com/macio/ruby-jira
|
|
55
|
+
licenses:
|
|
56
|
+
- BSD-2-Clause
|
|
57
|
+
metadata:
|
|
58
|
+
allowed_push_host: https://rubygems.org
|
|
59
|
+
homepage_uri: https://github.com/macio/ruby-jira
|
|
60
|
+
source_code_uri: https://github.com/macio/ruby-jira/tree/main
|
|
61
|
+
changelog_uri: https://github.com/macio/ruby-jira/blob/main/CHANGELOG.md
|
|
62
|
+
rubygems_mfa_required: 'true'
|
|
63
|
+
rdoc_options: []
|
|
64
|
+
require_paths:
|
|
65
|
+
- lib
|
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
67
|
+
requirements:
|
|
68
|
+
- - ">="
|
|
69
|
+
- !ruby/object:Gem::Version
|
|
70
|
+
version: 3.2.0
|
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
requirements: []
|
|
77
|
+
rubygems_version: 3.6.9
|
|
78
|
+
specification_version: 4
|
|
79
|
+
summary: Ruby client and CLI for Jira Cloud API
|
|
80
|
+
test_files: []
|