From 049964bb9b646ae845f908f5344f928167651e5b Mon Sep 17 00:00:00 2001 From: Opender Singh Date: Wed, 3 Mar 2021 10:16:48 +1300 Subject: [PATCH] Prompt for import as collection or document (#3130) * feat: add prompt and update import logic * feat: update usages and plugin api * chore: WIP * chore: convert to options * chore: don't create workspace model until needed * chore: add OR condition * chore: add workspace name and don't change scope * feat: prompt with appropriate name * chore: rename * chore: rename type * chore: update signature * chore: properly type the import functions * feat: don't activate the workspace after importing * chore: show loading on homepage * fix: typo * chore: fix tests and rename * Update packages/insomnia-app/app/common/__tests__/import.test.js Co-authored-by: David Marby Co-authored-by: David Marby --- .../app/common/__tests__/import.test.js | 18 +++- packages/insomnia-app/app/common/import.js | 90 ++++++++++++++----- packages/insomnia-app/app/models/api-spec.js | 2 +- packages/insomnia-app/app/models/workspace.js | 20 +++-- .../insomnia-app/app/plugins/context/data.js | 19 ++-- .../panes/placeholder-request-pane.js | 4 +- .../app/ui/components/panes/request-pane.js | 4 +- .../app/ui/components/wrapper-debug.js | 5 +- .../app/ui/components/wrapper-home.js | 25 +++--- .../app/ui/components/wrapper-onboarding.js | 18 ++-- .../insomnia-app/app/ui/components/wrapper.js | 35 ++++---- .../app/ui/redux/modules/global.js | 84 +++++++++-------- .../app/ui/redux/modules/helpers.js | 23 +++++ .../src/importers/openapi3.js | 1 + .../src/importers/swagger2.js | 1 + 15 files changed, 230 insertions(+), 119 deletions(-) diff --git a/packages/insomnia-app/app/common/__tests__/import.test.js b/packages/insomnia-app/app/common/__tests__/import.test.js index f792e6e31..5eba1f342 100644 --- a/packages/insomnia-app/app/common/__tests__/import.test.js +++ b/packages/insomnia-app/app/common/__tests__/import.test.js @@ -376,14 +376,26 @@ describe('export', () => { }); }); -describe('isApiSpec()', () => { +describe('isApiSpecImport()', () => { it.each(['swagger2', 'openapi3'])('should return true if spec id is %o', (id: string) => { - expect(importUtil.isApiSpec(id)).toBe(true); + expect(importUtil.isApiSpecImport({ id })).toBe(true); }); it('should return false if spec id is not valid', () => { const id = 'invalid-id'; - expect(importUtil.isApiSpec(id)).toBe(false); + expect(importUtil.isApiSpecImport({ id })).toBe(false); + }); +}); + +describe('isInsomniaV4Import()', () => { + it.each(['insomnia-4'])('should return true if spec id is %o', (id: string) => { + expect(importUtil.isInsomniaV4Import({ id })).toBe(true); + }); + + it('should return false if spec id is not valid', () => { + const id = 'invalid-id'; + + expect(importUtil.isInsomniaV4Import({ id })).toBe(false); }); }); diff --git a/packages/insomnia-app/app/common/import.js b/packages/insomnia-app/app/common/import.js index 63a36208f..24354fb80 100644 --- a/packages/insomnia-app/app/common/import.js +++ b/packages/insomnia-app/app/common/import.js @@ -20,6 +20,8 @@ import { isRequestGroup, isWorkspace, } from '../models/helpers/is-model'; +import type { Workspace, WorkspaceScope } from '../models/workspace'; +import type { ApiSpec } from '../models/api-spec'; const WORKSPACE_ID_KEY = '__WORKSPACE_ID__'; const BASE_ENVIRONMENT_ID_KEY = '__BASE_ENVIRONMENT_ID__'; @@ -61,10 +63,25 @@ export type ImportResult = { summary: { [string]: Array }, }; -export async function importUri( +type ConvertResultType = { + id: string, + name: string, + description: string, +}; + +type ConvertResult = { + type: ConvertResultType, + data: { + resources: Array, + }, +}; + +export type ImportRawConfig = { getWorkspaceId: () => Promise, - uri: string, -): Promise { + getWorkspaceScope?: string => Promise, +}; + +export async function importUri(uri: string, importConfig: ImportRawConfig): Promise { let rawText; // If GH preview, force raw @@ -86,7 +103,7 @@ export async function importUri( rawText = decodeURIComponent(uri); } - const result = await importRaw(getWorkspaceId, rawText); + const result = await importRaw(rawText, importConfig); const { summary, error } = result; if (error) { @@ -118,10 +135,10 @@ export async function importUri( } export async function importRaw( - getWorkspaceId: () => Promise, rawContent: string, + { getWorkspaceId, getWorkspaceScope }: ImportRawConfig, ): Promise { - let results; + let results: ConvertResult; try { results = await convert(rawContent); } catch (err) { @@ -132,7 +149,7 @@ export async function importRaw( }; } - const { data } = results; + const { data, type: resultsType } = results; // Generate all the ids we may need const generatedIds: { [string]: string | Function } = {}; @@ -147,17 +164,14 @@ export async function importRaw( const workspaceId = await getWorkspaceId(); // First try getting the workspace to overwrite - let workspace = await models.workspace.getById(workspaceId || 'n/a'); - - // If none provided, create a new workspace - if (workspace === null) { - workspace = await models.workspace.create({ name: 'Imported Workspace' }); - } + const workspace = await models.workspace.getById(workspaceId || 'n/a'); // Update this fn so it doesn't run again - generatedIds[WORKSPACE_ID_KEY] = workspace._id; + const idToUse = workspace?._id || generateId(models.workspace.prefix); - return workspace._id; + generatedIds[WORKSPACE_ID_KEY] = idToUse; + + return idToUse; }; // Contains the ID of the base environment to be used with the import @@ -240,8 +254,15 @@ export async function importRaw( const existingDoc = await db.get(model.type, resource._id); let newDoc: BaseModel; if (existingDoc) { + // If workspace, don't overwrite the existing scope + if (isWorkspace(model)) { + (resource: Workspace).scope = (existingDoc: Workspace).scope; + } newDoc = await db.docUpdate(existingDoc, resource); } else { + if (isWorkspace(model)) { + await updateWorkspaceScope(resource, resultsType, getWorkspaceScope); + } newDoc = await db.docCreate(model.type, resource); // Mark as not seen if we created a new workspace from sync @@ -256,7 +277,7 @@ export async function importRaw( // Store spec under workspace if it's OpenAPI for (const workspace of importedDocs[models.workspace.type]) { - if (isApiSpec(results.type.id)) { + if (isApiSpecImport(resultsType)) { const spec = await models.apiSpec.updateOrCreateForParentId(workspace._id, { contents: rawContent, contentType: 'yaml', @@ -274,17 +295,46 @@ export async function importRaw( await db.flushChanges(); - trackEvent('Data', 'Import', results.type.id); + trackEvent('Data', 'Import', resultsType.id); return { - source: results.type && typeof results.type.id === 'string' ? results.type.id : 'unknown', + source: resultsType && typeof resultsType.id === 'string' ? resultsType.id : 'unknown', summary: importedDocs, error: null, }; } -export function isApiSpec(content: string): boolean { - return content === 'openapi3' || content === 'swagger2'; +async function updateWorkspaceScope( + resource: Workspace, + resultType: ConvertResultType, + getWorkspaceScope?: string => Promise, +) { + // Set the workspace scope if creating a new workspace + // IF is creating a new workspace + // AND imported resource has no preset scope property OR scope is null + // AND we have a function to get scope + if ((!resource.hasOwnProperty('scope') || resource.scope === null) && getWorkspaceScope) { + const workspaceName = resource.name; + let specName; + // If is from insomnia v4 and the spec has contents, add to the name when prompting + if (isInsomniaV4Import(resultType)) { + const spec: ApiSpec | null = await models.apiSpec.getByParentId(resource._id); + + if (spec && spec.contents.trim()) { + specName = spec.fileName; + } + } + const nameToPrompt = specName ? `${specName} / ${workspaceName}` : workspaceName; + (resource: Workspace).scope = await getWorkspaceScope(nameToPrompt); + } +} + +export function isApiSpecImport({ id }: ConvertResultType): boolean { + return id === 'openapi3' || id === 'swagger2'; +} + +export function isInsomniaV4Import({ id }: ConvertResultType): boolean { + return id === 'insomnia-4'; } export async function exportWorkspacesHAR( diff --git a/packages/insomnia-app/app/models/api-spec.js b/packages/insomnia-app/app/models/api-spec.js index 0fbe3f23e..6945d2466 100644 --- a/packages/insomnia-app/app/models/api-spec.js +++ b/packages/insomnia-app/app/models/api-spec.js @@ -29,7 +29,7 @@ export async function migrate(doc: ApiSpec): Promise { return doc; } -export function getByParentId(workspaceId: string): Promise { +export function getByParentId(workspaceId: string): Promise { return db.getWhere(type, { parentId: workspaceId }); } diff --git a/packages/insomnia-app/app/models/workspace.js b/packages/insomnia-app/app/models/workspace.js index c492a0774..acd13de05 100644 --- a/packages/insomnia-app/app/models/workspace.js +++ b/packages/insomnia-app/app/models/workspace.js @@ -11,10 +11,17 @@ export const prefix = 'wrk'; export const canDuplicate = true; export const canSync = true; +export const WorkspaceScopeKeys = { + designer: 'designer', + collection: 'collection', +}; + +export type WorkspaceScope = $Keys; + type BaseWorkspace = { name: string, description: string, - scope: 'designer' | 'collection', + scope: WorkspaceScope, }; export type Workspace = BaseModel & BaseWorkspace; @@ -48,7 +55,7 @@ export async function all(): Promise> { if (workspaces.length === 0) { // Create default workspace - await create({ name: getAppName(), scope: 'collection' }); + await create({ name: getAppName(), scope: WorkspaceScopeKeys.collection }); return all(); } else { return workspaces; @@ -112,7 +119,10 @@ async function _migrateEnsureName(workspace: Workspace): Promise { * Ensure workspace scope is set to a valid entry */ function _migrateScope(workspace: Workspace): Workspace { - if (workspace.scope === 'designer' || workspace.scope === 'collection') { + if ( + workspace.scope === WorkspaceScopeKeys.designer || + workspace.scope === WorkspaceScopeKeys.collection + ) { return workspace; } @@ -120,13 +130,13 @@ function _migrateScope(workspace: Workspace): Workspace { type OldScopeTypes = 'spec' | 'debug' | null; switch ((workspace.scope: OldScopeTypes)) { case 'spec': { - workspace.scope = 'designer'; + workspace.scope = WorkspaceScopeKeys.designer; break; } case 'debug': case null: default: - workspace.scope = 'collection'; + workspace.scope = WorkspaceScopeKeys.collection; break; } diff --git a/packages/insomnia-app/app/plugins/context/data.js b/packages/insomnia-app/app/plugins/context/data.js index de83ae014..6900f5ebb 100644 --- a/packages/insomnia-app/app/plugins/context/data.js +++ b/packages/insomnia-app/app/plugins/context/data.js @@ -5,17 +5,20 @@ import { importRaw, importUri, } from '../../common/import'; -import type { Workspace } from '../../models/workspace'; +import type { Workspace, WorkspaceScope } from '../../models/workspace'; +import type { ImportRawConfig } from '../../common/import'; + +type PluginImportOptions = { workspaceId?: string, scope?: WorkspaceScope }; export function init(): { data: { import: Object, export: Object } } { return { data: { import: { - async uri(uri: string, options: { workspaceId?: string } = {}): Promise { - await importUri(() => Promise.resolve(options.workspaceId || null), uri); + async uri(uri: string, options: PluginImportOptions = {}): Promise { + await importUri(uri, buildImportRawConfig(options)); }, - async raw(text: string, options: { workspaceId?: string } = {}): Promise { - await importRaw(() => Promise.resolve(options.workspaceId || null), text); + async raw(text: string, options: PluginImportOptions = {}): Promise { + await importRaw(text, buildImportRawConfig(options)); }, }, export: { @@ -42,3 +45,9 @@ export function init(): { data: { import: Object, export: Object } } { }, }; } + +function buildImportRawConfig(options: PluginImportOptions): ImportRawConfig { + const getWorkspaceId = () => Promise.resolve(options.workspaceId || null); + const getWorkspaceScope = options.scope && (() => Promise.resolve(options.scope)); + return { getWorkspaceId, getWorkspaceScope }; +} diff --git a/packages/insomnia-app/app/ui/components/panes/placeholder-request-pane.js b/packages/insomnia-app/app/ui/components/panes/placeholder-request-pane.js index c1f188132..ca125078d 100644 --- a/packages/insomnia-app/app/ui/components/panes/placeholder-request-pane.js +++ b/packages/insomnia-app/app/ui/components/panes/placeholder-request-pane.js @@ -4,12 +4,12 @@ import Hotkey from '../hotkey'; import { hotKeyRefs } from '../../../common/hotkeys'; import * as hotkeys from '../../../common/hotkeys'; import type { Request } from '../../../models/request'; -import type { ForceToWorkspace } from '../../redux/modules/helpers'; import { Pane, PaneBody, PaneHeader } from './pane'; +import type { HandleImportFileCallback } from '../wrapper'; type Props = { hotKeyRegistry: hotkeys.HotKeyRegistry, - handleImportFile: (forceToWorkspace?: ForceToWorkspace) => void, + handleImportFile: HandleImportFileCallback, handleCreateRequest: () => Promise, }; diff --git a/packages/insomnia-app/app/ui/components/panes/request-pane.js b/packages/insomnia-app/app/ui/components/panes/request-pane.js index b69119508..8c90af261 100644 --- a/packages/insomnia-app/app/ui/components/panes/request-pane.js +++ b/packages/insomnia-app/app/ui/components/panes/request-pane.js @@ -28,11 +28,11 @@ import RenderedQueryString from '../rendered-query-string'; import RequestUrlBar from '../request-url-bar.js'; import type { Settings } from '../../../models/settings'; import RequestParametersEditor from '../editors/request-parameters-editor'; -import type { ForceToWorkspace } from '../../redux/modules/helpers'; import PlaceholderRequestPane from './placeholder-request-pane'; import { Pane, paneBodyClasses, PaneHeader } from './pane'; import classnames from 'classnames'; import { queryAllWorkspaceUrls } from '../../../models/helpers/query-all-workspace-urls'; +import type { HandleImportFileCallback } from '../wrapper'; type Props = { // Functions @@ -56,7 +56,7 @@ type Props = { updateSettingsUseBulkHeaderEditor: Function, updateSettingsUseBulkParametersEditor: Function, handleImport: Function, - handleImportFile: (forceToWorkspace?: ForceToWorkspace) => void, + handleImportFile: HandleImportFileCallback, // Other workspace: Workspace, diff --git a/packages/insomnia-app/app/ui/components/wrapper-debug.js b/packages/insomnia-app/app/ui/components/wrapper-debug.js index b3891cc39..898f91550 100644 --- a/packages/insomnia-app/app/ui/components/wrapper-debug.js +++ b/packages/insomnia-app/app/ui/components/wrapper-debug.js @@ -2,7 +2,7 @@ import * as React from 'react'; import { autoBindMethodsForReact } from 'class-autobind-decorator'; import PageLayout from './page-layout'; -import type { WrapperProps } from './wrapper'; +import type { HandleImportFileCallback, WrapperProps } from './wrapper'; import RequestPane from './panes/request-pane'; import ErrorBoundary from './error-boundary'; import ResponsePane from './panes/response-pane'; @@ -11,7 +11,6 @@ import SidebarFilter from './sidebar/sidebar-filter'; import EnvironmentsDropdown from './dropdowns/environments-dropdown'; import { AUTOBIND_CFG } from '../../common/constants'; import { isGrpcRequest } from '../../models/helpers/is-model'; -import type { ForceToWorkspace } from '../redux/modules/helpers'; import GrpcRequestPane from './panes/grpc-request-pane'; import GrpcResponsePane from './panes/grpc-response-pane'; import WorkspacePageHeader from './workspace-page-header'; @@ -31,7 +30,7 @@ type Props = { handleForceUpdateRequest: Function, handleForceUpdateRequestHeaders: Function, handleImport: Function, - handleImportFile: (forceToWorkspace?: ForceToWorkspace) => void, + handleImportFile: HandleImportFileCallback, handleRequestCreate: Function, handleRequestGroupCreate: Function, handleSendAndDownloadRequestWithActiveEnvironment: Function, diff --git a/packages/insomnia-app/app/ui/components/wrapper-home.js b/packages/insomnia-app/app/ui/components/wrapper-home.js index 8f4b3fa14..9263378d9 100644 --- a/packages/insomnia-app/app/ui/components/wrapper-home.js +++ b/packages/insomnia-app/app/ui/components/wrapper-home.js @@ -37,11 +37,15 @@ import Highlight from './base/highlight'; import type { GlobalActivity } from '../../common/constants'; import { fuzzyMatchAll } from '../../common/misc'; -import type { WrapperProps } from './wrapper'; +import type { + HandleImportClipboardCallback, + HandleImportFileCallback, + HandleImportUriCallback, + WrapperProps, +} from './wrapper'; import Notice from './notice'; import GitRepositorySettingsModal from '../components/modals/git-repository-settings-modal'; import PageLayout from './page-layout'; -import type { ForceToWorkspace } from '../redux/modules/helpers'; import { ForceToWorkspaceKeys } from '../redux/modules/helpers'; import coreLogo from '../images/insomnia-core-logo.png'; import { MemPlugin } from '../../sync/git/mem-plugin'; @@ -58,9 +62,9 @@ import AccountDropdown from './dropdowns/account-dropdown'; type Props = {| wrapperProps: WrapperProps, - handleImportFile: (forceToWorkspace: ForceToWorkspace) => void, - handleImportUri: (uri: string, forceToWorkspace: ForceToWorkspace) => void, - handleImportClipboard: (forceToWorkspace: ForceToWorkspace) => void, + handleImportFile: HandleImportFileCallback, + handleImportUri: HandleImportUriCallback, + handleImportClipboard: HandleImportClipboardCallback, |}; type State = {| @@ -121,11 +125,11 @@ class WrapperHome extends React.PureComponent { } _handleImportFile() { - this.props.handleImportFile(ForceToWorkspaceKeys.new); + this.props.handleImportFile({ forceToWorkspace: ForceToWorkspaceKeys.new }); } _handleImportClipBoard() { - this.props.handleImportClipboard(ForceToWorkspaceKeys.new); + this.props.handleImportClipboard({ forceToWorkspace: ForceToWorkspaceKeys.new }); } _handleImportUri() { @@ -135,7 +139,7 @@ class WrapperHome extends React.PureComponent { label: 'URL', placeholder: 'https://website.com/insomnia-import.json', onComplete: uri => { - this.props.handleImportUri(uri, ForceToWorkspaceKeys.new); + this.props.handleImportUri(uri, { forceToWorkspace: ForceToWorkspaceKeys.new }); }, }); } @@ -446,7 +450,7 @@ class WrapperHome extends React.PureComponent { New } onClick={this._handleDocumentCreate}> - Blank Document + Design Document } onClick={this._handleCollectionCreate}> Request Collection @@ -495,7 +499,7 @@ class WrapperHome extends React.PureComponent { } render() { - const { workspaces } = this.props.wrapperProps; + const { workspaces, isLoading } = this.props.wrapperProps; const { filter } = this.state; // Render each card, removing all the ones that don't match the filter @@ -511,6 +515,7 @@ class WrapperHome extends React.PureComponent { Insomnia + {isLoading ? : null} } gridRight={ diff --git a/packages/insomnia-app/app/ui/components/wrapper-onboarding.js b/packages/insomnia-app/app/ui/components/wrapper-onboarding.js index 5d0c95f3e..b6910c09e 100644 --- a/packages/insomnia-app/app/ui/components/wrapper-onboarding.js +++ b/packages/insomnia-app/app/ui/components/wrapper-onboarding.js @@ -6,17 +6,17 @@ import { showPrompt } from './modals'; import type { BaseModel } from '../../models'; import * as models from '../../models'; import { AUTOBIND_CFG, getAppLongName, getAppName } from '../../common/constants'; -import type { WrapperProps } from './wrapper'; +import type { HandleImportFileCallback, HandleImportUriCallback, WrapperProps } from './wrapper'; import * as db from '../../common/database'; import chartSrc from '../images/chart.svg'; -import type { ForceToWorkspace } from '../redux/modules/helpers'; import { ForceToWorkspaceKeys } from '../redux/modules/helpers'; import OnboardingContainer from './onboarding-container'; +import { WorkspaceScopeKeys } from '../../models/workspace'; type Props = {| wrapperProps: WrapperProps, - handleImportFile: (forceToWorkspace: ForceToWorkspace) => any, - handleImportUri: (uri: string, forceToWorkspace: ForceToWorkspace) => any, + handleImportFile: HandleImportFileCallback, + handleImportUri: HandleImportUriCallback, |}; type State = {| @@ -77,7 +77,10 @@ class WrapperOnboarding extends React.PureComponent { _handleImportFile() { const { handleImportFile } = this.props; - handleImportFile(ForceToWorkspaceKeys.new); + handleImportFile({ + forceToWorkspace: ForceToWorkspaceKeys.new, + forceToScope: WorkspaceScopeKeys.designer, + }); } _handleImportUri(defaultValue: string) { @@ -89,7 +92,10 @@ class WrapperOnboarding extends React.PureComponent { placeholder: 'https://example.com/openapi-spec.yaml', label: 'URI to Import', onComplete: value => { - handleImportUri(value, ForceToWorkspaceKeys.new); + handleImportUri(value, { + forceToWorkspace: ForceToWorkspaceKeys.new, + forceToScope: WorkspaceScopeKeys.designer, + }); }, }); } diff --git a/packages/insomnia-app/app/ui/components/wrapper.js b/packages/insomnia-app/app/ui/components/wrapper.js index dac749df0..4d032620c 100644 --- a/packages/insomnia-app/app/ui/components/wrapper.js +++ b/packages/insomnia-app/app/ui/components/wrapper.js @@ -86,7 +86,6 @@ import WrapperDebug from './wrapper-debug'; import { importRaw } from '../../common/import'; import GitSyncDropdown from './dropdowns/git-sync-dropdown'; import { DropdownButton } from './base/dropdown'; -import type { ForceToWorkspace } from '../redux/modules/helpers'; import type { UnitTest } from '../../models/unit-test'; import type { UnitTestResult } from '../../models/unit-test-result'; import type { UnitTestSuite } from '../../models/unit-test-suite'; @@ -95,6 +94,7 @@ import { Spectral } from '@stoplight/spectral'; import ProtoFilesModal from './modals/proto-files-modal'; import { GrpcDispatchModalWrapper } from '../context/grpc'; import WrapperMigration from './wrapper-migration'; +import type { ImportOptions } from '../redux/modules/global'; const spectral = new Spectral(); @@ -103,16 +103,9 @@ export type WrapperProps = { handleActivateRequest: Function, handleSetSidebarFilter: Function, handleToggleMenuBar: Function, - handleImportFileToWorkspace: (workspaceId: string, forceToWorkspace?: ForceToWorkspace) => void, - handleImportClipBoardToWorkspace: ( - workspaceId: string, - forceToWorkspace?: ForceToWorkspace, - ) => void, - handleImportUriToWorkspace: ( - workspaceId: string, - uri: string, - forceToWorkspace?: ForceToWorkspace, - ) => void, + handleImportFileToWorkspace: (workspaceId: string, options?: ImportOptions) => void, + handleImportClipBoardToWorkspace: (workspaceId: string, options?: ImportOptions) => void, + handleImportUriToWorkspace: (workspaceId: string, uri: string, options?: ImportOptions) => void, handleInitializeEntities: () => Promise, handleExportFile: Function, handleShowExportRequestsModal: Function, @@ -205,6 +198,10 @@ export type WrapperProps = { activeResponse: Response | null, }; +export type HandleImportFileCallback = (options?: ImportOptions) => void; +export type HandleImportClipboardCallback = (options?: ImportOptions) => void; +export type HandleImportUriCallback = (uri: string, options?: ImportOptions) => void; + type State = { forceRefreshKey: number, activeGitBranch: string, @@ -348,7 +345,9 @@ class Wrapper extends React.PureComponent { // Delaying generation so design to debug mode is smooth handleSetActiveActivity(nextActivity); setTimeout(() => { - importRaw(() => Promise.resolve(workspaceId), activeApiSpec.contents); + importRaw(activeApiSpec.contents, { + getWorkspaceId: () => Promise.resolve(workspaceId), + }); }, 1000); } @@ -367,16 +366,16 @@ class Wrapper extends React.PureComponent { return sUpdate(this.props.settings, { useBulkParametersEditor }); } - _handleImportFile(forceToWorkspace?: ForceToWorkspace): void { - this.props.handleImportFileToWorkspace(this.props.activeWorkspace._id, forceToWorkspace); + _handleImportFile(options?: ImportOptions): void { + this.props.handleImportFileToWorkspace(this.props.activeWorkspace._id, options); } - _handleImportUri(uri: string, forceToWorkspace?: ForceToWorkspace): void { - this.props.handleImportUriToWorkspace(this.props.activeWorkspace._id, uri, forceToWorkspace); + _handleImportUri(uri: string, options?: ImportOptions): void { + this.props.handleImportUriToWorkspace(this.props.activeWorkspace._id, uri, options); } - _handleImportClipBoard(forceToWorkspace?: ForceToWorkspace): void { - this.props.handleImportClipBoardToWorkspace(this.props.activeWorkspace._id, forceToWorkspace); + _handleImportClipBoard(options?: ImportOptions): void { + this.props.handleImportClipBoardToWorkspace(this.props.activeWorkspace._id, options); } _handleSetActiveResponse(responseId: string | null): void { diff --git a/packages/insomnia-app/app/ui/redux/modules/global.js b/packages/insomnia-app/app/ui/redux/modules/global.js index 6cecfd6b8..75d9a17db 100644 --- a/packages/insomnia-app/app/ui/redux/modules/global.js +++ b/packages/insomnia-app/app/ui/redux/modules/global.js @@ -7,7 +7,7 @@ import path from 'path'; import AskModal from '../../../ui/components/modals/ask-modal'; import * as moment from 'moment'; -import type { ImportResult } from '../../../common/import'; +import type { ImportRawConfig, ImportResult } from '../../../common/import'; import * as importUtils from '../../../common/import'; import AlertModal from '../../components/modals/alert-modal'; import PaymentNotificationModal from '../../components/modals/payment-notification-modal'; @@ -24,12 +24,12 @@ import SettingsModal, { } from '../../components/modals/settings-modal'; import install from '../../../plugins/install'; import type { ForceToWorkspace } from './helpers'; -import { askToImportIntoWorkspace } from './helpers'; +import { askToImportIntoWorkspace, askToSetWorkspaceScope } from './helpers'; import { createPlugin } from '../../../plugins/create'; import { reloadPlugins } from '../../../plugins'; import { setTheme } from '../../../plugins/misc'; import type { GlobalActivity } from '../../../common/constants'; -import type { Workspace } from '../../../models/workspace'; +import type { Workspace, WorkspaceScope } from '../../../models/workspace'; import { ACTIVITY_DEBUG, ACTIVITY_HOME, @@ -293,7 +293,15 @@ export function setActiveWorkspace(workspaceId: string) { return { type: SET_ACTIVE_WORKSPACE, workspaceId }; } -export function importFile(workspaceId: string, forceToWorkspace?: ForceToWorkspace) { +export type ImportOptions = { + forceToWorkspace?: ForceToWorkspace, + forceToScope?: WorkspaceScope, +}; + +export function importFile( + workspaceId: string, + { forceToScope, forceToWorkspace }: ImportOptions = {}, +) { return async dispatch => { dispatch(loadStart()); @@ -329,28 +337,22 @@ export function importFile(workspaceId: string, forceToWorkspace?: ForceToWorksp } // Let's import all the paths! - let importedWorkspaces = []; for (const p of paths) { try { const uri = `file://${p}`; - const result = await importUtils.importUri( - askToImportIntoWorkspace(workspaceId, forceToWorkspace), - uri, - ); - importedWorkspaces = handleImportResult( - result, - 'The file does not contain a valid specification.', - ); + + const options: ImportRawConfig = { + getWorkspaceScope: askToSetWorkspaceScope(forceToScope), + getWorkspaceId: askToImportIntoWorkspace(workspaceId, forceToWorkspace), + }; + const result = await importUtils.importUri(uri, options); + handleImportResult(result, 'The file does not contain a valid specification.'); } catch (err) { showModal(AlertModal, { title: 'Import Failed', message: err + '' }); } finally { dispatch(loadStop()); } } - - if (importedWorkspaces.length === 1) { - dispatch(setActiveWorkspace(importedWorkspaces[0]._id)); - } }; } @@ -369,7 +371,10 @@ function handleImportResult(result: ImportResult, errorMessage: string): Array { dispatch(loadStart()); const schema = electron.clipboard.readText(); @@ -381,16 +386,13 @@ export function importClipBoard(workspaceId: string, forceToWorkspace?: ForceToW return; } // Let's import all the paths! - let importedWorkspaces = []; try { - const result = await importUtils.importRaw( - askToImportIntoWorkspace(workspaceId, forceToWorkspace), - schema, - ); - importedWorkspaces = handleImportResult( - result, - 'Your clipboard does not contain a valid specification.', - ); + const options: ImportRawConfig = { + getWorkspaceScope: askToSetWorkspaceScope(forceToScope), + getWorkspaceId: askToImportIntoWorkspace(workspaceId, forceToWorkspace), + }; + const result = await importUtils.importRaw(schema, options); + handleImportResult(result, 'Your clipboard does not contain a valid specification.'); } catch (err) { showModal(AlertModal, { title: 'Import Failed', @@ -399,35 +401,29 @@ export function importClipBoard(workspaceId: string, forceToWorkspace?: ForceToW } finally { dispatch(loadStop()); } - if (importedWorkspaces.length === 1) { - dispatch(setActiveWorkspace(importedWorkspaces[0]._id)); - } }; } -export function importUri(workspaceId: string, uri: string, forceToWorkspace?: ForceToWorkspace) { +export function importUri( + workspaceId: string, + uri: string, + { forceToScope, forceToWorkspace }: ImportOptions = {}, +) { return async dispatch => { dispatch(loadStart()); - let importedWorkspaces = []; try { - const result = await importUtils.importUri( - askToImportIntoWorkspace(workspaceId, forceToWorkspace), - uri, - ); - importedWorkspaces = handleImportResult( - result, - 'The URI does not contain a valid specification.', - ); + const options: ImportRawConfig = { + getWorkspaceScope: askToSetWorkspaceScope(forceToScope), + getWorkspaceId: askToImportIntoWorkspace(workspaceId, forceToWorkspace), + }; + const result = await importUtils.importUri(uri, options); + handleImportResult(result, 'The URI does not contain a valid specification.'); } catch (err) { showModal(AlertModal, { title: 'Import Failed', message: err + '' }); } finally { dispatch(loadStop()); } - - if (importedWorkspaces.length === 1) { - dispatch(setActiveWorkspace(importedWorkspaces[0]._id)); - } }; } diff --git a/packages/insomnia-app/app/ui/redux/modules/helpers.js b/packages/insomnia-app/app/ui/redux/modules/helpers.js index 3610762a9..1917b4cb5 100644 --- a/packages/insomnia-app/app/ui/redux/modules/helpers.js +++ b/packages/insomnia-app/app/ui/redux/modules/helpers.js @@ -1,6 +1,7 @@ // @flow import { showModal } from '../../components/modals'; import AskModal from '../../components/modals/ask-modal'; +import { WorkspaceScopeKeys } from '../../../models/workspace'; export const ForceToWorkspaceKeys = { new: 'new', @@ -31,3 +32,25 @@ export function askToImportIntoWorkspace(workspaceId: string, forceToWorkspace?: } }; } + +export function askToSetWorkspaceScope(scope?: WorkspaceScope) { + return function(name: string) { + switch (scope) { + case WorkspaceScopeKeys.collection: + case WorkspaceScopeKeys.designer: + return scope; + default: + return new Promise(resolve => { + showModal(AskModal, { + title: 'Import As', + message: `How would you like to import "${name}"?`, + noText: 'Request Collection', + yesText: 'Design Document', + onDone: yes => { + resolve(yes ? WorkspaceScopeKeys.designer : WorkspaceScopeKeys.collection); + }, + }); + }); + } + }; +} diff --git a/packages/insomnia-importers/src/importers/openapi3.js b/packages/insomnia-importers/src/importers/openapi3.js index 956e03b79..b9fc8e590 100644 --- a/packages/insomnia-importers/src/importers/openapi3.js +++ b/packages/insomnia-importers/src/importers/openapi3.js @@ -60,6 +60,7 @@ module.exports.convert = async function(rawData) { parentId: null, name: `${api.info.title} ${api.info.version}`, description: api.info.description || '', + // scope is not set because it could be imported for design OR to generate requests }; const baseEnv = { diff --git a/packages/insomnia-importers/src/importers/swagger2.js b/packages/insomnia-importers/src/importers/swagger2.js index 89432d0e6..eee909aca 100644 --- a/packages/insomnia-importers/src/importers/swagger2.js +++ b/packages/insomnia-importers/src/importers/swagger2.js @@ -41,6 +41,7 @@ module.exports.convert = async function(rawData) { parentId: null, name: `${api.info.title} ${api.info.version}`, description: api.info.description || '', + // scope is not set because it could be imported for design OR to generate requests }; const baseEnv = {