diff --git a/README.md b/README.md index 6722de78..f8eddb03 100644 --- a/README.md +++ b/README.md @@ -28,12 +28,16 @@ and join the team! - [Enable GraphQL Servlet](#enable-graphql-servlet) - [Enable Graph*i*QL](#enable-graphiql) - [Enable Altair](#enable-altair) -- [Enable GraphQL Playground](#enable-graphql-playground) +- [Enable GraphQL Playground](#enable-graphql-voyager) - [Basic settings](#basic-settings) - [CDN](#cdn) - [Custom static resources](#custom-static-resources) - [Customizing GraphQL Playground](#customizing-graphql-playground) - [Tabs](#tabs) +- [Enable GraphQL Voyager](#enable-graphql-playground) + - [Basic settings](#graphql-voyager-basic-settings) + - [CDN](#graphql-voyager-cdn) + - [Customizing GraphQL Voyager](#customizing-graphql-voyager) - [Supported GraphQL-Java Libraries](#supported-graphql-java-libraries) - [GraphQL Java Tools](#graphql-java-tools) - [GraphQL Annotations](#graphql-annotations) @@ -433,6 +437,62 @@ You can configure the query, variables, headers and even supply sample responses , `variables` and `responses` are expected to be resources of the appropriate format (GraphQL for `query`, JSON for `variables` and `responses`). + +# Enable GraphQL Voyager + +**GraphQL Voyager** becomes accessible at root `/voyager` (or as configured +in `voyager.mapping`) +if `voyager-spring-boot-starter` is added as a dependency to a boot application. + +Available Spring Boot configuration parameters (either `application.yml` +or `application.properties`): + +```yaml +voyager: + enabled: true + basePath: / + mapping: /voyager + endpoint: /graphql + cdn: + enabled: false + version: latest + pageTitle: Voyager + displayOptions: + skipRelay: true + skipDeprecated: true + rootType: Query + sortByAlphabet: false + showLeafFields: true + hideRoot: false + hideDocs: false + hideSettings: false +``` + +## GraphQL Voyager Basic settings + +`mapping` and `endpoint` will default to `/voyager` and `/graphql`, respectively. Note that these values may not be empty. + +`enabled` defaults to `true`, and therefor **GraphQL Voyager** will be available by default if the dependency +is added to a Spring Boot Web Application project. + +`pageTitle` defaults to `Voyager`. + +All other properties default to the same as documented on the official [GraphQL Voyager readme](https://github.com/APIs-guru/graphql-voyager#properties) + +## GraphQL Voyager CDN + +The currently bundled version is `1.0.0-rc31`, which is - as of writing this - the latest release +of **GraphQL Voyager**. The CDN option uses `jsDelivr` CDN, if enabled. By default, it will +load the latest available release. Available CDN versions can be found on the project's +[jsDelivr page](https://www.jsdelivr.com/package/npm/graphql-voyager). The CDN option is +disabled by default. + +## Customizing GraphQL Voyager + +Further **GraphQL Voyager** `displayOptions`, `hideDocs` and `hideSettings` customizations can be configured, as documented in the official +[GraphQL Voyager readme](https://github.com/APIs-guru/graphql-voyager#properties). + + # Supported GraphQL-Java Libraries The following libraries have auto-configuration classes for creating a `GraphQLSchema`. diff --git a/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/ReactiveVoyagerAutoConfiguration.java b/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/ReactiveVoyagerAutoConfiguration.java index d93c1d43..7455db0c 100644 --- a/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/ReactiveVoyagerAutoConfiguration.java +++ b/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/ReactiveVoyagerAutoConfiguration.java @@ -1,8 +1,8 @@ package graphql.kickstart.voyager.boot; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; @@ -16,11 +16,9 @@ @Configuration @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) @ConditionalOnProperty(value = "voyager.enabled", havingValue = "true", matchIfMissing = true) +@EnableConfigurationProperties(VoyagerPropertiesConfiguration.class) public class ReactiveVoyagerAutoConfiguration { - @Value("${voyager.mapping:/voyager}") - private String voyagerPath; - @Bean ReactiveVoyagerController voyagerController() { return new ReactiveVoyagerController(); @@ -35,7 +33,7 @@ public RouterFunction voyagerStaticFilesRouter() { } @Bean - VoyagerIndexHtmlTemplate voyagerIndexHtmlTemplate() { - return new VoyagerIndexHtmlTemplate(); + VoyagerIndexHtmlTemplate voyagerIndexHtmlTemplate(final VoyagerPropertiesConfiguration voyagerPropertiesConfiguration) { + return new VoyagerIndexHtmlTemplate(voyagerPropertiesConfiguration); } } diff --git a/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/ReactiveVoyagerController.java b/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/ReactiveVoyagerController.java index c0fdc820..1733e883 100644 --- a/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/ReactiveVoyagerController.java +++ b/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/ReactiveVoyagerController.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.util.Map; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -13,6 +14,7 @@ * @author Max David Günther */ @Controller +@RequiredArgsConstructor public class ReactiveVoyagerController { @Autowired diff --git a/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/VoyagerAutoConfiguration.java b/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/VoyagerAutoConfiguration.java index d0762a96..aeff573e 100644 --- a/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/VoyagerAutoConfiguration.java +++ b/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/VoyagerAutoConfiguration.java @@ -2,6 +2,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -11,6 +12,7 @@ @Configuration @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) @ConditionalOnProperty(value = "voyager.enabled", havingValue = "true", matchIfMissing = true) +@EnableConfigurationProperties(VoyagerPropertiesConfiguration.class) public class VoyagerAutoConfiguration { @Bean @@ -19,7 +21,7 @@ VoyagerController voyagerController() { } @Bean - VoyagerIndexHtmlTemplate voyagerIndexHtmlTemplate() { - return new VoyagerIndexHtmlTemplate(); + VoyagerIndexHtmlTemplate voyagerIndexHtmlTemplate(final VoyagerPropertiesConfiguration voyagerPropertiesConfiguration) { + return new VoyagerIndexHtmlTemplate(voyagerPropertiesConfiguration); } } diff --git a/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/VoyagerIndexHtmlTemplate.java b/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/VoyagerIndexHtmlTemplate.java index ead7ef71..cc35bd26 100644 --- a/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/VoyagerIndexHtmlTemplate.java +++ b/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/VoyagerIndexHtmlTemplate.java @@ -4,9 +4,9 @@ import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; +import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.apache.commons.text.StringSubstitutor; -import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ClassPathResource; import org.springframework.util.StreamUtils; import org.springframework.web.bind.annotation.RequestParam; @@ -14,6 +14,7 @@ /** * @author Guilherme Blanco */ +@RequiredArgsConstructor public class VoyagerIndexHtmlTemplate { private static final String CDNJS_CLOUDFLARE_COM_AJAX_LIBS = "//cdnjs.cloudflare.com/ajax/libs/"; @@ -21,53 +22,55 @@ public class VoyagerIndexHtmlTemplate { private static final String VOYAGER = "graphql-voyager"; private static final String FAVICON_APIS_GURU = "//apis.guru/graphql-voyager/icons/favicon-16x16.png"; - @Value("${voyager.endpoint:/graphql}") - private String graphqlEndpoint; - - @Value("${voyager.pageTitle:Voyager}") - private String pageTitle; - - @Value("${voyager.static.basePath:/}") - private String staticBasePath; - - @Value("${voyager.cdn.enabled:false}") - private boolean voyagerCdnEnabled; - - @Value("${voyager.cdn.version:1.0.0-rc.31}") - private String voyagerCdnVersion; + private final VoyagerPropertiesConfiguration voyagerConfiguration; public String fillIndexTemplate(String contextPath, Map params) throws IOException { String template = StreamUtils .copyToString(new ClassPathResource("voyager.html").getInputStream(), Charset.defaultCharset()); + + String basePath = voyagerConfiguration.getBasePath(); + String voyagerCdnVersion = voyagerConfiguration.getCdn().getVersion(); + Map replacements = new HashMap<>(); replacements.put("graphqlEndpoint", constructGraphQlEndpoint(contextPath, params)); - replacements.put("pageTitle", pageTitle); + replacements.put("pageTitle", voyagerConfiguration.getPageTitle()); replacements - .put("pageFavicon", getResourceUrl(staticBasePath, "favicon.ico", FAVICON_APIS_GURU)); - replacements.put("es6PromiseJsUrl", getResourceUrl(staticBasePath, "es6-promise.auto.min.js", + .put("pageFavicon", getResourceUrl(basePath, "favicon.ico", FAVICON_APIS_GURU)); + replacements.put("es6PromiseJsUrl", getResourceUrl(basePath, "es6-promise.auto.min.js", joinCdnjsPath("es6-promise", "4.1.1", "es6-promise.auto.min.js"))); - replacements.put("fetchJsUrl", getResourceUrl(staticBasePath, "fetch.min.js", + replacements.put("fetchJsUrl", getResourceUrl(basePath, "fetch.min.js", joinCdnjsPath("fetch", "2.0.4", "fetch.min.js"))); - replacements.put("reactJsUrl", getResourceUrl(staticBasePath, "react.min.js", + replacements.put("reactJsUrl", getResourceUrl(basePath, "react.min.js", joinCdnjsPath("react", "16.8.3", "umd/react.production.min.js"))); - replacements.put("reactDomJsUrl", getResourceUrl(staticBasePath, "react-dom.min.js", + replacements.put("reactDomJsUrl", getResourceUrl(basePath, "react-dom.min.js", joinCdnjsPath("react-dom", "16.8.3", "umd/react-dom.production.min.js"))); - replacements.put("voyagerCssUrl", getResourceUrl(staticBasePath, "voyager.css", + replacements.put("voyagerCssUrl", getResourceUrl(basePath, "voyager.css", joinJsDelivrPath(voyagerCdnVersion, "dist/voyager.css"))); - replacements.put("voyagerJsUrl", getResourceUrl(staticBasePath, "voyager.min.js", + replacements.put("voyagerJsUrl", getResourceUrl(basePath, "voyager.min.js", joinJsDelivrPath(voyagerCdnVersion, "dist/voyager.min.js"))); - replacements.put("voyagerWorkerJsUrl", getResourceUrl(staticBasePath, "voyager.worker.js", + replacements.put("voyagerWorkerJsUrl", getResourceUrl(basePath, "voyager.worker.js", joinJsDelivrPath(voyagerCdnVersion, "dist/voyager.worker.min.js"))); replacements.put("contextPath", contextPath); + replacements.put("voyagerDisplayOptionsSkipRelay", Boolean.toString(voyagerConfiguration.getDisplayOptions().isSkipRelay())); + replacements.put("voyagerDisplayOptionsSkipDeprecated", Boolean.toString(voyagerConfiguration.getDisplayOptions().isSkipDeprecated())); + replacements.put("voyagerDisplayOptionsRootType", voyagerConfiguration.getDisplayOptions().getRootType()); + replacements.put("voyagerDisplayOptionsSortByAlphabet", Boolean.toString( + voyagerConfiguration.getDisplayOptions().isSortByAlphabet())); + replacements.put("voyagerDisplayOptionsShowLeafFields", Boolean.toString(voyagerConfiguration.getDisplayOptions().isShowLeafFields())); + replacements.put("voyagerDisplayOptionsHideRoot", Boolean.toString(voyagerConfiguration.getDisplayOptions().isHideRoot())); + replacements.put("voyagerHideDocs", Boolean.toString(voyagerConfiguration.isHideDocs())); + replacements.put("voyagerHideSettings", Boolean.toString(voyagerConfiguration.isHideSettings())); + + return StringSubstitutor.replace(template, replacements); } private String constructGraphQlEndpoint(String contextPath, @RequestParam Map params) { - String endpoint = graphqlEndpoint; + String endpoint = voyagerConfiguration.getEndpoint(); for (Map.Entry param : params.entrySet()) { endpoint = endpoint.replaceAll("\\{" + param.getKey() + "}", param.getValue()); } @@ -78,7 +81,7 @@ private String constructGraphQlEndpoint(String contextPath, } private String getResourceUrl(String staticBasePath, String staticFileName, String cdnUrl) { - if (voyagerCdnEnabled && StringUtils.isNotBlank(cdnUrl)) { + if (voyagerConfiguration.getCdn().isEnabled() && StringUtils.isNotBlank(cdnUrl)) { return cdnUrl; } return joinStaticPath(staticBasePath, staticFileName); diff --git a/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/VoyagerPropertiesConfiguration.java b/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/VoyagerPropertiesConfiguration.java new file mode 100644 index 00000000..f286e983 --- /dev/null +++ b/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/VoyagerPropertiesConfiguration.java @@ -0,0 +1,37 @@ +package graphql.kickstart.voyager.boot; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import graphql.kickstart.voyager.boot.properties.VoyagerCdn; +import graphql.kickstart.voyager.boot.properties.VoyagerDisplayOptions; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.validation.annotation.Validated; + + +@Data +@ConfigurationProperties(prefix = "voyager") +@Validated +public class VoyagerPropertiesConfiguration { + + private String endpoint = "/graphql"; + + private String pageTitle = "Voyager"; + + private String basePath = "/"; + + @NestedConfigurationProperty + @JsonIgnore + private VoyagerCdn cdn = new VoyagerCdn(); + + @NestedConfigurationProperty + @JsonIgnore + private VoyagerDisplayOptions displayOptions = new VoyagerDisplayOptions(); + + @JsonIgnore + private boolean hideDocs; + + @JsonIgnore + private boolean hideSettings; + +} \ No newline at end of file diff --git a/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/properties/VoyagerCdn.java b/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/properties/VoyagerCdn.java new file mode 100644 index 00000000..853a296a --- /dev/null +++ b/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/properties/VoyagerCdn.java @@ -0,0 +1,10 @@ +package graphql.kickstart.voyager.boot.properties; + +import lombok.Data; + +@Data +public class VoyagerCdn { + + private boolean enabled; + private String version = "latest"; +} diff --git a/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/properties/VoyagerDisplayOptions.java b/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/properties/VoyagerDisplayOptions.java new file mode 100644 index 00000000..078cbe77 --- /dev/null +++ b/voyager-spring-boot-autoconfigure/src/main/java/graphql/kickstart/voyager/boot/properties/VoyagerDisplayOptions.java @@ -0,0 +1,17 @@ +package graphql.kickstart.voyager.boot.properties; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class VoyagerDisplayOptions { + + private boolean skipRelay = true; + private boolean skipDeprecated = true; + private String rootType = "Query"; + private boolean sortByAlphabet = false; + private boolean showLeafFields = true; + private boolean hideRoot = false; + +} diff --git a/voyager-spring-boot-autoconfigure/src/main/resources/voyager.html b/voyager-spring-boot-autoconfigure/src/main/resources/voyager.html index 9bdc54dd..bfbb57ae 100644 --- a/voyager-spring-boot-autoconfigure/src/main/resources/voyager.html +++ b/voyager-spring-boot-autoconfigure/src/main/resources/voyager.html @@ -73,8 +73,15 @@

Transmitting...

GraphQLVoyager.init(document.getElementById('voyager'), { introspection: introspectionProvider, displayOptions: { - sortByAlphabet: true, - } + skipRelay: ${voyagerDisplayOptionsSkipRelay}, + skipDeprecated: ${voyagerDisplayOptionsSkipDeprecated}, + rootType: '${voyagerDisplayOptionsRootType}', + sortByAlphabet: ${voyagerDisplayOptionsSortByAlphabet}, + showLeafFields: ${voyagerDisplayOptionsShowLeafFields}, + hideRoot: ${voyagerDisplayOptionsHideRoot}, + }, + hideDocs: ${voyagerHideDocs}, + hideSettings: ${voyagerHideSettings}, })