You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by bd...@apache.org on 2021/05/10 16:28:12 UTC
[sling-org-apache-sling-graphql-core] 01/01: Experiment with
pagination driven by relay interfaces
This is an automated email from the ASF dual-hosted git repository.
bdelacretaz pushed a commit to branch SLING-10309/experiment
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-graphql-core.git
commit 189fc7f8ed55b1c29b6ae282ae27f1ff44856b5d
Author: Bertrand Delacretaz <bd...@apache.org>
AuthorDate: Mon May 10 18:27:37 2021 +0200
Experiment with pagination driven by relay interfaces
---
.../sling/graphql/core/engine/PaginationTest.java | 225 +++++++++++++++++++++
src/test/resources/test-schema.txt | 19 ++
2 files changed, 244 insertions(+)
diff --git a/src/test/java/org/apache/sling/graphql/core/engine/PaginationTest.java b/src/test/java/org/apache/sling/graphql/core/engine/PaginationTest.java
new file mode 100644
index 0000000..995beba
--- /dev/null
+++ b/src/test/java/org/apache/sling/graphql/core/engine/PaginationTest.java
@@ -0,0 +1,225 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.graphql.core.engine;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
+
+import static org.junit.Assert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+
+import org.apache.sling.graphql.core.mocks.TestUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.apache.sling.graphql.core.mocks.CharacterTypeResolver;
+import org.apache.sling.graphql.core.mocks.HumanDTO;
+import org.apache.sling.graphql.api.SlingDataFetcher;
+import org.apache.sling.graphql.api.SlingDataFetcherEnvironment;
+import org.junit.Test;
+
+import graphql.relay.Connection;
+import graphql.relay.ConnectionCursor;
+import graphql.relay.Edge;
+import graphql.relay.PageInfo;
+
+public class PaginationTest extends ResourceQueryTestBase {
+
+ static class ConnectionDataFetcher implements SlingDataFetcher<Connection<HumanDTO>> {
+ private List<HumanDTO> humans = new ArrayList<>();
+
+ ConnectionDataFetcher(List<HumanDTO> humans) {
+ this.humans = humans;
+ }
+
+ @Override
+ public @Nullable Connection<HumanDTO> get(@NotNull SlingDataFetcherEnvironment e) throws Exception {
+ final String cursor = e.getArgument("after", null);
+ final int limit = e.getArgument("limit", 2);
+ return new DataConnection(humans, cursor, limit);
+ }
+ }
+
+ static class Cursor implements ConnectionCursor {
+ private final String value;
+ Cursor(HumanDTO dto) {
+ value = dto.getId();
+ }
+ Cursor(String c) {
+ value = c;
+ }
+ @Override
+ public String getValue() {
+ return value == null ? "" : value;
+ }
+ @Override
+ public String toString() {
+ return getValue();
+ }
+ public boolean isEmpty() {
+ return value == null || value.length() == 0;
+ }
+ }
+
+ static class DataConnection implements Connection<HumanDTO> {
+
+ private final Cursor startCursor;
+ private Cursor endCursor;
+ private boolean hasPreviousPage;
+ private boolean hasNextPage;
+ private final int limit;
+ private final List<Edge<HumanDTO>> edges = new ArrayList();
+ private final PageInfo pageInfo;
+
+ DataConnection(List<HumanDTO> humans, String cursor, int limit) {
+ this.startCursor = new Cursor(cursor);
+ this.limit = limit;
+
+ // skip to cursor and add the following humanDTOs as edges
+ boolean inRange = false;
+ int remaining = limit;
+ for(HumanDTO dto : humans) {
+ if(remaining <= 0) {
+ hasNextPage = true;
+ break;
+ } else if(inRange) {
+ if(remaining -- <= 0) {
+ inRange = false;
+ }
+ edges.add(new Edge<HumanDTO>() {
+ @Override
+ public HumanDTO getNode() {
+ return dto;
+ }
+
+ @Override
+ public ConnectionCursor getCursor() {
+ return new Cursor(dto);
+ }
+
+ });
+ endCursor = new Cursor(dto);
+ } else if(startCursor.isEmpty()) {
+ inRange = true;
+ hasPreviousPage = false;
+ } else if(startCursor.toString().equals(dto.getId())) {
+ inRange = true;
+ hasPreviousPage = true;
+ }
+ }
+
+ // setup page info
+ pageInfo = new PageInfo() {
+ @Override
+ public ConnectionCursor getStartCursor() {
+ return startCursor;
+ }
+
+ @Override
+ public ConnectionCursor getEndCursor() {
+ return endCursor;
+ }
+
+ @Override
+ public boolean isHasPreviousPage() {
+ return hasPreviousPage;
+ }
+
+ @Override
+ public boolean isHasNextPage() {
+ return hasNextPage;
+ }
+
+ };
+ }
+
+ @Override
+ public List<Edge<HumanDTO>> getEdges() {
+ return edges;
+ }
+
+ @Override
+ public PageInfo getPageInfo() {
+ return pageInfo;
+ }
+ }
+
+ @Override
+ protected void setupAdditionalServices() {
+ final List<HumanDTO> humans = new ArrayList<>();
+
+ for(int i=0 ; i < 100 ; i++) {
+ humans.add(new HumanDTO("human-" + i, "Luke-" + i, "Tatooine"));
+ }
+ TestUtil.registerSlingTypeResolver(context.bundleContext(), "character/resolver", new CharacterTypeResolver());
+ TestUtil.registerSlingDataFetcher(context.bundleContext(), "humans/paginated", new ConnectionDataFetcher(humans));
+ }
+
+ private void assertPageInfo(String json, String startCursor, String endCursor, Boolean hasPreviousPage, Boolean hasNextPage) {
+ assertThat(json, hasJsonPath("$.data.paginatedQuery.pageInfo.startCursor", equalTo(startCursor)));
+ assertThat(json, hasJsonPath("$.data.paginatedQuery.pageInfo.endCursor", equalTo(endCursor)));
+ assertThat(json, hasJsonPath("$.data.paginatedQuery.pageInfo.hasPreviousPage", equalTo(hasPreviousPage)));
+ assertThat(json, hasJsonPath("$.data.paginatedQuery.pageInfo.hasNextPage", equalTo(hasNextPage)));
+ }
+
+ private void assertEdges(String json, int startIndex, int endIndex) {
+ int dataIndex = 0;
+ for(int i=startIndex; i <= endIndex; i++) {
+ final String id = "human-" + i;
+ final String name = "Luke-" + i;
+ assertThat(json, hasJsonPath("$.data.paginatedQuery.edges[" + dataIndex + "].cursor", equalTo(id)));
+ assertThat(json, hasJsonPath("$.data.paginatedQuery.edges[" + dataIndex + "].node.id", equalTo(id)));
+ assertThat(json, hasJsonPath("$.data.paginatedQuery.edges[" + dataIndex + "].node.name", equalTo(name)));
+ dataIndex++;
+ }
+ final int count = endIndex - startIndex + 1;
+ assertThat(json, hasJsonPath("$.data.paginatedQuery.edges.length()", equalTo(count)));
+ }
+
+ @Test
+ public void noArguments() throws Exception {
+ final String json = queryJSON("{ paginatedQuery {"
+ + " pageInfo { startCursor endCursor hasPreviousPage hasNextPage }"
+ + " edges { cursor node { id name }}"
+ +"}}");
+ assertPageInfo(json, "", "human-2", false, true );
+ assertEdges(json, 1, 2);
+ }
+
+ @Test
+ public void startCursorAndLimit() throws Exception {
+ final String json = queryJSON("{ paginatedQuery(after:\"human-5\", limit:6) {"
+ + " pageInfo { startCursor endCursor hasPreviousPage hasNextPage }"
+ + " edges { cursor node { id name }}"
+ +"}}");
+ assertPageInfo(json, "human-5", "human-11", true, true);
+ assertEdges(json, 6, 11);
+ }
+
+ @Test
+ public void startCursorNearEnd() throws Exception {
+ final String json = queryJSON("{ paginatedQuery(after:\"human-94\", limit:60) {"
+ + " pageInfo { startCursor endCursor hasPreviousPage hasNextPage }"
+ + " edges { cursor node { id name }}"
+ +"}}");
+ assertPageInfo(json, "human-94", "human-99", true, false);
+ assertEdges(json, 95, 99);
+ }
+}
diff --git a/src/test/resources/test-schema.txt b/src/test/resources/test-schema.txt
index e7fb1b0..f9fb5d7 100644
--- a/src/test/resources/test-schema.txt
+++ b/src/test/resources/test-schema.txt
@@ -44,6 +44,9 @@ type Query {
# Test interface query
interfaceQuery: CharactersAsInterface @fetcher(name:"character/fetcher")
+
+ # Test pagination
+ paginatedQuery (after : String, limit : Int) : PaginatedHumans @fetcher(name:"humans/paginated")
}
interface CharacterInterface @resolver(name:"character/resolver" source:"CharacterInterface") {
@@ -109,3 +112,19 @@ type Droid implements CharacterInterface {
primaryFunction: String
}
+type PageInfo {
+ startCursor : String
+ endCursor : String
+ hasPreviousPage : Boolean
+ hasNextPage : Boolean
+}
+
+type HumanEdge {
+ cursor: String
+ node: Human
+}
+
+type PaginatedHumans {
+ edges : [HumanEdge]
+ pageInfo : PageInfo
+}
\ No newline at end of file