Skip to content

Commit cfc8dc1

Browse files
authored
Merge pull request #548 from zendesk/fvilela/add-cbp-endpoints
[RED-1821] Cater for endpoints with CBP support
2 parents 2a8c364 + df417ff commit cfc8dc1

File tree

12 files changed

+262
-117
lines changed

12 files changed

+262
-117
lines changed

.rubocop.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ AllCops:
88
- spec/core/middleware/response/sanitize_response_spec.rb
99
- vendor/**/*
1010

11+
# Prevents Ruby 3.1 incompatibility error. You can enable this cop when Ruby 2.7 support is dropped.
12+
# See https://github.com/rubocop/rubocop/issues/10258
13+
Layout/BlockAlignment:
14+
Enabled: false
15+
1116
# Align ends correctly.
1217
Layout/EndAlignment:
1318
EnforcedStyleAlignWith: variable

.rubocop_todo.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ Naming/AccessorMethodName:
176176
- 'lib/zendesk_api/collection.rb'
177177
- 'lib/zendesk_api/lru_cache.rb'
178178
- 'lib/zendesk_api/resources.rb'
179+
- 'lib/zendesk_api/pagination.rb'
179180

180181
# Offense count: 1
181182
# Configuration parameters: EnforcedStyleForLeadingUnderscores.

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# CHANGELOG
22

3+
## v3.0.2
4+
5+
In this version, we are adding CBP support to Tickets, Triggers, Groups, Organizations, GroupMembership and OrganizationMembership.
6+
The supported enpoints will benefit from CBP requests and more endpoints should be added to the list in the near future.
7+
38
## v3.0.1
49

510
This version introduces a small change in order to prevent CBP request attempts for API endpoints in which we know this pagination is not supported. At the moment, the show_many endpoints have been excluded from attempting CBP requests.

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,17 @@ client.tickets.all! do |resource, page_number|
189189
end
190190
```
191191

192+
### Cursor Based Pagination
193+
194+
A few endpoints related to organizations, tickets, triggers and groups will now make use of cursor based pagination by default.
195+
It is also recommended to use CBP whenever [the Zendesk developer documentation](https://developer.zendesk.com/api-reference) says it's supported.
196+
Pass `page[size]=number` in the parameters to attempt a CBP request, like the example below:
197+
198+
```ruby
199+
client.connection.get('/api/v2/suspended_tickets', page: { size: 100 }).body
200+
{"suspended_tickets"=>[...], "meta"=>{"has_more"=>true, "after_cursor"=>" ... ", "before_cursor"=>nil}, "links"=>{"prev"=>nil, "next"=>"..."}}
201+
```
202+
192203
### Callbacks
193204

194205
Callbacks can be added to the `ZendeskAPI::Client` instance and will be called (with the response env) after all response middleware on a successful request.

lib/zendesk_api/collection.rb

Lines changed: 2 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
require 'zendesk_api/resource'
22
require 'zendesk_api/resources'
33
require 'zendesk_api/search'
4+
require 'zendesk_api/pagination'
45

56
module ZendeskAPI
67
# Represents a collection of resources. Lazily loaded, resources aren't
78
# actually fetched until explicitly needed (e.g. #each, {#fetch}).
89
class Collection
9-
DEFAULT_PAGE_SIZE = 100
10-
1110
include ZendeskAPI::Sideloading
11+
include Pagination
1212

1313
# Options passed in that are automatically converted from an array to a comma-separated list.
1414
SPECIALLY_JOINED_PARAMS = [:ids, :only]
@@ -116,30 +116,6 @@ def count!
116116
@count || -1
117117
end
118118

119-
# Changes the per_page option. Returns self, so it can be chained. No execution.
120-
# @return [Collection] self
121-
def per_page(count)
122-
clear_cache if count
123-
@options["per_page"] = count
124-
self
125-
end
126-
127-
# Changes the page option. Returns self, so it can be chained. No execution.
128-
# @return [Collection] self
129-
def page(number)
130-
clear_cache if number
131-
@options["page"] = number
132-
self
133-
end
134-
135-
def first_page?
136-
!@prev_page
137-
end
138-
139-
def last_page?
140-
!@next_page || @next_page == @query
141-
end
142-
143119
# Saves all newly created resources stored in this collection.
144120
# @return [Collection] self
145121
def save
@@ -323,11 +299,6 @@ def to_param
323299
map(&:to_param)
324300
end
325301

326-
def more_results?(response)
327-
Helpers.present?(response["meta"]) && response["meta"]["has_more"]
328-
end
329-
alias_method :has_more_results?, :more_results? # For backward compatibility with 1.33.0 and 1.34.0
330-
331302
def get_next_page_data(original_response_body)
332303
link = original_response_body["links"]["next"]
333304
result_key = @resource_class.model_key || "results"
@@ -344,35 +315,6 @@ def get_next_page_data(original_response_body)
344315

345316
private
346317

347-
def cbp_response?(body)
348-
!!(body["meta"] && body["links"])
349-
end
350-
351-
def set_cbp_options
352-
@options_per_page_was = @options.delete("per_page")
353-
# Default to CBP by using the page param as a map
354-
@options.page = { size: (@options_per_page_was || DEFAULT_PAGE_SIZE) }
355-
end
356-
357-
# CBP requests look like: `/resources?page[size]=100`
358-
# OBP requests look like: `/resources?page=2`
359-
def cbp_request?
360-
@options["page"].is_a?(Hash)
361-
end
362-
363-
def intentional_obp_request?
364-
Helpers.present?(@options["page"]) && !cbp_request?
365-
end
366-
367-
def supports_cbp?
368-
@resource_class.const_defined?(:CBP_ACTIONS) && @resource_class.const_get(:CBP_ACTIONS).any? { |supported_path| path.end_with?(supported_path) }
369-
end
370-
371-
def first_cbp_request?
372-
# @next_page will be nil when making the first cbp request
373-
@next_page.nil?
374-
end
375-
376318
def get_resources(path_query_link)
377319
if intentional_obp_request?
378320
warn "Offset Based Pagination will be deprecated soon"
@@ -390,27 +332,6 @@ def get_resources(path_query_link)
390332
end
391333
end
392334

393-
def set_page_and_count(body)
394-
@count = (body["count"] || @resources.size).to_i
395-
@next_page, @prev_page = page_links(body)
396-
397-
if cbp_response?(body)
398-
set_cbp_response_options(body)
399-
elsif @next_page =~ /page=(\d+)/
400-
@options["page"] = $1.to_i - 1
401-
elsif @prev_page =~ /page=(\d+)/
402-
@options["page"] = $1.to_i + 1
403-
end
404-
end
405-
406-
def page_links(body)
407-
if body["meta"] && body["links"]
408-
[body["links"]["next"], body["links"]["prev"]]
409-
else
410-
[body["next_page"], body["previous_page"]]
411-
end
412-
end
413-
414335
def _all(start_page = @options["page"], bang = false, &block)
415336
raise(ArgumentError, "must pass a block") unless block
416337

@@ -568,16 +489,5 @@ def assert_results(results, body)
568489
return if results
569490
raise ZendeskAPI::Error::ClientError, "Expected #{@resource_class.model_key} or 'results' in response keys: #{body.keys.inspect}"
570491
end
571-
572-
def set_cbp_response_options(body)
573-
@options.page = {} unless cbp_request?
574-
# the line above means an intentional CBP request where page[size] is passed on the query
575-
# this is to cater for CBP responses where we don't specify page[size] but the endpoint
576-
# responds CBP by default. i.e `client.trigger_categories.fetch`
577-
@options.page.merge!(
578-
before: body["meta"]["before_cursor"],
579-
after: body["meta"]["after_cursor"]
580-
)
581-
end
582492
end
583493
end

lib/zendesk_api/pagination.rb

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
module ZendeskAPI
2+
class Collection
3+
# Contains all methods related to pagination in an attempt to slim down collection.rb
4+
module Pagination
5+
DEFAULT_PAGE_SIZE = 100
6+
def more_results?(response)
7+
Helpers.present?(response["meta"]) && response["meta"]["has_more"]
8+
end
9+
alias has_more_results? more_results? # For backward compatibility with 1.33.0 and 1.34.0
10+
11+
# Changes the per_page option. Returns self, so it can be chained. No execution.
12+
# @return [Collection] self
13+
def per_page(count)
14+
clear_cache if count
15+
@options["per_page"] = count
16+
self
17+
end
18+
19+
# Changes the page option. Returns self, so it can be chained. No execution.
20+
# @return [Collection] self
21+
def page(number)
22+
clear_cache if number
23+
@options["page"] = number
24+
self
25+
end
26+
27+
def first_page?
28+
!@prev_page
29+
end
30+
31+
def last_page?
32+
!@next_page || @next_page == @query
33+
end
34+
35+
private
36+
37+
def page_links(body)
38+
if body["meta"] && body["links"]
39+
[body["links"]["next"], body["links"]["prev"]]
40+
else
41+
[body["next_page"], body["previous_page"]]
42+
end
43+
end
44+
45+
def cbp_response?(body)
46+
!!(body["meta"] && body["links"])
47+
end
48+
49+
def set_cbp_options
50+
@options_per_page_was = @options.delete("per_page")
51+
# Default to CBP by using the page param as a map
52+
@options.page = { size: (@options_per_page_was || DEFAULT_PAGE_SIZE) }
53+
end
54+
55+
# CBP requests look like: `/resources?page[size]=100`
56+
# OBP requests look like: `/resources?page=2`
57+
def cbp_request?
58+
@options["page"].is_a?(Hash)
59+
end
60+
61+
def intentional_obp_request?
62+
Helpers.present?(@options["page"]) && !cbp_request?
63+
end
64+
65+
def supports_cbp?
66+
@resource_class.cbp_path_regexes.any? { |supported_path_regex| path.match?(supported_path_regex) }
67+
end
68+
69+
def first_cbp_request?
70+
# @next_page will be nil when making the first cbp request
71+
@next_page.nil?
72+
end
73+
74+
def set_page_and_count(body)
75+
@count = (body["count"] || @resources.size).to_i
76+
@next_page, @prev_page = page_links(body)
77+
78+
if cbp_response?(body)
79+
set_cbp_response_options(body)
80+
elsif @next_page =~ /page=(\d+)/
81+
@options["page"] = Regexp.last_match(1).to_i - 1
82+
elsif @prev_page =~ /page=(\d+)/
83+
@options["page"] = Regexp.last_match(1).to_i + 1
84+
end
85+
end
86+
87+
def set_cbp_response_options(body)
88+
@options.page = {} unless cbp_request?
89+
# the line above means an intentional CBP request where page[size] is passed on the query
90+
# this is to cater for CBP responses where we don't specify page[size] but the endpoint
91+
# responds CBP by default. i.e `client.trigger_categories.fetch`
92+
@options.page.merge!(
93+
before: body["meta"]["before_cursor"],
94+
after: body["meta"]["after_cursor"]
95+
)
96+
end
97+
end
98+
end
99+
end

lib/zendesk_api/resource.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ def attribute_changes
163163
class DataResource < Data
164164
attr_accessor :error, :error_message
165165
extend Verbs
166+
167+
def self.cbp_path_regexes
168+
[]
169+
end
166170
end
167171

168172
# Represents a resource that can only GET

0 commit comments

Comments
 (0)