Skip to content

Commit 23c7571

Browse files
author
Jin Huang
committed
Added Cursor Based Pagination option for get tickets
Added Iterator to get tickets
1 parent 4b87e89 commit 23c7571

File tree

3 files changed

+259
-1
lines changed

3 files changed

+259
-1
lines changed

zendesk/mock/client.go

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

zendesk/ticket.go

Lines changed: 160 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ type Via struct {
129129
} `json:"source"`
130130
}
131131

132+
// TicketListOptions struct is used to specify options for listing tickets in OBP (Offset Based Pagination).
133+
// It embeds the PageOptions struct for pagination and provides options for sorting the result;
134+
// SortBy specifies the field to sort by, and SortOrder specifies the order (either 'asc' or 'desc').
132135
type TicketListOptions struct {
133136
PageOptions
134137

@@ -140,9 +143,119 @@ type TicketListOptions struct {
140143
SortOrder string `url:"sort_order,omitempty"`
141144
}
142145

146+
// TicketListCBPOptions struct is used to specify options for listing tickets in CBP (Cursor Based Pagination).
147+
// It embeds the CursorPagination struct for pagination and provides an option Sort for sorting the result.
148+
type TicketListCBPOptions struct {
149+
CursorPagination
150+
Sort string `url:"sort,omitempty"`
151+
}
152+
153+
// TicketListCBPResult struct represents the result of a ticket list operation in CBP. It includes an array of Ticket objects, and Meta that holds pagination metadata.
154+
type TicketListCBPResult struct {
155+
Tickets []Ticket `json:"tickets"`
156+
Meta CursorPaginationMeta `json:"meta"`
157+
}
158+
159+
// PaginationOptions struct represents general pagination options.
160+
// PageSize specifies the number of items per page, IsCBP indicates if it's cursor-based pagination,
161+
// SortBy and SortOrder describe how to sort the items in Offset Based Pagination, and Sort describes how to sort items in Cursor Based Pagination.
162+
type PaginationOptions struct {
163+
PageSize int //default is 100
164+
IsCBP bool //default is true
165+
166+
SortBy string
167+
// SortOrder can take "asc" or "desc"
168+
SortOrder string
169+
Sort string
170+
}
171+
172+
// NewPaginationOptions() returns a pointer to a new PaginationOptions struct with default values (PageSize is 100, IsCBP is true).
173+
func NewPaginationOptions() *PaginationOptions {
174+
return &PaginationOptions{
175+
PageSize: 100,
176+
IsCBP: true,
177+
}
178+
}
179+
180+
// TicketIterator struct provides a convenient way to iterate over pages of tickets in either OBP or CBP.
181+
// It holds state for iteration, including the current page size, a flag indicating more pages, pagination type (OBP or CBP), and sorting options.
182+
type TicketIterator struct {
183+
// generic fields
184+
pageSize int
185+
hasMore bool
186+
isCBP bool
187+
188+
// OBP fields
189+
sortBy string
190+
// SortOrder can take "asc" or "desc"
191+
sortOrder string
192+
pageIndex int
193+
194+
// CBP fields
195+
sort string
196+
pageAfter string
197+
198+
// common fields
199+
client *Client
200+
ctx context.Context
201+
}
202+
203+
// HasMore() returns a boolean indicating whether more pages are available for iteration.
204+
func (i *TicketIterator) HasMore() bool {
205+
return i.hasMore
206+
}
207+
208+
// GetNext() retrieves the next batch of tickets according to the current pagination and sorting options.
209+
// It updates the state of the iterator for subsequent calls.
210+
// In case of an error, it sets hasMore to false and returns an error.
211+
func (i *TicketIterator) GetNext() ([]Ticket, error) {
212+
if i.isCBP {
213+
cbpOps := &TicketListCBPOptions{
214+
CursorPagination: CursorPagination{
215+
PageSize: i.pageSize,
216+
PageAfter: i.pageAfter,
217+
},
218+
}
219+
if i.sort != "" {
220+
cbpOps.Sort = i.sort
221+
}
222+
ticketListCBPResult, err := i.client.GetTicketsCBP(i.ctx, cbpOps)
223+
if err != nil {
224+
i.hasMore = false
225+
return nil, err
226+
}
227+
i.hasMore = ticketListCBPResult.Meta.HasMore
228+
i.pageAfter = ticketListCBPResult.Meta.AfterCursor
229+
return ticketListCBPResult.Tickets, nil
230+
} else {
231+
obpOps := &TicketListOptions{
232+
PageOptions: PageOptions{
233+
PerPage: i.pageSize,
234+
Page: i.pageIndex,
235+
},
236+
}
237+
if i.sortBy != "" {
238+
obpOps.SortBy = i.sortBy
239+
}
240+
if i.sortOrder != "" {
241+
obpOps.SortOrder = i.sortOrder
242+
}
243+
tickets, page, err := i.client.GetTickets(i.ctx, obpOps)
244+
if err != nil {
245+
i.hasMore = false
246+
return nil, err
247+
}
248+
i.hasMore = page.HasNext()
249+
i.pageIndex++
250+
return tickets, nil
251+
}
252+
}
253+
143254
// TicketAPI an interface containing all ticket related methods
144255
type TicketAPI interface {
256+
GetTicketsEx(ctx context.Context, opts *PaginationOptions) *TicketIterator
145257
GetTickets(ctx context.Context, opts *TicketListOptions) ([]Ticket, Page, error)
258+
GetTicketsCBP(ctx context.Context, opts *TicketListCBPOptions) (*TicketListCBPResult, error)
146259
GetOrganizationTickets(ctx context.Context, organizationID int64, ops *TicketListOptions) ([]Ticket, Page, error)
147260
GetTicket(ctx context.Context, id int64) (Ticket, error)
148261
GetMultipleTickets(ctx context.Context, ticketIDs []int64) ([]Ticket, error)
@@ -151,7 +264,25 @@ type TicketAPI interface {
151264
DeleteTicket(ctx context.Context, ticketID int64) error
152265
}
153266

154-
// GetTickets get ticket list
267+
// GetTicketsEx returns a TicketIterator to iterate over tickets
268+
//
269+
// ref: https://developer.zendesk.com/rest_api/docs/support/tickets#list-tickets
270+
func (z *Client) GetTicketsEx(ctx context.Context, opts *PaginationOptions) *TicketIterator {
271+
return &TicketIterator{
272+
pageSize: opts.PageSize,
273+
hasMore: true,
274+
isCBP: opts.IsCBP,
275+
sort: opts.Sort,
276+
pageAfter: "",
277+
sortOrder: opts.SortOrder,
278+
sortBy: opts.SortBy,
279+
pageIndex: 1,
280+
client: z,
281+
ctx: ctx,
282+
}
283+
}
284+
285+
// GetTickets get ticket list with offset based pagination
155286
//
156287
// ref: https://developer.zendesk.com/rest_api/docs/support/tickets#list-tickets
157288
func (z *Client) GetTickets(ctx context.Context, opts *TicketListOptions) ([]Ticket, Page, error) {
@@ -182,6 +313,34 @@ func (z *Client) GetTickets(ctx context.Context, opts *TicketListOptions) ([]Tic
182313
return data.Tickets, data.Page, nil
183314
}
184315

316+
// GetTicketsCBP get ticket list with cursor based pagination
317+
//
318+
// ref: https://developer.zendesk.com/rest_api/docs/support/tickets#list-tickets
319+
func (z *Client) GetTicketsCBP(ctx context.Context, opts *TicketListCBPOptions) (*TicketListCBPResult, error) {
320+
var data TicketListCBPResult
321+
322+
tmp := opts
323+
if tmp == nil {
324+
tmp = &TicketListCBPOptions{}
325+
}
326+
327+
u, err := addOptions("/tickets.json", tmp)
328+
if err != nil {
329+
return nil, err
330+
}
331+
332+
body, err := z.get(ctx, u)
333+
if err != nil {
334+
return nil, err
335+
}
336+
337+
err = json.Unmarshal(body, &data)
338+
if err != nil {
339+
return nil, err
340+
}
341+
return &data, nil
342+
}
343+
185344
// GetOrganizationTickets get organization ticket list
186345
//
187346
// ref: https://developer.zendesk.com/rest_api/docs/support/tickets#list-tickets

zendesk/ticket_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,76 @@ func TestGetTickets(t *testing.T) {
3434
}
3535
}
3636

37+
func TestGetTicketsCBP(t *testing.T) {
38+
mockAPI := newMockAPI(http.MethodGet, "tickets.json")
39+
client := newTestClient(mockAPI)
40+
defer mockAPI.Close()
41+
42+
tickets, err := client.GetTicketsCBP(ctx, &TicketListCBPOptions{
43+
CursorPagination: CursorPagination{
44+
PageSize: 10,
45+
PageAfter: "",
46+
},
47+
})
48+
if err != nil {
49+
t.Fatalf("Failed to get tickets: %s", err)
50+
}
51+
52+
expectedLength := 2
53+
if len(tickets.Tickets) != expectedLength {
54+
t.Fatalf("Returned tickets does not have the expected length %d. Tickets length is %d", expectedLength, len(tickets.Tickets))
55+
}
56+
}
57+
58+
func TestGetTicketsExCBPDefault(t *testing.T) {
59+
mockAPI := newMockAPI(http.MethodGet, "tickets.json")
60+
client := newTestClient(mockAPI)
61+
defer mockAPI.Close()
62+
63+
ops := NewPaginationOptions()
64+
it := client.GetTicketsEx(ctx, ops)
65+
66+
expectedLength := 2
67+
ticketCount := 0
68+
for it.HasMore() {
69+
tickets, err := it.GetNext()
70+
if err == nil {
71+
for _, ticket := range tickets {
72+
println(ticket.Subject)
73+
ticketCount++
74+
}
75+
}
76+
}
77+
if ticketCount != expectedLength {
78+
t.Fatalf("Returned tickets does not have the expected length %d. Tickets length is %d", expectedLength, ticketCount)
79+
}
80+
}
81+
82+
func TestGetTicketsExOBPOptional(t *testing.T) {
83+
mockAPI := newMockAPI(http.MethodGet, "tickets.json")
84+
client := newTestClient(mockAPI)
85+
defer mockAPI.Close()
86+
87+
ops := NewPaginationOptions()
88+
ops.IsCBP = false
89+
it := client.GetTicketsEx(ctx, ops)
90+
91+
expectedLength := 2
92+
ticketCount := 0
93+
for it.HasMore() {
94+
tickets, err := it.GetNext()
95+
if err == nil {
96+
for _, ticket := range tickets {
97+
println(ticket.Subject)
98+
ticketCount++
99+
}
100+
}
101+
}
102+
if ticketCount != expectedLength {
103+
t.Fatalf("Returned tickets does not have the expected length %d. Tickets length is %d", expectedLength, ticketCount)
104+
}
105+
}
106+
37107
func TestGetOrganizationTickets(t *testing.T) {
38108
mockAPI := newMockAPI(http.MethodGet, "tickets.json")
39109
client := newTestClient(mockAPI)

0 commit comments

Comments
 (0)