Now based on the request type appropriate views are shown. (#3340)

* Now based on the request type appropriate views are shown.

Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
This commit is contained in:
Sanjai Kumar 2024-11-20 17:22:04 +05:30 committed by GitHub
parent 1cb0d4e191
commit 412a0ed078
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 138 additions and 14 deletions

View File

@ -39,6 +39,14 @@ const StyledWrapper = styled.div`
textarea.curl-command { textarea.curl-command {
min-height: 150px; min-height: 150px;
} }
.dropdown {
width: fit-content;
.dropdown-item {
padding: 0.2rem 0.6rem !important;
}
}
`; `;
export default StyledWrapper; export default StyledWrapper;

View File

@ -1,4 +1,4 @@
import React, { useRef, useEffect, useCallback } from 'react'; import React, { useRef, useEffect, useCallback, forwardRef, useState } from 'react';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import * as Yup from 'yup'; import * as Yup from 'yup';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
@ -12,6 +12,8 @@ import HttpMethodSelector from 'components/RequestPane/QueryUrl/HttpMethodSelect
import { getDefaultRequestPaneTab } from 'utils/collections'; import { getDefaultRequestPaneTab } from 'utils/collections';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
import { getRequestFromCurlCommand } from 'utils/curl'; import { getRequestFromCurlCommand } from 'utils/curl';
import Dropdown from 'components/Dropdown';
import { IconCaretDown } from '@tabler/icons';
const NewRequest = ({ collection, item, isEphemeral, onClose }) => { const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -19,6 +21,39 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
const { const {
brunoConfig: { presets: collectionPresets = {} } brunoConfig: { presets: collectionPresets = {} }
} = collection; } = collection;
const [curlRequestTypeDetected, setCurlRequestTypeDetected] = useState(null);
const dropdownTippyRef = useRef();
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const Icon = forwardRef((props, ref) => {
return (
<div ref={ref} className="flex items-center justify-end auth-type-label select-none">
{curlRequestTypeDetected === 'http-request' ? "HTTP" : "GraphQL"}
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
</div>
);
});
// This function analyzes a given cURL command string and determines whether the request is a GraphQL or HTTP request.
const identifyCurlRequestType = (url, headers, body) => {
if (url.endsWith('/graphql')) {
setCurlRequestTypeDetected('graphql-request');
return;
}
const contentType = headers?.find((h) => h.name.toLowerCase() === 'content-type')?.value;
if (contentType && contentType.includes('application/graphql')) {
setCurlRequestTypeDetected('graphql-request');
return;
}
setCurlRequestTypeDetected('http-request');
};
const curlRequestTypeChange = (type) => {
setCurlRequestTypeDetected(type);
};
const getRequestType = (collectionPresets) => { const getRequestType = (collectionPresets) => {
if (!collectionPresets || !collectionPresets.requestType) { if (!collectionPresets || !collectionPresets.requestType) {
@ -99,11 +134,11 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
}) })
.catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request')); .catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request'));
} else if (values.requestType === 'from-curl') { } else if (values.requestType === 'from-curl') {
const request = getRequestFromCurlCommand(values.curlCommand); const request = getRequestFromCurlCommand(values.curlCommand, curlRequestTypeDetected);
dispatch( dispatch(
newHttpRequest({ newHttpRequest({
requestName: values.requestName, requestName: values.requestName,
requestType: 'http-request', requestType: curlRequestTypeDetected,
requestUrl: request.url, requestUrl: request.url,
requestMethod: request.method, requestMethod: request.method,
collectionUid: collection.uid, collectionUid: collection.uid,
@ -158,6 +193,12 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
formik.setFieldValue('requestType', 'from-curl'); formik.setFieldValue('requestType', 'from-curl');
formik.setFieldValue('curlCommand', pastedData); formik.setFieldValue('curlCommand', pastedData);
// Identify the request type
const request = getRequestFromCurlCommand(pastedData);
if (request) {
identifyCurlRequestType(request.url, request.headers, request.body);
}
// Prevent the default paste behavior to avoid pasting into the textarea // Prevent the default paste behavior to avoid pasting into the textarea
event.preventDefault(); event.preventDefault();
} }
@ -165,6 +206,18 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
[formik] [formik]
); );
const handleCurlCommandChange = (event) => {
formik.handleChange(event);
if (event.target.name === 'curlCommand') {
const curlCommand = event.target.value;
const request = getRequestFromCurlCommand(curlCommand);
if (request) {
identifyCurlRequestType(request.url, request.headers, request.body);
}
}
};
return ( return (
<StyledWrapper> <StyledWrapper>
<Modal size="md" title="New Request" confirmText="Create" handleConfirm={onSubmit} handleCancel={onClose}> <Modal size="md" title="New Request" confirmText="Create" handleConfirm={onSubmit} handleCancel={onClose}>
@ -279,15 +332,37 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
</> </>
) : ( ) : (
<div className="mt-4"> <div className="mt-4">
<div className="flex justify-between">
<label htmlFor="request-url" className="block font-semibold"> <label htmlFor="request-url" className="block font-semibold">
cURL Command cURL Command
</label> </label>
<Dropdown className="dropdown" onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
curlRequestTypeChange('http-request');
}}
>
HTTP
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
curlRequestTypeChange('graphql-request');
}}
>
GraphQL
</div>
</Dropdown>
</div>
<textarea <textarea
name="curlCommand" name="curlCommand"
placeholder="Enter cURL request here.." placeholder="Enter cURL request here.."
className="block textbox w-full mt-4 curl-command" className="block textbox w-full mt-4 curl-command"
value={formik.values.curlCommand} value={formik.values.curlCommand}
onChange={formik.handleChange} onChange={handleCurlCommandChange}
></textarea> ></textarea>
{formik.touched.curlCommand && formik.errors.curlCommand ? ( {formik.touched.curlCommand && formik.errors.curlCommand ? (
<div className="text-red-500">{formik.errors.curlCommand}</div> <div className="text-red-500">{formik.errors.curlCommand}</div>

View File

@ -19,16 +19,23 @@ const createContentType = (mode) => {
} }
}; };
/**
* Creates a list of enabled headers for the request, ensuring no duplicate content-type headers.
*
* @param {Object} request - The request object.
* @param {Object[]} headers - The array of header objects, each containing name, value, and enabled properties.
* @returns {Object[]} - An array of enabled headers with normalized names and values.
*/
const createHeaders = (request, headers) => { const createHeaders = (request, headers) => {
const enabledHeaders = headers const enabledHeaders = headers
.filter((header) => header.enabled) .filter((header) => header.enabled)
.map((header) => ({ .map((header) => ({
name: header.name, name: header.name.toLowerCase(),
value: header.value value: header.value
})); }));
const contentType = createContentType(request.body?.mode); const contentType = createContentType(request.body?.mode);
if (contentType !== '') { if (contentType !== '' && !enabledHeaders.some((header) => header.name === 'content-type')) {
enabledHeaders.push({ name: 'content-type', value: contentType }); enabledHeaders.push({ name: 'content-type', value: contentType });
} }

View File

@ -36,6 +36,12 @@ function getQueries(request) {
return queries; return queries;
} }
/**
* Converts request data to a string based on its content type.
*
* @param {Object} request - The request object containing data and headers.
* @returns {Object} An object containing the data string.
*/
function getDataString(request) { function getDataString(request) {
if (typeof request.data === 'number') { if (typeof request.data === 'number') {
request.data = request.data.toString(); request.data = request.data.toString();
@ -44,8 +50,14 @@ function getDataString(request) {
const contentType = getContentType(request.headers); const contentType = getContentType(request.headers);
if (contentType && contentType.includes('application/json')) { if (contentType && contentType.includes('application/json')) {
try {
const parsedData = JSON.parse(request.data);
return { data: JSON.stringify(parsedData) };
} catch (error) {
console.error('Failed to parse JSON data:', error);
return { data: request.data.toString() }; return { data: request.data.toString() };
} }
}
const parsedQueryString = querystring.parse(request.data, { sort: false }); const parsedQueryString = querystring.parse(request.data, { sort: false });
// if missing `=`, `query-string` will set value as `null`. Reset value as empty string ('') here. // if missing `=`, `query-string` will set value as `null`. Reset value as empty string ('') here.

View File

@ -2,7 +2,7 @@ import { forOwn } from 'lodash';
import { convertToCodeMirrorJson } from 'utils/common'; import { convertToCodeMirrorJson } from 'utils/common';
import curlToJson from './curl-to-json'; import curlToJson from './curl-to-json';
export const getRequestFromCurlCommand = (curlCommand) => { export const getRequestFromCurlCommand = (curlCommand, requestType = 'http-request') => {
const parseFormData = (parsedBody) => { const parseFormData = (parsedBody) => {
const formData = []; const formData = [];
forOwn(parsedBody, (value, key) => { forOwn(parsedBody, (value, key) => {
@ -12,6 +12,22 @@ export const getRequestFromCurlCommand = (curlCommand) => {
return formData; return formData;
}; };
const parseGraphQL = (text) => {
try {
const graphql = JSON.parse(text);
return {
query: graphql.query,
variables: JSON.stringify(graphql.variables, null, 2)
};
} catch (e) {
return {
query: '',
variables: ''
};
}
};
try { try {
if (!curlCommand || typeof curlCommand !== 'string' || curlCommand.length === 0) { if (!curlCommand || typeof curlCommand !== 'string' || curlCommand.length === 0) {
return null; return null;
@ -24,6 +40,8 @@ export const getRequestFromCurlCommand = (curlCommand) => {
Object.keys(parsedHeaders).map((key) => ({ name: key, value: parsedHeaders[key], enabled: true })); Object.keys(parsedHeaders).map((key) => ({ name: key, value: parsedHeaders[key], enabled: true }));
const contentType = headers?.find((h) => h.name.toLowerCase() === 'content-type')?.value; const contentType = headers?.find((h) => h.name.toLowerCase() === 'content-type')?.value;
const parsedBody = request.data;
const body = { const body = {
mode: 'none', mode: 'none',
json: null, json: null,
@ -31,11 +49,15 @@ export const getRequestFromCurlCommand = (curlCommand) => {
xml: null, xml: null,
sparql: null, sparql: null,
multipartForm: null, multipartForm: null,
formUrlEncoded: null formUrlEncoded: null,
graphql: null
}; };
const parsedBody = request.data;
if (parsedBody && contentType && typeof contentType === 'string') { if (parsedBody && contentType && typeof contentType === 'string') {
if (contentType.includes('application/json')) { if (requestType === 'graphql-request' && (contentType.includes('application/json') || contentType.includes('application/graphql'))) {
body.mode = 'graphql';
body.graphql = parseGraphQL(parsedBody);
} else if (contentType.includes('application/json')) {
body.mode = 'json'; body.mode = 'json';
body.json = convertToCodeMirrorJson(parsedBody); body.json = convertToCodeMirrorJson(parsedBody);
} else if (contentType.includes('text/xml')) { } else if (contentType.includes('text/xml')) {