console: add code exporter to graphiql (close #4531) (#4652)

This commit is contained in:
Anuj Shah 2020-07-02 02:46:09 +05:30 committed by GitHub
parent b9918e0eac
commit a78f17b79c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 1047 additions and 795 deletions

View File

@ -476,6 +476,7 @@ See [upgrade docs](https://hasura.io/docs/1.0/graphql/manual/migrations/upgrade-
- console: fix parsing of wrapped types in SDL (close #4099) (#4167)
- console: misc actions fixes (#4059)
- console: action relationship page improvements (fix #4062, #4130) (#4133)
- console: add code exporter to graphiql (close #4531) #4652
- cli: fix init command to generate correct config (fix #4036) (#4038)
- cli: fix parse error returned on console api (close #4126) (#4152)
- cli: fix typo in cli example for squash (fix #4047) (#4049)

1638
console/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -55,6 +55,7 @@
"apollo-link-ws": "1.0.20",
"brace": "0.11.1",
"graphiql": "1.0.0-alpha.0",
"graphiql-code-exporter": "2.0.8",
"graphiql-explorer": "0.6.2",
"graphql": "14.5.8",
"graphql-voyager": "1.0.0-rc.29",

View File

@ -6,6 +6,11 @@ import PropTypes from 'prop-types';
import GraphiQLErrorBoundary from './GraphiQLErrorBoundary';
import OneGraphExplorer from '../OneGraphExplorer/OneGraphExplorer';
import AnalyzeButton from '../Analyzer/AnalyzeButton';
import CodeExporter from 'graphiql-code-exporter';
import {
getPersistedCodeExporterOpen,
persistCodeExporterOpen,
} from '../OneGraphExplorer/utils';
import {
clearCodeMirrorHints,
@ -28,9 +33,11 @@ import {
setDerivedActionParentOperation,
} from '../../Actions/Add/reducer';
import { getGraphQLEndpoint } from '../utils';
import snippets from './snippets';
import 'graphiql/graphiql.css';
import './GraphiQL.css';
import 'graphiql-code-exporter/CodeExporter.css';
class GraphiQLWrapper extends Component {
constructor(props) {
@ -40,6 +47,7 @@ class GraphiQLWrapper extends Component {
noSchema: false,
onBoardingEnabled: false,
copyButtonText: 'Copy',
codeExporterOpen: false,
};
}
@ -47,10 +55,23 @@ class GraphiQLWrapper extends Component {
setQueryVariableSectionHeight();
}
componentWillMount() {
const codeExporterOpen = getPersistedCodeExporterOpen();
this.setState({ codeExporterOpen });
}
componentWillUnmount() {
clearCodeMirrorHints();
}
_handleToggleCodeExporter = () => {
const nextState = !this.state.codeExporterOpen;
persistCodeExporterOpen(nextState);
this.setState({ codeExporterOpen: nextState });
};
render() {
const styles = require('../../../Common/Common.scss');
@ -62,6 +83,7 @@ class GraphiQLWrapper extends Component {
mode,
loading,
} = this.props;
const { codeExporterOpen } = this.state;
const graphqlNetworkData = this.props.data;
const graphQLFetcher = graphQLParams => {
if (headerFocus) {
@ -173,6 +195,11 @@ class GraphiQLWrapper extends Component {
title: 'Toggle Explorer',
onClick: graphiqlProps.toggleExplorer,
},
{
label: 'Code Exporter',
title: 'Toggle Code Exporter',
onClick: this._handleToggleCodeExporter,
},
{
label: 'Voyager',
title: 'GraphQL Voyager',
@ -193,24 +220,34 @@ class GraphiQLWrapper extends Component {
};
return (
<GraphiQL
{...graphiqlProps}
ref={c => {
graphiqlContext = c;
}}
fetcher={graphQLFetcher}
voyagerUrl={voyagerUrl}
>
<GraphiQL.Logo>GraphiQL</GraphiQL.Logo>
<GraphiQL.Toolbar>
{getGraphiqlButtons()}
<AnalyzeButton
operations={graphiqlContext && graphiqlContext.state.operations}
analyzeFetcher={analyzeFetcherInstance}
{...analyzerProps}
<>
<GraphiQL
{...graphiqlProps}
ref={c => {
graphiqlContext = c;
}}
fetcher={graphQLFetcher}
voyagerUrl={voyagerUrl}
>
<GraphiQL.Logo>GraphiQL</GraphiQL.Logo>
<GraphiQL.Toolbar>
{getGraphiqlButtons()}
<AnalyzeButton
operations={graphiqlContext && graphiqlContext.state.operations}
analyzeFetcher={analyzeFetcherInstance}
{...analyzerProps}
/>
</GraphiQL.Toolbar>
</GraphiQL>
{codeExporterOpen ? (
<CodeExporter
hideCodeExporter={this._handleToggleCodeExporter}
snippets={snippets}
query={graphiqlProps.query}
codeMirrorTheme="default"
/>
</GraphiQL.Toolbar>
</GraphiQL>
) : null}
</>
);
};

View File

@ -0,0 +1,109 @@
import snippets from 'graphiql-code-exporter/lib/snippets';
import { OperationTypeNode, OperationDefinitionNode } from 'graphql';
export type Options = Array<{ id: string; label: string; initial: boolean }>;
export type OptionValues = { [id: string]: boolean };
export type OperationData = {
query: string;
name: string;
displayName: string;
type: OperationTypeNode;
variableName: string;
variables: object;
operationDefinition: OperationDefinitionNode;
};
export type GenerateOptions = {
serverUrl: string;
headers: { [name: string]: string };
context: object;
operationDataList: Array<OperationData>;
options: OptionValues;
};
export type CodesandboxFile = {
content: string;
};
export type CodesandboxFiles = {
[filename: string]: CodesandboxFile;
};
export type Snippet = {
options: Options;
language: string;
codeMirrorMode: string;
name: string;
generate: (options: GenerateOptions) => string;
generateCodesandboxFiles?: (options: GenerateOptions) => CodesandboxFiles;
};
const getQuery = (query: string) => {
return ` `.repeat(2) + query.replace(/\n/g, `\n${` `.repeat(2)}`);
};
const getVariables = (operationData: OperationData): string => {
const params = (
operationData?.operationDefinition?.variableDefinitions || []
).map(def => def?.variable?.name?.value);
const variablesBody = params.map(param => `"${param}": ${param}`).join(', ');
const variables = `{${variablesBody}}`;
return variables;
};
const typeScriptSnippet: Snippet = {
name: `fetch`,
language: `TypeScript`,
codeMirrorMode: `jsx`,
options: [],
generate: ({ operationDataList }) => {
const queryDef = operationDataList[0];
return `
/*
This is an example snippet - you should consider tailoring it
to your service.
Note: we only handle the first operation here
*/
function fetchGraphQL(
operationsDoc: string,
operationName: string,
variables: Record<string, any>
) {
return fetch('undefined', {
method: 'POST',
body: JSON.stringify({
query: operationsDoc,
variables,
operationName,
}),
}).then(result => result.json());
}
const operation = \`
${getQuery(queryDef.query)}
\`;
function fetch${queryDef.name}() {
return fetchGraphQL(operations, ${queryDef.name}, ${getVariables(queryDef)})
}
fetch${queryDef.name}()
.then(({ data, errors }) => {
if (errors) {
console.error(errors);
}
console.log(data);
})
.catch(error => {
console.error(error);
});
`;
},
};
export default [...snippets, typeScriptSnippet];

View File

@ -32,3 +32,22 @@ export const getExplorerIsOpen = () => {
export const setExplorerIsOpen = isOpen => {
window.localStorage.setItem('graphiql:explorerOpen', isOpen);
};
export const persistCodeExporterOpen = isOpen => {
window.localStorage.setItem(
'graphiql:codeExporterOpen',
JSON.stringify(isOpen)
);
};
export const getPersistedCodeExporterOpen = () => {
const isOpen = window.localStorage.getItem('graphiql:codeExporterOpen');
if (!isOpen) return false;
try {
return JSON.parse(isOpen);
} catch {
return false;
}
};

View File

@ -12,3 +12,6 @@ declare namespace React {
css?: import('styled-components').CSSProp;
}
}
declare module 'graphiql-code-exporter/lib/snippets';
declare module 'graphiql-code-exporter';