Skip to content

Commit 14ccbd9

Browse files
authored
improvement: make pagination metadata more robust for offeset pagination (#320)
1 parent 9d62a0e commit 14ccbd9

File tree

3 files changed

+209
-14
lines changed

3 files changed

+209
-14
lines changed

lib/graphql/resolver.ex

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1328,9 +1328,37 @@ defmodule AshGraphql.Graphql.Resolver do
13281328
end
13291329
end
13301330

1331-
defp paginate_with_offset(%Ash.Page.Offset{results: results, count: count, more?: more?}) do
1332-
{:ok, %{results: results, count: count, more?: more?}}
1333-
end
1331+
defp paginate_with_offset(%Ash.Page.Offset{
1332+
results: results,
1333+
count: count,
1334+
more?: more?,
1335+
offset: offset,
1336+
limit: limit
1337+
}) do
1338+
total_pages = get_total_pages(count, limit)
1339+
has_next_page = more?
1340+
has_previous_page = offset > 0
1341+
page_number = get_current_page(%Ash.Page.Offset{limit: limit, offset: offset}, total_pages)
1342+
last_page = total_pages
1343+
1344+
{:ok,
1345+
%{
1346+
results: results,
1347+
count: count,
1348+
more?: more?,
1349+
has_next_page: has_next_page,
1350+
has_previous_page: has_previous_page,
1351+
page_number: page_number,
1352+
last_page: last_page
1353+
}}
1354+
end
1355+
1356+
defp get_total_pages(count, _) when count in [0, nil], do: 1
1357+
defp get_total_pages(_, nil), do: 1
1358+
defp get_total_pages(total_count, limit), do: ceil(total_count / limit)
1359+
1360+
defp get_current_page(%Ash.Page.Offset{limit: limit, offset: offset}, total),
1361+
do: min(ceil(offset / limit) + 1, total)
13341362

13351363
defp paginate(_resource, _gql_query, _action, %Ash.Page.Keyset{} = keyset, relay?) do
13361364
paginate_with_keyset(keyset, relay?)

lib/resource/resource.ex

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3847,6 +3847,46 @@ defmodule AshGraphql.Resource do
38473847
]
38483848
end
38493849

3850+
defp add_pagination_metadata(fields, schema, true) do
3851+
[
3852+
%Absinthe.Blueprint.Schema.FieldDefinition{
3853+
description: "Whether or not there is a next page",
3854+
identifier: :has_next_page,
3855+
module: schema,
3856+
name: "has_next_page",
3857+
__reference__: ref(__ENV__),
3858+
type: %Absinthe.Blueprint.TypeReference.NonNull{of_type: :boolean}
3859+
},
3860+
%Absinthe.Blueprint.Schema.FieldDefinition{
3861+
description: "Whether or not there is a previous page",
3862+
identifier: :has_previous_page,
3863+
module: schema,
3864+
name: "has_previous_page",
3865+
__reference__: ref(__ENV__),
3866+
type: %Absinthe.Blueprint.TypeReference.NonNull{of_type: :boolean}
3867+
},
3868+
%Absinthe.Blueprint.Schema.FieldDefinition{
3869+
description: "The number of the current page",
3870+
identifier: :page_number,
3871+
module: schema,
3872+
name: "page_number",
3873+
__reference__: ref(__ENV__),
3874+
type: %Absinthe.Blueprint.TypeReference.NonNull{of_type: :integer}
3875+
},
3876+
%Absinthe.Blueprint.Schema.FieldDefinition{
3877+
description: "The number of the last page",
3878+
identifier: :last_page,
3879+
module: schema,
3880+
name: "last_page",
3881+
__reference__: ref(__ENV__),
3882+
type: %Absinthe.Blueprint.TypeReference.NonNull{of_type: :integer}
3883+
}
3884+
| fields
3885+
]
3886+
end
3887+
3888+
defp add_pagination_metadata(fields, _, _), do: fields
3889+
38503890
defp add_count_to_page(fields, schema, true) do
38513891
[
38523892
%Absinthe.Blueprint.Schema.FieldDefinition{
@@ -3880,19 +3920,10 @@ defmodule AshGraphql.Resource do
38803920
of_type: type
38813921
}
38823922
}
3883-
},
3884-
%Absinthe.Blueprint.Schema.FieldDefinition{
3885-
description: "Whether or not there is a next page",
3886-
identifier: :more?,
3887-
module: schema,
3888-
name: "has_next_page",
3889-
__reference__: ref(__ENV__),
3890-
type: %Absinthe.Blueprint.TypeReference.NonNull{
3891-
of_type: :boolean
3892-
}
38933923
}
38943924
]
3895-
|> add_count_to_page(schema, countable?),
3925+
|> add_count_to_page(schema, countable?)
3926+
|> add_pagination_metadata(schema, countable?),
38963927
identifier: String.to_atom("page_of_#{type}"),
38973928
module: schema,
38983929
name: Macro.camelize("page_of_#{type}"),

test/paginate_test.exs

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,142 @@ defmodule AshGraphql.PaginateTest do
128128
:ok
129129
end
130130

131+
test "retruns pagination metadata" do
132+
doc = """
133+
query PaginatedPosts {
134+
paginatedPosts(limit: 1, sort: [{field: TEXT}]) {
135+
count
136+
hasNextPage
137+
hasPreviousPage
138+
pageNumber
139+
lastPage
140+
results{
141+
text
142+
}
143+
}
144+
}
145+
"""
146+
147+
assert {:ok,
148+
%{
149+
data: %{
150+
"paginatedPosts" => %{
151+
"count" => 5,
152+
"hasNextPage" => true,
153+
"hasPreviousPage" => false,
154+
"pageNumber" => 1,
155+
"lastPage" => 5,
156+
"results" => [
157+
%{"text" => "a"}
158+
]
159+
}
160+
}
161+
}} = Absinthe.run(doc, AshGraphql.Test.Schema)
162+
end
163+
164+
test "pagination metadata is correct when offset is 2" do
165+
doc = """
166+
query PaginatedPosts {
167+
paginatedPosts(limit: 1, offset: 2, sort: [{field: TEXT}]) {
168+
count
169+
hasNextPage
170+
hasPreviousPage
171+
pageNumber
172+
lastPage
173+
results{
174+
text
175+
}
176+
}
177+
}
178+
"""
179+
180+
assert {:ok,
181+
%{
182+
data: %{
183+
"paginatedPosts" => %{
184+
"count" => 5,
185+
"hasNextPage" => true,
186+
"hasPreviousPage" => true,
187+
"pageNumber" => 3,
188+
"lastPage" => 5,
189+
"results" => [
190+
%{"text" => "c"}
191+
]
192+
}
193+
}
194+
}} = Absinthe.run(doc, AshGraphql.Test.Schema)
195+
end
196+
197+
test "pagination metadata is correct when offset is 4" do
198+
doc = """
199+
query PaginatedPosts {
200+
paginatedPosts(limit: 1, offset: 4, sort: [{field: TEXT}]) {
201+
count
202+
hasNextPage
203+
hasPreviousPage
204+
pageNumber
205+
lastPage
206+
results{
207+
text
208+
}
209+
}
210+
}
211+
"""
212+
213+
assert {:ok,
214+
%{
215+
data: %{
216+
"paginatedPosts" => %{
217+
"count" => 5,
218+
"hasNextPage" => false,
219+
"hasPreviousPage" => true,
220+
"pageNumber" => 5,
221+
"lastPage" => 5,
222+
"results" => [
223+
%{"text" => "e"}
224+
]
225+
}
226+
}
227+
}} = Absinthe.run(doc, AshGraphql.Test.Schema)
228+
end
229+
230+
test "pagination metadata is correct when the is no limit" do
231+
doc = """
232+
query PaginatedPosts {
233+
paginatedPosts(sort: [{field: TEXT}]) {
234+
count
235+
hasNextPage
236+
hasPreviousPage
237+
pageNumber
238+
lastPage
239+
results{
240+
text
241+
}
242+
}
243+
}
244+
"""
245+
246+
assert {:ok,
247+
%{
248+
data: %{
249+
"paginatedPosts" => %{
250+
"count" => 5,
251+
"hasNextPage" => false,
252+
"hasPreviousPage" => false,
253+
"pageNumber" => 1,
254+
"lastPage" => 1,
255+
"results" => [
256+
%{"text" => "a"},
257+
%{"text" => "b"},
258+
%{"text" => "c"},
259+
%{"text" => "d"},
260+
%{"text" => "e"}
261+
]
262+
}
263+
}
264+
}} = Absinthe.run(doc, AshGraphql.Test.Schema)
265+
end
266+
131267
test "default_limit records are fetched" do
132268
doc = """
133269
query PaginatedPosts {

0 commit comments

Comments
 (0)