Skip to content

Commit

Permalink
feat(graphiql): add optional GraphiQL view handler
Browse files Browse the repository at this point in the history
  • Loading branch information
Maximilien-R committed Feb 21, 2019
1 parent 7ad837d commit 80b56c6
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 1 deletion.
1 change: 1 addition & 0 deletions MANIFEST.in
@@ -0,0 +1 @@
include tartiflette_aiohttp/_graphiql.html
25 changes: 24 additions & 1 deletion tartiflette_aiohttp/__init__.py
@@ -1,10 +1,27 @@
from functools import partial
from typing import List
from typing import List, Optional

from tartiflette import Engine

from tartiflette_aiohttp._graphiql import graphiql_handler
from tartiflette_aiohttp._handler import Handlers


def _set_graphiql_handler(
app, graphiql_endpoint, executor_http_endpoint, executor_http_methods
):
if graphiql_endpoint is not None:
app.router.add_route(
"GET",
graphiql_endpoint,
partial(
graphiql_handler,
executor_http_endpoint=executor_http_endpoint,
executor_http_methods=executor_http_methods,
),
)


def register_graphql_handlers(
app,
engine_sdl: str = None,
Expand All @@ -13,6 +30,7 @@ def register_graphql_handlers(
executor_http_endpoint: str = "/graphql",
executor_http_methods: List[str] = None,
engine: Engine = None,
graphiql_endpoint: Optional[str] = None,
):
"""register a Tartiflette Engine to an app
Expand All @@ -26,6 +44,7 @@ def register_graphql_handlers(
executor_http_endpoint {str} -- Path part of the URL the graphql endpoint will listen on (default: {"/graphql"})
executor_http_methods {list[str]} -- List of HTTP methods allowed on the endpoint (only GET and POST are supported) (default: {None})
engine {Engine} -- An already initialized Engine (default: {None})
graphiql_endpoint {str} -- Add a GraphiQL view to this path if defined (default: {None})
Raises:
Exception -- On bad sdl/engine parameter combinaison.
Expand Down Expand Up @@ -66,6 +85,10 @@ def register_graphql_handlers(
except AttributeError:
raise Exception("Unsupported < %s > http method" % method)

_set_graphiql_handler(
app, graphiql_endpoint, executor_http_endpoint, executor_http_methods
)

return app


Expand Down
157 changes: 157 additions & 0 deletions tartiflette_aiohttp/_graphiql.html
@@ -0,0 +1,157 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
height: 100%;
margin: 0;
width: 100%;
overflow: hidden;
}
#graphiql {
height: 100vh;
}
</style>

<meta charset="utf-8" />
<title>GraphiQL</title>
<meta name="robots" content="noindex" />
<link href="//cdn.jsdelivr.net/npm/graphiql@0.12.0/graphiql.css" rel="stylesheet">
<script src="//cdn.jsdelivr.net/es6-promise/4.0.5/es6-promise.auto.min.js"></script>
<script src="//cdn.jsdelivr.net/fetch/0.9.0/fetch.min.js"></script>
<script src="//cdn.jsdelivr.net/react/15.4.2/react.min.js"></script>
<script src="//cdn.jsdelivr.net/react/15.4.2/react-dom.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/graphiql@0.12.0/graphiql.js"></script>
</head>
<body>
<div id="graphiql">Loading...</div>
<script>
serialize = function(obj, prefix) {
var str = [],
p;
for (p in obj) {
if (obj.hasOwnProperty(p)) {
var k = prefix ? prefix + "[" + p + "]" : p,
v = obj[p];
str.push((v !== null && typeof v === "object") ?
serialize(v, k) :
encodeURIComponent(k) + "=" + encodeURIComponent(v));
}
}
return str.join("&");
}

/**
* This GraphiQL example illustrates how to use some of GraphiQL's props
* in order to enable reading and updating the URL parameters, making
* link sharing of queries a little bit easier.
*
* This is only one example of this kind of feature, GraphiQL exposes
* various React params to enable interesting integrations.
*/
// Parse the search string to get url parameters.
var search = window.location.search;
var parameters = {};
search.substr(1).split('&').forEach(function (entry) {
var eq = entry.indexOf('=');
if (eq >= 0) {
parameters[decodeURIComponent(entry.slice(0, eq))] =
decodeURIComponent(entry.slice(eq + 1));
}
});
// if variables was provided, try to format it.
if (parameters.variables) {
try {
parameters.variables =
JSON.stringify(JSON.parse(parameters.variables), null, 2);
} catch (e) {
// Do nothing, we want to display the invalid JSON as a string, rather
// than present an error.
}
}
// When the query and variables string is edited, update the URL bar so
// that it can be easily shared
function onEditQuery(newQuery) {
parameters.query = newQuery;
updateURL();
}
function onEditVariables(newVariables) {
parameters.variables = newVariables;
updateURL();
}
function onEditOperationName(newOperationName) {
parameters.operationName = newOperationName;
updateURL();
}
function updateURL() {
var newSearch = '?' + Object.keys(parameters).filter(function (key) {
return Boolean(parameters[key]);
}).map(function (key) {
return encodeURIComponent(key) + '=' +
encodeURIComponent(parameters[key]);
}).join('&');
history.replaceState(null, null, newSearch);
}
// Defines a GraphQL fetcher using the fetch API. You're not required to
// use fetch, and could instead implement graphQLFetcher however you like,
// as long as it returns a Promise or Observable.
function graphQLFetcher(graphQLParams) {
// This example expects a GraphQL server at the path /graphql.
// Change this to point wherever you host your GraphQL server.
if ('${http_method}' == 'POST') {
return fetch('${endpoint_url}', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(graphQLParams),
credentials: 'include',
}).then(function (response) {
return response.text();
}).then(function (responseBody) {
try {
return JSON.parse(responseBody);
} catch (error) {
return responseBody;
}
});
}
else {
return fetch('${endpoint_url}?' + serialize(graphQLParams), {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
credentials: 'include',
}).then(function (response) {
return response.text();
}).then(function (responseBody) {
try {
return JSON.parse(responseBody);
} catch (error) {
return responseBody;
}
});
}
}
// Render <GraphiQL /> into the body.
// See the README in the top level of this module to learn more about
// how you can customize GraphiQL by providing different values or
// additional child elements.
ReactDOM.render(
React.createElement(GraphiQL, {
fetcher: graphQLFetcher,
query: parameters.query,
variables: parameters.variables,
operationName: parameters.operationName,
onEditQuery: onEditQuery,
onEditVariables: onEditVariables,
onEditOperationName: onEditOperationName
}),
document.getElementById('graphiql')
);
</script>
</body>
</html>
27 changes: 27 additions & 0 deletions tartiflette_aiohttp/_graphiql.py
@@ -0,0 +1,27 @@
from string import Template

import pkg_resources

from aiohttp import web


_GRAPHIQL_TEMPLATE = pkg_resources.resource_string(
__name__, "_graphiql.html"
).decode("utf-8")


async def graphiql_handler(
request, executor_http_endpoint, executor_http_methods
):
# pylint: disable=unused-argument
return web.Response(
text=_render_graphiql(executor_http_endpoint, executor_http_methods),
headers={"Content-Type": "text/html"},
)


def _render_graphiql(executor_http_endpoint, executor_http_methods):
return Template(_GRAPHIQL_TEMPLATE).substitute(
endpoint_url=executor_http_endpoint,
http_method=("POST" if "POST" in executor_http_methods else "GET"),
)

0 comments on commit 80b56c6

Please sign in to comment.