import { LoadingSpinner } from '@sourcegraph/react-loading-spinner';
import { upperFirst } from 'lodash';
import * as React from 'react';
import { from as fromPromise, Subject, Subscription } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { debounceTime } from 'rxjs/operators';
import { eventLogger } from '../tracking/eventLogger';
import { asError, isErrorLike } from '../util/errors';
const defaultQuery = `# Type queries here, with completion, validation, and hovers.
#
# Here's an example query to get you started:

query {
  currentUser {
    username
  }
  repositories(first: 1) {
    nodes {
      name
    }
  }
}
`;
/**
 * Component to show the GraphQL API console.
 */
export class APIConsole extends React.PureComponent {
    constructor(props) {
        super(props);
        this.state = { parameters: {} };
        this.updates = new Subject();
        this.subscriptions = new Subscription();
        this.graphiQLRef = null;
        /**
         * Renders the API console once GraphiQL has loaded. This method should
         * only be invoked once this.state.graphiqlOrError is loaded successfully.
         */
        this.renderGraphiQL = () => {
            if (!this.state.graphiqlOrError || isErrorLike(this.state.graphiqlOrError)) {
                throw new Error('renderGraphiQL called illegally');
            }
            const GraphiQL = this.state.graphiqlOrError.default;
            return (React.createElement(React.Fragment, null,
                React.createElement(GraphiQL, { query: this.state.parameters.query, variables: this.state.parameters.variables, operationName: this.state.parameters.operationName, onEditQuery: this.onEditQuery, onEditVariables: this.onEditVariables, onEditOperationName: this.onEditOperationName, fetcher: fetcher, defaultQuery: defaultQuery, editorTheme: 'sourcegraph', ref: this.setGraphiQLRef },
                    React.createElement(GraphiQL.Logo, null, "GraphQL API Console"),
                    React.createElement(GraphiQL.Toolbar, null,
                        React.createElement(GraphiQL.Button, { onClick: this.handlePrettifyQuery, title: "Prettify Query (Shift-Ctrl-P)", label: "Prettify" }),
                        React.createElement(GraphiQL.Button, { onClick: this.handleToggleHistory, title: "Show History", label: "History" }),
                        React.createElement("div", { className: "api-console__alert alert alert-warning" },
                            "The API console uses your ",
                            React.createElement("strong", null, "real production data.")))),
                React.createElement("div", { className: "api-console__footer" },
                    React.createElement("span", { className: "api-console__footer-section api-console__footer-section--docs" },
                        React.createElement("a", { href: "https://about.sourcegraph.com/docs/features/api", target: "_blank" }, "General API documentation"),
                        React.createElement("span", { className: "api-console__footer-section-spacer" }, "\u00B7"),
                        React.createElement("a", { href: "https://about.sourcegraph.com/docs/features/api/examples", target: "_blank" }, "example queries")),
                    React.createElement("span", { className: "api-console__footer-section" },
                        "Powered by",
                        ' ',
                        React.createElement("a", { className: "api-console__footer-link", href: "https://github.com/graphql/graphiql", target: "_blank" },
                            "Graph",
                            React.createElement("em", null, "i"),
                            "QL"),
                        "."),
                    React.createElement("span", { className: "api-console__footer-section" }))));
        };
        // Update state.parameters when query/variables/operation name are changed
        // so that we can update the browser URL.
        this.onEditQuery = (newQuery) => this.updateStateParameters(params => (Object.assign({}, params, { query: newQuery })));
        this.onEditVariables = (newVariables) => this.updateStateParameters(params => (Object.assign({}, params, { variables: newVariables })));
        this.onEditOperationName = (newOperationName) => this.updateStateParameters(params => (Object.assign({}, params, { operationName: newOperationName })));
        // Foward GraphiQL prettify/history buttons directly to their original
        // implementation. We have to do this because it is impossible to inject
        // children into the GraphiQL toolbar unless you completely specify your
        // own.
        this.setGraphiQLRef = (ref) => {
            this.graphiQLRef = ref;
        };
        this.handlePrettifyQuery = () => {
            if (!this.graphiQLRef) {
                return;
            }
            this.graphiQLRef.handlePrettifyQuery();
        };
        this.handleToggleHistory = () => {
            if (!this.graphiQLRef) {
                return;
            }
            this.graphiQLRef.handleToggleHistory();
        };
        // Parse the location.hash JSON to get URL paramameters.
        //
        // Precaution: Use URL fragment (not querystring) to avoid leaking sensitive querystrings in
        // HTTP referer headers.
        const parameters = JSON.parse(decodeURIComponent(props.location.hash.slice('#'.length)) || '{}');
        // If variables were provided, try to format them.
        if (parameters.variables) {
            try {
                parameters.variables = JSON.stringify(JSON.parse(parameters.variables), null, 2);
            }
            catch (e) {
                // The parse error can be safely ignored because the string
                // will still be forwarded to the UI where the user will see
                // invalid JSON errors in the GraphiQL editor.
            }
        }
        this.state = { parameters };
    }
    componentDidMount() {
        eventLogger.logViewEvent('APIConsole');
        // Update the browser URL bar when query/variables/operation name are
        // changed so that the page can be easily shared.
        this.subscriptions.add(this.updates
            .pipe(debounceTime(500))
            .subscribe(data => this.props.history.replace({ hash: encodeURIComponent(JSON.stringify(data)) })));
        this.subscriptions.add(fromPromise(import('graphiql'))
            .pipe(catchError(error => {
            console.error(error);
            return [asError(error)];
        }))
            .subscribe(graphiqlOrError => {
            this.setState({ graphiqlOrError });
        }));
        // Ensure that the Doc Explorer page opens by default the first time a
        // user opens the API console.
        window.localStorage.setItem('graphiql:docExplorerOpen', window.localStorage.getItem('graphiql:docExplorerOpen') || 'true');
    }
    componentWillUnmount() {
        this.subscriptions.unsubscribe();
    }
    render() {
        return (React.createElement("div", { className: "api-console" }, this.state.graphiqlOrError === undefined ? (React.createElement("span", { className: "api-console__loader" },
            React.createElement(LoadingSpinner, { className: "icon-inline" }),
            " Loading\u2026")) : isErrorLike(this.state.graphiqlOrError) ? (React.createElement("div", { className: "alert alert-danger" },
            React.createElement("strong", null, "Error loading API console:"),
            " ",
            upperFirst(this.state.graphiqlOrError.message))) : (this.renderGraphiQL())));
    }
    updateStateParameters(update) {
        this.setState(state => (Object.assign({}, state, { parameters: update(state.parameters) })), () => this.updates.next(this.state.parameters));
    }
}
function fetcher(graphQLParams) {
    return fetch('/.api/graphql', {
        method: 'post',
        body: JSON.stringify(graphQLParams),
        credentials: 'include',
        headers: new Headers({ 'x-requested-with': 'Sourcegraph GraphQL Explorer' }),
    })
        .then(response => response.text())
        .then(responseBody => {
        try {
            return JSON.parse(responseBody);
        }
        catch (error) {
            return responseBody;
        }
    });
}
//# sourceMappingURL=APIConsole.js.map