Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gatsby): use graphiql-explorer #14280

Merged
merged 25 commits into from May 27, 2019
Merged
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3c8299d
feat(gatsby): use graphiql-explorer
pieh May 23, 2019
08f6417
chore(docs): add some basic info to the README
DSchau May 23, 2019
ca9f47c
chore: format
pieh May 23, 2019
311485b
Update README.md
DSchau May 23, 2019
f2c10ac
Update packages/gatsby-graphiql-explorer/package.json
pieh May 23, 2019
3c02259
Update packages/gatsby-graphiql-explorer/src/app/webpack.config.js
pieh May 23, 2019
a8fc37f
show welcom to graphiql message with example tailored for gatsby users
pieh May 23, 2019
0cae8b2
store explorer pane state
pieh May 23, 2019
0e3cd30
actually commit the-thing
pieh May 23, 2019
ee4c119
add graphiql e2e tests
pieh May 24, 2019
866c30f
ooops :)
pieh May 24, 2019
7688af9
seems like sometimes there is timing issue when we execute query to f…
pieh May 24, 2019
b5c209b
tmp: record to personal cypress dashboard to investigate
pieh May 24, 2019
fa07a83
doh
pieh May 24, 2019
93f4f4d
use queryString instead of typing
pieh May 24, 2019
d57901f
doh again and again
pieh May 24, 2019
504eb84
revert tmp cypress dashboard integration
pieh May 24, 2019
cd709e8
Merge remote-tracking branch 'origin/master' into graphiql-explorer
pieh May 24, 2019
0069988
revert changes to package.json, fix double spaces
pieh May 24, 2019
44cbf89
Update packages/gatsby-graphiql-explorer/README.md
DSchau May 24, 2019
d6502e7
Update e2e-tests/development-runtime/cypress/integration/functionalit…
pieh May 24, 2019
67acb19
chore: fix issue
DSchau May 24, 2019
0556731
Merge branch 'graphiql-explorer' of github.com:gatsbyjs/gatsby into g…
DSchau May 24, 2019
ea49673
underscoregate
pieh May 24, 2019
b5229d1
you won the battle, not the war, mr underscore
pieh May 24, 2019
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

@@ -0,0 +1,3 @@
/*.js
/*.html
yarn.lock
@@ -0,0 +1,34 @@
# Logs
logs
*.log

# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
*.un~
yarn.lock
src
flow-typed
coverage
decls
examples
@@ -0,0 +1,3 @@
# gatsby-graphiql-explorer

This comment has been minimized.

Copy link
@DSchau

DSchau May 23, 2019

Contributor

Simple and concise 🤷‍♂ I think this will suffice!


Stub README
@@ -0,0 +1,53 @@
{
"name": "gatsby-graphiql-explorer",

This comment has been minimized.

Copy link
@pieh

pieh May 23, 2019

Author Contributor

this probably need better package name - this is kind of express middleware, but not exactly, because it's not used as express.use(someMiddleware()), instead it is used as graphiqlExplorer(expressApp, params) as I need to have 2 route handlers (one for html and one for bundle)

This comment has been minimized.

Copy link
@DSchau

DSchau May 23, 2019

Contributor

It's easy to bike shed here--I don't really have a problem with the name. We could also scope it, e.g. @gatsbyjs/graphiql which is really what we've done here. We tweaked graphiql with some extra functionality.

"version": "0.0.1",
"description": "Stub description for gatsby-graphiql-explorer",
This conversation was marked as resolved by pieh

This comment has been minimized.

Copy link
@DSchau

DSchau May 23, 2019

Contributor
Suggested change
"description": "Stub description for gatsby-graphiql-explorer",
"description": "GraphiQL IDE with custom features for Gatsby users",
"main": "index.js",
"scripts": {
"build:app": "webpack --config ./src/app/webpack.config.js",
"build:babel": "babel src/index.js --out-dir . --ignore **/__tests__",
"build": "npm-run-all --parallel build:app build:babel",
"prepare": "cross-env NODE_ENV=production npm run build",
"test": "echo \"Error: no test specified\" && exit 1",
"watch:app": "npm run build:app -- --watch",
"watch:babel": "npm run build:babel -- --watch",
"watch": "npm-run-all --parallel watch:app watch:babel"
},
"keywords": [
"gatsby"
],
"author": "",
"bugs": {
"url": "https://github.com/gatsbyjs/gatsby/issues"
},
"homepage": "https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-graphiql-explorer#readme",
"repository": {
"type": "git",
"url": "https://github.com/gatsbyjs/gatsby.git"
},
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.0.0",
"graphiql": "^0.13.0",
"graphiql-explorer": "^0.3.7",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"whatwg-fetch": "^3.0.0"
},
"devDependencies": {
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/plugin-proposal-class-properties": "^7.0.0",
"@babel/plugin-transform-runtime": "^7.0.0",
"@babel/preset-env": "^7.4.1",
"@babel/preset-react": "^7.0.0",
"babel-loader": "^8.0.0",
"babel-preset-gatsby-package": "^0.1.4",
"cross-env": "^5.0.5",
"css-loader": "^1.0.0",
"html-webpack-plugin": "^3.2.0",
"npm-run-all": "4.1.5",
"style-loader": "^0.21.0",
"webpack-cli": "^3.3.2"
}
}
@@ -0,0 +1,19 @@
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}

.graphiql-container {
height: 100vh;
width: 100vw;
}
@@ -0,0 +1,159 @@
import React from "react"
import ReactDOM from "react-dom"

import GraphiQL from "graphiql"
import GraphiQLExplorer from "graphiql-explorer"
import { getIntrospectionQuery, buildClientSchema } from "graphql"

import "whatwg-fetch"

import "graphiql/graphiql.css"
import "./app.css"

const parameters = {}
window.location.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)
)
}
})
// Produce a Location query string from a parameter object.
function locationQuery(params) {
return (
`?` +
Object.keys(params)
.filter(function(key) {
return Boolean(params[key])
})
.map(function(key) {
return encodeURIComponent(key) + `=` + encodeURIComponent(params[key])
})
.join(`&`)
)
}

// Derive a fetch URL from the current URL, sans the GraphQL parameters.
const graphqlParamNames = {
query: true,
variables: true,
operationName: true,
}
const otherParams = {}
for (var k in parameters) {
if (parameters.hasOwnProperty(k) && graphqlParamNames[k] !== true) {
otherParams[k] = parameters[k]
}
}
const fetchURL = locationQuery(otherParams)

function graphQLFetcher(graphQLParams) {
return fetch(fetchURL, {
method: `post`,
headers: {
Accept: `application/json`,
"Content-Type": `application/json`,
},
body: JSON.stringify(graphQLParams),
credentials: `include`,
}).then(function(response) {
return response.json()
})
}

// When the query and variables string is edited, update the URL bar so
// that it can be easily shared.
function onEditVariables(newVariables) {
parameters.variables = newVariables
updateURL()
}
function onEditOperationName(newOperationName) {
parameters.operationName = newOperationName
updateURL()
}
function updateURL() {
history.replaceState(null, null, locationQuery(parameters))
}

// We control query, so we need to recreate initial query text that show up
// on visiting graphiql - in order it will be
// - query from query string (if set)
// - query stored in localStorage (which graphiql set when closing window)
// - default empty query
const DEFAULT_QUERY =
parameters.query ||
(window.localStorage && window.localStorage.getItem(`graphiql:query`)) ||
`{
}`
This conversation was marked as resolved by pieh

This comment has been minimized.

Copy link
@pieh

pieh May 23, 2019

Author Contributor

here we could add comments that will guide first time users

This comment has been minimized.

Copy link
@pieh

pieh May 23, 2019

Author Contributor

I should just copy & paste default graphiql "query" ( https://github.com/graphql/graphiql/blob/25006623dc56c78259cd1ab1401eea6b96343b42/src/components/GraphiQL.js#L1027-L1058 ) and we can iterate on that if we want to (so not blocker for the PR)

This comment has been minimized.

Copy link
@DSchau

DSchau May 23, 2019

Contributor

This works with me. We could customize the query to be Gatsby specific too, e.g. a query that will typically always work, a la

{
  site {
    siteMetadata {
      title
    }
  }
}

(also open to anything else here--just enough to convey a simple query that will rarely, ideally never, break)

This comment has been minimized.

Copy link
@pieh

pieh May 23, 2019

Author Contributor

This sounds good.

I'll check how hard is to see if site.siteMetadata.title is actually in schema (we do instrospection query, so this information should be available) and will just fallback to allSitePage.nodes.path which will be available always (but not as useful as siteMetadata query)

This comment has been minimized.

Copy link
@pieh

pieh May 23, 2019

Author Contributor

Done


class App extends React.Component {
state = { schema: null, query: DEFAULT_QUERY, explorerIsOpen: true }
This conversation was marked as resolved by pieh

This comment has been minimized.

Copy link
@pieh

pieh May 23, 2019

Author Contributor

explorer is open by default - probably would be better to track state and save it to localStorage

This comment has been minimized.

Copy link
@DSchau

DSchau May 23, 2019

Contributor

I like the idea of exposing it by default. It's a useful feature, let's make it obvious! 💪

This comment has been minimized.

Copy link
@pieh

pieh May 23, 2019

Author Contributor

I should be more clear - I mean it will always start with explorer opened, so even if user close this and never use this, next time he will visit /___graphiql explorer will be opened again. This might be frustrating for users pretty familiar with gatsby schema that won't benefit that much from explorer

This comment has been minimized.

Copy link
@DSchau

DSchau May 23, 2019

Contributor

No - I got that! I was commenting on the former part of the statement. I do think it could make sense to save a key to localStorage once closed (and opened).

This comment has been minimized.

Copy link
@pieh

pieh May 23, 2019

Author Contributor

done


componentDidMount() {
graphQLFetcher({
query: getIntrospectionQuery(),
}).then(result => {
this.setState({ schema: buildClientSchema(result.data) })
})
}

_handleEditQuery = query => {
parameters.query = query
updateURL()
this.setState({ query })
}

_handleToggleExplorer = () => {
this.setState({ explorerIsOpen: !this.state.explorerIsOpen })
}

render() {
const { query, schema } = this.state

return (
<React.Fragment>
<GraphiQLExplorer
schema={schema}
query={query}
onEdit={this._handleEditQuery}
explorerIsOpen={this.state.explorerIsOpen}
onToggleExplorer={this._handleToggleExplorer}
/>
<GraphiQL
ref={ref => (this._graphiql = ref)}
fetcher={graphQLFetcher}
schema={schema}
query={query}
onEditQuery={this._handleEditQuery}
onEditVariables={onEditVariables}
onEditOperationName={onEditOperationName}
>
<GraphiQL.Toolbar>
<GraphiQL.Button
onClick={() => this._graphiql.handlePrettifyQuery()}
label="Prettify"
title="Prettify Query (Shift-Ctrl-P)"
/>
<GraphiQL.Button
onClick={() => this._graphiql.handleToggleHistory()}
label="History"
title="Show History"
/>
<GraphiQL.Button
onClick={this._handleToggleExplorer}
label="Explorer"
title="Toggle Explorer"
/>
</GraphiQL.Toolbar>
</GraphiQL>
</React.Fragment>
)
}
}

ReactDOM.render(<App />, document.getElementById(`root`))
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>GraphiQL</title>
<meta name="robots" content="noindex" />
<meta name="referrer" content="origin" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<div id="root" class="graphiql-container">Loading...</div>
<script
type="text/javascript"
src="/___graphql/<%= htmlWebpackPlugin.files.chunks.main.entry %>?<%= htmlWebpackPlugin.files.chunks.main.hash %>"
This conversation was marked as resolved by pieh

This comment has been minimized.

Copy link
@DSchau

DSchau May 23, 2019

Contributor

Would this cause issues with #14234 (e.g. if navigating to /_graphql)? To avoid, we could just redirect to ___graphql, instead of exposing other endpoints--which is probably a better solution anyways.

This comment has been minimized.

Copy link
@pieh

pieh May 23, 2019

Author Contributor

as long as /___graphql is handled by gatsby develop - this is fine regardless of which of the variants we use. Initially wanted to make this relative, but it would require trailing slash after /___graphql/ to work

This comment has been minimized.

Copy link
@pieh

pieh May 23, 2019

Author Contributor

I can also adjust e2e test, to actually do something in IDE and verify that it's working (and not only check title as it works right now)

This comment has been minimized.

Copy link
@pieh

pieh May 24, 2019

Author Contributor

e2e tests added

></script>
</body>
</html>
@@ -0,0 +1,72 @@
const path = require(`path`)
const HtmlWebpackPlugin = require(`html-webpack-plugin`)
const webpack = require(`webpack`)

const mode = `production`
module.exports = {
entry: path.join(__dirname, `app.js`),
mode,
output: {
path: path.join(__dirname, `..`, `..`),
filename: `./app.js`,
},
devtool: false,
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: `babel-loader`,
options: {
presets: [
[
`@babel/preset-env`,
{
corejs: 2,
loose: true,
modules: `commonjs`,
useBuiltIns: `usage`,
targets: [`>0.25%`, `not dead`],
},
],
[
`@babel/preset-react`,
{
useBuiltIns: true,
pragma: `React.createElement`,
development: false,
},
],
],
plugins: [
[
`@babel/plugin-proposal-class-properties`,
{
loose: true,
},
],
],
},
},
},
{
test: /\.css$/,
use: [{ loader: `style-loader` }, { loader: `css-loader` }],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, `index.ejs`),
filename: `index.html`,
inject: false,
}),
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify(`${mode}`),
This conversation was marked as resolved by pieh

This comment has been minimized.

Copy link
@DSchau

DSchau May 23, 2019

Contributor

Guessing this is copypasta (so if so, ignore), but

Suggested change
"process.env.NODE_ENV": JSON.stringify(`${mode}`),
"process.env.NODE_ENV": JSON.stringify(mode),

This comment has been minimized.

Copy link
@pieh

pieh May 23, 2019

Author Contributor

🤦‍♂

}),
],
stats: {
warnings: false,
},
}
@@ -0,0 +1,13 @@
const path = require(`path`)

module.exports = (expressApp, { graphqlEndpoint }) => {
const bundleUrlHandler = path.posix.join(graphqlEndpoint, `app.js`)
expressApp.get(bundleUrlHandler, (req, res) => {
res.set(`Cache-Control`, `public, max-age=31557600`)
res.sendFile(path.join(__dirname, `app.js`))
})

expressApp.get(graphqlEndpoint, (req, res) => {
res.sendFile(path.join(__dirname, `index.html`))
})
}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.