feat: graphql schema introspection

This commit is contained in:
Anoop M D 2023-02-01 05:09:42 +05:30 committed by Anoop M D
parent 05a290839b
commit 8bfb2591c2
8 changed files with 89 additions and 27 deletions

View File

@ -27,7 +27,7 @@
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"formik": "^2.2.9", "formik": "^2.2.9",
"graphiql": "^1.5.9", "graphiql": "^1.5.9",
"graphql": "^16.2.0", "graphql": "^16.6.0",
"graphql-request": "^3.7.0", "graphql-request": "^3.7.0",
"idb": "^7.0.0", "idb": "^7.0.0",
"immer": "^9.0.15", "immer": "^9.0.15",

View File

@ -10,6 +10,7 @@ import RequestHeaders from 'components/RequestPane/RequestHeaders';
import { useTheme } from 'providers/Theme'; import { useTheme } from 'providers/Theme';
import { updateRequestGraphqlQuery } from 'providers/ReduxStore/slices/collections'; import { updateRequestGraphqlQuery } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import { findEnvironmentInCollection } from 'utils/collections';
import useGraphqlSchema from './useGraphqlSchema'; import useGraphqlSchema from './useGraphqlSchema';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
@ -22,13 +23,15 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
const { const {
storedTheme storedTheme
} = useTheme(); } = useTheme();
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
let { let {
schema, schema,
loadSchema, loadSchema,
isLoading: isSchemaLoading, isLoading: isSchemaLoading,
error: schemaError error: schemaError
} = useGraphqlSchema(url); } = useGraphqlSchema(url, environment);
const loadGqlSchema = () => { const loadGqlSchema = () => {
if(!isSchemaLoading) { if(!isSchemaLoading) {

View File

@ -1,27 +1,12 @@
import { useState } from 'react'; import { useState } from 'react';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { getIntrospectionQuery, buildClientSchema } from 'graphql'; import { buildClientSchema } from 'graphql';
import { fetchGqlSchema } from 'utils/network';
import { simpleHash } from 'utils/common'; import { simpleHash } from 'utils/common';
const schemaHashPrefix = 'bruno.graphqlSchema'; const schemaHashPrefix = 'bruno.graphqlSchema';
const fetchSchema = (endpoint) => { const useGraphqlSchema = (endpoint, environment) => {
const introspectionQuery = getIntrospectionQuery();
const queryParams = {
query: introspectionQuery
};
return fetch(endpoint, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(queryParams)
});
}
const useGraphqlSchema = (endpoint) => {
const localStorageKey = `${schemaHashPrefix}.${simpleHash(endpoint)}`; const localStorageKey = `${schemaHashPrefix}.${simpleHash(endpoint)}`;
const [error, setError] = useState(null); const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
@ -40,8 +25,8 @@ const useGraphqlSchema = (endpoint) => {
const loadSchema = () => { const loadSchema = () => {
setIsLoading(true); setIsLoading(true);
fetchSchema(endpoint) fetchGqlSchema(endpoint, environment)
.then((res) => res.json()) .then((res) => res.data)
.then((s) => { .then((s) => {
if (s && s.data) { if (s && s.data) {
setSchema(buildClientSchema(s.data)); setSchema(buildClientSchema(s.data));

View File

@ -40,7 +40,8 @@ export default class QueryEditor extends React.Component {
value: this.props.value || '', value: this.props.value || '',
lineNumbers: true, lineNumbers: true,
tabSize: 2, tabSize: 2,
mode: 'brunovariables', mode: 'graphql',
// mode: 'brunovariables',
brunoVarInfo: { brunoVarInfo: {
variables: getEnvironmentVariables(this.props.collection), variables: getEnvironmentVariables(this.props.collection),
}, },
@ -176,12 +177,14 @@ export default class QueryEditor extends React.Component {
} }
} }
// Todo: Overlay is messing up with schema hint
// Fix this
addOverlay = () => { addOverlay = () => {
let variables = getEnvironmentVariables(this.props.collection); // let variables = getEnvironmentVariables(this.props.collection);
this.variables = variables; // this.variables = variables;
defineCodeMirrorBrunoVariablesMode(variables, 'graphql'); // defineCodeMirrorBrunoVariablesMode(variables, 'graphql');
this.editor.setOption('mode', 'brunovariables'); // this.editor.setOption('mode', 'brunovariables');
} }
render() { render() {

View File

@ -31,6 +31,17 @@ const sendHttpRequest = async (item, collection, environment) => {
}); });
}; };
export const fetchGqlSchema = async (endpoint, environment) => {
return new Promise((resolve, reject) => {
const { ipcRenderer } = window;
ipcRenderer
.invoke('fetch-gql-schema', endpoint, environment)
.then(resolve)
.catch(reject);
});
};
export const cancelNetworkRequest = async (cancelTokenUid) => { export const cancelNetworkRequest = async (cancelTokenUid) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
ipcRenderer ipcRenderer

View File

@ -28,6 +28,7 @@
"electron-util": "^0.17.2", "electron-util": "^0.17.2",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"fs-extra": "^10.1.0", "fs-extra": "^10.1.0",
"graphql": "^16.6.0",
"is-valid-path": "^0.1.1", "is-valid-path": "^0.1.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"moment": "^2.29.4", "moment": "^2.29.4",

View File

@ -5,6 +5,7 @@ const { ipcMain } = require('electron');
const { forOwn, extend, each, get } = require('lodash'); const { forOwn, extend, each, get } = require('lodash');
const { ScriptRuntime, TestRuntime } = require('@usebruno/js'); const { ScriptRuntime, TestRuntime } = require('@usebruno/js');
const prepareRequest = require('./prepare-request'); const prepareRequest = require('./prepare-request');
const prepareGqlIntrospectionRequest = require('./prepare-gql-introspection-request');
const { cancelTokens, saveCancelToken, deleteCancelToken } = require('../../utils/cancel-token'); const { cancelTokens, saveCancelToken, deleteCancelToken } = require('../../utils/cancel-token');
const { uuid } = require('../../utils/common'); const { uuid } = require('../../utils/common');
const interpolateVars = require('./interpolate-vars'); const interpolateVars = require('./interpolate-vars');
@ -169,6 +170,33 @@ const registerNetworkIpc = (mainWindow, watcher, lastOpenedCollections) => {
} }
}); });
}); });
ipcMain.handle('fetch-gql-schema', async (event, endpoint, environment) => {
try {
const envVars = getEnvVars(environment);
const request = prepareGqlIntrospectionRequest(endpoint, envVars);
const response = await axios(request);
return {
status: response.status,
statusText: response.statusText,
headers: response.headers,
data: response.data
};
} catch (error) {
if(error.response) {
return {
status: error.response.status,
statusText: error.response.statusText,
headers: error.response.headers,
data: error.response.data
}
};
return Promise.reject(error);
}
});
}; };
module.exports = registerNetworkIpc; module.exports = registerNetworkIpc;

View File

@ -0,0 +1,31 @@
const Mustache = require('mustache');
const { getIntrospectionQuery } = require('graphql');
// override the default escape function to prevent escaping
Mustache.escape = function (value) {
return value;
};
const prepareGqlIntrospectionRequest = (endpoint, envVars) => {
if(endpoint && endpoint.length) {
endpoint = Mustache.render(endpoint, envVars);
}
const introspectionQuery = getIntrospectionQuery();
const queryParams = {
query: introspectionQuery
};
const request = {
method: 'POST',
url: endpoint,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
data: JSON.stringify(queryParams)
};
return request;
};
module.exports = prepareGqlIntrospectionRequest;