insomnia/packages/insomnia-app/app/common/import.ts

595 lines
19 KiB
TypeScript
Raw Normal View History

import { convert, Insomnia4Data } from 'insomnia-importers';
2019-04-18 18:13:12 +00:00
import clone from 'clone';
import { database as db } from './database';
import * as har from './har';
2018-06-25 17:42:50 +00:00
import type { BaseModel } from '../models/index';
import * as models from '../models/index';
2020-04-26 20:33:39 +00:00
import { CONTENT_TYPE_GRAPHQL, getAppVersion } from './constants';
import { showError, showModal } from '../ui/components/modals/index';
import AlertModal from '../ui/components/modals/alert-modal';
import fs from 'fs';
Differential (Additive/Removal) patching when switching between design / debug (#3124) * fix(import) do differential patching for design activity imports This patch adds differential patching for imports that occur during the switching between design and debug tabs inside of Designer. As reported through #2971, and others, this patch favors existing data over imported data, values that exist already on the document will remain unchanged, only new values (including array based values) will be added / removed. This also includes a bypass feature for urls, currently these options are not exposed through the interface but could be. This feature is an object to allow for future properties for preference-based patching. - Adds `options.enableDiffBasedPatching` and `options.enableDiffDeep` to `importRaw` for backwards compat. - Adds `options.bypassDiffProps.url` for url bypassing and use an object for future items. - Adds `diffPatchObj` based on differential patching for objects (works with arrays as well). Future ideas: - `hasBeenModifiedByUser` property map object to allow changing properties that haven't been touched by the user with options. fixes: #2971, #2882, #3038, #2442 * adds some basic tests - also adds jest (which was (mistakenly) not there before) - does not call `.hasOwnProperty` directly, per https://eslint.org/docs/rules/no-prototype-builtins (which, we will add more formally at a later date) * don't special-case workspaces this ensures the behavior of the initial PR is more preserved Co-authored-by: Dimitri Mitropoulos <dimitrimitropoulos@gmail.com>
2021-03-03 17:57:09 +00:00
import { fnOrString, generateId, diffPatchObj } from './misc';
import YAML from 'yaml';
2020-04-26 20:33:39 +00:00
import { trackEvent } from './analytics';
import {
isGrpcRequest,
isProtoDirectory,
isProtoFile,
isRequest,
isRequestGroup,
isWorkspace,
} from '../models/helpers/is-model';
import type { Workspace } from '../models/workspace';
import type { ApiSpec } from '../models/api-spec';
import { ImportToWorkspacePrompt, SetWorkspaceScopePrompt } from '../ui/redux/modules/helpers';
2016-11-19 03:21:15 +00:00
const WORKSPACE_ID_KEY = '__WORKSPACE_ID__';
const BASE_ENVIRONMENT_ID_KEY = '__BASE_ENVIRONMENT_ID__';
const EXPORT_FORMAT = 4;
2016-11-19 03:21:15 +00:00
const EXPORT_TYPE_REQUEST = 'request';
const EXPORT_TYPE_GRPC_REQUEST = 'grpc_request';
2016-11-19 03:21:15 +00:00
const EXPORT_TYPE_REQUEST_GROUP = 'request_group';
const EXPORT_TYPE_UNIT_TEST_SUITE = 'unit_test_suite';
const EXPORT_TYPE_UNIT_TEST = 'unit_test';
2016-11-19 03:21:15 +00:00
const EXPORT_TYPE_WORKSPACE = 'workspace';
const EXPORT_TYPE_COOKIE_JAR = 'cookie_jar';
const EXPORT_TYPE_ENVIRONMENT = 'environment';
2020-04-26 20:33:39 +00:00
const EXPORT_TYPE_API_SPEC = 'api_spec';
const EXPORT_TYPE_PROTO_FILE = 'proto_file';
const EXPORT_TYPE_PROTO_DIRECTORY = 'proto_directory';
2016-11-19 03:21:15 +00:00
// If we come across an ID of this form, we will replace it with a new one
const REPLACE_ID_REGEX = /__\w+_\d+__/g;
2016-11-19 03:21:15 +00:00
const MODELS = {
[EXPORT_TYPE_REQUEST]: models.request,
[EXPORT_TYPE_GRPC_REQUEST]: models.grpcRequest,
2016-11-19 03:21:15 +00:00
[EXPORT_TYPE_REQUEST_GROUP]: models.requestGroup,
[EXPORT_TYPE_UNIT_TEST_SUITE]: models.unitTestSuite,
[EXPORT_TYPE_UNIT_TEST]: models.unitTest,
2016-11-19 03:21:15 +00:00
[EXPORT_TYPE_WORKSPACE]: models.workspace,
[EXPORT_TYPE_COOKIE_JAR]: models.cookieJar,
[EXPORT_TYPE_ENVIRONMENT]: models.environment,
2020-04-26 20:33:39 +00:00
[EXPORT_TYPE_API_SPEC]: models.apiSpec,
[EXPORT_TYPE_PROTO_FILE]: models.protoFile,
[EXPORT_TYPE_PROTO_DIRECTORY]: models.protoDirectory,
2016-11-19 03:21:15 +00:00
};
export interface ImportResult {
source: string;
error: Error | null;
summary: Record<string, BaseModel[]>;
}
interface ConvertResultType {
id: string;
name: string;
description: string;
}
interface ConvertResult {
type: ConvertResultType;
data: {
resources: Record<string, any>[];
};
}
export interface ImportRawConfig {
getWorkspaceId: ImportToWorkspacePrompt;
getWorkspaceScope?: SetWorkspaceScopePrompt;
enableDiffBasedPatching?: boolean;
enableDiffDeep?: boolean;
Differential (Additive/Removal) patching when switching between design / debug (#3124) * fix(import) do differential patching for design activity imports This patch adds differential patching for imports that occur during the switching between design and debug tabs inside of Designer. As reported through #2971, and others, this patch favors existing data over imported data, values that exist already on the document will remain unchanged, only new values (including array based values) will be added / removed. This also includes a bypass feature for urls, currently these options are not exposed through the interface but could be. This feature is an object to allow for future properties for preference-based patching. - Adds `options.enableDiffBasedPatching` and `options.enableDiffDeep` to `importRaw` for backwards compat. - Adds `options.bypassDiffProps.url` for url bypassing and use an object for future items. - Adds `diffPatchObj` based on differential patching for objects (works with arrays as well). Future ideas: - `hasBeenModifiedByUser` property map object to allow changing properties that haven't been touched by the user with options. fixes: #2971, #2882, #3038, #2442 * adds some basic tests - also adds jest (which was (mistakenly) not there before) - does not call `.hasOwnProperty` directly, per https://eslint.org/docs/rules/no-prototype-builtins (which, we will add more formally at a later date) * don't special-case workspaces this ensures the behavior of the initial PR is more preserved Co-authored-by: Dimitri Mitropoulos <dimitrimitropoulos@gmail.com>
2021-03-03 17:57:09 +00:00
bypassDiffProps?: {
url: boolean;
};
}
export async function importUri(uri: string, importConfig: ImportRawConfig) {
let rawText;
2020-04-26 20:33:39 +00:00
// If GH preview, force raw
const url = new URL(uri);
2020-04-26 20:33:39 +00:00
if (url.origin === 'https://github.com') {
uri = uri
.replace('https://github.com', 'https://raw.githubusercontent.com')
.replace('blob/', '');
}
if (uri.match(/^(http|https):\/\//)) {
Version Control (beta) (#1439) * VCS proof of concept underway! * Stuff * Some things * Replace deprecated Electron makeSingleInstance * Rename `window` variables so not to be confused with window object * Don't unnecessarily update request when URL does not change * Regenerate package-lock * Fix tests + ESLint * Publish - insomnia-app@1.0.49 - insomnia-cookies@0.0.12 - insomnia-httpsnippet@1.16.18 - insomnia-importers@2.0.13 - insomnia-libcurl@0.0.23 - insomnia-prettify@0.1.7 - insomnia-url@0.1.6 - insomnia-xpath@1.0.9 - insomnia-plugin-base64@1.0.6 - insomnia-plugin-cookie-jar@1.0.8 - insomnia-plugin-core-themes@1.0.5 - insomnia-plugin-default-headers@1.1.9 - insomnia-plugin-file@1.0.7 - insomnia-plugin-hash@1.0.7 - insomnia-plugin-jsonpath@1.0.12 - insomnia-plugin-now@1.0.11 - insomnia-plugin-os@1.0.13 - insomnia-plugin-prompt@1.1.9 - insomnia-plugin-request@1.0.18 - insomnia-plugin-response@1.0.16 - insomnia-plugin-uuid@1.0.10 * Broken but w/e * Some tweaks * Big refactor. Create local snapshots and push done * POC merging and a lot of improvements * Lots of work done on initial UI/UX * Fix old tests * Atomic writes and size-based batches * Update StageEntry definition once again to be better * Factor out GraphQL query logic * Merge algorithm, history modal, other minor things * Fix test * Merge, checkout, revert w/ user changes now work * Force UI to refresh when switching branches changes active request * Rough draft pull() and some cleanup * E2EE stuff and some refactoring * Add ability to share project with team and fixed tests * VCS now created in root component and better remote project handling * Remove unused definition * Publish - insomnia-account@0.0.2 - insomnia-app@1.1.1 - insomnia-cookies@0.0.14 - insomnia-httpsnippet@1.16.20 - insomnia-importers@2.0.15 - insomnia-libcurl@0.0.25 - insomnia-prettify@0.1.9 - insomnia-sync@0.0.2 - insomnia-url@0.1.8 - insomnia-xpath@1.0.11 - insomnia-plugin-base64@1.0.8 - insomnia-plugin-cookie-jar@1.0.10 - insomnia-plugin-core-themes@1.0.7 - insomnia-plugin-file@1.0.9 - insomnia-plugin-hash@1.0.9 - insomnia-plugin-jsonpath@1.0.14 - insomnia-plugin-now@1.0.13 - insomnia-plugin-os@1.0.15 - insomnia-plugin-prompt@1.1.11 - insomnia-plugin-request@1.0.20 - insomnia-plugin-response@1.0.18 - insomnia-plugin-uuid@1.0.12 * Move some deps around * Fix Flow errors * Update package.json * Fix eslint errors * Fix tests * Update deps * bootstrap insomnia-sync * TRy fixing appveyor * Try something else * Bump lerna * try powershell * Try again * Fix imports * Fixed errors * sync types refactor * Show remote projects in workspace dropdown * Improved pulling of non-local workspaces * Loading indicators and some tweaks * Clean up sync staging modal * Some sync improvements: - No longer store stage - Upgrade Electron - Sync UI/UX improvements * Fix snyc tests * Upgraded deps and hot loader tweaks (it's broken for some reason) * Fix tests * Branches dialog, network refactoring, some tweaks * Fixed merging when other branch is empty * A bunch of small fixes from real testing * Fixed pull merge logic * Fix tests * Some bug fixes * A few small tweaks * Conflict resolution and other improvements * Fix tests * Add revert changes * Deal with duplicate projects per workspace * Some tweaks and accessibility improvements * Tooltip accessibility * Fix API endpoint * Fix tests * Remove jest dep from insomnia-importers
2019-04-18 00:50:03 +00:00
const response = await window.fetch(uri);
rawText = await response.text();
} else if (uri.match(/^(file):\/\//)) {
const path = uri.replace(/^(file):\/\//, '');
rawText = fs.readFileSync(path, 'utf8');
} else {
// Treat everything else as raw text
rawText = decodeURIComponent(uri);
}
const result = await importRaw(rawText, importConfig);
2018-06-25 17:42:50 +00:00
const { summary, error } = result;
if (error) {
showError({
title: 'Failed to import',
// @ts-expect-error -- TSCONVERSION appears to be a genuine error
error: error.message,
message: 'Import failed',
});
return result;
}
const statements = Object.keys(summary)
2018-06-25 17:42:50 +00:00
.map(type => {
const count = summary[type].length;
const name = models.getModelName(type, count);
return count === 0 ? null : `${count} ${name}`;
})
.filter(s => s !== null);
let message;
if (statements.length === 0) {
message = 'Nothing was found to import.';
} else {
message = `You imported ${statements.join(', ')}!`;
}
showModal(AlertModal, {
title: 'Import Succeeded',
message,
});
return result;
}
2018-06-25 17:42:50 +00:00
export async function importRaw(
rawContent: string,
Differential (Additive/Removal) patching when switching between design / debug (#3124) * fix(import) do differential patching for design activity imports This patch adds differential patching for imports that occur during the switching between design and debug tabs inside of Designer. As reported through #2971, and others, this patch favors existing data over imported data, values that exist already on the document will remain unchanged, only new values (including array based values) will be added / removed. This also includes a bypass feature for urls, currently these options are not exposed through the interface but could be. This feature is an object to allow for future properties for preference-based patching. - Adds `options.enableDiffBasedPatching` and `options.enableDiffDeep` to `importRaw` for backwards compat. - Adds `options.bypassDiffProps.url` for url bypassing and use an object for future items. - Adds `diffPatchObj` based on differential patching for objects (works with arrays as well). Future ideas: - `hasBeenModifiedByUser` property map object to allow changing properties that haven't been touched by the user with options. fixes: #2971, #2882, #3038, #2442 * adds some basic tests - also adds jest (which was (mistakenly) not there before) - does not call `.hasOwnProperty` directly, per https://eslint.org/docs/rules/no-prototype-builtins (which, we will add more formally at a later date) * don't special-case workspaces this ensures the behavior of the initial PR is more preserved Co-authored-by: Dimitri Mitropoulos <dimitrimitropoulos@gmail.com>
2021-03-03 17:57:09 +00:00
{
getWorkspaceId,
getWorkspaceScope,
enableDiffBasedPatching,
enableDiffDeep,
bypassDiffProps,
}: ImportRawConfig,
) {
let results: ConvertResult;
2016-11-19 03:21:15 +00:00
try {
results = await convert(rawContent);
} catch (err) {
const importResult: ImportResult = {
source: 'not found',
error: err,
summary: {},
};
return importResult;
2016-11-19 03:21:15 +00:00
}
const { data, type: resultsType } = results;
2016-11-19 03:21:15 +00:00
// Generate all the ids we may need
const generatedIds: Record<string, string | ((...args: any[]) => any)> = {};
2016-11-19 03:21:15 +00:00
for (const r of data.resources) {
for (const key of r._id.match(REPLACE_ID_REGEX) || []) {
generatedIds[key] = generateId(MODELS[r._type].prefix);
2016-11-19 03:21:15 +00:00
}
}
// Contains the ID of the workspace to be used with the import
generatedIds[WORKSPACE_ID_KEY] = async () => {
const workspaceId = await getWorkspaceId();
// First try getting the workspace to overwrite
const workspace = await models.workspace.getById(workspaceId || 'n/a');
// Update this fn so it doesn't run again
const idToUse = workspace?._id || generateId(models.workspace.prefix);
generatedIds[WORKSPACE_ID_KEY] = idToUse;
return idToUse;
};
// Contains the ID of the base environment to be used with the import
generatedIds[BASE_ENVIRONMENT_ID_KEY] = async () => {
const parentId = await fnOrString(generatedIds[WORKSPACE_ID_KEY]);
const baseEnvironment = await models.environment.getOrCreateForWorkspaceId(parentId);
// Update this fn so it doesn't run again
generatedIds[BASE_ENVIRONMENT_ID_KEY] = baseEnvironment._id;
return baseEnvironment._id;
};
2016-11-19 03:21:15 +00:00
2016-11-20 07:43:22 +00:00
// Import everything backwards so they get inserted in the correct order
data.resources.reverse();
const importedDocs = {};
2016-11-20 07:43:22 +00:00
for (const model of models.all()) {
importedDocs[model.type] = [];
}
2016-11-19 03:21:15 +00:00
for (const resource of data.resources) {
// Buffer DB changes
// NOTE: Doing it inside here so it's more "scalable"
await db.bufferChanges(100);
2016-11-19 03:21:15 +00:00
// Replace null parentIds with current workspace
if (!resource.parentId && resource._type !== EXPORT_TYPE_WORKSPACE) {
resource.parentId = WORKSPACE_ID_KEY;
2016-11-19 03:21:15 +00:00
}
// Replace ID placeholders (eg. __WORKSPACE_ID__) with generated values
for (const key of Object.keys(generatedIds)) {
const { parentId, _id } = resource;
2019-08-15 21:06:55 +00:00
if (parentId && parentId.includes(key)) {
resource.parentId = parentId.replace(key, await fnOrString(generatedIds[key]));
}
if (_id && _id.includes(key)) {
resource._id = _id.replace(key, await fnOrString(generatedIds[key]));
}
2016-11-19 03:21:15 +00:00
}
const model = MODELS[resource._type];
2016-11-19 03:21:15 +00:00
if (!model) {
console.warn('Unknown doc type for import', resource._type);
continue;
2016-11-19 03:21:15 +00:00
}
2020-04-26 20:33:39 +00:00
// Hack to switch to GraphQL based on finding `graphql` in the URL path
// TODO: Support this in a better way
if (
isRequest(model) &&
2020-04-26 20:33:39 +00:00
resource.body &&
typeof resource.body.text === 'string' &&
typeof resource.url === 'string' &&
resource.body.text.includes('"query"') &&
resource.url.includes('graphql')
) {
resource.body.mimeType = CONTENT_TYPE_GRAPHQL;
}
// Try adding Content-Type JSON if no Content-Type exists
if (
isRequest(model) &&
resource.body &&
typeof resource.body.text === 'string' &&
Array.isArray(resource.headers) &&
!resource.headers.find(h => h.name.toLowerCase() === 'content-type')
) {
try {
JSON.parse(resource.body.text);
resource.headers.push({
name: 'Content-Type',
value: 'application/json',
});
} catch (err) {
// Not JSON
}
}
const existingDoc = await db.get(model.type, resource._id);
let newDoc: BaseModel;
2017-07-27 22:59:07 +00:00
if (existingDoc) {
Differential (Additive/Removal) patching when switching between design / debug (#3124) * fix(import) do differential patching for design activity imports This patch adds differential patching for imports that occur during the switching between design and debug tabs inside of Designer. As reported through #2971, and others, this patch favors existing data over imported data, values that exist already on the document will remain unchanged, only new values (including array based values) will be added / removed. This also includes a bypass feature for urls, currently these options are not exposed through the interface but could be. This feature is an object to allow for future properties for preference-based patching. - Adds `options.enableDiffBasedPatching` and `options.enableDiffDeep` to `importRaw` for backwards compat. - Adds `options.bypassDiffProps.url` for url bypassing and use an object for future items. - Adds `diffPatchObj` based on differential patching for objects (works with arrays as well). Future ideas: - `hasBeenModifiedByUser` property map object to allow changing properties that haven't been touched by the user with options. fixes: #2971, #2882, #3038, #2442 * adds some basic tests - also adds jest (which was (mistakenly) not there before) - does not call `.hasOwnProperty` directly, per https://eslint.org/docs/rules/no-prototype-builtins (which, we will add more formally at a later date) * don't special-case workspaces this ensures the behavior of the initial PR is more preserved Co-authored-by: Dimitri Mitropoulos <dimitrimitropoulos@gmail.com>
2021-03-03 17:57:09 +00:00
let updateDoc = resource;
// Do differential patching when enabled
if (enableDiffBasedPatching) {
updateDoc = diffPatchObj(resource, existingDoc, enableDiffDeep);
}
// Bypass differential update for urls when enabled
if (bypassDiffProps?.url && updateDoc.url) {
updateDoc.url = resource.url;
}
// If workspace, don't overwrite the existing scope
if (isWorkspace(model)) {
(updateDoc as Workspace).scope = (existingDoc as Workspace).scope;
}
Differential (Additive/Removal) patching when switching between design / debug (#3124) * fix(import) do differential patching for design activity imports This patch adds differential patching for imports that occur during the switching between design and debug tabs inside of Designer. As reported through #2971, and others, this patch favors existing data over imported data, values that exist already on the document will remain unchanged, only new values (including array based values) will be added / removed. This also includes a bypass feature for urls, currently these options are not exposed through the interface but could be. This feature is an object to allow for future properties for preference-based patching. - Adds `options.enableDiffBasedPatching` and `options.enableDiffDeep` to `importRaw` for backwards compat. - Adds `options.bypassDiffProps.url` for url bypassing and use an object for future items. - Adds `diffPatchObj` based on differential patching for objects (works with arrays as well). Future ideas: - `hasBeenModifiedByUser` property map object to allow changing properties that haven't been touched by the user with options. fixes: #2971, #2882, #3038, #2442 * adds some basic tests - also adds jest (which was (mistakenly) not there before) - does not call `.hasOwnProperty` directly, per https://eslint.org/docs/rules/no-prototype-builtins (which, we will add more formally at a later date) * don't special-case workspaces this ensures the behavior of the initial PR is more preserved Co-authored-by: Dimitri Mitropoulos <dimitrimitropoulos@gmail.com>
2021-03-03 17:57:09 +00:00
newDoc = await db.docUpdate(existingDoc, updateDoc);
2017-07-27 22:59:07 +00:00
} else {
if (isWorkspace(model)) {
await updateWorkspaceScope(resource as Workspace, resultsType, getWorkspaceScope);
}
newDoc = await db.docCreate(model.type, resource);
2017-07-27 22:59:07 +00:00
// Mark as not seen if we created a new workspace from sync
if (isWorkspace(newDoc)) {
2018-10-17 16:42:33 +00:00
const workspaceMeta = await models.workspaceMeta.getOrCreateByParentId(newDoc._id);
await models.workspaceMeta.update(workspaceMeta, {
hasSeen: false,
});
2017-07-27 22:59:07 +00:00
}
}
2016-11-19 03:21:15 +00:00
2016-11-20 07:43:22 +00:00
importedDocs[newDoc.type].push(newDoc);
2016-11-19 03:21:15 +00:00
}
2020-04-26 20:33:39 +00:00
// Store spec under workspace if it's OpenAPI
for (const workspace of importedDocs[models.workspace.type]) {
if (isApiSpecImport(resultsType)) {
2020-04-26 20:33:39 +00:00
const spec = await models.apiSpec.updateOrCreateForParentId(workspace._id, {
contents: rawContent,
contentType: 'yaml',
});
importedDocs[spec.type].push(spec);
}
// Set active environment when none is currently selected and one exists
2020-04-26 20:33:39 +00:00
const meta = await models.workspaceMeta.getOrCreateByParentId(workspace._id);
const envs = importedDocs[models.environment.type];
if (!meta.activeEnvironmentId && envs.length > 0) {
meta.activeEnvironmentId = envs[0]._id;
await models.workspaceMeta.update(meta);
}
2020-04-26 20:33:39 +00:00
}
await db.flushChanges();
trackEvent('Data', 'Import', resultsType.id);
const importRequest: ImportResult = {
source: resultsType && typeof resultsType.id === 'string' ? resultsType.id : 'unknown',
summary: importedDocs,
error: null,
2016-12-01 00:02:35 +00:00
};
return importRequest;
2016-11-19 03:21:15 +00:00
}
async function updateWorkspaceScope(
resource: Workspace,
resultType: ConvertResultType,
getWorkspaceScope?: SetWorkspaceScopePrompt,
) {
// 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 as Workspace).scope = await getWorkspaceScope(nameToPrompt);
}
}
export function isApiSpecImport({ id }: ConvertResultType) {
return id === 'openapi3' || id === 'swagger2';
}
export function isInsomniaV4Import({ id }: ConvertResultType) {
return id === 'insomnia-4';
2020-04-26 20:33:39 +00:00
}
export async function exportWorkspacesHAR(
parentDoc: BaseModel | null = null,
includePrivateDocs = false,
) {
const docs: BaseModel[] = await getDocWithDescendants(parentDoc, includePrivateDocs);
const requests: BaseModel[] = docs.filter(isRequest);
return exportRequestsHAR(requests, includePrivateDocs);
}
export async function exportRequestsHAR(
requests: BaseModel[],
includePrivateDocs = false,
) {
const workspaces: BaseModel[] = [];
const mapRequestIdToWorkspace: Record<string, any> = {};
const workspaceLookup: Record<string, any> = {};
for (const request of requests) {
const ancestors: BaseModel[] = await db.withAncestors(request, [
models.workspace.type,
models.requestGroup.type,
]);
const workspace = ancestors.find(isWorkspace);
mapRequestIdToWorkspace[request._id] = workspace;
if (workspace == null || workspaceLookup.hasOwnProperty(workspace._id)) {
continue;
}
workspaceLookup[workspace._id] = true;
workspaces.push(workspace);
}
const mapWorkspaceIdToEnvironmentId: Record<string, any> = {};
for (const workspace of workspaces) {
const workspaceMeta = await models.workspaceMeta.getByParentId(workspace._id);
let environmentId = workspaceMeta ? workspaceMeta.activeEnvironmentId : null;
const environment = await models.environment.getById(environmentId || 'n/a');
if (!environment || (environment.isPrivate && !includePrivateDocs)) {
environmentId = 'n/a';
}
mapWorkspaceIdToEnvironmentId[workspace._id] = environmentId;
}
requests = requests.sort((a: Record<string, any>, b: Record<string, any>) =>
a.metaSortKey < b.metaSortKey ? -1 : 1,
);
const harRequests: har.ExportRequest[] = [];
for (const request of requests) {
const workspace = mapRequestIdToWorkspace[request._id];
if (workspace == null) {
// Workspace not found for request, so don't export it.
continue;
}
const environmentId = mapWorkspaceIdToEnvironmentId[workspace._id];
harRequests.push({
requestId: request._id,
environmentId: environmentId,
});
}
const data = await har.exportHar(harRequests);
2020-04-26 20:33:39 +00:00
trackEvent('Data', 'Export', 'HAR');
return JSON.stringify(data, null, '\t');
}
export async function exportWorkspacesData(
parentDoc: BaseModel | null,
includePrivateDocs: boolean,
format: 'json' | 'yaml',
) {
const docs: BaseModel[] = await getDocWithDescendants(parentDoc, includePrivateDocs);
const requests: BaseModel[] = docs.filter(doc => isRequest(doc) || isGrpcRequest(doc));
return exportRequestsData(requests, includePrivateDocs, format);
2016-11-19 03:21:15 +00:00
}
export async function exportRequestsData(
requests: BaseModel[],
includePrivateDocs: boolean,
format: 'json' | 'yaml',
) {
const data: Insomnia4Data = {
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
_type: 'export',
__export_format: EXPORT_FORMAT,
__export_date: new Date(),
__export_source: `insomnia.desktop.app:v${getAppVersion()}`,
resources: [],
};
const docs: BaseModel[] = [];
const workspaces: BaseModel[] = [];
const mapTypeAndIdToDoc: Record<string, any> = {};
for (const req of requests) {
const ancestors: BaseModel[] = clone(await db.withAncestors(req));
for (const ancestor of ancestors) {
const key = ancestor.type + '___' + ancestor._id;
if (mapTypeAndIdToDoc.hasOwnProperty(key)) {
continue;
}
mapTypeAndIdToDoc[key] = ancestor;
docs.push(ancestor);
if (isWorkspace(ancestor)) {
workspaces.push(ancestor);
}
}
}
for (const workspace of workspaces) {
const descendants: BaseModel[] = (await db.withDescendants(workspace)).filter(d => {
// Only interested in these additional model types.
2020-04-26 20:33:39 +00:00
return (
d.type === models.cookieJar.type ||
d.type === models.environment.type ||
d.type === models.apiSpec.type ||
d.type === models.unitTestSuite.type ||
d.type === models.unitTest.type ||
isProtoFile(d) ||
isProtoDirectory(d)
2020-04-26 20:33:39 +00:00
);
});
docs.push(...descendants);
}
data.resources = docs
.filter(d => {
// Only export these model types.
if (
!(
d.type === models.unitTestSuite.type ||
d.type === models.unitTest.type ||
isRequest(d) ||
isGrpcRequest(d) ||
isRequestGroup(d) ||
isProtoFile(d) ||
isProtoDirectory(d) ||
isWorkspace(d) ||
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
d.type === models.cookieJar.type ||
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
2020-04-26 20:33:39 +00:00
d.type === models.environment.type ||
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
2020-04-26 20:33:39 +00:00
d.type === models.apiSpec.type
)
) {
return false;
}
// BaseModel doesn't have isPrivate, so cast it first.
return !d.isPrivate || includePrivateDocs;
})
.map(d => {
if (isWorkspace(d)) {
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
d._type = EXPORT_TYPE_WORKSPACE;
} else if (d.type === models.cookieJar.type) {
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
d._type = EXPORT_TYPE_COOKIE_JAR;
} else if (d.type === models.environment.type) {
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
d._type = EXPORT_TYPE_ENVIRONMENT;
} else if (d.type === models.unitTestSuite.type) {
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
d._type = EXPORT_TYPE_UNIT_TEST_SUITE;
} else if (d.type === models.unitTest.type) {
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
d._type = EXPORT_TYPE_UNIT_TEST;
} else if (isRequestGroup(d)) {
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
d._type = EXPORT_TYPE_REQUEST_GROUP;
} else if (isRequest(d)) {
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
d._type = EXPORT_TYPE_REQUEST;
} else if (isGrpcRequest(d)) {
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
d._type = EXPORT_TYPE_GRPC_REQUEST;
} else if (isProtoFile(d)) {
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
d._type = EXPORT_TYPE_PROTO_FILE;
} else if (isProtoDirectory(d)) {
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
d._type = EXPORT_TYPE_PROTO_DIRECTORY;
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
2020-04-26 20:33:39 +00:00
} else if (d.type === models.apiSpec.type) {
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
2020-04-26 20:33:39 +00:00
d._type = EXPORT_TYPE_API_SPEC;
}
// @ts-expect-error -- TSCONVERSION maybe this needs to be added to the upstream type?
// Delete the things we don't want to export
delete d.type;
return d;
});
2020-04-26 20:33:39 +00:00
trackEvent('Data', 'Export', `Insomnia ${format}`);
if (format.toLowerCase() === 'yaml') {
return YAML.stringify(data);
} else if (format.toLowerCase() === 'json') {
return JSON.stringify(data);
} else {
throw new Error(`Invalid export format ${format}. Must be "json" or "yaml"`);
}
}
2018-06-25 17:42:50 +00:00
async function getDocWithDescendants(
parentDoc: BaseModel | null = null,
includePrivateDocs = false,
): Promise<BaseModel[]> {
const docs = await db.withDescendants(parentDoc);
2018-06-25 17:42:50 +00:00
return docs.filter(
// Don't include if private, except if we want to
doc => !doc?.isPrivate || includePrivateDocs,
2018-06-25 17:42:50 +00:00
);
}