removes insomnia designer migration code (#4404)

This commit is contained in:
Dimitri Mitropoulos
2022-01-25 10:14:24 -06:00
committed by GitHub
parent 48ac330d95
commit 4940348dd9
44 changed files with 13 additions and 830 deletions

View File

@@ -1,7 +1,6 @@
import {
ACTIVITY_DEBUG,
ACTIVITY_HOME,
ACTIVITY_MIGRATION,
ACTIVITY_SPEC,
ACTIVITY_UNIT_TEST,
FLEXIBLE_URL_REGEX,
@@ -43,7 +42,6 @@ describe('isWorkspaceActivity', () => {
it('should return false', () => {
expect(isWorkspaceActivity(ACTIVITY_HOME)).toBe(false);
expect(isWorkspaceActivity(ACTIVITY_MIGRATION)).toBe(false);
});
});
@@ -53,7 +51,6 @@ describe('isValidActivity', () => {
expect(isValidActivity(ACTIVITY_DEBUG)).toBe(true);
expect(isValidActivity(ACTIVITY_UNIT_TEST)).toBe(true);
expect(isValidActivity(ACTIVITY_HOME)).toBe(true);
expect(isValidActivity(ACTIVITY_MIGRATION)).toBe(true);
});
it('should return false', () => {

View File

@@ -159,13 +159,11 @@ export type GlobalActivity =
| 'spec'
| 'debug'
| 'unittest'
| 'home'
| 'migration';
| 'home';
export const ACTIVITY_SPEC: GlobalActivity = 'spec';
export const ACTIVITY_DEBUG: GlobalActivity = 'debug';
export const ACTIVITY_UNIT_TEST: GlobalActivity = 'unittest';
export const ACTIVITY_HOME: GlobalActivity = 'home';
export const ACTIVITY_MIGRATION: GlobalActivity = 'migration';
export const isWorkspaceActivity = (activity?: string): activity is GlobalActivity =>
isDesignActivity(activity) || isCollectionActivity(activity);
@@ -178,7 +176,6 @@ export const isDesignActivity = (activity?: string): activity is GlobalActivity
return true;
case ACTIVITY_HOME:
case ACTIVITY_MIGRATION:
default:
return false;
}
@@ -192,7 +189,6 @@ export const isCollectionActivity = (activity?: string): activity is GlobalActiv
case ACTIVITY_SPEC:
case ACTIVITY_UNIT_TEST:
case ACTIVITY_HOME:
case ACTIVITY_MIGRATION:
default:
return false;
}
@@ -204,7 +200,6 @@ export const isValidActivity = (activity: string): activity is GlobalActivity =>
case ACTIVITY_DEBUG:
case ACTIVITY_UNIT_TEST:
case ACTIVITY_HOME:
case ACTIVITY_MIGRATION:
return true;
default:

View File

@@ -12,11 +12,6 @@ export function clickLink(href: string) {
}
}
export function getDesignerDataDir() {
const { app } = electron.remote || electron;
return process.env.DESIGNER_DATA_PATH || join(app.getPath('appData'), 'Insomnia Designer');
}
/**
* This environment variable is added by electron-builder.
* see: https://www.electron.build/configuration/nsis.html#portable\

View File

@@ -1,252 +0,0 @@
import fs from 'fs';
import fsx from 'fs-extra';
import { difference } from 'lodash';
import NeDB from 'nedb';
import fsPath from 'path';
import type { BaseModel } from '../models';
import * as models from '../models';
import { getModelName } from '../models';
import type { Settings } from '../models/settings';
import { forceWorkspaceScopeToDesign } from '../sync/git/force-workspace-scope-to-design';
import { database as db } from './database';
async function loadDesignerDb(
types: string[],
designerDataDir: string,
): Promise<Record<string, any>> {
const designerDb = {};
types.forEach(type => {
designerDb[type] = []; // initialize each type to empty array
});
const promises = types.map(
type =>
new Promise<void>((resolve, reject) => {
const filePath = fsPath.join(designerDataDir, `insomnia.${type}.db`);
if (!fs.existsSync(filePath)) {
console.log(`[db] db file for ${type} not found: ${filePath}`);
resolve();
}
// Load the data
const collection = new NeDB({
autoload: true,
filename: filePath,
corruptAlertThreshold: 0.9,
});
// Find every entry and store in memory
collection.find({}, (err, docs: BaseModel[]) => {
if (err) {
return reject(err);
}
(designerDb[type] as Record<string, any>[]).push(...docs);
resolve();
});
}),
);
await Promise.all(promises);
// Return entries, but no longer linked to the database files
return designerDb;
}
type DBType = Record<string, BaseModel[]>;
export interface MigrationOptions {
useDesignerSettings: boolean;
copyPlugins: boolean;
copyWorkspaces: boolean;
designerDataDir: string;
coreDataDir: string;
}
export interface MigrationResult {
error?: Error;
}
async function createCoreBackup(modelTypes: string[], coreDataDir: string) {
console.log('[db-merge] creating backup');
const backupDir = fsPath.join(coreDataDir, 'core-backup');
await fsx.remove(backupDir);
await fsx.ensureDir(backupDir);
// Copy db files
const filesToCopy = modelTypes.map(modelType => `insomnia.${modelType}.db`);
for (const entryName of filesToCopy) {
const src = fsPath.join(coreDataDir, entryName);
const dest = fsPath.join(backupDir, entryName);
await fsx.copy(src, dest);
}
// Copy dirs
const dirsToCopy = ['plugins', 'responses', 'version-control'];
await copyDirs(dirsToCopy, coreDataDir, backupDir);
console.log(`[db-merge] backup created at ${backupDir}`);
return backupDir;
}
async function migratePlugins(designerDataDir: string, coreDataDir: string) {
const designerPluginDir = fsPath.join(designerDataDir, 'plugins');
const corePluginDir = fsPath.join(coreDataDir, 'plugins');
// get list of plugins in Designer
const designerPlugins = await readDirs(designerPluginDir);
await removeDirs(designerPlugins, corePluginDir);
await copyDirs(designerPlugins, designerPluginDir, corePluginDir);
// Remove plugin bundle from installed plugins because it's included with the app now
const pluginsToDelete = [
'insomnia-plugin-kong-bundle',
'insomnia-plugin-kong-declarative-config',
'insomnia-plugin-kong-kubernetes-config',
'insomnia-plugin-kong-portal',
];
await removeDirs(pluginsToDelete, corePluginDir);
}
async function readDirs(srcDir: string) {
if (existsAndIsDirectory(srcDir)) {
return await fs.promises.readdir(srcDir);
} else {
return [];
}
}
async function copyDirs(dirs: string[], srcDir: string, destDir: string) {
for (const dir of dirs.filter(c => c)) {
const src = fsPath.join(srcDir, dir);
const dest = fsPath.join(destDir, dir);
// If source exists, ensure the destination exists, and copy into it
if (existsAndIsDirectory(src)) {
await fsx.ensureDir(dest);
await fsx.copy(src, dest);
}
}
}
async function removeDirs(dirs: string[], srcDir: string) {
for (const dir of dirs.filter(c => c)) {
const dirToRemove = fsPath.join(srcDir, dir);
if (existsAndIsDirectory(dirToRemove)) {
await fsx.remove(dirToRemove);
}
}
}
export function existsAndIsDirectory(name: string) {
return fs.existsSync(name) && fs.statSync(name).isDirectory();
}
export default async function migrateFromDesigner({
useDesignerSettings,
designerDataDir,
coreDataDir,
copyPlugins,
copyWorkspaces,
}: MigrationOptions) {
console.log(
`[db-merge] starting process for migrating from ${designerDataDir} to ${coreDataDir}`,
);
const nonWorkspaceModels = [
models.stats.type, // TODO: investigate further any implications that may invalidate collected stats
models.settings.type,
];
// Every model except those to ignore and settings is a "workspace" model
const workspaceModels = difference(models.types(), nonWorkspaceModels);
const modelTypesToMerge = [];
if (useDesignerSettings) {
// @ts-expect-error -- TSCONVERSION
modelTypesToMerge.push(models.settings.type);
console.log('[db-merge] keeping settings from Insomnia Designer');
} else {
console.log('[db-merge] keeping settings from Insomnia Core');
}
if (copyWorkspaces) {
// @ts-expect-error -- TSCONVERSION
modelTypesToMerge.push(...workspaceModels);
}
let backupDir = '';
try {
// Create core backup
backupDir = await createCoreBackup(modelTypesToMerge, coreDataDir);
// Load designer database
const designerDb: DBType = await loadDesignerDb(modelTypesToMerge, designerDataDir);
// For each model, batch upsert entries into the Core database
for (const modelType of modelTypesToMerge) {
const entries = designerDb[modelType];
// Persist some settings from core
if (modelType === models.settings.type) {
const coreSettings = await models.settings.getOrCreate();
const settingsToPersist: (keyof Settings)[] = [
'_id',
'hasPromptedToMigrateFromDesigner',
];
settingsToPersist.forEach(setting => {
if (coreSettings.hasOwnProperty(setting)) {
entries[0][setting] = coreSettings[setting];
}
});
}
// For each workspace coming from Designer, mark workspace.scope as 'design'
if (modelType === models.workspace.type) {
entries.forEach(forceWorkspaceScopeToDesign);
}
const entryCount = entries.length;
console.log(
`[db-merge] merging ${entryCount} ${getModelName(modelType, entryCount)} from Designer`,
);
await db.batchModifyDocs({
upsert: entries,
remove: [],
});
}
if (copyWorkspaces) {
console.log('[db-merge] migrating version control data from designer to core');
await copyDirs(['version-control'], designerDataDir, coreDataDir);
console.log('[db-merge] migrating response cache from designer to core');
await copyDirs(['responses'], designerDataDir, coreDataDir);
}
if (copyPlugins) {
console.log('[db-merge] migrating plugins from designer to core');
await migratePlugins(designerDataDir, coreDataDir);
}
console.log('[db-merge] done!');
return {};
} catch (error) {
console.log('[db-merge] an error occurred while migrating');
console.error(error);
await restoreCoreBackup(backupDir, coreDataDir);
return {
error,
} as MigrationResult;
}
}
export async function restoreCoreBackup(backupDir: string, coreDataDir: string) {
if (!backupDir) {
console.log('[db-merge] nothing to restore; no backup was created');
return;
}
if (!existsAndIsDirectory(backupDir)) {
console.log(`[db-merge] nothing to restore: backup directory doesn't exist at ${backupDir}`);
return;
}
console.log('[db-merge] restoring from backup');
await removeDirs(['plugins', 'responses', 'version-control'], coreDataDir);
await fsx.copy(backupDir, coreDataDir);
console.log('[db-merge] restored from backup');
}

View File

@@ -57,7 +57,6 @@ export function init(): BaseSettings {
* So by default this flag is set to false, and is toggled to true during initialization for new users.
*/
hasPromptedAnalytics: false,
hasPromptedToMigrateFromDesigner: false,
hotKeyRegistry: hotkeys.newDefaultRegistry(),
httpProxy: '',
httpsProxy: '',

View File

@@ -106,7 +106,7 @@ export class SettingsModal extends PureComponent<Props, State> {
</Tab>
</TabList>
<TabPanel className="react-tabs__tab-panel pad scrollable">
<General hideModal={this.hide} />
<General />
</TabPanel>
<TabPanel className="react-tabs__tab-panel pad scrollable">
<ImportExport

View File

@@ -1,10 +1,9 @@
import { EnvironmentHighlightColorStyle, HttpVersion, HttpVersions, UpdateChannel } from 'insomnia-common';
import { Tooltip } from 'insomnia-components';
import React, { FC, Fragment, useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useSelector } from 'react-redux';
import {
ACTIVITY_MIGRATION,
EditorKeyMap,
isDevelopment,
isMac,
@@ -18,7 +17,6 @@ import { docsKeyMaps } from '../../../common/documentation';
import { strings } from '../../../common/strings';
import * as models from '../../../models';
import { initNewOAuthSession } from '../../../network/o-auth-2/misc';
import { setActiveActivity } from '../../redux/modules/global';
import { selectSettings, selectStats } from '../../redux/selectors';
import { Link } from '../base/link';
import { CheckForUpdatesButton } from '../check-for-updates-button';
@@ -59,13 +57,6 @@ const DevelopmentOnlySettings: FC = () => {
<hr className="pad-top" />
<h2>Development</h2>
<div className="form-row pad-top-sm">
<BooleanSetting
label="Has been prompted to migrate from Insomnia Designer"
setting="hasPromptedToMigrateFromDesigner"
/>
</div>
<div className="form-row pad-top-sm">
<BooleanSetting
label="Has seen analytics prompt"
@@ -92,19 +83,8 @@ const DevelopmentOnlySettings: FC = () => {
);
};
interface Props {
hideModal: () => void;
}
export const General: FC<Props> = ({ hideModal }) => {
const dispatch = useDispatch();
export const General: FC = () => {
const settings = useSelector(selectSettings);
const handleStartMigration = useCallback(() => {
dispatch(setActiveActivity(ACTIVITY_MIGRATION));
hideModal();
}, [hideModal, dispatch]);
return (
<div className="pad-bottom">
<div className="row-fill row-fill--top">
@@ -466,15 +446,6 @@ export const General: FC<Props> = ({ hideModal }) => {
setting="allowNotificationRequests"
/>
<hr className="pad-top" />
<h2>Migrate from Designer</h2>
<div className="form-row--start pad-top-sm">
<button className="btn btn--clicky pointer" onClick={handleStartMigration}>
Show migration workflow
</button>
</div>
<DevelopmentOnlySettings />
</div>
);

View File

@@ -1,325 +0,0 @@
import { Button, ToggleSwitch } from 'insomnia-components';
import React, { FunctionComponent, useCallback, useMemo, useState } from 'react';
import { useDispatch } from 'react-redux';
import { useMount } from 'react-use';
import { ACTIVITY_HOME } from '../../common/constants';
import { getDataDirectory, getDesignerDataDir, restartApp } from '../../common/electron-helpers';
import type { MigrationOptions } from '../../common/migrate-from-designer';
import migrateFromDesigner, { existsAndIsDirectory } from '../../common/migrate-from-designer';
import * as models from '../../models';
import coreLogo from '../images/insomnia-core-logo.png';
import { setActiveActivity } from '../redux/modules/global';
import { HelpTooltip } from './help-tooltip';
import { PageLayout } from './page-layout';
import type { WrapperProps } from './wrapper';
type Step = 'options' | 'migrating' | 'results';
interface SettingProps {
label: string;
name: keyof MigrationOptions;
options: MigrationOptions;
}
type TextSettingProps = SettingProps & {
handleChange: (arg0: React.SyntheticEvent<HTMLInputElement>) => void;
errorMessage?: string;
};
const TextSetting: FunctionComponent<TextSettingProps> = ({ handleChange, label, name, options, errorMessage }) => {
if (!options.hasOwnProperty(name)) {
throw new Error(`Invalid text setting name ${name}`);
}
const hasError = !!errorMessage;
return (
<div className="form-control form-control--outlined margin-bottom">
<label>
{label}
<input
className={hasError ? 'input--error' : ''}
type="text"
name={name}
// @ts-expect-error -- TSCONVERSION this is a genuine error. booleans shoudl not be allowed here
defaultValue={options[name]}
onBlur={handleChange}
/>
{hasError && <div className="font-error space-top">{errorMessage}</div>}
</label>
</div>
);
};
type BooleanSettingProps = SettingProps & {
handleChange: (arg0: boolean, arg1: Record<string, any>, arg2: string) => void;
help?: string;
};
const BooleanSetting = ({ handleChange, label, name, options, help }: BooleanSettingProps) => {
if (!options.hasOwnProperty(name)) {
throw new Error(`Invalid text setting name ${name}`);
}
const labelNode = useMemo(
() => (
<>
{label}
{help && <HelpTooltip className="space-left">{help}</HelpTooltip>}
</>
),
[help, label],
);
return (
<ToggleSwitch
labelClassName="row margin-bottom wide"
checked={Boolean(options[name])}
// @ts-expect-error -- TSCONVERSION
id={name}
label={labelNode}
onChange={handleChange}
/>
);
};
interface OptionsProps {
start: (arg0: MigrationOptions) => void;
cancel: () => void;
}
const Options = ({ start, cancel }: OptionsProps) => {
const [options, setOptions] = useState<MigrationOptions>(() => ({
useDesignerSettings: false,
copyWorkspaces: false,
copyPlugins: false,
designerDataDir: getDesignerDataDir(),
coreDataDir: getDataDirectory(),
}));
const handleInputChange = useCallback((e: React.SyntheticEvent<HTMLInputElement>) => {
const { name, value } = e.currentTarget;
setOptions(prevOpts => ({ ...prevOpts, [name]: value }));
}, []);
const handleSwitchChange = useCallback(
(checked: boolean, _event, id: string) => {
setOptions(prevOpts => ({ ...prevOpts, [id]: checked }));
},
[],
);
const {
coreDataDir,
designerDataDir,
useDesignerSettings,
copyWorkspaces,
copyPlugins,
} = options;
const coreExists = useMemo(() => existsAndIsDirectory(coreDataDir), [coreDataDir]);
const designerExists = useMemo(() => existsAndIsDirectory(designerDataDir), [
designerDataDir,
]);
const hasSomethingToMigrate = useDesignerSettings || copyWorkspaces || copyPlugins;
const dirsExist = coreExists && designerExists;
const canStart = hasSomethingToMigrate && dirsExist;
return (
<>
<p>
From the list below, select the individual items you would like to migrate from Designer.
</p>
<div className="text-left">
<BooleanSetting
label="Copy Workspaces"
name="copyWorkspaces"
options={options}
handleChange={handleSwitchChange}
help={
'This includes all resources linked to a workspace (eg. requests, proto files, api specs, environments, etc)'
}
/>
<BooleanSetting
label="Copy Plugins"
name="copyPlugins"
options={options}
handleChange={handleSwitchChange}
help={
'Merge plugins between Designer and Insomnia, keeping the Designer version where a duplicate exists'
}
/>
<BooleanSetting
label="Copy Designer Application Settings"
name="useDesignerSettings"
options={options}
handleChange={handleSwitchChange}
help={'Keep user preferences from Designer'}
/>
<details>
<summary className="margin-bottom">Advanced options</summary>
<TextSetting
label="Designer Data Directory"
name="designerDataDir"
options={options}
handleChange={handleInputChange}
errorMessage={designerExists ? undefined : 'Directory does not exist'}
/>
<TextSetting
label="Insomnia Data Directory"
name="coreDataDir"
options={options}
handleChange={handleInputChange}
errorMessage={coreExists ? undefined : 'Directory does not exist'}
/>
</details>
</div>
<div className="margin-top">
<Button
key="start"
bg="surprise"
radius="3px"
size="medium"
variant="contained"
onClick={() => start(options)}
disabled={!canStart}
>
Start Migration
</Button>
<button key="cancel" className="btn btn--super-compact" onClick={cancel}>
Skip for now
</button>
</div>
</>
);
};
const Migrating = () => (
<>
<p>
<strong>Migrating</strong>
</p>
<p>
<i className="fa fa-spin fa-refresh" />
</p>
</>
);
const RestartButton = () => (
<button key="restart" className="btn btn--clicky" onClick={restartApp}>
Restart Now
</button>
);
const Success = () => (
<>
<p>
<strong>Migrated successfully!</strong>
</p>
<p>
<i className="fa fa-check" />
</p>
<p>Please restart the application for your changes to take effect.</p>
<div className="margin-top">
<RestartButton />
</div>
</>
);
interface FailProps {
error: Error;
}
const Fail = ({ error }: FailProps) => (
<>
<p>
<strong>Something went wrong!!</strong>
</p>
<p>
<i className="fa fa-exclamation" />
</p>
<div className="wide text-left">
<p>
Something went wrong with the migration and all changes made have been reverted. Please
restart the application.
</p>
{error && (
<details>
<summary>Additional information</summary>
<pre className="pad-top-sm selectable">
<code className="wide">{error.stack || error}</code>
</pre>
</details>
)}
</div>
<div className="margin-top">
<RestartButton />
</div>
</>
);
const MigrationBody = () => {
// The migration step does not need to be in redux, but a loading state does need to exist there.
const [step, setStep] = useState<Step>('options');
const [error, setError] = useState<Error | null>(null);
const start = useCallback(async (options: MigrationOptions) => {
setStep('migrating');
const { error } = await migrateFromDesigner(options);
if (error) {
setError(error);
}
setStep('results');
}, []);
const reduxDispatch = useDispatch();
const cancel = useCallback(() => {
reduxDispatch(setActiveActivity(ACTIVITY_HOME));
}, [reduxDispatch]);
useMount(() => (
models.settings.patch({ hasPromptedToMigrateFromDesigner: true })
));
switch (step) {
case 'options':
return <Options start={start} cancel={cancel} />;
case 'migrating':
return <Migrating />;
case 'results':
return error ? <Fail error={error} /> : <Success />;
default:
throw new Error(`${step} is not recognized as a migration step.`);
}
};
interface Props {
wrapperProps: WrapperProps;
}
export const WrapperMigration: FunctionComponent<Props> = ({ wrapperProps }) => (
<PageLayout
wrapperProps={wrapperProps}
renderPageBody={() => (
<div className="migration">
<div className="migration__background theme--sidebar" />
<div className="migration__content theme--dialog">
<div className="img-container">
<img src={coreLogo} alt="Kong" />
</div>
<header className="migration__content__header">
<h1>Migrate from Insomnia Designer</h1>
<h2>Insomnia Designer and Core are now Insomnia!</h2>
</header>
<div className="migration__content__body">
<MigrationBody />
</div>
</div>
</div>
)}
/>
);

View File

@@ -6,7 +6,6 @@ import type { GlobalActivity } from '../../common/constants';
import {
ACTIVITY_DEBUG,
ACTIVITY_HOME,
ACTIVITY_MIGRATION,
ACTIVITY_SPEC,
ACTIVITY_UNIT_TEST,
AUTOBIND_CFG,
@@ -79,7 +78,6 @@ import { WrapperModal } from './modals/wrapper-modal';
import { WrapperDebug } from './wrapper-debug';
import { WrapperDesign } from './wrapper-design';
import WrapperHome from './wrapper-home';
import { WrapperMigration } from './wrapper-migration';
import { WrapperUnitTest } from './wrapper-unit-test';
const spectral = initializeSpectral();
@@ -700,8 +698,6 @@ export class Wrapper extends PureComponent<WrapperProps, State> {
wrapperProps={this.props}
/>
)}
{activity === ACTIVITY_MIGRATION && <WrapperMigration wrapperProps={this.props} />}
</Fragment>
</Fragment>
);

View File

@@ -12,7 +12,6 @@ import { parse as urlParse } from 'url';
import { SegmentEvent, trackSegmentEvent } from '../../common/analytics';
import {
ACTIVITY_HOME,
ACTIVITY_MIGRATION,
AUTOBIND_CFG,
COLLAPSE_SIDEBAR_REMS,
DEFAULT_PANE_HEIGHT,
@@ -1041,7 +1040,7 @@ class App extends PureComponent<AppProps, State> {
} = this.props;
let title;
if (activity === ACTIVITY_HOME || activity === ACTIVITY_MIGRATION) {
if (activity === ACTIVITY_HOME) {
title = getAppName();
} else if (activeWorkspace && activeWorkspaceName) {
title = activeProject.name;

View File

@@ -1,4 +1,3 @@
import fs from 'fs';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
@@ -6,13 +5,11 @@ import { globalBeforeEach } from '../../../../__jest__/before-each';
import {
ACTIVITY_DEBUG,
ACTIVITY_HOME,
ACTIVITY_MIGRATION,
ACTIVITY_SPEC,
ACTIVITY_UNIT_TEST,
GlobalActivity,
SORT_MODIFIED_DESC,
} from '../../../../common/constants';
import { getDesignerDataDir } from '../../../../common/electron-helpers';
import * as models from '../../../../models';
import { DEFAULT_PROJECT_ID } from '../../../../models/project';
import {
@@ -35,23 +32,17 @@ jest.mock('../../../../common/analytics');
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const createSettings = (
hasPromptedMigration: boolean,
hasPromptedAnalytics: boolean,
) => {
const createSettings = (hasPromptedAnalytics: boolean) => {
const settings = models.settings.init();
settings.hasPromptedToMigrateFromDesigner = hasPromptedMigration;
settings.hasPromptedAnalytics = hasPromptedAnalytics;
return settings;
};
describe('global', () => {
let fsExistsSyncSpy;
beforeEach(async () => {
await globalBeforeEach();
jest.resetAllMocks();
global.localStorage.clear();
fsExistsSyncSpy = jest.spyOn(fs, 'existsSync');
});
describe('setActiveActivity', () => {
@@ -60,7 +51,6 @@ describe('global', () => {
ACTIVITY_DEBUG,
ACTIVITY_UNIT_TEST,
ACTIVITY_HOME,
ACTIVITY_MIGRATION,
])('should update local storage and track event: %s', (activity: GlobalActivity) => {
const expectedEvent = {
type: SET_ACTIVE_ACTIVITY,
@@ -177,7 +167,7 @@ describe('global', () => {
ACTIVITY_UNIT_TEST,
ACTIVITY_HOME,
])('should initialize %s from local storage', async activity => {
const settings = createSettings(true, true);
const settings = createSettings(true);
const store = mockStore({
global: {},
entities: {
@@ -193,30 +183,12 @@ describe('global', () => {
expect(store.getActions()).toEqual([expectedEvent]);
});
it('should go to home if initialized at migration', async () => {
const settings = createSettings(true, true);
const store = mockStore({
global: {},
entities: {
settings: [settings],
},
});
const activity = ACTIVITY_MIGRATION;
global.localStorage.setItem(`${LOCALSTORAGE_PREFIX}::activity`, JSON.stringify(activity));
const expectedEvent = {
type: SET_ACTIVE_ACTIVITY,
activity: ACTIVITY_HOME,
};
store.dispatch(initActiveActivity());
expect(store.getActions()).toEqual([expectedEvent]);
});
it.each(
'something',
null,
undefined,
)('should go to home if initialized with an unsupported value: %s', async activity => {
const settings = createSettings(true, true);
const settings = createSettings(true);
const store = mockStore({
global: {},
entities: {
@@ -233,7 +205,7 @@ describe('global', () => {
});
it('should go to home if local storage key not found', async () => {
const settings = createSettings(true, true);
const settings = createSettings(true);
const store = mockStore({
global: {},
entities: {
@@ -248,62 +220,5 @@ describe('global', () => {
expect(store.getActions()).toEqual([expectedEvent]);
});
it('should go to home if initialized at migration seen', async () => {
const settings = createSettings(true, true);
const store = mockStore({
global: {},
entities: {
settings: [settings],
},
});
const activity = ACTIVITY_MIGRATION;
global.localStorage.setItem(`${LOCALSTORAGE_PREFIX}::activity`, JSON.stringify(activity));
const expectedEvent = {
type: SET_ACTIVE_ACTIVITY,
activity: ACTIVITY_HOME,
};
store.dispatch(initActiveActivity());
expect(store.getActions()).toEqual([expectedEvent]);
});
it('should prompt to migrate', async () => {
const settings = createSettings(false, true);
fsExistsSyncSpy.mockReturnValue(true);
const store = mockStore({
global: {},
entities: {
settings: [settings],
},
});
const activity = ACTIVITY_HOME;
global.localStorage.setItem(`${LOCALSTORAGE_PREFIX}::activity`, JSON.stringify(activity));
const expectedEvent = {
type: SET_ACTIVE_ACTIVITY,
activity: ACTIVITY_MIGRATION,
};
store.dispatch(initActiveActivity());
expect(store.getActions()).toEqual([expectedEvent]);
expect(fsExistsSyncSpy).toHaveBeenCalledWith(getDesignerDataDir());
});
it('should not prompt to migrate if default directory not found', async () => {
const settings = createSettings(false, true);
fsExistsSyncSpy.mockReturnValue(false);
const store = mockStore({
global: {},
entities: {
settings: [settings],
},
});
const activity = ACTIVITY_HOME;
global.localStorage.setItem(`${LOCALSTORAGE_PREFIX}::activity`, JSON.stringify(activity));
const expectedEvent = {
type: SET_ACTIVE_ACTIVITY,
activity,
};
store.dispatch(initActiveActivity());
expect(store.getActions()).toEqual([expectedEvent]);
expect(fsExistsSyncSpy).toHaveBeenCalledWith(getDesignerDataDir());
});
});
});

View File

@@ -11,11 +11,9 @@ import type { DashboardSortOrder, GlobalActivity } from '../../../common/constan
import {
ACTIVITY_DEBUG,
ACTIVITY_HOME,
ACTIVITY_MIGRATION,
isValidActivity,
} from '../../../common/constants';
import { database } from '../../../common/database';
import { getDesignerDataDir } from '../../../common/electron-helpers';
import {
exportRequestsData,
exportRequestsHAR,
@@ -45,7 +43,7 @@ import {
TAB_INDEX_PLUGINS,
TAB_INDEX_THEMES,
} from '../../components/modals/settings-modal';
import { selectActiveActivity, selectActiveProjectName, selectSettings, selectStats, selectWorkspacesForActiveProject } from '../selectors';
import { selectActiveProjectName, selectStats, selectWorkspacesForActiveProject } from '../selectors';
import { importUri } from './import';
import { activateWorkspace } from './workspace';
@@ -667,7 +665,6 @@ function _normalizeActivity(activity: GlobalActivity): GlobalActivity {
*/
export const initActiveActivity = () => (dispatch, getState) => {
const state = getState();
const settings = selectSettings(state);
// Default to home
let activeActivity = ACTIVITY_HOME;
@@ -680,21 +677,7 @@ export const initActiveActivity = () => (dispatch, getState) => {
// Nothing here...
}
activeActivity = _normalizeActivity(activeActivity);
let overrideActivity: GlobalActivity | null = null;
if (activeActivity === ACTIVITY_MIGRATION) {
// If relaunched after a migration, go to the next activity
// Don't need to do this for migration because that doesn't require a restart
overrideActivity = ACTIVITY_HOME;
} else {
// Always check if user has been prompted to migrate or onboard
if (!settings.hasPromptedToMigrateFromDesigner && fs.existsSync(getDesignerDataDir())) {
overrideActivity = ACTIVITY_MIGRATION;
}
}
const initializeToActivity = overrideActivity || activeActivity;
const initializeToActivity = _normalizeActivity(activeActivity);
if (initializeToActivity === state.global.activeActivity) {
// no need to dispatch the action twice if it has already been set to the correct value.
return;
@@ -705,17 +688,6 @@ export const initActiveActivity = () => (dispatch, getState) => {
export const initFirstLaunch = () => async (dispatch, getState) => {
const state = getState();
const activeActivity = selectActiveActivity(state);
// If the active activity is migration, then don't initialize into the analytics prompt, because we'll migrate the analytics opt-in setting from Designer.
if (activeActivity === ACTIVITY_MIGRATION) {
const { hasPromptedToMigrateFromDesigner } = selectSettings(state);
if (!hasPromptedToMigrateFromDesigner) {
await models.settings.patch({ hasPromptedAnalytics: true });
dispatch(setIsFinishedBooting(true));
return;
}
}
const stats = selectStats(state);
if (stats.launches > 1) {
dispatch(setIsFinishedBooting(true));

View File

@@ -56,7 +56,6 @@ export interface Settings {
fontVariantLigatures: boolean;
forceVerticalLayout: boolean;
hasPromptedAnalytics: boolean;
hasPromptedToMigrateFromDesigner: boolean;
hotKeyRegistry: HotKeyRegistry;
httpProxy: string;
httpsProxy: string;

View File

@@ -73,17 +73,6 @@ export const loadDb = async ({
const dir = src || appDataDir || getAppDataDir(getDefaultAppName());
db = await neDbAdapter(dir, filterTypes);
db && logger.debug(`Data store configured from app data directory at \`${path.resolve(dir)}\``); // Try to load from the Designer data dir, if the Core data directory does not exist
if (!db && !appDataDir && !src) {
const designerDir = getAppDataDir('Insomnia Designer');
db = await neDbAdapter(designerDir);
db &&
logger.debug(
`Data store configured from Insomnia Designer app data directory at \`${path.resolve(
designerDir,
)}\``,
);
}
} // return empty db
appDataDir && logger.warn(

View File

@@ -18,6 +18,3 @@ fixtures/inso-nedb/insomnia.CookieJar.db
fixtures/inso-nedb/insomnia.GitRepository.db
fixtures/inso-nedb/insomnia.OAuth2Token.db
fixtures/inso-nedb/insomnia.PluginData.db
# ignore new database models added since designer
fixtures/basic-designer/insomnia.Project.db

View File

@@ -33,7 +33,7 @@ PWDEBUG=1 npm run test:smoke:dev # Write Insomnia tests with the
DEBUG=pw:browser,pw:api npm run test:smoke:dev # Run Insomnia tests, with verbose output
```
Sometimes, you might need to run tests against a _packaged_ application. A packaged application is the final artifact which bundles all of the various resources together, and is created for distribution in the form of a `.dmg` or `.exe`, etc. Packaging takes longer to do and is only required for edge cases (such as a <!-- TODO(TSCONVERSION) update this link -->[plugin installation](https://github.com/Kong/insomnia/blob/357b8f05f89fd5c07a75d8418670abe37b2882dc/packages/insomnia-smoke-test/designer/app.test.js#L36)), so we typically run tests against a build. To run packaged tests, from the root:
Sometimes, you might need to run tests against a _packaged_ application. A packaged application is the final artifact which bundles all of the various resources together, and is created for distribution in the form of a `.dmg` or `.exe`, etc. Packaging takes longer to do and is only required for edge cases, so we typically run tests against a build. To run packaged tests, from the root:
```shell
npm run app-package:smoke # Package Insomnia

View File

@@ -1 +0,0 @@
{"_id":"spc_bf4e17489f7c49cfb74a6722744066b2","type":"ApiSpec","parentId":"wrk_53481b87089c49008da88e2ebf48438e","modified":1613973681201,"created":1613973668125,"fileName":"BASIC-DESIGNER-FIXTURE","contents":"","contentType":"yaml"}

View File

@@ -1 +0,0 @@
{"_id":"jar_6fa8be5aaab927c09ca7f5871d995217a548e272","type":"CookieJar","parentId":"wrk_53481b87089c49008da88e2ebf48438e","modified":1613973668156,"created":1613973668156,"name":"Default Jar","cookies":[]}

View File

@@ -1 +0,0 @@
{"_id":"env_6fa8be5aaab927c09ca7f5871d995217a548e272","type":"Environment","parentId":"wrk_53481b87089c49008da88e2ebf48438e","modified":1613973668154,"created":1613973668154,"name":"Base Environment","data":{},"dataPropertyOrder":null,"color":null,"isPrivate":false,"metaSortKey":1613973668154}

View File

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +0,0 @@
{"_id":"sta_380865ffe61042d0a73454aaed572db4","type":"Stats","parentId":null,"modified":1613973665818,"created":1613973665814,"currentLaunch":1613973665817,"lastLaunch":null,"currentVersion":"2020.5.2","lastVersion":null,"launches":1,"createdRequests":0,"deletedRequests":0,"executedRequests":0}
{"_id":"sta_380865ffe61042d0a73454aaed572db4","type":"Stats","parentId":null,"modified":1613974089067,"created":1613973665814,"currentLaunch":1613974089067,"lastLaunch":1613973665817,"currentVersion":"2020.5.2","lastVersion":"2020.5.2","launches":2,"createdRequests":0,"deletedRequests":0,"executedRequests":0}

View File

@@ -1 +0,0 @@
{"_id":"wrk_53481b87089c49008da88e2ebf48438e","type":"Workspace","parentId":null,"modified":1613973668125,"created":1613973668125,"name":"BASIC-DESIGNER-FIXTURE","description":"","scope":"spec"}

View File

@@ -1 +0,0 @@
{"_id":"wrkm_5200884fd0f449458e8f7eec6ddca28d","type":"WorkspaceMeta","parentId":"wrk_53481b87089c49008da88e2ebf48438e","modified":1613973668157,"created":1613973668157,"activeActivity":null,"activeEnvironmentId":null,"activeRequestId":null,"activeUnitTestSuiteId":null,"cachedGitLastAuthor":null,"cachedGitLastCommitTime":null,"cachedGitRepositoryBranch":null,"gitRepositoryId":null,"hasSeen":true,"paneHeight":0.5,"paneWidth":0.5,"previewHidden":false,"sidebarFilter":"","sidebarHidden":false,"sidebarWidth":19}

View File

@@ -10,7 +10,6 @@ export const loadFixture = async (fixturePath: string) => {
};
export const randomDataPath = () => path.join(os.tmpdir(), 'insomnia-smoke-test', `${uuid.v4()}`);
export const DESIGNER_DATA_PATH = path.join(__dirname, '..', 'fixtures', 'basic-designer');
export const INSOMNIA_DATA_PATH = randomDataPath();
const pathLookup = {

View File

@@ -1,30 +1,24 @@
// Read more about creating fixtures https://playwright.dev/docs/test-fixtures
import { ElectronApplication, Page, test as baseTest } from '@playwright/test';
import { ElectronApplication, test as baseTest } from '@playwright/test';
import { platform } from 'os';
import {
cwd,
DESIGNER_DATA_PATH,
executablePath,
INSOMNIA_DATA_PATH,
mainPath,
randomDataPath,
} from './paths';
interface EnvOptions {
INSOMNIA_DATA_PATH: string;
DESIGNER_DATA_PATH?: string;
}
export const test = baseTest.extend<{
app: ElectronApplication;
appWithDesignerDataPath: ElectronApplication;
pageWithDesignerDataPath: Page;
}>({
app: async ({ playwright }, use) => {
const options: EnvOptions = {
INSOMNIA_DATA_PATH: randomDataPath(),
DESIGNER_DATA_PATH: 'doesnt-exist',
};
const electronApp = await playwright._electron.launch({
@@ -54,39 +48,6 @@ export const test = baseTest.extend<{
await use(page);
await page.close();
},
appWithDesignerDataPath: async ({ playwright }, use) => {
const options: EnvOptions = {
INSOMNIA_DATA_PATH,
DESIGNER_DATA_PATH,
};
const electronApp = await playwright._electron.launch({
cwd,
executablePath,
args: process.env.BUNDLE === 'package' ? [] : [mainPath],
env: {
...process.env,
...options,
PLAYWRIGHT: 'true',
},
});
await use(electronApp);
// Closing the window (page) doesn't close the app on osx
if (platform() === 'darwin') {
await electronApp.close();
}
},
pageWithDesignerDataPath: async ({ appWithDesignerDataPath }, use) => {
const page = await appWithDesignerDataPath.firstWindow();
if (process.platform === 'win32') await page.reload();
await use(page);
await page.close();
},
});

View File

@@ -31,18 +31,3 @@ test('can send requests', async ({ app, page }) => {
await page.click('text=http://127.0.0.1:4010/auth/basicSend >> button');
await page.click('text=200 OK');
});
test.describe.serial('given a designer and data directory', () => {
test('should complete migration dialog', async ({ pageWithDesignerDataPath: page }) => {
await page.click('text=Copy Workspaces');
await page.click('text=Copy Plugins');
await page.click('text=Copy Designer Application Settings');
await page.click('text=Start Migration');
await page.click('text=Migrated successfully!');
});
test('then on restart should see the migrated workspace', async ({ pageWithDesignerDataPath: page }) => {
await page.click('text=Don\'t share usage analytics');
await page.click('text=BASIC-DESIGNER-FIXTURE');
});
});

View File

@@ -11,7 +11,6 @@
"include": [
"cli",
"core",
"designer",
"fixtures",
"modules",
"server"