feat: lower Import UX friction#9643
Conversation
e72b619 to
e4a0e2a
Compare
There was a problem hiding this comment.
Pull request overview
This PR improves the import UX (notably cURL) by adding source/origin context, immediate cURL validation feedback, and a project/collection selection flow, while also refactoring the cURL importer and aligning importer authentication typing with the main request model.
Changes:
- Update Import modal UI (warning banner, larger cURL input, inline validation messaging, show origin link, allow selecting a target project/collection).
- Refactor the cURL importer (better structure, bearer/basic auth parsing changes) and update tests/snapshots accordingly.
- Align importer
authenticationtyping withRequestAuthenticationused by request models.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/insomnia/src/ui/components/modals/import-modal/import-modal.tsx | Import modal UX updates (cURL textarea + validation, origin display, project/collection selectors) |
| packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.new.tsx | Safer optional chaining for cookie counting |
| packages/insomnia/src/root.tsx | Propagate origin through deep-link import params |
| packages/insomnia/src/main/importers/importers/openapi-3.ts | Remove old Authentication type usage and rely on updated typing |
| packages/insomnia/src/main/importers/importers/curl.ts | Refactor cURL parsing/building; add bearer/basic auth extraction changes |
| packages/insomnia/src/main/importers/importers/curl.test.ts | Rewrite/condense cURL tests for refactored importer |
| packages/insomnia/src/main/importers/importers/snapshots/index.test.ts.snap | Snapshot update for auth shape (basic includes type) |
| packages/insomnia/src/main/importers/entities.ts | Replace custom Authentication with `RequestAuthentication |
| packages/insomnia/src/common/import.ts | Rename default imported workspace label to “Imported Collection” |
Comments suppressed due to low confidence (1)
packages/insomnia/src/ui/components/modals/import-modal/import-modal.tsx:503
ImportResourcesForm'sonImportprop signature was changed to includeselectedProjectId, but the call sites still pass onlyselectedWorkspaceId(e.g. the Import button). This will shift arguments so the selected workspace id is treated as the project id, leading to imports targeting the wrong project/workspace. Update the handler plumbing so the Import button passes(overrideBaseEnvironmentData, selectedProjectId, selectedWorkspaceId)and the parentImportModalusesselectedProjectIdwhen submitting the import action.
}: {
scanResults: ScanResult[];
errors?: string[];
onImport: (overrideBaseEnvironmentData: boolean, selectedProjectId?: string, selectedWorkspaceId?: string) => void;
disabled: boolean;
loading: boolean;
isImportingBaseEnvironmentToWorkspace: boolean;
}) => {
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const extractHeaders = (pairsByName: PairsByName) => { | ||
| return [...((pairsByName.header as string[] | undefined) || []), ...((pairsByName.H as string[] | undefined) || [])] | ||
| .filter(header => { | ||
| const [name, value] = header.split(/:(.*)$/); | ||
| return isBearerAuth(name, value) === false; | ||
| }) |
There was a problem hiding this comment.
extractHeaders filters out bearer auth headers by splitting on : and calling isBearerAuth(name, value), but value can be undefined for malformed headers (no colon). This will throw before the later if (!value) handling. Ensure isBearerAuth handles missing values or default value to an empty string before calling it.
|
|
||
| import type { Parameter } from '../entities'; | ||
| import { convert } from './curl'; | ||
| import { convert, name } from './curl'; |
There was a problem hiding this comment.
Unused import: name is imported from ./curl but never referenced in this test file. This will fail lint/typecheck in many setups; remove it or use it in an assertion.
| import { convert, name } from './curl'; | |
| import { convert } from './curl'; |
| const validateCurl = async (isMounted: boolean, value: string) => { | ||
| if (!value) { | ||
| isMounted && setMessage('Invalid cURL request'); | ||
| return; | ||
| } | ||
| try { | ||
| const { data } = await window.main.parseImport( | ||
| { | ||
| contentStr: value, | ||
| }, | ||
| { | ||
| importerId: 'curl', | ||
| }, | ||
| ); | ||
| const { resources } = data; | ||
| const importedRequest = resources[0]; | ||
| isMounted && | ||
| setMessage( | ||
| importedRequest.url | ||
| ? `Detected ${importedRequest.method} request to ${importedRequest.url}` | ||
| : 'Invalid cURL request', | ||
| ); | ||
| } catch (error) { | ||
| console.log('[importer] error', error); | ||
|
|
||
| isMounted && | ||
| setMessage( | ||
| error.message.includes('No importers found for file') | ||
| ? 'Invalid cURL request' | ||
| : error.message.replace("Error invoking remote method 'parseImport': Error: ", ''), | ||
| ); | ||
| } | ||
| }; | ||
| useEffect(() => { | ||
| let isMounted = true; | ||
|
|
||
| validateCurl(isMounted, from?.type === 'curl' && from.defaultValue ? from.defaultValue : ''); | ||
| return () => { | ||
| isMounted = false; | ||
| }; | ||
| }, [from]); |
There was a problem hiding this comment.
The isMounted guard in validateCurl doesn't work as intended: you're passing a boolean value into the async function, so setting isMounted = false in the cleanup won't affect an in-flight validation (it captured true). This can still call setMessage after unmount. Use a mutable ref (e.g. useRef(true)) or close over the isMounted variable directly (don’t pass it as a parameter), or use an AbortController/sequence id to ignore stale results.
| const { resources } = data; | ||
| const importedRequest = resources[0]; | ||
| isMounted && | ||
| setMessage( | ||
| importedRequest.url | ||
| ? `Detected ${importedRequest.method} request to ${importedRequest.url}` | ||
| : 'Invalid cURL request', | ||
| ); |
There was a problem hiding this comment.
resources[0] is used without checking resources.length. If parseImport returns an empty resources array for invalid input, importedRequest will be undefined and importedRequest.url will throw. Guard against an empty result before reading url/method and set an appropriate message.
| isMounted && | ||
| setMessage( | ||
| error.message.includes('No importers found for file') | ||
| ? 'Invalid cURL request' | ||
| : error.message.replace("Error invoking remote method 'parseImport': Error: ", ''), | ||
| ); |
There was a problem hiding this comment.
In the catch block, error is unknown and may not have a .message property. Accessing error.message can throw and mask the original failure. Narrow with error instanceof Error (or safely stringify) before reading message, and avoid relying on exact remote-method prefix strings for parsing.
| isMounted && | |
| setMessage( | |
| error.message.includes('No importers found for file') | |
| ? 'Invalid cURL request' | |
| : error.message.replace("Error invoking remote method 'parseImport': Error: ", ''), | |
| ); | |
| const rawMessage = | |
| error instanceof Error | |
| ? error.message | |
| : String(error); | |
| const cleanedMessage = rawMessage.replace( | |
| "Error invoking remote method 'parseImport': Error: ", | |
| '', | |
| ); | |
| const finalMessage = rawMessage.includes('No importers found for file') | |
| ? 'Invalid cURL request' | |
| : cleanedMessage; | |
| isMounted && setMessage(finalMessage); |
| {from?.type === 'curl' && from.origin && ( | ||
| <div className="flex w-full justify-start py-1"> | ||
| <CurlIcon /> | ||
| cURL from{' '} | ||
| <Link className="px-2 font-bold underline" href={from.origin}> | ||
| {' '} | ||
| {from.origin}{' '} | ||
| </Link>{' '} | ||
| ⚠️ | ||
| </div> |
There was a problem hiding this comment.
from.origin is untrusted input (comes from URL params) and is being placed directly into an anchor href via react-aria-components Link. This can enable javascript:/unexpected schemes in a clickable link. Prefer the app’s ~/ui/components/base/link (which routes through window.main.openInBrowser) and/or validate/allowlist schemes (http/https) before rendering/clicking.
7e4b656 to
e80762a
Compare
e80762a to
2bc6ea4
Compare
Motivation: reduce friction to import collections and single requests and add flexibility to import location.
Goal: make curl a reasonable way to deeplink into the app allowing them to be added separately or in a collection
TODO
future work
closes INS-1966