feat: graphql support (#65)

This commit is contained in:
Anoop M D 2022-11-07 02:56:58 +05:30
parent 530af1f929
commit 2aef7c61a4
22 changed files with 306 additions and 171 deletions

View File

@ -92,8 +92,6 @@ export default class QueryEditor extends React.Component {
}
if (this.props.theme !== prevProps.theme && this.editor) {
this.cachedValue = this.props.value;
this.editor.setValue(this.props.value);
this.editor.setOption('theme', this.props.theme === 'dark' ? 'monokai' : 'default');
}
this.ignoreChangeEvent = false;

View File

@ -1,22 +1,14 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
.react-tabs__tab-list {
border-bottom: none !important;
padding-top: 0;
padding-left: 0 !important;
display: flex;
align-items: center;
margin: 0;
.react-tabs__tab {
div.tabs {
div.tab {
padding: 6px 0px;
border: none;
user-select: none;
border-bottom: solid 2px transparent;
margin-right: 20px;
color: rgb(125 125 125);
outline: none !important;
margin-right: 1.25rem;
color: var(--color-tab-inactive);
cursor: pointer;
&:focus,
&:active,
@ -27,36 +19,12 @@ const StyledWrapper = styled.div`
box-shadow: none !important;
}
&:after {
display: none !important;
&.active {
color: ${(props) => props.theme.tabs.active.color} !important;
border-bottom: solid 2px ${(props) => props.theme.tabs.active.border} !important;
}
}
}
.react-tabs__tab--selected {
border: none;
color: ${(props) => props.theme.tabs.active.color} !important;
border-bottom: solid 2px ${(props) => props.theme.tabs.active.border} !important;
border-color: var(--color-tab-active-border) !important;
background: inherit;
outline: none !important;
box-shadow: none !important;
&:focus,
&:active,
&:focus-within,
&:focus-visible,
&:target {
border: none;
outline: none !important;
box-shadow: none !important;
border-bottom: solid 2px ${(props) => props.theme.tabs.active.border} !important;
border-color: var(--color-tab-active-border) !important;
background: inherit;
outline: none !important;
box-shadow: none !important;
}
}
`;
export default StyledWrapper;

View File

@ -1,26 +1,115 @@
import React from 'react';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import find from 'lodash/find';
import get from 'lodash/get';
import classnames from 'classnames';
import { IconRefresh, IconLoader2, IconBook, IconDownload } from '@tabler/icons';
import { useSelector, useDispatch } from 'react-redux';
import { updateRequestPaneTab } from 'providers/ReduxStore/slices/tabs';
import QueryEditor from 'components/RequestPane/QueryEditor';
import RequestHeaders from 'components/RequestPane/RequestHeaders';
import { useTheme } from 'providers/Theme';
import { updateRequestGraphqlQuery } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import useGraphqlSchema from './useGraphqlSchema';
import StyledWrapper from './StyledWrapper';
const GraphQLRequestPane = ({ onRunQuery, schema, leftPaneWidth, value, onQueryChange }) => {
const GraphQLRequestPane = ({ item, collection, leftPaneWidth }) => {
const dispatch = useDispatch();
const tabs = useSelector((state) => state.tabs.tabs);
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
const query = item.draft ? get(item, 'draft.request.body.graphql.query') : get(item, 'request.body.graphql.query');
const url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url');
const {
storedTheme
} = useTheme();
let {
schema,
loadSchema,
isLoading: isSchemaLoading,
error: schemaError
} = useGraphqlSchema(url);
const loadGqlSchema = () => {
if(!isSchemaLoading) {
loadSchema();
}
};
const onQueryChange = (value) => {
dispatch(
updateRequestGraphqlQuery({
query: value,
itemUid: item.uid,
collectionUid: collection.uid
})
);
};
const onRun = () => dispatch(sendRequest(item, collection.uid));
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
const selectTab = (tab) => {
dispatch(
updateRequestPaneTab({
uid: item.uid,
requestPaneTab: tab
})
);
};
const getTabPanel = (tab) => {
switch (tab) {
case 'query': {
return <QueryEditor theme={storedTheme} schema={schema} width={leftPaneWidth} onSave={onSave} value={query} onRun={onRun} onEdit={onQueryChange} />;
}
case 'headers': {
return <RequestHeaders item={item} collection={collection} />;
}
default: {
return <div className="mt-4">404 | Not found</div>;
}
}
};
if (!activeTabUid) {
return <div>Something went wrong</div>;
}
const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
if (!focusedTab || !focusedTab.uid || !focusedTab.requestPaneTab) {
return <div className="pb-4 px-4">An error occured!</div>;
}
const getTabClassname = (tabName) => {
return classnames(`tab select-none ${tabName}`, {
active: tabName === focusedTab.requestPaneTab
});
};
return (
<StyledWrapper className="h-full">
<Tabs className="react-tabs mt-1 flex flex-grow flex-col h-full" forceRenderTabPanel>
<TabList>
<Tab tabIndex="-1">Query</Tab>
<Tab tabIndex="-1">Headers</Tab>
</TabList>
<TabPanel>
<div className="mt-4">
<QueryEditor schema={schema} width={leftPaneWidth} value={value} onRunQuery={onRunQuery} onEdit={onQueryChange} />
<StyledWrapper className="flex flex-col h-full relative">
<div className="flex items-center tabs" role="tablist">
<div className={getTabClassname('query')} role="tab" onClick={() => selectTab('query')}>
Query
</div>
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
Headers
</div>
<div className="flex flex-grow justify-end items-center" style={{fontSize: 13}}>
<div className='flex items-center cursor-pointer hover:underline' onClick={loadGqlSchema}>
{isSchemaLoading ? (
<IconLoader2 className="animate-spin" size={18} strokeWidth={1.5}/>
) : null}
{!isSchemaLoading && !schema ? <IconDownload size={18} strokeWidth={1.5}/> : null }
{!isSchemaLoading && schema ? <IconRefresh size={18} strokeWidth={1.5}/> : null }
<span className='ml-1'>{schema ? 'Schema' : 'Load Schema'}</span>
</div>
</TabPanel>
<TabPanel>
<RequestHeaders />
</TabPanel>
</Tabs>
{/* <div className='flex items-center cursor-pointer hover:underline ml-2'>
<IconBook size={18} strokeWidth={1.5} /><span className='ml-1'>Docs</span>
</div> */}
</div>
</div>
<section className="flex w-full mt-5">{getTabPanel(focusedTab.requestPaneTab)}</section>
</StyledWrapper>
);
};

View File

@ -0,0 +1,70 @@
import { useState } from 'react';
import toast from 'react-hot-toast';
import { getIntrospectionQuery, buildClientSchema } from 'graphql';
import { simpleHash } from 'utils/common';
const schemaHashPrefix = 'bruno.graphqlSchema';
const fetchSchema = (endpoint) => {
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 [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [schema, setSchema] = useState(() => {
try {
const saved = localStorage.getItem(localStorageKey);
if(!saved) {
return null;
}
return buildClientSchema(JSON.parse(saved));
} catch {
localStorage.setItem(localStorageKey, null);
return null;
}
});
const loadSchema = () => {
setIsLoading(true);
fetchSchema(endpoint)
.then((res) => res.json())
.then((s) => {
if (s && s.data) {
setSchema(buildClientSchema(s.data));
setIsLoading(false);
localStorage.setItem(localStorageKey, JSON.stringify(s.data));
toast.success('Graphql Schema loaded successfully');
} else {
return Promise.reject(new Error('An error occurred while introspecting schema'));
}
})
.catch((err) => {
setIsLoading(false);
setError(err);
toast.error('Error occured while loading Graphql Schema');
});
};
return {
isLoading,
schema,
loadSchema,
error
};
};
export default useGraphqlSchema;

View File

@ -5,12 +5,30 @@ const StyledWrapper = styled.div`
background: ${(props) => props.theme.codemirror.bg};
border: solid 1px ${(props) => props.theme.codemirror.border};
/* todo: find a better way */
height: calc(100vh - 250px);
height: calc(100vh - 220px);
}
textarea.cm-editor {
position: relative;
}
// Todo: dark mode temporary fix
// Clean this
.cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute {
color: #9cdcfe !important;
}
.cm-s-monokai span.cm-string {
color: #ce9178 !important;
}
.cm-s-monokai span.cm-number{
color: #b5cea8 !important;
}
.cm-s-monokai span.cm-atom{
color: #569cd6 !important;
}
`;
export default StyledWrapper;

View File

@ -38,6 +38,7 @@ export default class QueryEditor extends React.Component {
tabSize: 2,
mode: 'graphql',
theme: this.props.editorTheme || 'graphiql',
theme: this.props.theme === 'dark' ? 'monokai' : 'default',
keyMap: 'sublime',
autoCloseBrackets: true,
matchBrackets: true,
@ -75,54 +76,51 @@ export default class QueryEditor extends React.Component {
'Alt-Space': () => editor.showHint({ completeSingle: true, container: this._node }),
'Shift-Space': () => editor.showHint({ completeSingle: true, container: this._node }),
'Shift-Alt-Space': () => editor.showHint({ completeSingle: true, container: this._node }),
'Cmd-Enter': () => {
if (this.props.onRunQuery) {
this.props.onRunQuery();
if (this.props.onRun) {
this.props.onRun();
}
},
'Ctrl-Enter': () => {
if (this.props.onRunQuery) {
this.props.onRunQuery();
if (this.props.onRun) {
this.props.onRun();
}
},
'Shift-Ctrl-C': () => {
if (this.props.onCopyQuery) {
this.props.onCopyQuery();
}
},
'Shift-Ctrl-P': () => {
if (this.props.onPrettifyQuery) {
this.props.onPrettifyQuery();
}
},
/* Shift-Ctrl-P is hard coded in Firefox for private browsing so adding an alternative to Pretiffy */
'Shift-Ctrl-F': () => {
if (this.props.onPrettifyQuery) {
this.props.onPrettifyQuery();
}
},
'Shift-Ctrl-M': () => {
if (this.props.onMergeQuery) {
this.props.onMergeQuery();
}
},
'Cmd-S': () => {
if (this.props.onRunQuery) {
// empty
if (this.props.onSave) {
this.props.onSave();
return false;
}
},
'Ctrl-S': () => {
if (this.props.onRunQuery) {
// empty
if (this.props.onSave) {
this.props.onSave();
return false;
}
}
},
'Cmd-F': 'findPersistent',
'Ctrl-F': 'findPersistent'
}
}));
if (editor) {
@ -149,6 +147,10 @@ export default class QueryEditor extends React.Component {
this.cachedValue = this.props.value;
this.editor.setValue(this.props.value);
}
if (this.props.theme !== prevProps.theme && this.editor) {
this.editor.setOption('theme', this.props.theme === 'dark' ? 'monokai' : 'default');
}
this.ignoreChangeEvent = false;
}
@ -164,7 +166,7 @@ export default class QueryEditor extends React.Component {
render() {
return (
<StyledWrapper
className="h-full"
className="h-full w-full"
aria-label="Query Editor"
ref={(node) => {
this._node = node;
@ -173,8 +175,11 @@ export default class QueryEditor extends React.Component {
);
}
_onKeyUp = (_cm, event) => {
if (AUTO_COMPLETE_AFTER_KEY.test(event.key) && this.editor) {
_onKeyUp = (_cm, e) => {
if (e.metaKey || e.ctrlKey || e.altKey) {
return;
}
if (AUTO_COMPLETE_AFTER_KEY.test(e.key) && this.editor) {
this.editor.execCommand('autocomplete');
}
};

View File

@ -11,7 +11,7 @@ const QueryUrl = ({ item, collection, handleRun }) => {
const { theme } = useTheme();
const dispatch = useDispatch();
const method = item.draft ? get(item, 'draft.request.method') : get(item, 'request.method');
let url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url');
const url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url');
const onUrlChange = (value) => {
dispatch(

View File

@ -12,7 +12,6 @@ import { sendRequest } from 'providers/ReduxStore/slices/collections/actions';
import RequestNotFound from './RequestNotFound';
import QueryUrl from 'components/RequestPane/QueryUrl';
import NetworkError from 'components/ResponsePane/NetworkError';
import useGraphqlSchema from '../../hooks/useGraphqlSchema';
import StyledWrapper from './StyledWrapper';
@ -65,11 +64,6 @@ const RequestTabPanel = () => {
setDragging(true);
};
let schema = null;
// let {
// schema
// } = useGraphqlSchema('https://api.spacex.land/graphql');
useEffect(() => {
document.addEventListener('mouseup', handleMouseUp);
document.addEventListener('mousemove', handleMouseMove);
@ -105,8 +99,6 @@ const RequestTabPanel = () => {
})
);
};
const onGraphqlQueryChange = (value) => {};
const runQuery = async () => {};
return (
<StyledWrapper className={`flex flex-col flex-grow ${dragging ? 'dragging' : ''}`}>
@ -118,11 +110,9 @@ const RequestTabPanel = () => {
<div className="px-4" style={{ width: `${leftPaneWidth}px`, height: 'calc(100% - 5px)' }}>
{item.type === 'graphql-request' ? (
<GraphQLRequestPane
onRunQuery={runQuery}
schema={schema}
item={item}
collection={collection}
leftPaneWidth={leftPaneWidth}
value={item.request.body.graphql.query}
onQueryChange={onGraphqlQueryChange}
/>
) : null}

View File

@ -15,6 +15,7 @@ import CloneCollectionItem from './CloneCollectionItem';
import DeleteCollectionItem from './DeleteCollectionItem';
import { isItemARequest, isItemAFolder, itemIsOpenedInTabs } from 'utils/tabs';
import { doesRequestMatchSearchText, doesFolderHaveItemsMatchSearchText } from 'utils/collections/search';
import { getDefaultRequestPaneTab } from 'utils/collections';
import { hideHomePage } from 'providers/ReduxStore/slices/app';
import StyledWrapper from './StyledWrapper';
@ -69,7 +70,8 @@ const CollectionItem = ({ item, collection, searchText }) => {
dispatch(
addTab({
uid: item.uid,
collectionUid: collection.uid
collectionUid: collection.uid,
requestPaneTab: getDefaultRequestPaneTab(item)
})
);
}

View File

@ -9,6 +9,7 @@ import { newEphermalHttpRequest } from 'providers/ReduxStore/slices/collections'
import { newHttpRequest } from 'providers/ReduxStore/slices/collections/actions';
import { addTab } from 'providers/ReduxStore/slices/tabs';
import HttpMethodSelector from 'components/RequestPane/QueryUrl/HttpMethodSelector';
import { getDefaultRequestPaneTab } from 'utils/collections';
import StyledWrapper from './StyledWrapper';
const NewRequest = ({ collection, item, isEphermal, onClose }) => {
@ -42,7 +43,8 @@ const NewRequest = ({ collection, item, isEphermal, onClose }) => {
dispatch(
addTab({
uid: uid,
collectionUid: collection.uid
collectionUid: collection.uid,
requestPaneTab: getDefaultRequestPaneTab({type: values.requestType})
})
);
onClose();
@ -77,27 +79,27 @@ const NewRequest = ({ collection, item, isEphermal, onClose }) => {
<StyledWrapper>
<Modal size="md" title="New Request" confirmText="Create" handleConfirm={onSubmit} handleCancel={onClose}>
<form className="bruno-form" onSubmit={formik.handleSubmit}>
<div className="hidden">
<div>
<label htmlFor="requestName" className="block font-semibold">
Type
</label>
<div className="flex items-center mt-2">
<input
id="http"
id="http-request"
className="cursor-pointer"
type="radio"
name="requestType"
onChange={formik.handleChange}
value="http"
value="http-request"
checked={formik.values.requestType === 'http-request'}
/>
<label htmlFor="http" className="ml-1 cursor-pointer select-none">
<label htmlFor="http-request" className="ml-1 cursor-pointer select-none">
Http
</label>
<input
id="graphql"
id="graphql-request"
className="ml-4 cursor-pointer"
type="radio"
name="requestType"
@ -105,16 +107,16 @@ const NewRequest = ({ collection, item, isEphermal, onClose }) => {
formik.setFieldValue('requestMethod', 'POST');
formik.handleChange(event);
}}
value="graphql"
value="graphql-request"
checked={formik.values.requestType === 'graphql-request'}
/>
<label htmlFor="graphql" className="ml-1 cursor-pointer select-none">
<label htmlFor="graphql-request" className="ml-1 cursor-pointer select-none">
Graphql
</label>
</div>
</div>
<div>
<div className="mt-4">
<label htmlFor="requestName" className="block font-semibold">
Name
</label>

View File

@ -54,7 +54,7 @@ const Wrapper = styled.div`
&:hover div.drag-request-border {
width: 2px;
height: 100%;
border-left: solid 1px var(--color-request-dragbar-background-active);
border-left: solid 1px ${(props) => props.theme.sidebar.dragbar};
}
}
`;

View File

@ -22,6 +22,10 @@ const GlobalStyle = createGlobalStyle`
padding: .215rem .6rem .215rem .6rem;
}
.btn-xs {
padding: .2rem .4rem .2rem .4rem;
}
.btn-md {
padding: .4rem 1.1rem;
line-height: 1.47;

View File

@ -1,44 +0,0 @@
import { useState, useEffect } from 'react';
import { getIntrospectionQuery, buildClientSchema } from 'graphql';
const useGraphqlSchema = (endpoint) => {
const [isLoaded, setIsLoaded] = useState(false);
const [schema, setSchema] = useState(null);
const [error, setError] = useState(null);
const introspectionQuery = getIntrospectionQuery();
const queryParams = {
query: introspectionQuery
};
useEffect(() => {
fetch(endpoint, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(queryParams)
})
.then((res) => res.json())
.then((s) => {
if (s && s.data) {
setSchema(buildClientSchema(s.data));
setIsLoaded(true);
} else {
return Promise.reject(new Error('An error occurred while introspecting schema'));
}
})
.catch((err) => {
setError(err);
});
}, []);
return {
isLoaded,
schema,
error
};
};
export default useGraphqlSchema;

View File

@ -3,8 +3,8 @@ import { createSlice } from '@reduxjs/toolkit';
const initialState = {
isDragging: false,
idbConnectionReady: false,
leftSidebarWidth: 270,
leftMenuBarOpen: true,
leftSidebarWidth: 222,
leftMenuBarOpen: false,
screenWidth: 500,
showHomePage: false
};

View File

@ -16,7 +16,8 @@ import {
findEnvironmentInCollection,
isItemAFolder,
refreshUidsInItem,
interpolateEnvironmentVars
interpolateEnvironmentVars,
getDefaultRequestPaneTab
} from 'utils/collections';
import { collectionSchema, itemSchema, environmentsSchema } from '@usebruno/schema';
import { waitForNextTick } from 'utils/common';
@ -108,7 +109,8 @@ export const createCollection = (collectionName) => (dispatch, getState) => {
dispatch(
addTab({
uid: requestItem.uid,
collectionUid: newCollection.uid
collectionUid: newCollection.uid,
requestPaneTab: getDefaultRequestPaneTab(requestItem)
})
)
)
@ -635,7 +637,8 @@ export const newHttpRequest = (params) => (dispatch, getState) => {
dispatch(
addTab({
uid: item.uid,
collectionUid: collection.uid
collectionUid: collection.uid,
requestPaneTab: getDefaultRequestPaneTab(item)
})
);
})

View File

@ -606,6 +606,22 @@ export const collectionsSlice = createSlice({
}
}
},
updateRequestGraphqlQuery: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
if (collection) {
const item = findItemInCollection(collection, action.payload.itemUid);
if (item && isItemARequest(item)) {
if (!item.draft) {
item.draft = cloneDeep(item);
}
item.draft.request.body.mode = 'graphql';
item.draft.request.body.graphql = item.draft.request.body.graphql || {};
item.draft.request.body.graphql.query = action.payload.query;
}
}
},
updateRequestMethod: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
@ -791,6 +807,7 @@ export const {
deleteMultipartFormParam,
updateRequestBodyMode,
updateRequestBody,
updateRequestGraphqlQuery,
updateRequestMethod,
localCollectionAddFileEvent,
localCollectionAddDirectoryEvent,

View File

@ -23,7 +23,7 @@ export const tabsSlice = createSlice({
uid: action.payload.uid,
collectionUid: action.payload.collectionUid,
requestPaneWidth: null,
requestPaneTab: 'params',
requestPaneTab: action.payload.requestPaneTab || 'params',
responsePaneTab: 'response'
});
state.activeTabUid = action.payload.uid;

View File

@ -22,6 +22,7 @@ const darkTheme = {
color: '#ccc',
muted: '#9d9d9d',
bg: '#252526',
dragbar: '#8a8a8a',
workspace: {
bg: '#3D3D3D'

View File

@ -22,6 +22,7 @@ const lightTheme = {
color: 'rgb(52, 52, 52)',
muted: '#4b5563',
bg: '#F3F3F3',
dragbar: 'rgb(200, 200, 200)',
workspace: {
bg: '#e1e1e1'

View File

@ -196,7 +196,7 @@ export const transformCollectionToSaveToIdb = (collection, options = {}) => {
json: si.draft.request.body.json,
text: si.draft.request.body.text,
xml: si.draft.request.body.xml,
multipartForm: si.draft.request.body.multipartForm,
graphql: si.draft.request.body.graphql,
formUrlEncoded: copyFormUrlEncodedParams(si.draft.request.body.formUrlEncoded),
multipartForm: copyMultipartFormParams(si.draft.request.body.multipartForm)
}
@ -214,6 +214,7 @@ export const transformCollectionToSaveToIdb = (collection, options = {}) => {
json: si.request.body.json,
text: si.request.body.text,
xml: si.request.body.xml,
graphql: si.request.body.graphql,
formUrlEncoded: copyFormUrlEncodedParams(si.request.body.formUrlEncoded),
multipartForm: copyMultipartFormParams(si.request.body.multipartForm)
}
@ -410,3 +411,13 @@ export const interpolateEnvironmentVars = (item, variables) => {
return request;
};
export const getDefaultRequestPaneTab = (item) => {
if(item.type === 'http-request') {
return 'params';
}
if(item.type === 'graphql-request') {
return 'query';
}
};

View File

@ -1,14 +1,14 @@
import get from 'lodash/get';
import each from 'lodash/each';
import filter from 'lodash/filter';
import qs from 'qs';
import { rawRequest, gql } from 'graphql';
import { sendHttpRequestInBrowser } from './browser';
import { isElectron } from 'utils/common/platform';
import cancelTokens, { deleteCancelToken } from 'utils/network/cancelTokens';
export const sendNetworkRequest = async (item, options) => {
return new Promise((resolve, reject) => {
if (item.type === 'http-request') {
if (['http-request', 'graphql-request'].includes(item.type)) {
const timeStart = Date.now();
sendHttpRequest(item.draft ? item.draft.request : item.request, options)
.then((response) => {
@ -79,6 +79,15 @@ const sendHttpRequest = async (request, options) => {
axiosRequest.data = params;
}
if (request.body.mode === 'graphql') {
const graphqlQuery = {
query: get(request, 'body.graphql.query'),
variables: JSON.parse(get(request, 'body.graphql.variables') || '{}')
};
axiosRequest.headers['content-type'] = 'application/json';
axiosRequest.data = graphqlQuery;
}
console.log('>>> Sending Request');
console.log(axiosRequest);
@ -90,21 +99,6 @@ const sendHttpRequest = async (request, options) => {
});
};
const sendGraphqlRequest = async (request) => {
const query = gql`
${request.request.body.graphql.query}
`;
const { data, errors, extensions, headers, status } = await rawRequest(request.request.url, query);
return {
data,
headers,
data,
errors
};
};
export const cancelNetworkRequest = async (cancelTokenUid) => {
if (isElectron()) {
return new Promise((resolve, reject) => {

View File

@ -29,13 +29,19 @@ const keyValueSchema = Yup.object({
const requestUrlSchema = Yup.string().min(0).max(2048, 'name must be 2048 characters or less').defined();
const requestMethodSchema = Yup.string().oneOf(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD']).required('method is required');
const graphqlBodySchema = Yup.object({
query: Yup.string().max(10240, 'json must be 10240 characters or less').nullable(),
variables: Yup.string().max(10240, 'text must be 10240 characters or less').nullable(),
}).noUnknown(true).strict();
const requestBodySchema = Yup.object({
mode: Yup.string().oneOf(['none', 'json', 'text', 'xml', 'formUrlEncoded', 'multipartForm']).required('mode is required'),
mode: Yup.string().oneOf(['none', 'json', 'text', 'xml', 'formUrlEncoded', 'multipartForm', 'graphql']).required('mode is required'),
json: Yup.string().max(10240, 'json must be 10240 characters or less').nullable(),
text: Yup.string().max(10240, 'text must be 10240 characters or less').nullable(),
xml: Yup.string().max(10240, 'xml must be 10240 characters or less').nullable(),
formUrlEncoded: Yup.array().of(keyValueSchema).nullable(),
multipartForm: Yup.array().of(keyValueSchema).nullable(),
graphql: graphqlBodySchema.nullable(),
}).noUnknown(true).strict();
// Right now, the request schema is very tightly coupled with http request