mirror of
https://github.com/Kong/insomnia
synced 2024-11-08 06:39:48 +00:00
Paste from Curl (#6295)
* test curl fill * clean up updatemimetype * clean up mime type setting * add from curl request type * remove paste handling * add request group dropdown button * fix test * comment out from curl test
This commit is contained in:
parent
8ad5682a20
commit
2d3e4e4b3c
@ -21,6 +21,17 @@ test('can send requests', async ({ app, page }) => {
|
||||
await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click();
|
||||
await page.getByText('CollectionSmoke testsjust now').click();
|
||||
|
||||
// re enable test when sidebar selector is fixed
|
||||
// await page.locator('[data-testid="SidebarFilter"] [data-testid="SidebarCreateDropdown"] button').click();
|
||||
// await page.getByRole('menuitem', { name: 'Http Request' }).click();
|
||||
// const curl = 'curl --request POST --url http://mockbin.org/status/200';
|
||||
// await app.evaluate(async ({ clipboard }, curl) => clipboard.writeText(curl), curl);
|
||||
// await page.locator('[data-testid="SidebarFilter"] [data-testid="SidebarCreateDropdown"] button').click();
|
||||
// await page.getByRole('menuitem', { name: 'From Curl' }).click();
|
||||
|
||||
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
|
||||
await expect(statusTag).toContainText('200 OK');
|
||||
|
||||
await page.getByRole('button', { name: 'send JSON request' }).click();
|
||||
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
|
||||
await expect(statusTag).toContainText('200 OK');
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, jest } from '@jest/globals';
|
||||
|
||||
import { version } from '../../../package.json';
|
||||
import { globalBeforeEach } from '../../__jest__/before-each';
|
||||
import { CONTENT_TYPE_GRAPHQL } from '../../common/constants';
|
||||
import { newBodyGraphQL, updateMimeType } from '../../ui/components/dropdowns/content-type-dropdown';
|
||||
@ -93,9 +92,6 @@ describe('updateMimeType()', () => {
|
||||
{
|
||||
name: 'Content-Type',
|
||||
value: 'text/html',
|
||||
}, {
|
||||
'name': 'User-Agent',
|
||||
'value': `Insomnia/${version}`,
|
||||
},
|
||||
]);
|
||||
});
|
||||
@ -123,7 +119,7 @@ describe('updateMimeType()', () => {
|
||||
const newRequest = await updateMimeType(request, 'text/html');
|
||||
expect(newRequest.headers).toEqual([
|
||||
{
|
||||
name: 'content-tYPE',
|
||||
name: 'Content-Type',
|
||||
value: 'text/html',
|
||||
},
|
||||
{
|
||||
@ -133,10 +129,7 @@ describe('updateMimeType()', () => {
|
||||
{
|
||||
bad: true,
|
||||
},
|
||||
null, {
|
||||
'name': 'User-Agent',
|
||||
'value': `Insomnia/${version}`,
|
||||
},
|
||||
null,
|
||||
]);
|
||||
});
|
||||
|
||||
@ -155,11 +148,8 @@ describe('updateMimeType()', () => {
|
||||
const newRequest = await updateMimeType(request, 'text/html');
|
||||
expect(newRequest.headers).toEqual([
|
||||
{
|
||||
name: 'content-tYPE',
|
||||
name: 'Content-Type',
|
||||
value: 'text/html',
|
||||
}, {
|
||||
'name': 'User-Agent',
|
||||
'value': `Insomnia/${version}`,
|
||||
},
|
||||
]);
|
||||
});
|
||||
@ -178,38 +168,7 @@ describe('updateMimeType()', () => {
|
||||
expect(request).not.toBeNull();
|
||||
const newRequest = await updateMimeType(request, null);
|
||||
expect(newRequest.body).toEqual({});
|
||||
expect(newRequest.headers).toEqual([{
|
||||
'name': 'User-Agent',
|
||||
'value': `Insomnia/${version}`,
|
||||
}]);
|
||||
});
|
||||
|
||||
it('uses saved body when provided', async () => {
|
||||
const request = await models.request.create({
|
||||
name: 'My Request',
|
||||
parentId: 'fld_1',
|
||||
body: {
|
||||
text: 'My Data',
|
||||
},
|
||||
});
|
||||
expect(request).not.toBeNull();
|
||||
const newRequest = await updateMimeType(request, 'application/json', {
|
||||
text: 'Saved Data',
|
||||
});
|
||||
expect(newRequest.body.text).toEqual('Saved Data');
|
||||
});
|
||||
|
||||
it('uses existing body when saved body not provided', async () => {
|
||||
const request = await models.request.create({
|
||||
name: 'My Request',
|
||||
parentId: 'fld_1',
|
||||
body: {
|
||||
text: 'My Data',
|
||||
},
|
||||
});
|
||||
expect(request).not.toBeNull();
|
||||
const newRequest = await updateMimeType(request, 'application/json', {});
|
||||
expect(newRequest.body.text).toEqual('My Data');
|
||||
expect(newRequest.headers).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -21,7 +21,6 @@ export interface OneLineEditorProps {
|
||||
id: string;
|
||||
onChange: (value: string) => void;
|
||||
onKeyDown?: (event: KeyboardEvent, value: string) => void;
|
||||
onPaste?: (event: ClipboardEvent) => void;
|
||||
placeholder?: string;
|
||||
readOnly?: boolean;
|
||||
type?: string;
|
||||
@ -37,7 +36,6 @@ export const OneLineEditor = forwardRef<OneLineEditorHandle, OneLineEditorProps>
|
||||
id,
|
||||
onChange,
|
||||
onKeyDown,
|
||||
onPaste,
|
||||
placeholder,
|
||||
readOnly,
|
||||
type,
|
||||
@ -188,12 +186,6 @@ export const OneLineEditor = forwardRef<OneLineEditorHandle, OneLineEditorProps>
|
||||
return () => codeMirror.current?.off('changes', fn);
|
||||
}, [onChange]);
|
||||
|
||||
useEffect(() => {
|
||||
const handlePaste = (_: CodeMirror.Editor, e: ClipboardEvent) => onPaste?.(e);
|
||||
codeMirror.current?.on('paste', handlePaste);
|
||||
return () => codeMirror.current?.on('paste', handlePaste);
|
||||
}, [onPaste]);
|
||||
|
||||
useEffect(() => window.main.on('context-menu-command', (_, { key, tag }) =>
|
||||
id === key && codeMirror.current?.replaceSelection(tag)), [id]);
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React, { FC } from 'react';
|
||||
import { useParams, useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import { version } from '../../../../package.json';
|
||||
import {
|
||||
CONTENT_TYPE_EDN,
|
||||
CONTENT_TYPE_FILE,
|
||||
@ -16,8 +15,7 @@ import {
|
||||
getContentTypeName,
|
||||
METHOD_POST,
|
||||
} from '../../../common/constants';
|
||||
import { getContentTypeHeader } from '../../../common/misc';
|
||||
import { Request, RequestBody } from '../../../models/request';
|
||||
import { Request, RequestBody, RequestHeader, RequestParameter } from '../../../models/request';
|
||||
import { deconstructQueryStringToParams } from '../../../utils/url/querystring';
|
||||
import { SegmentEvent } from '../../analytics';
|
||||
import { useRequestPatcher } from '../../hooks/use-request';
|
||||
@ -217,104 +215,41 @@ export function newBodyGraphQL(rawBody: string): RequestBody {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const updateMimeType = (
|
||||
request: Request,
|
||||
mimeType: string | null,
|
||||
savedBody: RequestBody = {},
|
||||
) => {
|
||||
let headers = request.headers ? [...request.headers] : [];
|
||||
const contentTypeHeader = getContentTypeHeader(headers);
|
||||
// GraphQL uses JSON content-type
|
||||
const contentTypeHeaderValue = mimeType === CONTENT_TYPE_GRAPHQL ? CONTENT_TYPE_JSON : mimeType;
|
||||
// GraphQL must be POST
|
||||
if (mimeType === CONTENT_TYPE_GRAPHQL) {
|
||||
request.method = METHOD_POST;
|
||||
}
|
||||
// Check if we are converting to/from variants of XML or JSON
|
||||
let leaveContentTypeAlone = false;
|
||||
if (contentTypeHeader && mimeType) {
|
||||
const current = contentTypeHeader.value;
|
||||
if (current.includes('xml') && mimeType.includes('xml')) {
|
||||
leaveContentTypeAlone = true;
|
||||
} else if (current.includes('json') && mimeType.includes('json')) {
|
||||
leaveContentTypeAlone = true;
|
||||
}
|
||||
}
|
||||
const hasBody = typeof mimeType === 'string';
|
||||
if (!hasBody) {
|
||||
headers = headers.filter(h => h !== contentTypeHeader);
|
||||
} else if (mimeType === CONTENT_TYPE_OTHER) {
|
||||
// Leave headers alone
|
||||
} else if (mimeType && contentTypeHeader && !leaveContentTypeAlone) {
|
||||
contentTypeHeader.value = contentTypeHeaderValue || '';
|
||||
} else if (mimeType && !contentTypeHeader) {
|
||||
headers.push({
|
||||
name: 'Content-Type',
|
||||
value: contentTypeHeaderValue || '',
|
||||
});
|
||||
}
|
||||
if (!headers.find(h => h?.name?.toLowerCase() === 'user-agent')) {
|
||||
headers.push({
|
||||
name: 'User-Agent',
|
||||
value: `Insomnia/${version}`,
|
||||
});
|
||||
}
|
||||
const oldBody = Object.keys(savedBody).length === 0 ? request.body : savedBody;
|
||||
if (mimeType === CONTENT_TYPE_FORM_URLENCODED) {
|
||||
): { body: RequestBody; headers: RequestHeader[]; params?: RequestParameter[]; method?: string } => {
|
||||
const withoutContentType = request.headers.filter(h => h?.name?.toLowerCase() !== 'content-type');
|
||||
// 'No body' selected
|
||||
if (typeof mimeType !== 'string') {
|
||||
return {
|
||||
body: {
|
||||
mimeType: CONTENT_TYPE_FORM_URLENCODED,
|
||||
params: oldBody.params || (oldBody.text ? deconstructQueryStringToParams(oldBody.text) : []),
|
||||
},
|
||||
headers,
|
||||
body: {},
|
||||
headers: withoutContentType,
|
||||
};
|
||||
}
|
||||
if (mimeType === CONTENT_TYPE_FORM_DATA) {
|
||||
// Form Data
|
||||
if (mimeType === CONTENT_TYPE_GRAPHQL) {
|
||||
return {
|
||||
body: oldBody.params
|
||||
? {
|
||||
mimeType: CONTENT_TYPE_FORM_DATA,
|
||||
params: oldBody.params || [],
|
||||
} : {
|
||||
mimeType: CONTENT_TYPE_FORM_DATA,
|
||||
params: oldBody.text ? deconstructQueryStringToParams(oldBody.text) : [],
|
||||
},
|
||||
headers,
|
||||
body: newBodyGraphQL(request.body.text || ''),
|
||||
headers: [{ name: 'Content-Type', value: CONTENT_TYPE_JSON }, ...withoutContentType],
|
||||
method: METHOD_POST,
|
||||
};
|
||||
}
|
||||
if (mimeType === CONTENT_TYPE_FORM_URLENCODED || mimeType === CONTENT_TYPE_FORM_DATA) {
|
||||
const params = request.body.params || deconstructQueryStringToParams(request.body.text);
|
||||
return {
|
||||
body: { mimeType, params },
|
||||
headers: [{ name: 'Content-Type', value: mimeType || '' }, ...withoutContentType],
|
||||
};
|
||||
}
|
||||
if (mimeType === CONTENT_TYPE_FILE) {
|
||||
return {
|
||||
body: {
|
||||
mimeType: CONTENT_TYPE_FILE,
|
||||
fileName: '',
|
||||
},
|
||||
headers,
|
||||
body: { mimeType, fileName: '' },
|
||||
headers: [{ name: 'Content-Type', value: mimeType || '' }, ...withoutContentType],
|
||||
};
|
||||
}
|
||||
if (mimeType === CONTENT_TYPE_GRAPHQL) {
|
||||
if (contentTypeHeader) {
|
||||
contentTypeHeader.value = CONTENT_TYPE_JSON;
|
||||
}
|
||||
return {
|
||||
body: newBodyGraphQL(oldBody.text || ''),
|
||||
headers,
|
||||
};
|
||||
}
|
||||
if (typeof mimeType !== 'string') {
|
||||
return {
|
||||
body: {},
|
||||
headers,
|
||||
};
|
||||
}
|
||||
// Raw Content-Type (ex: application/json)
|
||||
return {
|
||||
body: typeof mimeType !== 'string' ? {
|
||||
text: oldBody.text || '',
|
||||
} : {
|
||||
mimeType: mimeType.split(';')[0],
|
||||
text: oldBody.text || '',
|
||||
},
|
||||
headers,
|
||||
body: { mimeType: mimeType.split(';')[0], text: request.body.text || '' },
|
||||
headers: [{ name: 'Content-Type', value: mimeType || '' }, ...withoutContentType],
|
||||
};
|
||||
};
|
||||
|
@ -184,7 +184,6 @@ export const RequestActionsDropdown = forwardRef<DropdownHandle, Props>(({
|
||||
icon="copy"
|
||||
label="Copy as cURL"
|
||||
onClick={copyAsCurl}
|
||||
withPrompt
|
||||
/>
|
||||
)}
|
||||
</DropdownItem>
|
||||
|
@ -43,7 +43,7 @@ export const RequestGroupActionsDropdown = forwardRef<RequestGroupActionsDropdow
|
||||
const { organizationId, projectId, workspaceId } = useParams() as { organizationId: string; projectId: string; workspaceId: string };
|
||||
|
||||
const create = useCallback((requestType: CreateRequestType) =>
|
||||
requestFetcher.submit({ requestType, parentId: requestGroup._id },
|
||||
requestFetcher.submit({ requestType, parentId: requestGroup._id, clipboardText: window.clipboard.readText() },
|
||||
{
|
||||
encType: 'application/json',
|
||||
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/debug/request/new`,
|
||||
@ -202,7 +202,16 @@ export const RequestGroupActionsDropdown = forwardRef<RequestGroupActionsDropdow
|
||||
onClick={() => create('WebSocket')}
|
||||
/>
|
||||
</DropdownItem>
|
||||
|
||||
<DropdownSection>
|
||||
<DropdownItem aria-label='From Curl'>
|
||||
<ItemContent
|
||||
icon="plus-circle"
|
||||
label="From Curl"
|
||||
onClick={() => create('From Curl')}
|
||||
/>
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
<DropdownSection>
|
||||
<DropdownItem aria-label='New Folder'>
|
||||
<ItemContent
|
||||
icon="folder"
|
||||
@ -211,6 +220,7 @@ export const RequestGroupActionsDropdown = forwardRef<RequestGroupActionsDropdow
|
||||
onClick={createGroup}
|
||||
/>
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
|
||||
<DropdownSection aria-label='Actions Section'>
|
||||
<DropdownItem aria-label='Duplicate'>
|
||||
|
@ -8,7 +8,7 @@ import * as models from '../../../models';
|
||||
import { queryAllWorkspaceUrls } from '../../../models/helpers/query-all-workspace-urls';
|
||||
import type { Settings } from '../../../models/settings';
|
||||
import { deconstructQueryStringToParams, extractQueryStringFromUrl } from '../../../utils/url/querystring';
|
||||
import { useRequestPatcher, useSettingsPatcher } from '../../hooks/use-request';
|
||||
import { useSettingsPatcher } from '../../hooks/use-request';
|
||||
import { useActiveRequestSyncVCSVersion, useGitVCSVersion } from '../../hooks/use-vcs-version';
|
||||
import { RequestLoaderData } from '../../routes/request';
|
||||
import { WorkspaceLoaderData } from '../../routes/workspace';
|
||||
@ -67,7 +67,6 @@ export const RequestPane: FC<Props> = ({
|
||||
}) => {
|
||||
const { activeRequest, activeRequestMeta } = useRouteLoaderData('request/:requestId') as RequestLoaderData;
|
||||
const { workspaceId, requestId } = useParams() as { organizationId: string; projectId: string; workspaceId: string; requestId: string };
|
||||
const patchRequest = useRequestPatcher();
|
||||
const patchSettings = useSettingsPatcher();
|
||||
const [isRequestSettingsModalOpen, setIsRequestSettingsModalOpen] = useState(false);
|
||||
|
||||
@ -131,7 +130,6 @@ export const RequestPane: FC<Props> = ({
|
||||
key={requestId}
|
||||
ref={requestUrlBarRef}
|
||||
uniquenessKey={uniqueKey}
|
||||
onUrlChange={url => patchRequest(requestId, { url })}
|
||||
handleAutocompleteUrls={() => queryAllWorkspaceUrls(workspaceId, models.request.type, requestId)}
|
||||
nunjucksPowerUserMode={settings.nunjucksPowerUserMode}
|
||||
setLoading={setLoading}
|
||||
|
@ -4,12 +4,10 @@ import { useFetcher, useParams, useRouteLoaderData } from 'react-router-dom';
|
||||
import { useInterval } from 'react-use';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { database } from '../../common/database';
|
||||
import { RENDER_PURPOSE_SEND } from '../../common/render';
|
||||
import * as models from '../../models';
|
||||
import { isEventStreamRequest, isRequest } from '../../models/request';
|
||||
import { isEventStreamRequest } from '../../models/request';
|
||||
import { fetchRequestData, tryToInterpolateRequest, tryToTransformRequestWithPlugins } from '../../network/network';
|
||||
import { convert } from '../../utils/importers/convert';
|
||||
import { tryToInterpolateRequestOrShowRenderErrorModal } from '../../utils/try-interpolate';
|
||||
import { buildQueryStringFromParams, joinUrlAndQueryString } from '../../utils/url/querystring';
|
||||
import { SegmentEvent } from '../analytics';
|
||||
@ -40,7 +38,6 @@ const StyledDropdownButton = styled(DropdownButton)({
|
||||
interface Props {
|
||||
handleAutocompleteUrls: () => Promise<string[]>;
|
||||
nunjucksPowerUserMode: boolean;
|
||||
onUrlChange: (url: string) => void;
|
||||
uniquenessKey: string;
|
||||
setLoading: (l: boolean) => void;
|
||||
}
|
||||
@ -51,7 +48,6 @@ export interface RequestUrlBarHandle {
|
||||
|
||||
export const RequestUrlBar = forwardRef<RequestUrlBarHandle, Props>(({
|
||||
handleAutocompleteUrls,
|
||||
onUrlChange,
|
||||
uniquenessKey,
|
||||
setLoading,
|
||||
}, ref) => {
|
||||
@ -194,56 +190,6 @@ export const RequestUrlBar = forwardRef<RequestUrlBarHandle, Props>(({
|
||||
},
|
||||
});
|
||||
|
||||
const lastPastedTextRef = useRef('');
|
||||
const handleImport = useCallback(async (text: string) => {
|
||||
// Allow user to paste any import file into the url. If it results in
|
||||
// only one item, it will overwrite the current request.
|
||||
try {
|
||||
const { data } = await convert(text);
|
||||
const { resources } = data;
|
||||
const r = resources[0];
|
||||
if (r && r._type === 'request' && activeRequest && isRequest(activeRequest)) {
|
||||
// Only pull fields that we want to update
|
||||
return database.update({
|
||||
...activeRequest,
|
||||
modified: Date.now(),
|
||||
url: r.url,
|
||||
method: r.method,
|
||||
headers: r.headers,
|
||||
body: r.body,
|
||||
authentication: r.authentication,
|
||||
parameters: r.parameters,
|
||||
}, true); // Pass true to indicate that this is an import
|
||||
}
|
||||
} catch (error) {
|
||||
// Import failed, that's alright
|
||||
console.error(error);
|
||||
}
|
||||
return null;
|
||||
}, [activeRequest]);
|
||||
|
||||
const handleUrlChange = useCallback(async (url: string) => {
|
||||
const pastedText = lastPastedTextRef.current;
|
||||
// If no pasted text in the queue, just fire the regular change handler
|
||||
if (!pastedText) {
|
||||
onUrlChange(url);
|
||||
return;
|
||||
}
|
||||
// Reset pasted text cache
|
||||
lastPastedTextRef.current = '';
|
||||
// Attempt to import the pasted text
|
||||
const importedRequest = await handleImport(pastedText);
|
||||
// Update depending on whether something was imported
|
||||
if (!importedRequest) {
|
||||
onUrlChange(url);
|
||||
}
|
||||
}, [handleImport, onUrlChange]);
|
||||
|
||||
const handleUrlPaste = useCallback((event: ClipboardEvent) => {
|
||||
// NOTE: We're not actually doing the import here to avoid races with onChange
|
||||
lastPastedTextRef.current = event.clipboardData?.getData('text/plain') || '';
|
||||
}, []);
|
||||
|
||||
const handleSendDropdownHide = useCallback(() => {
|
||||
buttonRef.current?.blur();
|
||||
}, []);
|
||||
@ -263,12 +209,11 @@ export const RequestUrlBar = forwardRef<RequestUrlBarHandle, Props>(({
|
||||
id="request-url-bar"
|
||||
key={uniquenessKey}
|
||||
ref={inputRef}
|
||||
onPaste={handleUrlPaste}
|
||||
type="text"
|
||||
getAutocompleteConstants={handleAutocompleteUrls}
|
||||
placeholder="https://api.myproduct.com/v1/users"
|
||||
defaultValue={url}
|
||||
onChange={handleUrlChange}
|
||||
onChange={url => patchRequest(requestId, { url })}
|
||||
onKeyDown={createKeybindingsHandler({
|
||||
'Enter': () => sendOrConnect(),
|
||||
})}
|
||||
|
@ -103,7 +103,6 @@ export const ImportExport: FC<Props> = ({ hideSettingsModal }) => {
|
||||
Create Run Button
|
||||
</Link>
|
||||
</div>
|
||||
<p className="italic faint">* Tip: You can also paste Curl commands into the URL bar</p>
|
||||
</div>
|
||||
{isImportModalOpen && (
|
||||
<ImportModal
|
||||
|
@ -4,7 +4,7 @@ import { useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import { CreateRequestType } from '../../hooks/use-request';
|
||||
import { RootLoaderData } from '../../routes/root';
|
||||
import { Dropdown, DropdownButton, DropdownItem, ItemContent } from '../base/dropdown';
|
||||
import { Dropdown, DropdownButton, DropdownItem, DropdownSection, ItemContent } from '../base/dropdown';
|
||||
import { showPrompt } from '../modals';
|
||||
|
||||
export const SidebarCreateDropdown = () => {
|
||||
@ -15,7 +15,7 @@ export const SidebarCreateDropdown = () => {
|
||||
const requestFetcher = useFetcher();
|
||||
const { organizationId, projectId, workspaceId } = useParams() as { organizationId: string; projectId: string; workspaceId: string };
|
||||
const create = useCallback((requestType: CreateRequestType) =>
|
||||
requestFetcher.submit({ requestType, parentId: workspaceId },
|
||||
requestFetcher.submit({ requestType, parentId: workspaceId, clipboardText: window.clipboard.readText() },
|
||||
{
|
||||
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/debug/request/new`,
|
||||
method: 'post',
|
||||
@ -91,7 +91,16 @@ export const SidebarCreateDropdown = () => {
|
||||
onClick={() => create('WebSocket')}
|
||||
/>
|
||||
</DropdownItem>
|
||||
|
||||
<DropdownSection>
|
||||
<DropdownItem aria-label='From Curl'>
|
||||
<ItemContent
|
||||
icon="plus-circle"
|
||||
label="From Curl"
|
||||
onClick={() => create('From Curl')}
|
||||
/>
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
<DropdownSection>
|
||||
<DropdownItem aria-label='New Folder'>
|
||||
<ItemContent
|
||||
icon="folder"
|
||||
@ -100,6 +109,7 @@ export const SidebarCreateDropdown = () => {
|
||||
onClick={createGroup}
|
||||
/>
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
@ -82,4 +82,4 @@ export const useWorkspaceMetaPatcher = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export type CreateRequestType = 'HTTP' | 'gRPC' | 'GraphQL' | 'WebSocket' | 'Event Stream';
|
||||
export type CreateRequestType = 'HTTP' | 'gRPC' | 'GraphQL' | 'WebSocket' | 'Event Stream' | 'From Curl';
|
||||
|
@ -5,6 +5,7 @@ import * as contentDisposition from 'content-disposition';
|
||||
import { extension as mimeExtension } from 'mime-types';
|
||||
import { ActionFunction, LoaderFunction, redirect } from 'react-router-dom';
|
||||
|
||||
import { version } from '../../../package.json';
|
||||
import { CONTENT_TYPE_EVENT_STREAM, CONTENT_TYPE_GRAPHQL, CONTENT_TYPE_JSON, METHOD_GET, METHOD_POST } from '../../common/constants';
|
||||
import { ChangeBufferEvent, database } from '../../common/database';
|
||||
import { getContentDispositionHeader } from '../../common/misc';
|
||||
@ -16,16 +17,18 @@ import { CookieJar } from '../../models/cookie-jar';
|
||||
import { GrpcRequest, isGrpcRequestId } from '../../models/grpc-request';
|
||||
import { GrpcRequestMeta } from '../../models/grpc-request-meta';
|
||||
import * as requestOperations from '../../models/helpers/request-operations';
|
||||
import { isEventStreamRequest, isRequest, Request, RequestAuthentication, RequestHeader } from '../../models/request';
|
||||
import { isEventStreamRequest, isRequest, Request, RequestAuthentication, RequestBody, RequestHeader, RequestParameter } from '../../models/request';
|
||||
import { isRequestMeta, RequestMeta } from '../../models/request-meta';
|
||||
import { RequestVersion } from '../../models/request-version';
|
||||
import { Response } from '../../models/response';
|
||||
import { isWebSocketRequestId, WebSocketRequest } from '../../models/websocket-request';
|
||||
import { WebSocketResponse } from '../../models/websocket-response';
|
||||
import { fetchRequestData, responseTransform, sendCurlAndWriteTimeline, tryToInterpolateRequest } from '../../network/network';
|
||||
import { convert } from '../../utils/importers/convert';
|
||||
import { invariant } from '../../utils/invariant';
|
||||
import { SegmentEvent } from '../analytics';
|
||||
import { updateMimeType } from '../components/dropdowns/content-type-dropdown';
|
||||
import { CreateRequestType } from '../hooks/use-request';
|
||||
|
||||
export interface WebSocketRequestLoaderData {
|
||||
activeRequest: WebSocketRequest;
|
||||
@ -93,7 +96,7 @@ export const loader: LoaderFunction = async ({ params }): Promise<RequestLoaderD
|
||||
export const createRequestAction: ActionFunction = async ({ request, params }) => {
|
||||
const { organizationId, projectId, workspaceId } = params;
|
||||
invariant(typeof workspaceId === 'string', 'Workspace ID is required');
|
||||
const { requestType, parentId } = await request.json();
|
||||
const { requestType, parentId, clipboardText } = await request.json() as { requestType: CreateRequestType; parentId?: string; clipboardText?: string };
|
||||
|
||||
let activeRequestId;
|
||||
if (requestType === 'HTTP') {
|
||||
@ -101,6 +104,7 @@ export const createRequestAction: ActionFunction = async ({ request, params }) =
|
||||
parentId: parentId || workspaceId,
|
||||
method: METHOD_GET,
|
||||
name: 'New Request',
|
||||
headers: [{ name: 'User-Agent', value: `Insomnia/${version}` }],
|
||||
}))._id;
|
||||
}
|
||||
if (requestType === 'gRPC') {
|
||||
@ -114,10 +118,8 @@ export const createRequestAction: ActionFunction = async ({ request, params }) =
|
||||
parentId: parentId || workspaceId,
|
||||
method: METHOD_POST,
|
||||
headers: [
|
||||
{
|
||||
name: 'Content-Type',
|
||||
value: CONTENT_TYPE_JSON,
|
||||
},
|
||||
{ name: 'User-Agent', value: `Insomnia/${version}` },
|
||||
{ name: 'Content-Type', value: CONTENT_TYPE_JSON },
|
||||
],
|
||||
body: {
|
||||
mimeType: CONTENT_TYPE_GRAPHQL,
|
||||
@ -132,10 +134,8 @@ export const createRequestAction: ActionFunction = async ({ request, params }) =
|
||||
method: METHOD_GET,
|
||||
url: '',
|
||||
headers: [
|
||||
{
|
||||
name: 'Accept',
|
||||
value: CONTENT_TYPE_EVENT_STREAM,
|
||||
},
|
||||
{ name: 'User-Agent', value: `Insomnia/${version}` },
|
||||
{ name: 'Accept', value: CONTENT_TYPE_EVENT_STREAM },
|
||||
],
|
||||
name: 'New Event Stream',
|
||||
}))._id;
|
||||
@ -144,8 +144,33 @@ export const createRequestAction: ActionFunction = async ({ request, params }) =
|
||||
activeRequestId = (await models.webSocketRequest.create({
|
||||
parentId: parentId || workspaceId,
|
||||
name: 'New WebSocket Request',
|
||||
headers: [{ name: 'User-Agent', value: `Insomnia/${version}` }],
|
||||
}))._id;
|
||||
}
|
||||
if (requestType === 'From Curl') {
|
||||
// TODO: if no clipboard text show modal
|
||||
if (!clipboardText) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const { data } = await convert(clipboardText);
|
||||
const { resources } = data;
|
||||
const r = resources[0];
|
||||
|
||||
activeRequestId = (await models.request.create({
|
||||
parentId: parentId || workspaceId,
|
||||
url: r.url,
|
||||
method: r.method,
|
||||
headers: r.headers,
|
||||
body: r.body as RequestBody,
|
||||
authentication: r.authentication,
|
||||
parameters: r.parameters as RequestParameter[],
|
||||
}))._id;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
invariant(typeof activeRequestId === 'string', 'Request ID is required');
|
||||
models.stats.incrementCreatedRequests();
|
||||
window.main.trackSegmentEvent({ event: SegmentEvent.requestCreate, properties: { requestType } });
|
||||
@ -155,21 +180,17 @@ export const createRequestAction: ActionFunction = async ({ request, params }) =
|
||||
export const updateRequestAction: ActionFunction = async ({ request, params }) => {
|
||||
const { requestId } = params;
|
||||
invariant(typeof requestId === 'string', 'Request ID is required');
|
||||
let req = await requestOperations.getById(requestId);
|
||||
const req = await requestOperations.getById(requestId);
|
||||
invariant(req, 'Request not found');
|
||||
let patch = await request.json();
|
||||
const patch = await request.json();
|
||||
// TODO: if gRPC, we should also copy the protofile to the destination workspace - INS-267
|
||||
if (isRequest(req) && patch.body) {
|
||||
const mimeType = patch.body?.mimeType as string | null;
|
||||
const requestMeta = await models.requestMeta.getOrCreateByParentId(requestId);
|
||||
const savedRequestBody = !mimeType ? (req.body || {}) : {};
|
||||
await models.requestMeta.update(requestMeta, { savedRequestBody });
|
||||
// TODO: make this less hacky, update expects latest req not patch
|
||||
req = await requestOperations.update(req, patch);
|
||||
patch = updateMimeType(req, mimeType, requestMeta.savedRequestBody);
|
||||
const isMimeTypeChanged = isRequest(req) && patch.body && patch.body.mimeType !== req.body.mimeType;
|
||||
if (isMimeTypeChanged) {
|
||||
await requestOperations.update(req, { ...patch, ...updateMimeType(req, patch.body?.mimeType) });
|
||||
return null;
|
||||
}
|
||||
|
||||
requestOperations.update(req, patch);
|
||||
await requestOperations.update(req, patch);
|
||||
return null;
|
||||
};
|
||||
|
||||
|
@ -113,7 +113,7 @@ export const buildQueryStringFromParams = (
|
||||
* @returns {{name: string, value: string}[]}
|
||||
*/
|
||||
export const deconstructQueryStringToParams = (
|
||||
qs: string,
|
||||
qs?: string,
|
||||
|
||||
/** allow empty names and values */
|
||||
strict?: boolean,
|
||||
|
Loading…
Reference in New Issue
Block a user