From b1eae5e91a711231aaef5dc7a263ad29bac7b299 Mon Sep 17 00:00:00 2001 From: Matthew Hokinson <123108941+MKHokinson@users.noreply.github.com> Date: Mon, 1 Apr 2024 07:33:01 -0400 Subject: [PATCH 1/4] Fix local state updates for GraphQL Query Variables (#7205) * Fix local state updates for GraphQL Query Variables * Fix typo in comment * Fix local state updates for GraphQL Query Variables * Fix typo in comment * Assert params are strings before checking length property --------- Co-authored-by: Hexxa --- .../ui/components/editors/body/graph-ql-editor.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) 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 96fd9816e68c..10877a87403b 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 @@ -47,12 +47,14 @@ function getGraphQLContent(body: GraphQLBody, query?: string, operationName?: st content.query = query; } - if (operationName) { - content.operationName = operationName; + // The below items are optional; should be set to undefined if present and empty + const isString = (value?: string): value is string => typeof value === 'string' || (value as unknown) instanceof String; + if (isString(operationName)) { + content.operationName = operationName.length ? operationName : undefined; } - if (variables) { - content.variables = variables; + if (isString(variables)) { + content.variables = variables.length ? variables : undefined; } return JSON.stringify(content); @@ -284,9 +286,11 @@ export const GraphQLEditor: FC = ({ try { const content = getGraphQLContent(state.body, undefined, operationName, variablesInput); onChange(content); + setState(state => ({ ...state, - body: { ...state.body, variablesInput }, + // If variables are empty, remove them from the body + body: { ...state.body, variables: variablesInput.length ? variablesInput : undefined }, variablesSyntaxError: '', })); } catch (err) { From 39bc37bf10c38caa5eefe7703e4aee831a2102fa Mon Sep 17 00:00:00 2001 From: JohnEndson <165029498+JohnEndson@users.noreply.github.com> Date: Tue, 2 Apr 2024 16:06:46 +0800 Subject: [PATCH 2/4] chore: remove repetitive words (#7223) Signed-off-by: JohnEndson --- packages/insomnia/bin/yarn-standalone.js | 2 +- packages/insomnia/src/common/constants.ts | 2 +- .../insomnia/src/utils/importers/importers/postman-2.0.types.ts | 2 +- .../insomnia/src/utils/importers/importers/postman-2.1.types.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/insomnia/bin/yarn-standalone.js b/packages/insomnia/bin/yarn-standalone.js index cf5dcca678d7..fe66383a5d94 100755 --- a/packages/insomnia/bin/yarn-standalone.js +++ b/packages/insomnia/bin/yarn-standalone.js @@ -76190,7 +76190,7 @@ function read(buf, options) { * gotten conjoined to the key or otherwise shenanigan'd. * * Work out how much base64 we used, then drop all non-base64 - * chars from the beginning up to this point in the the string. + * chars from the beginning up to this point in the string. * Then offset in this and try to make up for missing = chars. */ var data = m[2] + (m[3] ? m[3] : ''); diff --git a/packages/insomnia/src/common/constants.ts b/packages/insomnia/src/common/constants.ts index 680dfb77578c..96d897f83265 100644 --- a/packages/insomnia/src/common/constants.ts +++ b/packages/insomnia/src/common/constants.ts @@ -102,7 +102,7 @@ export const displayModifierKey = (key: keyof Omit) = } if (isWindows()) { - // Note: Although this unicode character for the Windows doesn't exist, the the Unicode character U+229E ⊞ SQUARED PLUS is very commonly used for this purpose. For example, Wikipedia uses it as a simulation of the windows logo. Though, Windows itself uses `Windows` or `Win`, so we'll go with `Win` here. + // Note: Although this unicode character for the Windows doesn't exist, the Unicode character U+229E ⊞ SQUARED PLUS is very commonly used for this purpose. For example, Wikipedia uses it as a simulation of the windows logo. Though, Windows itself uses `Windows` or `Win`, so we'll go with `Win` here. // see: https://en.wikipedia.org/wiki/Windows_key return 'Win'; } diff --git a/packages/insomnia/src/utils/importers/importers/postman-2.0.types.ts b/packages/insomnia/src/utils/importers/importers/postman-2.0.types.ts index 4880d668cf79..01ed271b3916 100644 --- a/packages/insomnia/src/utils/importers/importers/postman-2.0.types.ts +++ b/packages/insomnia/src/utils/importers/importers/postman-2.0.types.ts @@ -288,7 +288,7 @@ export interface Event { [k: string]: unknown; } /** - * A script is a snippet of Javascript code that can be used to to perform setup or teardown operations on a particular response. + * A script is a snippet of Javascript code that can be used to perform setup or teardown operations on a particular response. */ export interface Script { /** diff --git a/packages/insomnia/src/utils/importers/importers/postman-2.1.types.ts b/packages/insomnia/src/utils/importers/importers/postman-2.1.types.ts index 14ddad0655fa..beed69e8dcda 100644 --- a/packages/insomnia/src/utils/importers/importers/postman-2.1.types.ts +++ b/packages/insomnia/src/utils/importers/importers/postman-2.1.types.ts @@ -328,7 +328,7 @@ export interface Event { [k: string]: unknown; } /** - * A script is a snippet of Javascript code that can be used to to perform setup or teardown operations on a particular response. + * A script is a snippet of Javascript code that can be used to perform setup or teardown operations on a particular response. */ export interface Script { /** From e38badfbf39a4d4b694e0d7ea34a0e315ddab0da Mon Sep 17 00:00:00 2001 From: Jack Kavanagh Date: Tue, 2 Apr 2024 15:42:39 +0200 Subject: [PATCH 3/4] mock-extraction feedback (#7207) * save point * both cases work * use action and hack * throw error on naming collision * fix type * fix test --- .../tests/smoke/mock.test.ts | 17 +-- .../editors/mock-response-extractor.tsx | 101 +++++++++++++++--- packages/insomnia/src/ui/routes/actions.tsx | 21 +++- .../insomnia/src/ui/routes/mock-route.tsx | 24 ++++- .../insomnia/src/ui/routes/mock-server.tsx | 23 +++- 5 files changed, 154 insertions(+), 32 deletions(-) diff --git a/packages/insomnia-smoke-test/tests/smoke/mock.test.ts b/packages/insomnia-smoke-test/tests/smoke/mock.test.ts index 95bcd29864d5..c349fef4b78c 100644 --- a/packages/insomnia-smoke-test/tests/smoke/mock.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/mock.test.ts @@ -1,22 +1,15 @@ -import { loadFixture } from '../../playwright/paths'; import { test } from '../../playwright/test'; -test('can make a mock route', async ({ app, page }) => { +test('can make a mock route', async ({ page }) => { test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms'); - const text = await loadFixture('smoke-test-collection.yaml'); - await app.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), text); - - await page.getByRole('button', { name: 'Create in project' }).click(); - await page.getByRole('menuitemradio', { name: 'Import' }).click(); - await page.locator('[data-test-id="import-from-clipboard"]').click(); - await page.getByRole('button', { name: 'Scan' }).click(); - await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click(); await page.getByLabel('New Mock Server').click(); await page.getByRole('button', { name: 'Create', exact: true }).click(); await page.getByRole('button', { name: 'New Mock Route' }).click(); - await page.getByText('GET/').click(); - await page.getByTestId('CodeEditor').getByRole('textbox').fill('123'); + await page.getByLabel('Project Actions').click(); + await page.getByText('Rename').click(); + await page.locator('#prompt-input').fill('/123'); + await page.getByRole('button', { name: 'Rename' }).click(); await page.getByRole('button', { name: 'Test' }).click(); await page.getByText('No body returned for response').click(); diff --git a/packages/insomnia/src/ui/components/editors/mock-response-extractor.tsx b/packages/insomnia/src/ui/components/editors/mock-response-extractor.tsx index 1b99484af194..5e0d9debd6a4 100644 --- a/packages/insomnia/src/ui/components/editors/mock-response-extractor.tsx +++ b/packages/insomnia/src/ui/components/editors/mock-response-extractor.tsx @@ -2,21 +2,32 @@ import fs from 'fs/promises'; import React, { useState } from 'react'; import { Button } from 'react-aria-components'; import { useNavigate, useParams } from 'react-router-dom'; -import { useRouteLoaderData } from 'react-router-dom'; +import { + useFetcher, + useRouteLoaderData, +} from 'react-router-dom'; +import { invariant } from '../../../utils/invariant'; import { useMockRoutePatcher } from '../../routes/mock-route'; import { RequestLoaderData } from '../../routes/request'; +import { WorkspaceLoaderData } from '../../routes/workspace'; import { HelpTooltip } from '../help-tooltip'; import { Icon } from '../icon'; +import { showPrompt } from '../modals'; export const MockResponseExtractor = () => { + const { + activeWorkspace, + } = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData; const { mockServerAndRoutes, activeResponse } = useRouteLoaderData('request/:requestId') as RequestLoaderData; const patchMockRoute = useMockRoutePatcher(); const navigate = useNavigate(); const { organizationId, projectId, + workspaceId, } = useParams(); + const fetcher = useFetcher(); const [selectedMockServer, setSelectedMockServer] = useState(''); const [selectedMockRoute, setSelectedMockRoute] = useState(''); return ( @@ -30,21 +41,84 @@ export const MockResponseExtractor = () => {
{ e.preventDefault(); - if (!selectedMockServer || !selectedMockRoute) { + if (selectedMockServer && selectedMockRoute) { + if (activeResponse) { + // TODO: move this out of the renderer, and upsert mock + const body = await fs.readFile(activeResponse.bodyPath); + + patchMockRoute(selectedMockRoute, { + body: body.toString(), + mimeType: activeResponse.contentType, + statusCode: activeResponse.statusCode, + headers: activeResponse.headers, + }); + } + return; + } + let path = '/new-route'; + try { + path = activeResponse ? new URL(activeResponse.url).pathname : '/new-route'; + } catch (e) { + console.log(e); + } + if (!selectedMockServer) { + showPrompt({ + title: 'Create Mock Route', + defaultValue: path, + label: 'Name', + onComplete: async name => { + invariant(activeResponse, 'Active response must be defined'); + const body = await fs.readFile(activeResponse.bodyPath); + // TODO: consider setting selected mock server here rather than redirecting + fetcher.submit( + JSON.stringify({ + name: name, + body: body.toString(), + mimeType: activeResponse.contentType, + statusCode: activeResponse.statusCode, + headers: activeResponse.headers, + mockServerName: activeWorkspace.name, + }), + { + encType: 'application/json', + method: 'post', + action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/mock-server/mock-route/new`, + } + ); + }, + }); return; } + if (!selectedMockRoute) { + showPrompt({ + title: 'Create Mock Route', + defaultValue: path, + label: 'Name', + onComplete: async name => { + invariant(activeResponse, 'Active response must be defined'); + const body = await fs.readFile(activeResponse.bodyPath); - if (activeResponse) { - // TODO: move this out of the renderer, and upsert mock - const body = await fs.readFile(activeResponse.bodyPath); + // setSelectedMockRoute(newRoute._id); - patchMockRoute(selectedMockRoute, { - body: body.toString(), - mimeType: activeResponse.contentType, - statusCode: activeResponse.statusCode, - headers: activeResponse.headers, + fetcher.submit( + JSON.stringify({ + name: name, + parentId: selectedMockServer, + body: body.toString(), + mimeType: activeResponse.contentType, + statusCode: activeResponse.statusCode, + headers: activeResponse.headers, + }), + { + encType: 'application/json', + method: 'post', + action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/mock-server/mock-route/new`, + } + ); + }, }); } + }} >
@@ -61,7 +135,7 @@ export const MockResponseExtractor = () => { setSelectedMockServer(selected); }} > - + {mockServerAndRoutes .map(w => ( + {mockServerAndRoutes.find(s => s._id === selectedMockServer)?.routes .map(w => (
diff --git a/packages/insomnia/src/ui/routes/actions.tsx b/packages/insomnia/src/ui/routes/actions.tsx index 94343c784a80..1267e33e2acb 100644 --- a/packages/insomnia/src/ui/routes/actions.tsx +++ b/packages/insomnia/src/ui/routes/actions.tsx @@ -1211,9 +1211,26 @@ export const createMockRouteAction: ActionFunction = async ({ request, params }) const patch = await request.json(); invariant(typeof patch.name === 'string', 'Name is required'); - invariant(typeof patch.parentId === 'string', 'parentId is required'); + // TODO: remove this hack + if (patch.mockServerName) { + const activeWorkspace = await models.workspace.getById(workspaceId); + invariant(activeWorkspace, 'Active workspace not found'); + const workspace = await models.workspace.create({ + name: activeWorkspace.name, + scope: 'mock-server', + parentId: projectId, + }); + invariant(workspace, 'Workspace not found'); + // create a mock server under the workspace with the same name + const newServer = await models.mockServer.getOrCreateForParentId(workspace._id, { name: activeWorkspace.name }); + // TODO: filterout the mockServerName from the patch, or use an alternate method to create new workspace and server + const mockRoute = await models.mockRoute.create({ ...patch, parentId: newServer._id }); + return redirect(`/organization/${organizationId}/project/${projectId}/workspace/${newServer.parentId}/mock-server/mock-route/${mockRoute._id}`); + } + const mockServer = await models.mockServer.getById(patch.parentId); + invariant(mockServer, 'Mock server not found'); const mockRoute = await models.mockRoute.create(patch); - return redirect(`/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/mock-server/mock-route/${mockRoute._id}`); + return redirect(`/organization/${organizationId}/project/${projectId}/workspace/${mockServer.parentId}/mock-server/mock-route/${mockRoute._id}`); }; export const updateMockRouteAction: ActionFunction = async ({ request, params }) => { const { mockRouteId } = params; diff --git a/packages/insomnia/src/ui/routes/mock-route.tsx b/packages/insomnia/src/ui/routes/mock-route.tsx index d9e0844f5cf7..87a6f801745d 100644 --- a/packages/insomnia/src/ui/routes/mock-route.tsx +++ b/packages/insomnia/src/ui/routes/mock-route.tsx @@ -17,10 +17,12 @@ import { CodeEditor } from '../components/codemirror/code-editor'; import { MockResponseHeadersEditor } from '../components/editors/mock-response-headers-editor'; import { MockResponsePane } from '../components/mocks/mock-response-pane'; import { MockUrlBar } from '../components/mocks/mock-url-bar'; -import { showAlert } from '../components/modals'; +import { showAlert, showModal } from '../components/modals'; +import { AlertModal } from '../components/modals/alert-modal'; import { EmptyStatePane } from '../components/panes/empty-state-pane'; import { Pane, PaneBody, PaneHeader } from '../components/panes/pane'; import { SvgIcon } from '../components/svg-icon'; +import { MockServerLoaderData } from './mock-server'; import { useRootLoaderData } from './root'; export interface MockRouteLoaderData { @@ -91,6 +93,8 @@ export const useMockRoutePatcher = () => { export const MockRouteRoute = () => { const { mockServer, mockRoute } = useRouteLoaderData(':mockRouteId') as MockRouteLoaderData; + const { mockRoutes } = useRouteLoaderData('mock-server') as MockServerLoaderData; + const { userSession } = useRootLoaderData(); const patchMockRoute = useMockRoutePatcher(); const mockbinUrl = mockServer.useInsomniaCloud ? getMockServiceURL() : mockServer.url; @@ -142,6 +146,15 @@ export const MockRouteRoute = () => { }); const upsertMockbinHar = async (pathInput?: string) => { + const hasRouteInServer = mockRoutes.filter(m => m._id !== mockRoute._id).find(m => m.name === pathInput); + if (hasRouteInServer) { + showModal(AlertModal, { + title: 'Error', + message: `Path "${pathInput}" must be unique. Please enter a different name.`, + }); + + return; + }; const compoundId = mockRoute.parentId + pathInput; const error = await upsertBinOnRemoteFromResponse(compoundId); if (error) { @@ -163,6 +176,15 @@ export const MockRouteRoute = () => { }); }; const onSend = async (pathInput: string) => { + const hasRouteInServer = mockRoutes.filter(m => m._id !== mockRoute._id).find(m => m.name === pathInput); + if (hasRouteInServer) { + showModal(AlertModal, { + title: 'Error', + message: `Path "${pathInput}" must be unique. Please enter a different name.`, + }); + + return; + }; await upsertMockbinHar(pathInput); const compoundId = mockRoute.parentId + pathInput; createandSendPrivateRequest({ diff --git a/packages/insomnia/src/ui/routes/mock-server.tsx b/packages/insomnia/src/ui/routes/mock-server.tsx index 79940413f72c..5ab40c5de95d 100644 --- a/packages/insomnia/src/ui/routes/mock-server.tsx +++ b/packages/insomnia/src/ui/routes/mock-server.tsx @@ -11,17 +11,18 @@ import { WorkspaceSyncDropdown } from '../components/dropdowns/workspace-sync-dr import { EditableInput } from '../components/editable-input'; import { Icon } from '../components/icon'; import { showModal, showPrompt } from '../components/modals'; +import { AlertModal } from '../components/modals/alert-modal'; import { AskModal } from '../components/modals/ask-modal'; import { EmptyStatePane } from '../components/panes/empty-state-pane'; import { SidebarLayout } from '../components/sidebar-layout'; import { SvgIcon } from '../components/svg-icon'; import { formatMethodName } from '../components/tags/method-tag'; import { MockRouteResponse, MockRouteRoute, useMockRoutePatcher } from './mock-route'; -interface LoaderData { +export interface MockServerLoaderData { mockServerId: string; mockRoutes: MockRoute[]; } -export const loader: LoaderFunction = async ({ params }): Promise => { +export const loader: LoaderFunction = async ({ params }): Promise => { const { organizationId, projectId, workspaceId } = params; invariant(organizationId, 'Organization ID is required'); invariant(projectId, 'Project ID is required'); @@ -46,7 +47,7 @@ const MockServerRoute = () => { workspaceId: string; mockRouteId: string; }; - const { mockServerId, mockRoutes } = useLoaderData() as LoaderData; + const { mockServerId, mockRoutes } = useLoaderData() as MockServerLoaderData; const fetcher = useFetcher(); const navigate = useNavigate(); const patchMockRoute = useMockRoutePatcher(); @@ -66,6 +67,14 @@ const MockServerRoute = () => { defaultValue: mockRoutes.find(s => s._id === id)?.name, submitName: 'Rename', onComplete: name => { + const hasRouteInServer = mockRoutes.filter(m => m._id !== id).find(m => m.name === name); + if (hasRouteInServer) { + showModal(AlertModal, { + title: 'Error', + message: `Path "${name}" must be unique. Please enter a different name.`, + }); + return; + }; name && patchMockRoute(id, { name }); }, }); @@ -195,6 +204,14 @@ const MockServerRoute = () => { }); }} onSubmit={name => { + const hasRouteInServer = mockRoutes.filter(m => m._id !== item._id).find(m => m.name === name); + if (hasRouteInServer) { + showModal(AlertModal, { + title: 'Error', + message: `Path "${name}" must be unique. Please enter a different name.`, + }); + return; + }; name && fetcher.submit( { name }, { From c12c4ff05f735e99ff347f861450772d110dcb4b Mon Sep 17 00:00:00 2001 From: zhengjitf Date: Tue, 2 Apr 2024 22:28:23 +0800 Subject: [PATCH 4/4] fix: repeat on interval may lead to stay requesting (#6936) --- packages/insomnia/src/ui/components/request-url-bar.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/insomnia/src/ui/components/request-url-bar.tsx b/packages/insomnia/src/ui/components/request-url-bar.tsx index 72720cf38518..dcaffcdbabf2 100644 --- a/packages/insomnia/src/ui/components/request-url-bar.tsx +++ b/packages/insomnia/src/ui/components/request-url-bar.tsx @@ -190,7 +190,7 @@ export const RequestUrlBar = forwardRef(({ }; }, [sendOrConnect]); - useInterval(sendOrConnect, currentInterval ? currentInterval : null); + useInterval(sendOrConnect, currentInterval && fetcher.state === 'idle' ? currentInterval : null); useTimeoutWhen(sendOrConnect, currentTimeout, !!currentTimeout); const patchRequest = useRequestPatcher(); @@ -327,6 +327,7 @@ export const RequestUrlBar = forwardRef(({ defaultValue: '3', submitName: 'Start', onComplete: seconds => { + sendOrConnect(); setCurrentInterval(+seconds * 1000); }, })}