ruby-jira 0.1.0 → 0.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/CHANGELOG.md +8 -0
- data/README.md +53 -18
- data/lib/jira/client/issue_comments.rb +80 -0
- data/lib/jira/client/issue_search.rb +60 -0
- data/lib/jira/client/issue_worklogs.rb +123 -0
- data/lib/jira/client/issues.rb +191 -0
- data/lib/jira/client/project_categories.rb +60 -0
- data/lib/jira/client/project_permission_schemes.rb +18 -0
- data/lib/jira/client/project_properties.rb +54 -0
- data/lib/jira/client/projects.rb +60 -0
- data/lib/jira/client/time_tracking.rb +57 -0
- data/lib/jira/client.rb +6 -0
- data/lib/jira/configuration.rb +2 -0
- data/lib/jira/error.rb +3 -0
- data/lib/jira/logging.rb +15 -0
- data/lib/jira/pagination/collection_behavior.rb +72 -0
- data/lib/jira/pagination/cursor_paginated_response.rb +57 -41
- data/lib/jira/pagination/paginated_response.rb +46 -50
- data/lib/jira/request/rate_limiting.rb +6 -6
- data/lib/jira/request/response_parsing.rb +82 -16
- data/lib/jira/request.rb +44 -16
- data/lib/jira/version.rb +1 -1
- data/lib/jira.rb +2 -0
- metadata +9 -2
- data/lib/jira/request/paginated_response.rb +0 -4
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jira
|
|
4
|
+
class Client
|
|
5
|
+
# Defines methods related to project categories.
|
|
6
|
+
#
|
|
7
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-categories/
|
|
8
|
+
module ProjectCategories
|
|
9
|
+
# Returns all project categories
|
|
10
|
+
#
|
|
11
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-categories/#api-rest-api-3-projectcategory-get
|
|
12
|
+
#
|
|
13
|
+
# @return [Array<Hash>]
|
|
14
|
+
def project_categories
|
|
15
|
+
get("/projectCategory")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Creates a project category
|
|
19
|
+
#
|
|
20
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-categories/#api-rest-api-3-projectcategory-post
|
|
21
|
+
#
|
|
22
|
+
# @param payload [Hash] Project category payload
|
|
23
|
+
# @return [Hash]
|
|
24
|
+
def create_project_category(payload = {})
|
|
25
|
+
post("/projectCategory", body: payload)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns a project category by ID
|
|
29
|
+
#
|
|
30
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-categories/#api-rest-api-3-projectcategory-id-get
|
|
31
|
+
#
|
|
32
|
+
# @param id [Integer, String] The ID of the project category
|
|
33
|
+
# @return [Hash]
|
|
34
|
+
def project_category(id)
|
|
35
|
+
get("/projectCategory/#{url_encode(id)}")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Updates a project category
|
|
39
|
+
#
|
|
40
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-categories/#api-rest-api-3-projectcategory-id-put
|
|
41
|
+
#
|
|
42
|
+
# @param id [Integer, String] The ID of the project category
|
|
43
|
+
# @param payload [Hash] Project category payload
|
|
44
|
+
# @return [Hash]
|
|
45
|
+
def update_project_category(id, payload = {})
|
|
46
|
+
put("/projectCategory/#{url_encode(id)}", body: payload)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Deletes a project category
|
|
50
|
+
#
|
|
51
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-categories/#api-rest-api-3-projectcategory-id-delete
|
|
52
|
+
#
|
|
53
|
+
# @param id [Integer, String] The ID of the project category
|
|
54
|
+
# @return [nil]
|
|
55
|
+
def delete_project_category(id)
|
|
56
|
+
delete("/projectCategory/#{url_encode(id)}")
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -3,9 +3,13 @@
|
|
|
3
3
|
module Jira
|
|
4
4
|
class Client
|
|
5
5
|
# Defines methods related to project permission schemes.
|
|
6
|
+
#
|
|
7
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-permission-schemes/
|
|
6
8
|
module ProjectPermissionSchemes
|
|
7
9
|
# Gets assigned issue security level scheme for a project
|
|
8
10
|
#
|
|
11
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-permission-schemes/#api-rest-api-3-project-projectkeyorid-issuesecuritylevelscheme-get
|
|
12
|
+
#
|
|
9
13
|
# @param project_key_or_id [Integer, String] Project ID or key
|
|
10
14
|
# @param options [Hash] Query parameters
|
|
11
15
|
# @return [Hash]
|
|
@@ -15,6 +19,8 @@ module Jira
|
|
|
15
19
|
|
|
16
20
|
# Gets assigned permission scheme for a project
|
|
17
21
|
#
|
|
22
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-permission-schemes/#api-rest-api-3-project-projectkeyorid-permissionscheme-get
|
|
23
|
+
#
|
|
18
24
|
# @param project_key_or_id [Integer, String] Project ID or key
|
|
19
25
|
# @param options [Hash] Query parameters
|
|
20
26
|
# @return [Hash]
|
|
@@ -22,8 +28,20 @@ module Jira
|
|
|
22
28
|
get("/project/#{url_encode(project_key_or_id)}/permissionscheme", query: options)
|
|
23
29
|
end
|
|
24
30
|
|
|
31
|
+
# Returns security levels for a project
|
|
32
|
+
#
|
|
33
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-permission-schemes/#api-rest-api-3-project-projectkeyorid-securitylevel-get
|
|
34
|
+
#
|
|
35
|
+
# @param project_key_or_id [Integer, String] Project ID or key
|
|
36
|
+
# @return [Hash]
|
|
37
|
+
def project_security_levels(project_key_or_id)
|
|
38
|
+
get("/project/#{url_encode(project_key_or_id)}/securitylevel")
|
|
39
|
+
end
|
|
40
|
+
|
|
25
41
|
# Assigns permission scheme to a project
|
|
26
42
|
#
|
|
43
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-permission-schemes/#api-rest-api-3-project-projectkeyorid-permissionscheme-put
|
|
44
|
+
#
|
|
27
45
|
# @param project_key_or_id [Integer, String] Project ID or key
|
|
28
46
|
# @param scheme_id [Integer] Permission scheme ID
|
|
29
47
|
# @param options [Hash] Additional payload
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jira
|
|
4
|
+
class Client
|
|
5
|
+
# Defines methods related to project properties.
|
|
6
|
+
#
|
|
7
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-properties/
|
|
8
|
+
module ProjectProperties
|
|
9
|
+
# Returns all property keys for a project
|
|
10
|
+
#
|
|
11
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-properties/#api-rest-api-3-project-projectidorkey-properties-get
|
|
12
|
+
#
|
|
13
|
+
# @param project_id_or_key [Integer, String] The ID or key of the project
|
|
14
|
+
# @return [Hash]
|
|
15
|
+
def project_property_keys(project_id_or_key)
|
|
16
|
+
get("/project/#{url_encode(project_id_or_key)}/properties")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Returns the value of a project property
|
|
20
|
+
#
|
|
21
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-properties/#api-rest-api-3-project-projectidorkey-properties-propertykey-get
|
|
22
|
+
#
|
|
23
|
+
# @param project_id_or_key [Integer, String] The ID or key of the project
|
|
24
|
+
# @param property_key [String] The key of the property
|
|
25
|
+
# @return [Hash]
|
|
26
|
+
def project_property(project_id_or_key, property_key)
|
|
27
|
+
get("/project/#{url_encode(project_id_or_key)}/properties/#{url_encode(property_key)}")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Sets the value of a project property
|
|
31
|
+
#
|
|
32
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-properties/#api-rest-api-3-project-projectidorkey-properties-propertykey-put
|
|
33
|
+
#
|
|
34
|
+
# @param project_id_or_key [Integer, String] The ID or key of the project
|
|
35
|
+
# @param property_key [String] The key of the property
|
|
36
|
+
# @param payload [Hash] The value to set
|
|
37
|
+
# @return [nil]
|
|
38
|
+
def set_project_property(project_id_or_key, property_key, payload = {})
|
|
39
|
+
put("/project/#{url_encode(project_id_or_key)}/properties/#{url_encode(property_key)}", body: payload)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Deletes a project property
|
|
43
|
+
#
|
|
44
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-properties/#api-rest-api-3-project-projectidorkey-properties-propertykey-delete
|
|
45
|
+
#
|
|
46
|
+
# @param project_id_or_key [Integer, String] The ID or key of the project
|
|
47
|
+
# @param property_key [String] The key of the property
|
|
48
|
+
# @return [nil]
|
|
49
|
+
def delete_project_property(project_id_or_key, property_key)
|
|
50
|
+
delete("/project/#{url_encode(project_id_or_key)}/properties/#{url_encode(property_key)}")
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/jira/client/projects.rb
CHANGED
|
@@ -3,9 +3,13 @@
|
|
|
3
3
|
module Jira
|
|
4
4
|
class Client
|
|
5
5
|
# Defines methods related to projects.
|
|
6
|
+
#
|
|
7
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-projects/
|
|
6
8
|
module Projects
|
|
7
9
|
# Search projects
|
|
8
10
|
#
|
|
11
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-projects/#api-rest-api-3-project-search-get
|
|
12
|
+
#
|
|
9
13
|
# @param options [Hash] Query parameters
|
|
10
14
|
# @return [Jira::Request::PaginatedResponse]
|
|
11
15
|
def projects(options = {})
|
|
@@ -14,6 +18,8 @@ module Jira
|
|
|
14
18
|
|
|
15
19
|
# Gets a single project
|
|
16
20
|
#
|
|
21
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-projects/#api-rest-api-3-project-projectidorkey-get
|
|
22
|
+
#
|
|
17
23
|
# @param project_id_or_key [Integer, String] Project ID or key
|
|
18
24
|
# @param options [Hash] Query parameters
|
|
19
25
|
# @return [Hash]
|
|
@@ -23,11 +29,65 @@ module Jira
|
|
|
23
29
|
|
|
24
30
|
# Archives a project
|
|
25
31
|
#
|
|
32
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-projects/#api-rest-api-3-project-projectidorkey-archive-post
|
|
33
|
+
#
|
|
26
34
|
# @param project_id_or_key [Integer, String] Project ID or key
|
|
27
35
|
# @return [Hash]
|
|
28
36
|
def archive_project(project_id_or_key)
|
|
29
37
|
post("/project/#{url_encode(project_id_or_key)}/archive")
|
|
30
38
|
end
|
|
39
|
+
|
|
40
|
+
# Updates a project
|
|
41
|
+
#
|
|
42
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-projects/#api-rest-api-3-project-projectidorkey-put
|
|
43
|
+
#
|
|
44
|
+
# @param project_id_or_key [Integer, String] Project ID or key
|
|
45
|
+
# @param payload [Hash] Fields to update
|
|
46
|
+
# @return [Hash]
|
|
47
|
+
def update_project(project_id_or_key, payload = {})
|
|
48
|
+
put("/project/#{url_encode(project_id_or_key)}", body: payload)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Deletes a project
|
|
52
|
+
#
|
|
53
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-projects/#api-rest-api-3-project-projectidorkey-delete
|
|
54
|
+
#
|
|
55
|
+
# @param project_id_or_key [Integer, String] Project ID or key
|
|
56
|
+
# @return [Hash]
|
|
57
|
+
def delete_project(project_id_or_key)
|
|
58
|
+
delete("/project/#{url_encode(project_id_or_key)}")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Gets all issue types with their statuses for a project
|
|
62
|
+
#
|
|
63
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-projects/#api-rest-api-3-project-projectidorkey-statuses-get
|
|
64
|
+
#
|
|
65
|
+
# @param project_id_or_key [Integer, String] Project ID or key
|
|
66
|
+
# @return [Array<Hash>]
|
|
67
|
+
def project_statuses(project_id_or_key)
|
|
68
|
+
get("/project/#{url_encode(project_id_or_key)}/statuses")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Gets the issue type hierarchy for a project
|
|
72
|
+
#
|
|
73
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-projects/#api-rest-api-3-project-projectid-hierarchy-get
|
|
74
|
+
#
|
|
75
|
+
# @param project_id [Integer, String] Project ID
|
|
76
|
+
# @return [Hash]
|
|
77
|
+
def project_issue_type_hierarchy(project_id)
|
|
78
|
+
get("/project/#{url_encode(project_id)}/hierarchy")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Gets the notification scheme for a project
|
|
82
|
+
#
|
|
83
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-projects/#api-rest-api-3-project-projectkeyorid-notificationscheme-get
|
|
84
|
+
#
|
|
85
|
+
# @param project_key_or_id [Integer, String] Project key or ID
|
|
86
|
+
# @param options [Hash] Query parameters (e.g. expand:)
|
|
87
|
+
# @return [Hash]
|
|
88
|
+
def project_notification_scheme(project_key_or_id, options = {})
|
|
89
|
+
get("/project/#{url_encode(project_key_or_id)}/notificationscheme", query: options)
|
|
90
|
+
end
|
|
31
91
|
end
|
|
32
92
|
end
|
|
33
93
|
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jira
|
|
4
|
+
class Client
|
|
5
|
+
# Defines methods related to time tracking.
|
|
6
|
+
#
|
|
7
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-time-tracking/
|
|
8
|
+
module TimeTracking
|
|
9
|
+
# Returns the time tracking provider that is currently selected
|
|
10
|
+
#
|
|
11
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-time-tracking/#api-rest-api-3-configuration-timetracking-get
|
|
12
|
+
#
|
|
13
|
+
# @return [Hash]
|
|
14
|
+
def time_tracking_provider
|
|
15
|
+
get("/configuration/timetracking")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Selects a time tracking provider
|
|
19
|
+
#
|
|
20
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-time-tracking/#api-rest-api-3-configuration-timetracking-put
|
|
21
|
+
#
|
|
22
|
+
# @param payload [Hash] Time tracking provider payload
|
|
23
|
+
# @return [nil]
|
|
24
|
+
def select_time_tracking_provider(payload = {})
|
|
25
|
+
put("/configuration/timetracking", body: payload)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns all time tracking providers
|
|
29
|
+
#
|
|
30
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-time-tracking/#api-rest-api-3-configuration-timetracking-list-get
|
|
31
|
+
#
|
|
32
|
+
# @return [Array<Hash>]
|
|
33
|
+
def time_tracking_providers
|
|
34
|
+
get("/configuration/timetracking/list")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Returns the time tracking settings
|
|
38
|
+
#
|
|
39
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-time-tracking/#api-rest-api-3-configuration-timetracking-options-get
|
|
40
|
+
#
|
|
41
|
+
# @return [Hash]
|
|
42
|
+
def time_tracking_settings
|
|
43
|
+
get("/configuration/timetracking/options")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Sets the time tracking settings
|
|
47
|
+
#
|
|
48
|
+
# @url https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-time-tracking/#api-rest-api-3-configuration-timetracking-options-put
|
|
49
|
+
#
|
|
50
|
+
# @param payload [Hash] Time tracking settings payload
|
|
51
|
+
# @return [Hash]
|
|
52
|
+
def set_time_tracking_settings(payload = {})
|
|
53
|
+
put("/configuration/timetracking/options", body: payload)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
data/lib/jira/client.rb
CHANGED
|
@@ -4,9 +4,15 @@ module Jira
|
|
|
4
4
|
class Client < API
|
|
5
5
|
Dir[File.expand_path("client/*.rb", __dir__)].each { |file| require file }
|
|
6
6
|
|
|
7
|
+
include IssueComments
|
|
8
|
+
include IssueSearch
|
|
7
9
|
include Issues
|
|
10
|
+
include IssueWorklogs
|
|
11
|
+
include ProjectCategories
|
|
8
12
|
include ProjectPermissionSchemes
|
|
13
|
+
include ProjectProperties
|
|
9
14
|
include Projects
|
|
15
|
+
include TimeTracking
|
|
10
16
|
|
|
11
17
|
# Text representation of the client, masking auth secrets.
|
|
12
18
|
#
|
data/lib/jira/configuration.rb
CHANGED
|
@@ -21,6 +21,7 @@ module Jira
|
|
|
21
21
|
ratelimit_retries
|
|
22
22
|
ratelimit_base_delay
|
|
23
23
|
ratelimit_max_delay
|
|
24
|
+
logger
|
|
24
25
|
].freeze
|
|
25
26
|
|
|
26
27
|
DEFAULT_USER_AGENT = "Ruby Jira Gem #{Jira::VERSION}".freeze
|
|
@@ -63,6 +64,7 @@ module Jira
|
|
|
63
64
|
self.ratelimit_max_delay = float_env("JIRA_RATELIMIT_MAX_DELAY", DEFAULT_RATELIMIT_MAX_DELAY)
|
|
64
65
|
self.httparty = get_httparty_config(ENV.fetch("JIRA_HTTPARTY_OPTIONS", nil))
|
|
65
66
|
self.user_agent = DEFAULT_USER_AGENT
|
|
67
|
+
self.logger = nil
|
|
66
68
|
end
|
|
67
69
|
|
|
68
70
|
private
|
data/lib/jira/error.rb
CHANGED
|
@@ -11,6 +11,9 @@ module Jira
|
|
|
11
11
|
# Raised when impossible to parse response body.
|
|
12
12
|
class Parsing < Base; end
|
|
13
13
|
|
|
14
|
+
# Raised when pagination cannot continue safely.
|
|
15
|
+
class Pagination < Base; end
|
|
16
|
+
|
|
14
17
|
# Custom error class for rescuing from HTTP response errors.
|
|
15
18
|
class ResponseError < Base
|
|
16
19
|
POSSIBLE_MESSAGE_KEYS = %i[message error_description error].freeze
|
data/lib/jira/logging.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jira
|
|
4
|
+
# Provides a #log helper that delegates to the configured logger.
|
|
5
|
+
# Mixed into Request and pagination classes to emit debug information.
|
|
6
|
+
#
|
|
7
|
+
# @example Enable logging
|
|
8
|
+
# require "logger"
|
|
9
|
+
# Jira.configure { |c| c.logger = Logger.new($stdout) }
|
|
10
|
+
module Logging
|
|
11
|
+
def log(message)
|
|
12
|
+
Jira.logger&.debug("[ruby-jira] #{message}")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jira
|
|
4
|
+
module Pagination
|
|
5
|
+
# Shared behavior for paginated collection wrappers.
|
|
6
|
+
module CollectionBehavior
|
|
7
|
+
def inspect
|
|
8
|
+
@array.inspect
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def method_missing(name, *, &)
|
|
12
|
+
return @array.send(name, *, &) if @array.respond_to?(name)
|
|
13
|
+
|
|
14
|
+
super
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
18
|
+
super || @array.respond_to?(method_name, include_private)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def each_page # rubocop:disable Metrics/MethodLength
|
|
22
|
+
return enum_for(:each_page) unless block_given?
|
|
23
|
+
|
|
24
|
+
current = self
|
|
25
|
+
seen_markers = {}
|
|
26
|
+
loop do
|
|
27
|
+
yield current
|
|
28
|
+
break unless current.has_next_page?
|
|
29
|
+
|
|
30
|
+
marker = current.pagination_progress_marker
|
|
31
|
+
check_repeated_marker!(seen_markers, marker)
|
|
32
|
+
seen_markers[marker] = true if marker
|
|
33
|
+
current = next_page_with_progress_check(current, marker)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def lazy_paginate
|
|
38
|
+
to_enum(:each_page).lazy.flat_map(&:to_ary)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def auto_paginate(&block)
|
|
42
|
+
return lazy_paginate.to_a unless block
|
|
43
|
+
|
|
44
|
+
lazy_paginate.each(&block)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def paginate_with_limit(limit, &block)
|
|
48
|
+
return lazy_paginate.take(limit).to_a unless block
|
|
49
|
+
|
|
50
|
+
lazy_paginate.take(limit).each(&block)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def check_repeated_marker!(seen_markers, marker)
|
|
56
|
+
return unless marker
|
|
57
|
+
return unless seen_markers.key?(marker)
|
|
58
|
+
|
|
59
|
+
raise Error::Pagination, "pagination cursor repeated: #{marker.inspect}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def next_page_with_progress_check(current, marker)
|
|
63
|
+
next_page = current.next_page
|
|
64
|
+
raise Error::Pagination, "pagination returned nil page" unless next_page
|
|
65
|
+
return next_page unless marker
|
|
66
|
+
return next_page unless next_page.pagination_progress_marker == marker
|
|
67
|
+
|
|
68
|
+
raise Error::Pagination, "pagination did not advance from #{marker.inspect}"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
3
5
|
module Jira
|
|
4
|
-
# Wrapper for Jira cursor-paginated responses
|
|
6
|
+
# Wrapper for Jira cursor-paginated responses.
|
|
5
7
|
#
|
|
6
8
|
# Endpoints like POST /search/jql return a body of:
|
|
7
9
|
# { nextPageToken: "token", total: int, <items_key>: [...] }
|
|
10
|
+
# Some Jira APIs use alternative cursor fields:
|
|
11
|
+
# { nextPageCursor: "cursor", ... } or { cursor: "...", nextPageCursor: "...", last: false, ... }
|
|
12
|
+
# Other APIs use URL-based cursor fields:
|
|
13
|
+
# { nextPage: "https://...", lastPage: false, ... }
|
|
8
14
|
#
|
|
9
15
|
# The items array key varies by endpoint (e.g. "issues", "worklogs").
|
|
10
16
|
# This class detects it automatically as the first non-metadata Array value.
|
|
@@ -12,77 +18,87 @@ module Jira
|
|
|
12
18
|
# Pagination is driven by a +next_page_fetcher+ proc set by the Request layer,
|
|
13
19
|
# which re-issues the original request with +nextPageToken+ injected.
|
|
14
20
|
class CursorPaginatedResponse
|
|
15
|
-
|
|
21
|
+
include Logging
|
|
22
|
+
include Pagination::CollectionBehavior
|
|
23
|
+
|
|
24
|
+
METADATA_KEYS = %i[
|
|
25
|
+
nextPageToken nextPageCursor cursor nextPage total self isLast last lastPage expand warningMessages maxResults
|
|
26
|
+
startAt size
|
|
27
|
+
].freeze
|
|
16
28
|
|
|
17
29
|
attr_accessor :client, :next_page_fetcher
|
|
18
|
-
attr_reader :next_page_token, :total
|
|
30
|
+
attr_reader :next_page_token, :next_page_cursor, :next_page_url, :total
|
|
19
31
|
|
|
20
|
-
def initialize(body)
|
|
21
|
-
@body = body
|
|
32
|
+
def initialize(body) # rubocop:disable Metrics/AbcSize
|
|
22
33
|
@next_page_token = body[:nextPageToken]
|
|
34
|
+
@next_page_cursor = body[:nextPageCursor] || body[:cursor]
|
|
35
|
+
@next_page_url = body[:nextPage]
|
|
36
|
+
@is_last = body[:isLast]
|
|
37
|
+
@last = body[:last]
|
|
38
|
+
@last_page = body[:lastPage]
|
|
23
39
|
@total = body.fetch(:total, 0).to_i
|
|
24
|
-
|
|
40
|
+
items_key, items = detect_items_array(body)
|
|
41
|
+
log "CursorPaginatedResponse: items_key=#{items_key.inspect} count=#{items.length}"
|
|
42
|
+
@array = wrap_items(items)
|
|
25
43
|
end
|
|
26
44
|
|
|
27
|
-
def
|
|
28
|
-
@
|
|
45
|
+
def next_page?
|
|
46
|
+
return false if @is_last == true || @last == true || @last_page == true
|
|
47
|
+
|
|
48
|
+
!next_page_locator.to_s.empty?
|
|
29
49
|
end
|
|
50
|
+
alias has_next_page? next_page?
|
|
30
51
|
|
|
31
|
-
def
|
|
32
|
-
return
|
|
52
|
+
def next_page
|
|
53
|
+
return nil unless has_next_page?
|
|
54
|
+
return next_page_by_link unless @next_page_url.to_s.empty?
|
|
55
|
+
raise Error::MissingCredentials, "next_page_fetcher not set on CursorPaginatedResponse" unless @next_page_fetcher
|
|
33
56
|
|
|
34
|
-
|
|
57
|
+
@next_page_fetcher.call(next_page_locator)
|
|
35
58
|
end
|
|
36
59
|
|
|
37
|
-
def
|
|
38
|
-
|
|
39
|
-
|
|
60
|
+
def cursor_parameter_key
|
|
61
|
+
return :nextPageToken unless @next_page_token.to_s.empty?
|
|
62
|
+
return :cursor unless @next_page_cursor.to_s.empty?
|
|
40
63
|
|
|
41
|
-
|
|
42
|
-
current = self
|
|
43
|
-
yield current
|
|
44
|
-
while current.has_next_page?
|
|
45
|
-
current = current.next_page
|
|
46
|
-
yield current
|
|
47
|
-
end
|
|
64
|
+
nil
|
|
48
65
|
end
|
|
49
66
|
|
|
50
|
-
def
|
|
51
|
-
|
|
67
|
+
def fetcher_based_pagination?
|
|
68
|
+
@next_page_url.to_s.empty?
|
|
52
69
|
end
|
|
53
70
|
|
|
54
|
-
def
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
lazy_paginate.each(&block)
|
|
71
|
+
def pagination_progress_marker
|
|
72
|
+
next_page_locator
|
|
58
73
|
end
|
|
59
74
|
|
|
60
|
-
|
|
61
|
-
return lazy_paginate.take(limit).to_a unless block
|
|
75
|
+
private
|
|
62
76
|
|
|
63
|
-
|
|
64
|
-
|
|
77
|
+
def next_page_locator
|
|
78
|
+
return @next_page_token unless @next_page_token.to_s.empty?
|
|
79
|
+
return @next_page_cursor unless @next_page_cursor.to_s.empty?
|
|
80
|
+
return @next_page_url unless @next_page_url.to_s.empty?
|
|
65
81
|
|
|
66
|
-
|
|
67
|
-
!@next_page_token.to_s.empty?
|
|
82
|
+
nil
|
|
68
83
|
end
|
|
69
|
-
alias has_next_page? next_page?
|
|
70
84
|
|
|
71
|
-
def
|
|
72
|
-
|
|
73
|
-
raise Error::MissingCredentials, "next_page_fetcher not set on CursorPaginatedResponse" unless @next_page_fetcher
|
|
85
|
+
def next_page_by_link
|
|
86
|
+
raise Error::MissingCredentials, "client not set on CursorPaginatedResponse" unless @client
|
|
74
87
|
|
|
75
|
-
@
|
|
88
|
+
@client.get(client_relative_path(@next_page_url))
|
|
76
89
|
end
|
|
77
90
|
|
|
78
|
-
|
|
91
|
+
def client_relative_path(link)
|
|
92
|
+
client_endpoint_path = @client.api_request_path
|
|
93
|
+
URI.parse(link).request_uri.sub(client_endpoint_path, "")
|
|
94
|
+
end
|
|
79
95
|
|
|
80
96
|
def detect_items_array(body)
|
|
81
97
|
body.each do |key, value|
|
|
82
98
|
next if METADATA_KEYS.include?(key)
|
|
83
|
-
return value if value.is_a?(Array)
|
|
99
|
+
return [key, value] if value.is_a?(Array)
|
|
84
100
|
end
|
|
85
|
-
[]
|
|
101
|
+
[nil, []]
|
|
86
102
|
end
|
|
87
103
|
|
|
88
104
|
def wrap_items(items)
|