// @flow import type {Request} from '../../../../models/request'; import {newBodyRaw} from '../../../../models/request'; import React from 'react'; import autobind from 'autobind-decorator'; import {parse, print} from 'graphql'; import {introspectionQuery} from 'graphql/utilities/introspectionQuery'; import {buildClientSchema} from 'graphql/utilities/buildClientSchema'; import clone from 'clone'; import CodeEditor from '../../codemirror/code-editor'; import {jsonParseOr} from '../../../../common/misc'; import HelpTooltip from '../../help-tooltip'; import {CONTENT_TYPE_JSON, DEBOUNCE_MILLIS} from '../../../../common/constants'; import {prettifyJson} from '../../../../common/prettify'; import * as network from '../../../../network/network'; import type {Workspace} from '../../../../models/workspace'; import type {Settings} from '../../../../models/settings'; import type {RenderedRequest} from '../../../../common/render'; import {getRenderedRequest} from '../../../../common/render'; import TimeFromNow from '../../time-from-now'; type GraphQLBody = { query: string, variables: Object, operationName?: string } type Props = { onChange: Function, content: string, fontSize: number, indentSize: number, keyMap: string, lineWrapping: boolean, render: Function, getRenderContext: Function, request: Request, workspace: Workspace, settings: Settings, environmentId: string, // Optional className?: string }; @autobind class GraphQLEditor extends React.PureComponent { props: Props; state: { body: GraphQLBody, schema: Object | null, schemaFetchError: string, schemaLastFetchTime: number, schemaIsFetching: boolean, hideSchemaFetchErrors: boolean, variablesSyntaxError: string, forceRefreshKey: number }; _isMounted: boolean; constructor (props: Props) { super(props); this._isMounted = false; this.state = { body: this._stringToGraphQL(props.content), schema: null, schemaFetchError: '', schemaLastFetchTime: 0, schemaIsFetching: false, hideSchemaFetchErrors: false, variablesSyntaxError: '', forceRefreshKey: 0 }; } _hideSchemaFetchError () { this.setState({hideSchemaFetchErrors: true}); } async _fetchAndSetSchema (rawRequest: Request) { this.setState({schemaIsFetching: true}); const {workspace, settings, environmentId} = this.props; const newState = { schema: this.state.schema, schemaFetchError: '', schemaLastFetchTime: this.state.schemaLastFetchTime, schemaIsFetching: false }; let request: RenderedRequest | null = null; try { request = await getRenderedRequest(rawRequest, environmentId); } catch (err) { newState.schemaFetchError = `Failed to fetch schema: ${err}`; } if (request) { try { // TODO: Use Insomnia's network stack to handle things like authentication const bodyJson = JSON.stringify({query: introspectionQuery}); const introspectionRequest = Object.assign({}, request, { body: newBodyRaw(bodyJson, CONTENT_TYPE_JSON), // NOTE: We're not actually saving this request or response but let's pretend // like we are by setting these properties to prevent bugs in the future. _id: request._id + '.graphql', parentId: request._id }); const {bodyBuffer, response} = await network._actuallySend( introspectionRequest, workspace, settings ); const status = response.statusCode || 0; if (response.error) { newState.schemaFetchError = response.error; } else if (status < 200 || status >= 300) { const msg = `Got status ${status} fetching schema from "${request.url}"`; newState.schemaFetchError = msg; } else if (bodyBuffer) { const {data} = JSON.parse(bodyBuffer.toString()); const schema = buildClientSchema(data); newState.schema = schema; newState.schemaLastFetchTime = Date.now(); } else { newState.schemaFetchError = 'No response body received when fetching schema'; } } catch (err) { console.warn(`Failed to fetch GraphQL schema from ${request.url}`, err); newState.schemaFetchError = `Failed to contact "${request.url}" to fetch schema`; } } if (this._isMounted) { this.setState(newState); } } _handlePrettify () { const {body, forceRefreshKey} = this.state; const {variables, query} = body; const prettyQuery = query && print(parse(query)); const prettyVariables = variables && JSON.parse(prettifyJson(JSON.stringify(variables))); this._handleBodyChange(prettyQuery, prettyVariables); setTimeout(() => { this.setState({forceRefreshKey: forceRefreshKey + 1}); }, 200); } _handleBodyChange (query: string, variables: Object): void { const body = clone(this.state.body); const newState = {variablesSyntaxError: '', body}; newState.body.query = query; newState.body.variables = variables; this.setState(newState); this.props.onChange(this._graphQLToString(body)); } _handleQueryChange (query: string): void { this._handleBodyChange(query, this.state.body.variables); } _handleVariablesChange (variables: string): void { try { const variablesObj = JSON.parse(variables || '{}'); this._handleBodyChange(this.state.body.query, variablesObj); } catch (err) { this.setState({variablesSyntaxError: err.message}); } } _stringToGraphQL (text: string): GraphQLBody { let obj; try { obj = JSON.parse(text); } catch (err) { obj = {query: '', variables: {}}; } if (typeof obj.variables === 'string') { obj.variables = jsonParseOr(obj.variables, {}); } return { query: obj.query || '', variables: obj.variables || {} }; } _graphQLToString (body: GraphQLBody): string { return JSON.stringify(body); } componentWillReceiveProps (nextProps: Props) { if (nextProps.request.url !== this.props.request.url) { (async () => await this._fetchAndSetSchema(nextProps.request))(); } } componentDidMount () { this._fetchAndSetSchema(this.props.request); this._isMounted = true; } componentWillUnmount () { this._isMounted = false; } renderSchemaFetchMessage () { let message; const {schemaLastFetchTime, schemaIsFetching} = this.state; if (schemaIsFetching) { message = 'Fetching schema...'; } else if (schemaLastFetchTime > 0) { message = schema last fetched ; } else { message = 'schema not yet fetched'; } return (
{message}
); } render () { const { content, fontSize, indentSize, keyMap, render, getRenderContext, lineWrapping, className } = this.props; const { schema, schemaFetchError, hideSchemaFetchErrors, variablesSyntaxError, forceRefreshKey } = this.state; const { query, variables: variablesObject } = this._stringToGraphQL(content); const variables = prettifyJson(JSON.stringify(variablesObject)); return (
{!hideSchemaFetchErrors && schemaFetchError && (
{schemaFetchError}
)}
{this.renderSchemaFetchMessage()}

Query Variables Variables to use in GraphQL query
(JSON format)
{variablesSyntaxError && ( {variablesSyntaxError} )}

); } } export default GraphQLEditor;