diff --git a/packages/insomnia-smoke-test/tests/smoke/environment-editor-interactions.test.ts b/packages/insomnia-smoke-test/tests/smoke/environment-editor-interactions.test.ts
index 864f12517b7..067bf016e54 100644
--- a/packages/insomnia-smoke-test/tests/smoke/environment-editor-interactions.test.ts
+++ b/packages/insomnia-smoke-test/tests/smoke/environment-editor-interactions.test.ts
@@ -73,7 +73,7 @@ test.describe('Environment Editor', async () => {
await page.getByLabel('Request Collection').getByTestId('New Request').press('Enter');
// Add number variable to request body
- await page.getByRole('tab', { name: 'Plain' }).click();
+ await page.getByRole('tab', { name: 'Body' }).click();
await page.locator('pre').filter({ hasText: '_.exampleObject.anotherNumber' }).press('Enter');
await page.getByTestId('CodeEditor').getByRole('textbox').press('Enter');
diff --git a/packages/insomnia-smoke-test/tests/smoke/graphql.test.ts b/packages/insomnia-smoke-test/tests/smoke/graphql.test.ts
index b747a66fa75..33b4940b339 100644
--- a/packages/insomnia-smoke-test/tests/smoke/graphql.test.ts
+++ b/packages/insomnia-smoke-test/tests/smoke/graphql.test.ts
@@ -21,9 +21,9 @@ test('can render schema and send GraphQL requests', async ({ app, page }) => {
// Open the graphql request
await page.getByLabel('Request Collection').getByTestId('GraphQL request').press('Enter');
- await page.getByRole('tab', { name: 'GraphQL' }).click();
+ await page.getByRole('tab', { name: 'Body' }).click();
// Assert the schema is fetched after switching to GraphQL request
- await expect(page.locator('.graphql-editor__meta')).toContainText('schema fetched just now');
+ await expect(page.getByText('Schema fetched just now')).toBeVisible();
// Assert schema documentation stuff
await page.getByRole('button', { name: 'schema' }).click();
@@ -63,9 +63,9 @@ test('can render schema and send GraphQL requests with object variables', async
// Open the graphql request
await page.getByLabel('Request Collection').getByTestId('GraphQL request with variables').press('Enter');
- await page.getByRole('tab', { name: 'GraphQL' }).click();
+ await page.getByRole('tab', { name: 'Body' }).click();
// Assert the schema is fetched after switching to GraphQL request
- await expect(page.locator('.graphql-editor__meta')).toContainText('schema fetched just now');
+ await expect(page.getByText('Schema fetched just now')).toBeVisible();
// Assert schema documentation stuff
await page.getByRole('button', { name: 'schema' }).click();
@@ -105,9 +105,9 @@ test('can render numeric environment', async ({ app, page }) => {
// Open the graphql request
await page.getByLabel('Request Collection').getByTestId('GraphQL request with number').press('Enter');
- await page.getByRole('tab', { name: 'GraphQL' }).click();
+ await page.getByRole('tab', { name: 'Body' }).click();
// Assert the schema is fetched after switching to GraphQL request
- await expect(page.locator('.graphql-editor__meta')).toContainText('schema fetched just now');
+ await expect(page.getByText('Schema fetched just now')).toBeVisible();
// Assert schema documentation stuff
await page.getByRole('button', { name: 'schema' }).click();
@@ -144,7 +144,7 @@ test('can send GraphQL requests after editing and prettifying query', async ({ a
await page.getByLabel('Request Collection').getByTestId('GraphQL request').press('Enter');
// Edit and prettify query
- await page.getByRole('tab', { name: 'GraphQL' }).click();
+ await page.getByRole('tab', { name: 'Body' }).click();
await page.locator('pre[role="presentation"]:has-text("bearer")').click();
await page.locator('.app').press('Enter');
await page.locator('text=Prettify GraphQL').click();
diff --git a/packages/insomnia-smoke-test/tests/smoke/oauth.test.ts b/packages/insomnia-smoke-test/tests/smoke/oauth.test.ts
index 7e763643001..2193b2eea32 100644
--- a/packages/insomnia-smoke-test/tests/smoke/oauth.test.ts
+++ b/packages/insomnia-smoke-test/tests/smoke/oauth.test.ts
@@ -47,7 +47,8 @@ test('can make oauth2 requests', async ({ app, page }) => {
await expect(responseBody).toContainText('"sub": "admin"');
// Navigate to the OAuth2 Tab and refresh the token from there
- await page.getByRole('tab', { name: 'OAuth 2' }).click();
+ await page.getByRole('tab', { name: 'Auth' }).click();
+ await expect(page.getByRole('button', { name: 'OAuth 2.0' })).toBeVisible();
const tokenInput = page.locator('[for="Access-Token"] > input');
const prevToken = await tokenInput.inputValue();
diff --git a/packages/insomnia-smoke-test/tests/smoke/pre-request-script-features.test.ts b/packages/insomnia-smoke-test/tests/smoke/pre-request-script-features.test.ts
index 9f8bb4ad0ef..49b0e108e22 100644
--- a/packages/insomnia-smoke-test/tests/smoke/pre-request-script-features.test.ts
+++ b/packages/insomnia-smoke-test/tests/smoke/pre-request-script-features.test.ts
@@ -217,13 +217,13 @@ test.describe('pre-request features tests', async () => {
// set request body
await page.getByRole('tab', { name: 'Body' }).click();
await page.getByRole('button', { name: 'Body' }).click();
- await page.getByRole('menuitem', { name: 'JSON' }).click();
+ await page.getByRole('option', { name: 'JSON' }).click();
const bodyEditor = page.getByTestId('CodeEditor').getByRole('textbox');
await bodyEditor.fill('{ "rawBody": {{ _.rawBody }}, "urlencodedBody": {{ _.urlencodedBody }}, "gqlBody": {{ _.gqlBody }}, "fileBody": {{ _.fileBody }}, "formdataBody": {{ _.formdataBody }} }');
// enter script
- await page.getByTestId('pre-request-script-tab').click();
+ await page.getByRole('tab', { name: 'Scripts' }).click();
const preRequestScriptEditor = page.getByTestId('CodeEditor').getByRole('textbox');
await preRequestScriptEditor.fill(`
const rawReq = {
@@ -500,10 +500,10 @@ test.describe('unhappy paths', async () => {
// set request body
await page.getByRole('tab', { name: 'Body' }).click();
await page.getByRole('button', { name: 'Body' }).click();
- await page.getByRole('menuitem', { name: 'JSON' }).click();
+ await page.getByRole('option', { name: 'JSON' }).click();
// enter script
- await page.getByTestId('pre-request-script-tab').click();
+ await page.getByRole('tab', { name: 'Scripts' }).click();
const preRequestScriptEditor = page.getByTestId('CodeEditor').getByRole('textbox');
await preRequestScriptEditor.fill(tc.preReqScript);
diff --git a/packages/insomnia-smoke-test/tests/smoke/request-pane-tab.test.ts b/packages/insomnia-smoke-test/tests/smoke/request-pane-tab.test.ts
index e9b4aae2af1..6503cf7ccdf 100644
--- a/packages/insomnia-smoke-test/tests/smoke/request-pane-tab.test.ts
+++ b/packages/insomnia-smoke-test/tests/smoke/request-pane-tab.test.ts
@@ -8,11 +8,11 @@ test('Request tabs', async ({ page }) => {
await page.getByRole('menuitemradio', { name: 'HTTP Request' }).press('Enter');
await page.getByRole('tab', { name: 'Body' }).click();
await page.getByRole('button', { name: 'Body' }).click();
- await page.getByRole('menuitem', { name: 'JSON' }).click();
+ await page.getByRole('option', { name: 'JSON' }).click();
await page.getByRole('tab', { name: 'Auth' }).click();
await page.getByRole('button', { name: 'Auth' }).click();
- await page.getByRole('menuitem', { name: 'OAuth 1.0' }).click();
- await page.getByRole('tab', { name: 'Parameters' }).click();
+ await page.getByLabel('OAuth 1.0', { exact: true }).click();
+ await page.getByRole('tab', { name: 'Params' }).click();
await page.getByRole('tab', { name: 'Headers' }).click();
await page.getByRole('tab', { name: 'Docs' }).click();
await page.locator('text=Add Description').click();
@@ -26,11 +26,11 @@ test('WS tabs', async ({ page }) => {
await page.getByLabel('Create in collection').click();
await page.getByRole('menuitemradio', { name: 'WebSocket Request' }).click();
- await page.getByRole('tab', { name: 'JSON' }).click();
- await page.getByLabel('Websocket request pane tabs').getByRole('button', { name: 'JSON' }).click();
- await page.getByRole('menuitem', { name: 'JSON' }).click();
+ await page.getByRole('tab', { name: 'Body' }).click();
+ await page.getByRole('button', { name: 'JSON' }).click();
+ await page.getByRole('option', { name: 'JSON' }).click();
await page.getByRole('tab', { name: 'Auth' }).click();
- await page.getByRole('tab', { name: 'Parameters' }).click();
+ await page.getByRole('tab', { name: 'Params' }).click();
await page.getByRole('tab', { name: 'Headers' }).click();
await page.getByRole('tab', { name: 'Docs' }).click();
await page.getByRole('button', { name: 'Add Description' }).click();
diff --git a/packages/insomnia/src/index.html b/packages/insomnia/src/index.html
index 72fab88293a..82a08ffcbd3 100644
--- a/packages/insomnia/src/index.html
+++ b/packages/insomnia/src/index.html
@@ -1,5 +1,5 @@
-
+
= ({ authentication, authTypes = defaultTypes, disabled = false }) => {
const { requestId, requestGroupId } = useParams() as { organizationId: string; projectId: string; workspaceId: string; requestId?: string; requestGroupId?: string };
const patchRequest = useRequestPatcher();
@@ -147,57 +149,141 @@ export const AuthDropdown: FC = ({ authentication, authTypes = defaultTyp
requestGroupId && patchRequestGroup(requestGroupId, { authentication: newAuthentication });
}, [authentication, patchRequest, patchRequestGroup, requestGroupId, requestId]);
- const isSelected = useCallback((type: AuthTypes) => {
- return type === getAuthObjectOrNull(authentication)?.type;
- }, [authentication]);
+ const selectedAuthType = getAuthObjectOrNull(authentication)?.type || 'none';
+
+ const authTypesItems: {
+ id: AuthTypes;
+ name: string;
+ }[] = [
+ {
+ id: 'apikey',
+ name: 'API Key',
+ },
+ {
+ id: 'basic',
+ name: 'Basic',
+ },
+ {
+ id: 'digest',
+ name: 'Digest',
+ },
+ {
+ id: 'ntlm',
+ name: 'NTLM',
+ },
+ {
+ id: 'oauth1',
+ name: 'OAuth 1.0',
+ },
+ {
+ id: 'oauth2',
+ name: 'OAuth 2.0',
+ },
+ {
+ id: 'iam',
+ name: 'AWS IAM',
+ },
+ {
+ id: 'bearer',
+ name: 'Bearer Token',
+ },
+ {
+ id: 'hawk',
+ name: 'Hawk',
+ },
+ {
+ id: 'asap',
+ name: 'Atlassian ASAP',
+ },
+ {
+ id: 'netrc',
+ name: 'Netrc',
+ },
+ ];
+
+ const authTypeSections: {
+ id: string;
+ icon: IconName;
+ name: string;
+ items: {
+ id: AuthTypes;
+ name: string;
+ }[];
+ }[] = [
+ {
+ id: 'Auth Types',
+ name: 'Auth Types',
+ icon: 'lock',
+ items: authTypesItems.filter(item => authTypes.includes(item.id)),
+ },
+ {
+ id: 'Other',
+ name: 'Other',
+ icon: 'ellipsis-h',
+ items: [
+ {
+ id: 'none',
+ name: 'None',
+ },
+ ],
+ },
+ ];
return (
-
- {getAuthTypeName(getAuthObjectOrNull(authentication)?.type)}
-
-
- }
+ aria-label="Change Authentication type"
+ name="auth-type"
+ onSelectionChange={authType => {
+ onClick(authType as AuthTypes);
+ }}
+ selectedKey={selectedAuthType}
>
-
- {authTypes.map(authType =>
-
- onClick(authType)}
- />
-
- )}
-
-
-
- onClick('none')}
- />
-
-
- onClick()}
- />
-
-
-
+
+
+
+ {item => (
+
+
+
+ {item => (
+
+ {({ isSelected }) => (
+ <>
+ {item.name}
+ {isSelected && (
+
+ )}
+ >
+ )}
+
+ )}
+
+
+ )}
+
+
+
);
};
diff --git a/packages/insomnia/src/ui/components/dropdowns/content-type-dropdown.tsx b/packages/insomnia/src/ui/components/dropdowns/content-type-dropdown.tsx
index de67873f9b6..bd530bfcafc 100644
--- a/packages/insomnia/src/ui/components/dropdowns/content-type-dropdown.tsx
+++ b/packages/insomnia/src/ui/components/dropdowns/content-type-dropdown.tsx
@@ -1,4 +1,6 @@
+import { IconName } from '@fortawesome/fontawesome-svg-core';
import React, { FC } from 'react';
+import { Button, Collection, Header, ListBox, ListBoxItem, Popover, Section, Select, SelectValue } from 'react-aria-components';
import { useParams, useRouteLoaderData } from 'react-router-dom';
import {
@@ -20,12 +22,87 @@ import { deconstructQueryStringToParams } from '../../../utils/url/querystring';
import { SegmentEvent } from '../../analytics';
import { useRequestPatcher } from '../../hooks/use-request';
import { RequestLoaderData } from '../../routes/request';
-import { Dropdown, DropdownButton, DropdownItem, DropdownSection, ItemContent } from '../base/dropdown';
-import { AlertModal } from '../modals/alert-modal';
-import { showModal } from '../modals/index';
+import { Icon } from '../icon';
+import { showAlert } from '../modals/index';
const EMPTY_MIME_TYPE = null;
+const contentTypeSections: {
+ id: string;
+ icon: IconName;
+ name: string;
+ items: {
+ id: string;
+ name: string;
+ }[];
+}[] = [
+ {
+ id: 'structured',
+ name: 'Structured',
+ icon: 'bars',
+ items: [
+ {
+ id: CONTENT_TYPE_FORM_DATA,
+ name: 'Form Data',
+ },
+ {
+ id: CONTENT_TYPE_FORM_URLENCODED,
+ name: 'Form URL Encoded',
+ },
+ {
+ id: CONTENT_TYPE_GRAPHQL,
+ name: 'GraphQL',
+ },
+ ],
+ },
+ {
+ id: 'text',
+ icon: 'code',
+ name: 'Text',
+ items: [
+ {
+ id: CONTENT_TYPE_JSON,
+ name: 'JSON',
+ },
+ {
+ id: CONTENT_TYPE_XML,
+ name: 'XML',
+ },
+ {
+ id: CONTENT_TYPE_YAML,
+ name: 'YAML',
+ },
+ {
+ id: CONTENT_TYPE_EDN,
+ name: 'EDN',
+ },
+ {
+ id: CONTENT_TYPE_PLAINTEXT,
+ name: 'Plain Text',
+ },
+ {
+ id: CONTENT_TYPE_OTHER,
+ name: 'Other',
+ },
+ ],
+ },
+ {
+ id: 'other',
+ icon: 'ellipsis-h',
+ name: 'Other',
+ items: [
+ {
+ id: CONTENT_TYPE_FILE,
+ name: 'File',
+ },
+ {
+ id: 'no-body',
+ name: 'No Body',
+ },
+ ],
+ },
+ ];
+
export const ContentTypeDropdown: FC = () => {
const { activeRequest } = useRouteLoaderData('request/:requestId') as RequestLoaderData;
const patchRequest = useRequestPatcher();
@@ -54,14 +131,19 @@ export const ContentTypeDropdown: FC = () => {
const willPreserveForm = isFormUrlEncoded && willBeMultipart;
if (!isEmpty && !willPreserveText && !willPreserveForm) {
- await showModal(AlertModal, {
+ showAlert({
title: 'Switch Body Type?',
message: 'Current body will be lost. Are you sure you want to continue?',
addCancel: true,
+ onConfirm: async () => {
+ patchRequest(requestId, { body: { mimeType } });
+ window.main.trackSegmentEvent({ event: SegmentEvent.requestBodyTypeSelect, properties: { type: mimeType } });
+ },
});
+ } else {
+ patchRequest(requestId, { body: { mimeType } });
+ window.main.trackSegmentEvent({ event: SegmentEvent.requestBodyTypeSelect, properties: { type: mimeType } });
}
- patchRequest(requestId, { body: { mimeType } });
- window.main.trackSegmentEvent({ event: SegmentEvent.requestBodyTypeSelect, properties: { type: mimeType } });
};
const { body } = activeRequest;
@@ -69,137 +151,71 @@ export const ContentTypeDropdown: FC = () => {
const hasParams = body && 'params' in body && body.params;
const numBodyParams = hasParams ? body.params?.filter(({ disabled }) => !disabled).length : 0;
- const getIcon = (mimeType: string | null) => {
- const contentType = activeRequest?.body && 'mimeType' in activeRequest.body ? activeRequest.body.mimeType : null;
- const contentTypeFallback = typeof contentType === 'string' ? contentType : EMPTY_MIME_TYPE;
-
- return mimeType === contentTypeFallback ? 'check' : 'empty';
- };
-
return (
-
-
- {hasMimeType ? getContentTypeName(body.mimeType) : 'Body'}
+
);
};
+
export function newBodyGraphQL(rawBody: string): RequestBody {
try {
// Only strip the newlines if rawBody is a parsable JSON
diff --git a/packages/insomnia/src/ui/components/dropdowns/websocket-preview-mode.tsx b/packages/insomnia/src/ui/components/dropdowns/websocket-preview-mode.tsx
index 08e0f1829b7..0acafa17d91 100644
--- a/packages/insomnia/src/ui/components/dropdowns/websocket-preview-mode.tsx
+++ b/packages/insomnia/src/ui/components/dropdowns/websocket-preview-mode.tsx
@@ -1,38 +1,76 @@
import React, { FC } from 'react';
+import { Button, ListBox, ListBoxItem, Popover, Select, SelectValue } from 'react-aria-components';
import { CONTENT_TYPE_JSON, CONTENT_TYPE_PLAINTEXT } from '../../../common/constants';
-import { Dropdown, DropdownButton, DropdownItem, ItemContent } from '../base/dropdown';
+import { Icon } from '../icon';
interface Props {
previewMode: string;
- onClick: (previewMode: string) => void;
+ onSelect: (previewMode: string) => void;
}
-export const WebSocketPreviewMode: FC
= ({ previewMode, onClick }) => {
+
+const contentTypes: {
+ id: string;
+ name: string;
+}[] = [
+ {
+ id: CONTENT_TYPE_JSON,
+ name: 'JSON',
+ },
+ {
+ id: CONTENT_TYPE_PLAINTEXT,
+ name: 'Raw',
+ },
+ ];
+
+export const WebSocketPreviewMode: FC = ({ previewMode, onSelect }) => {
return (
-
- {{
- [CONTENT_TYPE_JSON]: 'JSON',
- [CONTENT_TYPE_PLAINTEXT]: 'Raw',
- }[previewMode]}
-
-
- }
+
+
+
+
+ {item => (
+
+ {({ isSelected }) => (
+ <>
+ {item.name}
+ {isSelected && (
+
+ )}
+ >
+ )}
+
+ )}
+
+
+
);
};
diff --git a/packages/insomnia/src/ui/components/editors/auth/auth-wrapper.tsx b/packages/insomnia/src/ui/components/editors/auth/auth-wrapper.tsx
index c65762ec514..c0e49b6935c 100644
--- a/packages/insomnia/src/ui/components/editors/auth/auth-wrapper.tsx
+++ b/packages/insomnia/src/ui/components/editors/auth/auth-wrapper.tsx
@@ -1,4 +1,5 @@
import React, { FC, ReactNode } from 'react';
+import { Toolbar } from 'react-aria-components';
import {
AUTH_API_KEY,
@@ -13,8 +14,9 @@ import {
AUTH_OAUTH_1,
AUTH_OAUTH_2,
} from '../../../../common/constants';
-import { RequestAuthentication } from '../../../../models/request';
+import { AuthTypes, RequestAuthentication } from '../../../../models/request';
import { getAuthObjectOrNull } from '../../../../network/authentication';
+import { AuthDropdown } from '../../dropdowns/auth-dropdown';
import { ApiKeyAuth } from './api-key-auth';
import { AsapAuth } from './asap-auth';
import { AWSAuth } from './aws-auth';
@@ -27,7 +29,7 @@ import { NTLMAuth } from './ntlm-auth';
import { OAuth1Auth } from './o-auth-1-auth';
import { OAuth2Auth } from './o-auth-2-auth';
-export const AuthWrapper: FC<{ authentication?: RequestAuthentication | {}; disabled?: boolean }> = ({ authentication, disabled = false }) => {
+export const AuthWrapper: FC<{ authentication?: RequestAuthentication | {}; disabled?: boolean; authTypes?: AuthTypes[] }> = ({ authentication, disabled = false, authTypes }) => {
const type = getAuthObjectOrNull(authentication)?.type || '';
let authBody: ReactNode = null;
@@ -55,8 +57,8 @@ export const AuthWrapper: FC<{ authentication?: RequestAuthentication | {}; disa
authBody = ;
} else {
authBody = (
-
-
+
;
+ return <>
+
+
+
+
+ {authBody}
+
+ >;
};
diff --git a/packages/insomnia/src/ui/components/editors/body/body-editor.tsx b/packages/insomnia/src/ui/components/editors/body/body-editor.tsx
index 2ff49c63012..ad8d0bdb3b1 100644
--- a/packages/insomnia/src/ui/components/editors/body/body-editor.tsx
+++ b/packages/insomnia/src/ui/components/editors/body/body-editor.tsx
@@ -1,6 +1,7 @@
import clone from 'clone';
import { lookup } from 'mime-types';
import React, { FC, useCallback } from 'react';
+import { Toolbar } from 'react-aria-components';
import { useParams } from 'react-router-dom';
import {
@@ -19,6 +20,7 @@ import {
} from '../../../../models/request';
import { NunjucksEnabledProvider } from '../../../context/nunjucks/nunjucks-enabled-context';
import { useRequestPatcher } from '../../../hooks/use-request';
+import { ContentTypeDropdown } from '../../dropdowns/content-type-dropdown';
import { AskModal } from '../../modals/ask-modal';
import { showModal } from '../../modals/index';
import { EmptyStatePane } from '../../panes/empty-state-pane';
@@ -108,7 +110,7 @@ export const BodyEditor: FC
= ({
const mimeType = request.body.mimeType;
const isBodyEmpty = typeof mimeType !== 'string' && !request.body.text;
- const _render = () => {
+ function renderBodyEditor() {
if (mimeType === CONTENT_TYPE_FORM_URLENCODED) {
return ;
} else if (mimeType === CONTENT_TYPE_FORM_DATA) {
@@ -121,14 +123,31 @@ export const BodyEditor: FC = ({
const contentType = getContentTypeFromHeaders(request.headers) || mimeType;
return ;
} else if (isEventStreamRequest(request)) {
- return }
- documentationLinks={[]}
- title="Enter a URL and connect to start receiving event stream data"
- />;
+ return (
+ }
+ documentationLinks={[]}
+ title="Enter a URL and connect to start receiving event stream data"
+ />
+ );
+ } else {
+ return (
+ }
+ documentationLinks={[documentationLinks.introductionToInsomnia]}
+ secondaryAction="Select a body type from above to send data in the body of a request"
+ title="Enter a URL and send to get a response"
+ />
+ );
}
- return } documentationLinks={[documentationLinks.introductionToInsomnia]} secondaryAction="Select a body type from above to send data in the body of a request" title="Enter a URL and send to get a response" />;
- };
+ }
- return {_render()};
+ return (
+
+
+
+
+ {renderBodyEditor()}
+
+ );
};
diff --git a/packages/insomnia/src/ui/components/editors/body/graph-ql-editor.tsx b/packages/insomnia/src/ui/components/editors/body/graph-ql-editor.tsx
index 0565869077e..c0e2f81626e 100644
--- a/packages/insomnia/src/ui/components/editors/body/graph-ql-editor.tsx
+++ b/packages/insomnia/src/ui/components/editors/body/graph-ql-editor.tsx
@@ -8,8 +8,9 @@ import { DefinitionNode, DocumentNode, GraphQLNonNull, GraphQLSchema, Kind, NonN
import { buildClientSchema, getIntrospectionQuery } from 'graphql/utilities';
import { Maybe } from 'graphql-language-service';
import React, { FC, useEffect, useRef, useState } from 'react';
-import { Button, Toolbar } from 'react-aria-components';
+import { Button, Group, Heading, Toolbar, Tooltip, TooltipTrigger } from 'react-aria-components';
import ReactDOM from 'react-dom';
+import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
import { useLocalStorage } from 'react-use';
import { CONTENT_TYPE_JSON } from '../../../../common/constants';
@@ -28,6 +29,7 @@ import { CodeEditor, CodeEditorHandle } from '../../codemirror/code-editor';
import { GraphQLExplorer } from '../../graph-ql-explorer/graph-ql-explorer';
import { ActiveReference } from '../../graph-ql-explorer/graph-ql-types';
import { HelpTooltip } from '../../help-tooltip';
+import { Icon } from '../../icon';
import { useDocBodyKeyboardShortcuts } from '../../keydown-binder';
import { TimeFromNow } from '../../time-from-now';
@@ -174,7 +176,6 @@ interface Props {
interface State {
body: GraphQLBody;
operations: string[];
- hideSchemaFetchErrors: boolean;
variablesSyntaxError: string;
explorerVisible: boolean;
activeReference: null | ActiveReference;
@@ -214,7 +215,6 @@ export const GraphQLEditor: FC = ({
operationName,
},
operations,
- hideSchemaFetchErrors: false,
variablesSyntaxError: '',
activeReference: null,
explorerVisible: false,
@@ -343,16 +343,16 @@ export const GraphQLEditor: FC = ({
return '';
}
if (schemaIsFetching) {
- return 'fetching schema...';
+ return 'Fetching schema...';
}
if (schemaLastFetchTime > 0) {
return (
- schema fetched
+ Schema fetched
);
}
- return schema not yet fetched;
+ return Schema not fetched yet;
};
const loadAndSetLocalSchema = async () => {
@@ -393,7 +393,6 @@ export const GraphQLEditor: FC = ({
};
const {
- hideSchemaFetchErrors,
variablesSyntaxError,
activeReference,
explorerVisible,
@@ -468,13 +467,13 @@ export const GraphQLEditor: FC = ({
}
const canShowSchema = schema && !schemaIsFetching && !schemaFetchError && schemaLastFetchTime > 0;
return (
-
-
+ <>
+
+
{state.body.operationName || 'Operations'}
}
@@ -495,7 +494,7 @@ export const GraphQLEditor: FC = ({
aria-label='Schema Dropdown'
triggerButton={
@@ -523,9 +522,6 @@ export const GraphQLEditor: FC = ({
icon={`refresh ${schemaIsFetching ? 'fa-spin' : ''}`}
label="Refresh Schema"
onClick={async () => {
- // First, "forget" preference to hide errors so they always show
- // again after a refresh
- setState(state => ({ ...state, hideSchemaFetchErrors: false }));
setSchemaIsFetching(true);
const newState = await fetchGraphQLSchemaForRequest({
requestId: request._id,
@@ -573,7 +569,6 @@ export const GraphQLEditor: FC = ({
>
}
onClick={() => {
- setState(state => ({ ...state, hideSchemaFetchErrors: false }));
loadAndSetLocalSchema();
}}
/>
@@ -581,80 +576,85 @@ export const GraphQLEditor: FC = ({
-
-
-
-
-
- {!hideSchemaFetchErrors && schemaFetchError && (
-
-
-
-
- {schemaFetchError.message}
-
+
+
+
+
+
+
+
+ Query Variables
+
+ Variables to use in GraphQL query
+ (JSON format)
+
+ {variablesSyntaxError && (
+ {variablesSyntaxError}
+ )}
+
+
+ Object.keys(variableTypes)}
+ lintOptions={{
+ variableToType: variableTypes,
+ }}
+ noLint={!variableTypes}
+ onChange={changeVariables}
+ mode="graphql-variables"
+ placeholder=""
+ />
- )}
-
-
- {renderSchemaFetchMessage()}
-
-
- Query Variables
-
- Variables to use in GraphQL query
- (JSON format)
-
- {variablesSyntaxError && (
- {variablesSyntaxError}
- )}
-
-
- Object.keys(variableTypes)}
- lintOptions={{
- variableToType: variableTypes,
- }}
- noLint={!variableTypes}
- onChange={changeVariables}
- mode="graphql-variables"
- placeholder=""
- />
-
-
-
-
+
+ {!schemaFetchError &&
+
+ {renderSchemaFetchMessage()}
+
}
+ {schemaFetchError && (
+
+
+
+
+ Error fetching Schema
+
+
+ {schemaFetchError.message}
+
+
+
+ )}
+
{graphQLExplorerPortal}
-
+ >
);
};
diff --git a/packages/insomnia/src/ui/components/editors/request-script-editor.tsx b/packages/insomnia/src/ui/components/editors/request-script-editor.tsx
index 798f3a02be9..260e17a357a 100644
--- a/packages/insomnia/src/ui/components/editors/request-script-editor.tsx
+++ b/packages/insomnia/src/ui/components/editors/request-script-editor.tsx
@@ -1,11 +1,12 @@
import { Snippet } from 'codemirror';
import { CookieObject, Environment, InsomniaObject, Request as ScriptRequest, RequestInfo, Url, Variables } from 'insomnia-sdk';
import React, { FC, useRef } from 'react';
+import { Button, Collection, Header, Menu, MenuItem, MenuTrigger, Popover, Section, Toolbar } from 'react-aria-components';
import { Settings } from '../../../models/settings';
import { translateHandlersInScript } from '../../../utils/importers/importers/postman';
-import { Dropdown, DropdownButton, DropdownItem, DropdownSection, ItemContent } from '../base/dropdown';
import { CodeEditor, CodeEditorHandle } from '../codemirror/code-editor';
+import { Icon } from '../icon';
interface Props {
onChange: (value: string) => void;
@@ -139,6 +140,207 @@ function getRequestScriptSnippets(insomniaObject: InsomniaObject, path: string):
return snippets;
}
+interface SnippetMenuItem {
+ id: string;
+ name: string;
+ items: ({
+ id: string;
+ name: string;
+ snippet: string;
+ } | {
+ id: string;
+ name: string;
+ items: {
+ id: string;
+ name: string;
+ snippet: string;
+ }[];
+ })[];
+}
+
+const variableSnippetsMenu: SnippetMenuItem = {
+ 'id': 'variable-snippets',
+ 'name': 'Variable Snippets',
+ items: [
+ {
+ 'id': 'get-values',
+ 'name': 'Get values',
+ items: [
+ {
+ 'id': 'get-env-var',
+ 'name': 'Get an environment variable',
+ 'snippet': getEnvVar,
+ },
+ // {
+ // "id": "get-glb-var",
+ // "name": "Get a global variable",
+ // "snippet": getGlbVar,
+ // },
+ {
+ 'id': 'get-var',
+ 'name': 'Get a variable',
+ 'snippet': getVar,
+ },
+ {
+ 'id': 'get-collection-var',
+ 'name': 'Get a collection variable',
+ 'snippet': getCollectionVar,
+ },
+ ],
+ },
+ {
+ id: 'set-values',
+ name: 'Set values',
+ items: [
+ {
+ 'id': 'set-env-var',
+ 'name': 'Set an environment variable',
+ 'snippet': setEnvVar,
+ },
+ // {
+ // "id": "set-glb-var",
+ // "name": "Set a global variable",
+ // "snippet": setGlbVar,
+ // },
+ {
+ 'id': 'set-var',
+ 'name': 'Set a variable',
+ 'snippet': setVar,
+ },
+ {
+ 'id': 'set-collection-var',
+ 'name': 'Set a collection variable',
+ 'snippet': setCollectionVar,
+ },
+ ],
+ },
+ {
+ id: 'clear-values',
+ name: 'Clear values',
+ items: [
+ {
+ 'id': 'unset-env-var',
+ 'name': 'Clear an environment variable',
+ 'snippet': unsetEnvVar,
+ },
+ // {
+ // "id": "unset-glb-var",
+ // "name": "Clear a global variable",
+ // "snippet": unsetGlbVar,
+ // },
+ {
+ 'id': 'unset-collection-var',
+ 'name': 'Clear a collection variable',
+ 'snippet': unsetCollectionVar,
+ },
+ ],
+ },
+ ],
+};
+
+const requestManipulationMenu: SnippetMenuItem = {
+ id: 'request-manipulation',
+ name: 'Request Manipulation',
+ items: [
+ {
+ 'id': 'add-query-param',
+ 'name': 'Add query param',
+ 'snippet': addQueryParams,
+ },
+ {
+ 'id': 'set-method',
+ 'name': 'Set method',
+ 'snippet': setMethod,
+ },
+ {
+ 'id': 'add-header',
+ 'name': 'Add a header',
+ 'snippet': addHeader,
+ },
+ {
+ 'id': 'remove-header',
+ 'name': 'Remove header',
+ 'snippet': removeHeader,
+ },
+ {
+ 'id': 'update-body-raw',
+ 'name': 'Update body as raw',
+ 'snippet': updateRequestBody,
+ },
+ {
+ 'id': 'update-auth-method',
+ 'name': 'Update auth method',
+ 'snippet': updateRequestAuth,
+ },
+ ],
+};
+
+const responseHandlingMenu: SnippetMenuItem = {
+ id: 'response-handling',
+ name: 'Response Handling',
+ items: [
+ {
+ 'id': 'get-status-code',
+ 'name': 'Get status code',
+ 'snippet': getStatusCode,
+ },
+ {
+ 'id': 'get-status-message',
+ 'name': 'Get status message',
+ 'snippet': getStatusMsg,
+ },
+ {
+ 'id': 'get-response-time',
+ 'name': 'Get response time',
+ 'snippet': getRespTime,
+ },
+ {
+ 'id': 'get-body-json',
+ 'name': 'Get body as JSON',
+ 'snippet': getJsonBody,
+ },
+ {
+ 'id': 'get-body-text',
+ 'name': 'Get body as text',
+ 'snippet': getTextBody,
+ },
+ {
+ 'id': 'find-header',
+ 'name': 'Find a header by name',
+ 'snippet': findHeader,
+ },
+ {
+ 'id': 'get-cookies',
+ 'name': 'Get cookies',
+ 'snippet': getCookies,
+ },
+ ],
+};
+
+const miscMenu: SnippetMenuItem = {
+ id: 'misc',
+ name: 'Misc',
+ items: [
+ {
+ 'id': 'send-request',
+ 'name': 'Send a request',
+ 'snippet': sendReq,
+ },
+ {
+ 'id': 'print-log',
+ 'name': 'Print log',
+ 'snippet': logValue,
+ },
+ {
+ 'id': 'require-module',
+ 'name': 'Require a module',
+ 'snippet': requireAModule,
+ },
+ ],
+};
+
+const snippetsMenus: SnippetMenuItem[] = [variableSnippetsMenu, requestManipulationMenu, responseHandlingMenu, miscMenu];
+
export const RequestScriptEditor: FC = ({
className,
defaultValue,
@@ -194,6 +396,7 @@ export const RequestScriptEditor: FC = ({
cookies: [],
}),
requestInfo: new RequestInfo({
+ // @TODO - Look into this event name when we introduce iteration data
eventName: 'prerequest',
iteration: 1,
iterationCount: 1,
@@ -208,289 +411,63 @@ export const RequestScriptEditor: FC = ({
);
return (
-
-
- requestScriptSnippets}
- onPaste={translateHandlersInScript}
- />
-
-
-
-
-
- }
- >
-
-
-
- addSnippet(getEnvVar)}
- />
-
- {/*
- addSnippet(getGlbVar)}
- />
- */}
-
- addSnippet(getVar)}
- />
-
-
- addSnippet(getCollectionVar)}
- />
-
-
-
-
-
- addSnippet(setEnvVar)}
- />
-
- {/*
- addSnippet(setGlbVar)}
- />
- */}
-
- addSnippet(setVar)}
- />
-
-
- addSnippet(setCollectionVar)}
- />
-
-
-
-
-
- addSnippet(unsetEnvVar)}
- />
-
- {/*
- addSnippet(unsetGlbVar)}
- />
- */}
-
- addSnippet(unsetCollectionVar)}
- />
-
-
-
-
-
-
-
- }
- >
-
- addSnippet(addQueryParams)}
- />
-
-
- addSnippet(setMethod)}
- />
-
-
- addSnippet(addHeader)}
- />
-
-
- addSnippet(removeHeader)}
- />
-
-
- addSnippet(updateRequestBody)}
- />
-
-
- addSnippet(updateRequestAuth)}
- />
-
-
+
+
requestScriptSnippets}
+ onPaste={translateHandlersInScript}
+ />
+
+ {snippetsMenus.map(menu => (
+
+
+
+ {menu.name}
+
+
+
+
+
+ ))}
-
-
-
- }
- >
-
- addSnippet(sendReq)}
- />
-
-
- addSnippet(logValue)}
- />
-
-
- addSnippet(requireAModule)}
- />
-
-
-
+
);
};
diff --git a/packages/insomnia/src/ui/components/panes/request-group-pane.tsx b/packages/insomnia/src/ui/components/panes/request-group-pane.tsx
index 87fecc0d2c1..1f2ad1b1ff4 100644
--- a/packages/insomnia/src/ui/components/panes/request-group-pane.tsx
+++ b/packages/insomnia/src/ui/components/panes/request-group-pane.tsx
@@ -1,12 +1,11 @@
import React, { FC, useState } from 'react';
+import { Tab, TabList, TabPanel, Tabs } from 'react-aria-components';
import { useRouteLoaderData } from 'react-router-dom';
import { Settings } from '../../../models/settings';
import { useActiveRequestSyncVCSVersion, useGitVCSVersion } from '../../hooks/use-vcs-version';
import { RequestGroupLoaderData } from '../../routes/request-group';
import { WorkspaceLoaderData } from '../../routes/workspace';
-import { PanelContainer, TabItem, Tabs } from '../base/tabs';
-import { AuthDropdown } from '../dropdowns/auth-dropdown';
import { AuthWrapper } from '../editors/auth/auth-wrapper';
import { RequestHeadersEditor } from '../editors/request-headers-editor';
import { ErrorBoundary } from '../error-boundary';
@@ -25,106 +24,105 @@ export const RequestGroupPane: FC<{ settings: Settings }> = ({ }) => {
return (
<>
-
- }>
+
+
+
+ Auth
+
+
+
+ Docs
+
+
+
-
-
-
- Headers{' '}
- {headersCount > 0 && (
- {headersCount}
- )}
-
- }
- >
-
-
-
- Docs
- {activeRequestGroup.description && (
-
-
-
- )}
- >
- }
- >
-
- {activeRequestGroup.description ? (
-
-
- setIsRequestGroupSettingsModalOpen(true)}
- >
- Edit
-
-
-
-
-
-
-
+
+
+
+ {activeRequestGroup.description ? (
+
+
+ setIsRequestGroupSettingsModalOpen(true)}
+ >
+ Edit
+
- ) : (
-
-
-
-
-
-
-
- setIsRequestGroupSettingsModalOpen(true)}
- >
- Add Description
-
-
+
+
+
+
- )}
-
-
+
+ ) : (
+
+
+
+
+
+
+
+ setIsRequestGroupSettingsModalOpen(true)}
+ >
+ Add Description
+
+
+
+ )}
+
- {isRequestGroupSettingsModalOpen && (
-
setIsRequestGroupSettingsModalOpen(false)}
- />
- )}>
+ {
+ isRequestGroupSettingsModalOpen && (
+ setIsRequestGroupSettingsModalOpen(false)}
+ />
+ )
+ }
+ >
);
};
diff --git a/packages/insomnia/src/ui/components/panes/request-pane.tsx b/packages/insomnia/src/ui/components/panes/request-pane.tsx
index 30a94684659..789bd36b947 100644
--- a/packages/insomnia/src/ui/components/panes/request-pane.tsx
+++ b/packages/insomnia/src/ui/components/panes/request-pane.tsx
@@ -1,5 +1,5 @@
import React, { FC, Fragment, useState } from 'react';
-import { Button, Heading, ToggleButton } from 'react-aria-components';
+import { Button, Heading, Tab, TabList, TabPanel, Tabs, ToggleButton } from 'react-aria-components';
import { useParams, useRouteLoaderData } from 'react-router-dom';
import { useLocalStorage } from 'react-use';
@@ -13,10 +13,7 @@ import { useRequestPatcher, useSettingsPatcher } from '../../hooks/use-request';
import { useActiveRequestSyncVCSVersion, useGitVCSVersion } from '../../hooks/use-vcs-version';
import { RequestLoaderData } from '../../routes/request';
import { WorkspaceLoaderData } from '../../routes/workspace';
-import { PanelContainer, TabItem, Tabs } from '../base/tabs';
import { OneLineEditor } from '../codemirror/one-line-editor';
-import { AuthDropdown } from '../dropdowns/auth-dropdown';
-import { ContentTypeDropdown } from '../dropdowns/content-type-dropdown';
import { AuthWrapper } from '../editors/auth/auth-wrapper';
import { BodyEditor } from '../editors/body/body-editor';
import { RequestHeadersEditor } from '../editors/request-headers-editor';
@@ -112,39 +109,86 @@ export const RequestPane: FC = ({
/>
-
-
- Parameters
- {parametersCount > 0 && (
- {parametersCount}
- )}
-
- }
- >
-
-
-
-
-
-
-
-
+
+
+
+ Params
+ {parametersCount > 0 && (
+
+ {parametersCount}
+
+ )}
+
+
+ Body
+
+
+ Auth
+
+
+
+ Scripts
+ {Boolean(activeRequest.preRequestScript || activeRequest.afterResponseScript) && (
+
+
+
+ )}
+
+
+ Docs
+ {activeRequest.description && (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
Query parameters
Import from URL
@@ -155,7 +199,7 @@ export const RequestPane: FC
= ({
});
}}
isSelected={settings.useBulkParametersEditor}
- className="w-[14ch] flex flex-shrink-0 gap-2 items-center justify-start px-2 py-1 h-full rounded-sm text-[--color-font] hover:bg-[--hl-xs] focus:ring-inset ring-1 ring-transparent focus:ring-[--hl-md] transition-all text-sm"
+ className="w-[14ch] flex flex-shrink-0 gap-2 items-center justify-start px-2 py-1 h-full rounded-sm text-[--color-font] hover:bg-[--hl-xs] focus:ring-inset ring-1 ring-transparent focus:ring-[--hl-md] transition-colors text-sm"
>
{({ isSelected }) => (
@@ -178,170 +222,151 @@ export const RequestPane: FC = ({
/>
-
+
Path parameters
- {pathParameters.length > 0 && (
-
-
- {pathParameters.map(pathParameter => (
-
-
- {pathParameter.name}
-
-
- {
- onPathParameterChange(pathParameters.map(p => p.name === pathParameter.name ? { ...p, value: name } : p));
- }}
- />
-
-
- ))}
-
-
- )}
- {pathParameters.length === 0 && !dismissPathParameterTip && (
-
-
-
Path parameters are url path segments that start with a colon ':' e.g. ':id'
-
setDismissPathParameterTip('true')}
- >
-
-
+ {pathParameters.length > 0 && (
+
+
+ {pathParameters.map(pathParameter => (
+
+
+ {pathParameter.name}
+
+
+ {
+ onPathParameterChange(pathParameters.map(p => p.name === pathParameter.name ? { ...p, value: name } : p));
+ }}
+ />
+
+
+ ))}
- )}
-
+
+ )}
+ {pathParameters.length === 0 && !dismissPathParameterTip && (
+
+
+ Path parameters are url path segments that start with a colon ':' e.g. ':id'
+ setDismissPathParameterTip('true')}
+ >
+
+
+
+ )}
-
-
}>
+
+
-
- }>
+
+
-
-
- Headers{' '}
- {headersCount > 0 && (
- {headersCount}
- )}
-
- }
- >
-
-
-
-
-
-
-
-
-
- patchSettings({
- useBulkHeaderEditor: !settings.useBulkHeaderEditor,
- })
- }
- >
- {settings.useBulkHeaderEditor ? 'Regular Edit' : 'Bulk Edit'}
-
-
-
-
-
- Pre-request Script{' '}
- {activeRequest.preRequestScript && (
-
-
-
- )}
-
- }
- aria-label={'experimental'}
- >
+
+
+
+
+
+
+
+
+ Pre-request
+
+ {Boolean(activeRequest.preRequestScript) && (
+
+
+
+ )}
+
+
+
+
+ After-response
+
+ {Boolean(activeRequest.afterResponseScript) && (
+
+
+
+ )}
+
+
+
+
+ patchRequest(requestId, { preRequestScript })}
+ settings={settings}
+ />
+
+
+
+
+ patchRequest(requestId, { afterResponseScript })}
+ settings={settings}
+ />
+
+
+
+
+
+
{activeRequest.description ? (
@@ -384,8 +409,8 @@ export const RequestPane: FC
= ({
)}
-
-
+
+
{isRequestSettingsModalOpen && (
(({ isConnected }) => ({
padding: '0 var(--padding-md)',
marginLeft: 'var(--padding-xs)',
@@ -55,15 +54,6 @@ const SendButton = styled.button<{ isConnected: boolean }>(({ isConnected }) =>
},
}));
-const PaneSendButton = styled.div({
- display: 'flex',
- flexDirection: 'row',
- justifyContent: 'flex-end',
- boxSizing: 'border-box',
- height: 'var(--line-height-sm)',
- borderBottom: '1px solid var(--hl-lg)',
- padding: 3,
-});
const PaneHeader = styled(OriginalPaneHeader)({
'&&': { alignItems: 'stretch' },
});
@@ -209,10 +199,7 @@ export const WebSocketRequestPane: FC = ({ environment }) => {
const { workspaceId, requestId } = useParams() as { organizationId: string; projectId: string; workspaceId: string; requestId: string };
const readyState = useReadyState({ requestId: activeRequest._id, protocol: 'webSocket' });
- const {
- settings,
- } = useRootLoaderData();
- const { useBulkParametersEditor } = settings;
+ const { settings } = useRootLoaderData();
const disabled = readyState;
@@ -248,7 +235,7 @@ export const WebSocketRequestPane: FC = ({ environment }) => {
const parametersCount = pathParameters.length + activeRequest.parameters.filter(p => !p.disabled).length;
const headersCount = activeRequest.headers.filter(h => !h.disabled).length;
-
+ const patchSettings = useSettingsPatcher();
const upsertPayloadWithMode = async (mode: string) => {
// @TODO: multiple payloads
const payload = await models.webSocketPayload.getByParentId(requestId);
@@ -264,9 +251,33 @@ export const WebSocketRequestPane: FC = ({ environment }) => {
};
const [isRequestSettingsModalOpen, setIsRequestSettingsModalOpen] = useState(false);
+ const handleImportQueryFromUrl = () => {
+ let query;
+
+ try {
+ query = extractQueryStringFromUrl(activeRequest.url);
+ } catch (error) {
+ console.warn('Failed to parse url to import querystring');
+ return;
+ }
+
+ // Remove the search string (?foo=bar&...) from the Url
+ const url = activeRequest.url.replace(`?${query}`, '');
+ const parameters = [
+ ...activeRequest.parameters,
+ ...deconstructQueryStringToParams(query),
+ ];
+
+ // Only update if url changed
+ if (url !== activeRequest.url) {
+ patchRequest(requestId, { url, parameters });
+ }
+ };
+
const gitVersion = useGitVCSVersion();
const activeRequestSyncVersion = useActiveRequestSyncVCSVersion();
const patchRequest = useRequestPatcher();
+ const urlHasQueryParameters = activeRequest.url.indexOf('?') >= 0;
// Reset the response pane state when we switch requests, the environment gets modified, or the (Git|Sync)VCS version changes
const uniqueKey = `${environment?.modified}::${requestId}::${gitVersion}::${activeRequestSyncVersion}::${activeRequestMeta.activeResponseId}`;
@@ -282,136 +293,176 @@ export const WebSocketRequestPane: FC = ({ environment }) => {
onChange={url => patchRequest(requestId, { url })}
/>
-
-
- Parameters
- {parametersCount > 0 && (
- {parametersCount}
- )}
+
+
+
+ Params
+ {parametersCount > 0 && (
+
+ {parametersCount}
+
+ )}
+
+
+ Body
+
+
+ Auth
+
+
+
+ Docs
+
+
+
+ {disabled && }
+
+
+
+
+
+
- }
- >
-
- {disabled &&
}
-
-
-
-
-
+
+
+
+
Query parameters
+
+
+ Import from URL
+
+ {
+ patchSettings({
+ useBulkParametersEditor: isSelected,
+ });
+ }}
+ isSelected={settings.useBulkParametersEditor}
+ className="w-[14ch] flex flex-shrink-0 gap-2 items-center justify-start px-2 py-1 h-full rounded-sm text-[--color-font] hover:bg-[--hl-xs] focus:ring-inset ring-1 ring-transparent focus:ring-[--hl-md] transition-colors text-sm"
>
-
-
+ {({ isSelected }) => (
+
+
+ {
+ isSelected ? 'Regular Edit' : 'Bulk Edit'
+ }
+
+ )}
+
-
-
-
- Query parameters
+
+
+
+
+
+
Path parameters
+ {pathParameters.length > 0 && (
+
+
+ {pathParameters.map(pathParameter => (
+
+
+ {pathParameter.name}
+
+
+ {
+ onPathParameterChange(pathParameters.map(p => p.name === pathParameter.name ? { ...p, value: name } : p));
+ }}
+ />
+
+
+ ))}
-
-
-
-
-
Path parameters
- {pathParameters.length > 0 && (
-
-
- {pathParameters.map(pathParameter => (
-
-
- {pathParameter.name}
-
-
- {
- onPathParameterChange(pathParameters.map(p => p.name === pathParameter.name ? { ...p, value: name } : p));
- }}
- />
-
-
- ))}
-
-
- )}
- {pathParameters.length === 0 && !dismissPathParameterTip && (
-
-
- Path parameters are url path segments that start with a colon ':' e.g. ':id'
- setDismissPathParameterTip('true')}
- >
-
-
-
- )}
+ )}
+ {pathParameters.length === 0 && !dismissPathParameterTip && (
+
+
+ Path parameters are url path segments that start with a colon ':' e.g. ':id'
+ setDismissPathParameterTip('true')}
+ >
+
+
-
+ )}
-
-
}>
-
-
-
}>
+
+
+
+
+
+ Send
+
+
+
+
+
{disabled && }
-
-
- Headers{' '}
- {headersCount > 0 && (
- {headersCount}
- )}
-
- }
- >
+
+
+
{activeRequest.description ? (
@@ -470,7 +509,7 @@ export const WebSocketRequestPane: FC
= ({ environment }) => {
)}
-
+
{isRequestSettingsModalOpen && (