mirror of
https://github.com/Kong/insomnia.git
synced 2026-10-25 08:09:06 -04:00
Ensure a base space always exists (+ fix gRPC sub-environments) (#3819)
This commit is contained in:
1 parent
eb67958846
commit
7abde2a017
49 files changed
+1551
-1607
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { ACTIVITY_DEBUG } from '../common/constants';
|
||||
import { BASE_SPACE_ID } from '../models/space';
|
||||
import { RootState } from '../ui/redux/modules';
|
||||
import * as entities from '../ui/redux/modules/entities';
|
||||
import { GlobalState } from '../ui/redux/modules/global';
|
||||
@@ -8,7 +9,7 @@ export const reduxStateForTest = async (global: Partial<GlobalState> = {}): Prom
|
||||
global: {
|
||||
activeWorkspaceId: null,
|
||||
activeActivity: ACTIVITY_DEBUG,
|
||||
activeSpaceId: null,
|
||||
activeSpaceId: BASE_SPACE_ID,
|
||||
isLoading: false,
|
||||
isLoggedIn: false,
|
||||
loadingRequestIds: {},
|
||||
|
||||
@@ -263,8 +263,10 @@ describe('_repairDatabase()', () => {
|
||||
|
||||
it('fixes duplicate environments', async () => {
|
||||
// Create Workspace with no children
|
||||
const space = await models.space.create();
|
||||
const workspace = await models.workspace.create({
|
||||
_id: 'w1',
|
||||
parentId: space._id,
|
||||
});
|
||||
const spec = await models.apiSpec.getByParentId(workspace._id);
|
||||
expect((await db.withDescendants(workspace)).length).toBe(2);
|
||||
@@ -326,7 +328,7 @@ describe('_repairDatabase()', () => {
|
||||
{
|
||||
_id: 'w1',
|
||||
data: null,
|
||||
parentId: null,
|
||||
parentId: workspace.parentId,
|
||||
},
|
||||
{
|
||||
_id: 'b1',
|
||||
@@ -392,7 +394,7 @@ describe('_repairDatabase()', () => {
|
||||
{
|
||||
_id: 'w1',
|
||||
data: null,
|
||||
parentId: null,
|
||||
parentId: workspace.parentId,
|
||||
},
|
||||
{
|
||||
_id: 'b1',
|
||||
@@ -442,8 +444,10 @@ describe('_repairDatabase()', () => {
|
||||
|
||||
it('fixes duplicate cookie jars', async () => {
|
||||
// Create Workspace with no children
|
||||
const space = await models.space.create();
|
||||
const workspace = await models.workspace.create({
|
||||
_id: 'w1',
|
||||
parentId: space._id,
|
||||
});
|
||||
const spec = await models.apiSpec.getByParentId(workspace._id);
|
||||
expect((await db.withDescendants(workspace)).length).toBe(2);
|
||||
@@ -496,7 +500,7 @@ describe('_repairDatabase()', () => {
|
||||
{
|
||||
_id: 'w1',
|
||||
cookies: null,
|
||||
parentId: null,
|
||||
parentId: workspace.parentId,
|
||||
},
|
||||
{
|
||||
_id: 'j1',
|
||||
@@ -548,7 +552,7 @@ describe('_repairDatabase()', () => {
|
||||
{
|
||||
_id: 'w1',
|
||||
cookies: null,
|
||||
parentId: null,
|
||||
parentId: workspace.parentId,
|
||||
},
|
||||
{
|
||||
_id: 'j1',
|
||||
@@ -713,7 +717,10 @@ describe('withAncestors()', () => {
|
||||
beforeEach(globalBeforeEach);
|
||||
|
||||
it('should return itself and all parents but exclude siblings', async () => {
|
||||
const wrk = await models.workspace.create();
|
||||
const spc = await models.space.create();
|
||||
const wrk = await models.workspace.create({
|
||||
parentId: spc._id,
|
||||
});
|
||||
const wrkReq = await models.request.create({
|
||||
parentId: wrk._id,
|
||||
});
|
||||
@@ -730,14 +737,14 @@ describe('withAncestors()', () => {
|
||||
parentId: grp._id,
|
||||
});
|
||||
// Workspace child searching for ancestors
|
||||
await expect(db.withAncestors(wrk)).resolves.toStrictEqual([wrk]);
|
||||
await expect(db.withAncestors(wrkReq)).resolves.toStrictEqual([wrkReq, wrk]);
|
||||
await expect(db.withAncestors(wrkGrpcReq)).resolves.toStrictEqual([wrkGrpcReq, wrk]);
|
||||
await expect(db.withAncestors(wrk)).resolves.toStrictEqual([wrk, spc]);
|
||||
await expect(db.withAncestors(wrkReq)).resolves.toStrictEqual([wrkReq, wrk, spc]);
|
||||
await expect(db.withAncestors(wrkGrpcReq)).resolves.toStrictEqual([wrkGrpcReq, wrk, spc]);
|
||||
// Group searching for ancestors
|
||||
await expect(db.withAncestors(grp)).resolves.toStrictEqual([grp, wrk]);
|
||||
await expect(db.withAncestors(grp)).resolves.toStrictEqual([grp, wrk, spc]);
|
||||
// Group child searching for ancestors
|
||||
await expect(db.withAncestors(grpReq)).resolves.toStrictEqual([grpReq, grp, wrk]);
|
||||
await expect(db.withAncestors(grpGrpcReq)).resolves.toStrictEqual([grpGrpcReq, grp, wrk]);
|
||||
await expect(db.withAncestors(grpReq)).resolves.toStrictEqual([grpReq, grp, wrk, spc]);
|
||||
await expect(db.withAncestors(grpGrpcReq)).resolves.toStrictEqual([grpGrpcReq, grp, wrk, spc]);
|
||||
// Group child searching for ancestors with filters
|
||||
await expect(db.withAncestors(grpGrpcReq, [models.requestGroup.type])).resolves.toStrictEqual([
|
||||
grpGrpcReq,
|
||||
|
||||
@@ -6,7 +6,10 @@ import { getAppVersion } from '../constants';
|
||||
import { exportRequestsData, exportRequestsHAR, exportWorkspacesData, exportWorkspacesHAR } from '../export';
|
||||
|
||||
describe('exportWorkspacesHAR() and exportRequestsHAR()', () => {
|
||||
beforeEach(globalBeforeEach);
|
||||
beforeEach(async () => {
|
||||
await globalBeforeEach();
|
||||
await models.space.all();
|
||||
});
|
||||
|
||||
it('exports a single workspace and some requests only as an HTTP Archive', async () => {
|
||||
const wrk1 = await models.workspace.create({
|
||||
@@ -172,7 +175,7 @@ describe('exportWorkspacesHAR() and exportRequestsHAR()', () => {
|
||||
activeEnvironmentId: env2Private._id,
|
||||
});
|
||||
const includePrivateDocs = false;
|
||||
const json = await exportWorkspacesHAR([], includePrivateDocs);
|
||||
const json = await exportWorkspacesHAR([wrk1, wrk2], includePrivateDocs);
|
||||
const data = JSON.parse(json);
|
||||
expect(data).toMatchObject({
|
||||
log: {
|
||||
@@ -260,8 +263,8 @@ describe('export', () => {
|
||||
parentId: eBase._id,
|
||||
});
|
||||
// Test export whole workspace.
|
||||
const exportedWorkspacesJson = await exportWorkspacesData([], false, 'json');
|
||||
const exportedWorkspacesYaml = await exportWorkspacesData([], false, 'yaml');
|
||||
const exportedWorkspacesJson = await exportWorkspacesData([w], false, 'json');
|
||||
const exportedWorkspacesYaml = await exportWorkspacesData([w], false, 'yaml');
|
||||
const exportWorkspacesDataJson = JSON.parse(exportedWorkspacesJson);
|
||||
const exportWorkspacesDataYaml = YAML.parse(exportedWorkspacesYaml);
|
||||
// Ensure JSON is the same as YAML
|
||||
@@ -276,7 +279,7 @@ describe('export', () => {
|
||||
_id: w._id,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
_id: spec._id,
|
||||
_id: spec?._id,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
_id: eBase._id,
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -549,7 +549,7 @@ export const database = {
|
||||
|
||||
let docsToReturn: T[] = doc ? [doc] : [];
|
||||
|
||||
async function next(docs: T[]) {
|
||||
async function next(docs: T[]): Promise<T[]> {
|
||||
const foundDocs: T[] = [];
|
||||
|
||||
for (const d of docs) {
|
||||
|
||||
@@ -48,9 +48,7 @@ export async function exportWorkspacesHAR(
|
||||
workspaces: Workspace[],
|
||||
includePrivateDocs = false,
|
||||
) {
|
||||
// regarding `[null]`, see the comment here in `exportWorkspacesData`
|
||||
const rootDocs = workspaces.length === 0 ? [null] : workspaces;
|
||||
const promises = rootDocs.map(getDocWithDescendants(includePrivateDocs));
|
||||
const promises = workspaces.map(getDocWithDescendants(includePrivateDocs));
|
||||
const docs = (await Promise.all(promises)).flat();
|
||||
const requests = docs.filter(isRequest);
|
||||
return exportRequestsHAR(requests, includePrivateDocs);
|
||||
@@ -124,9 +122,7 @@ export async function exportWorkspacesData(
|
||||
includePrivateDocs: boolean,
|
||||
format: 'json' | 'yaml',
|
||||
) {
|
||||
// Semantically, if an empty array is passed, then nothing will be returned. What an empty array really signifies is "no parent", which, at the database layer is the same as "parentId === null", hence we add null in ourselves.
|
||||
const rootDocs = workspaces.length === 0 ? [null] : workspaces;
|
||||
const promises = rootDocs.map(getDocWithDescendants(includePrivateDocs));
|
||||
const promises = workspaces.map(getDocWithDescendants(includePrivateDocs));
|
||||
const docs = (await Promise.all(promises)).flat();
|
||||
const requests = docs.filter(doc => isRequest(doc) || isGrpcRequest(doc));
|
||||
return exportRequestsData(requests, includePrivateDocs, format);
|
||||
|
||||
@@ -186,7 +186,7 @@ export async function exportHar(exportRequests: ExportRequest[]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const harRequest = await exportHarWithRequest(request, exportRequest.environmentId);
|
||||
const harRequest = await exportHarWithRequest(request, exportRequest.environmentId || undefined);
|
||||
|
||||
if (!harRequest) {
|
||||
continue;
|
||||
@@ -283,11 +283,11 @@ export async function exportHarRequest(
|
||||
|
||||
export async function exportHarWithRequest(
|
||||
request: Request,
|
||||
environmentId: string | null,
|
||||
environmentId?: string,
|
||||
addContentLength = false,
|
||||
) {
|
||||
try {
|
||||
const renderResult = await getRenderedRequestAndContext(request, environmentId);
|
||||
const renderResult = await getRenderedRequestAndContext({ request, environmentId });
|
||||
const renderedRequest = await _applyRequestPluginHooks(
|
||||
renderResult.request,
|
||||
renderResult.context,
|
||||
|
||||
@@ -40,7 +40,7 @@ interface ConvertResult {
|
||||
|
||||
export interface ImportRawConfig {
|
||||
getWorkspaceId: ImportToWorkspacePrompt;
|
||||
getSpaceId?: () => Promise<string | null>;
|
||||
getSpaceId?: () => Promise<string>;
|
||||
getWorkspaceScope?: SetWorkspaceScopePrompt;
|
||||
enableDiffBasedPatching?: boolean;
|
||||
enableDiffDeep?: boolean;
|
||||
@@ -336,11 +336,10 @@ async function updateWorkspaceScope(
|
||||
|
||||
async function createWorkspaceInSpace(
|
||||
resource: Workspace,
|
||||
getSpaceId?: () => Promise<string | null>,
|
||||
getSpaceId?: () => Promise<string>,
|
||||
) {
|
||||
if (getSpaceId) {
|
||||
// Set the workspace parent if creating a new workspace during import
|
||||
// @ts-expect-error workspace parent can be null or string
|
||||
resource.parentId = await getSpaceId();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ import * as models from '../models';
|
||||
import type { CookieJar } from '../models/cookie-jar';
|
||||
import type { Environment } from '../models/environment';
|
||||
import type { GrpcRequest, GrpcRequestBody } from '../models/grpc-request';
|
||||
import type { BaseModel } from '../models/index';
|
||||
import type { Request } from '../models/request';
|
||||
import { isRequestGroup } from '../models/request-group';
|
||||
import { isWorkspace } from '../models/workspace';
|
||||
import { isRequestGroup, RequestGroup } from '../models/request-group';
|
||||
import { isSpace, Space } from '../models/space';
|
||||
import { isWorkspace, Workspace } from '../models/workspace';
|
||||
import * as templating from '../templating';
|
||||
import * as templatingUtils from '../templating/utils';
|
||||
import { CONTENT_TYPE_GRAPHQL, JSON_ORDER_SEPARATOR } from './constants';
|
||||
@@ -54,10 +54,17 @@ export type HandleGetRenderContext = () => Promise<RenderContextAndKeys>;
|
||||
export type HandleRender = <T>(object: T, contextCacheKey?: string | null) => Promise<T>;
|
||||
|
||||
export async function buildRenderContext(
|
||||
ancestors: BaseModel[] | null,
|
||||
rootEnvironment: Environment | null,
|
||||
subEnvironment: Environment | null,
|
||||
baseContext: Record<string, any> = {},
|
||||
{
|
||||
ancestors,
|
||||
rootEnvironment,
|
||||
subEnvironment,
|
||||
baseContext = {},
|
||||
}: {
|
||||
ancestors?: RenderContextAncestor[];
|
||||
rootEnvironment?: Environment;
|
||||
subEnvironment?: Environment;
|
||||
baseContext?: Record<string, any>;
|
||||
},
|
||||
) {
|
||||
const envObjects: Record<string, any>[] = [];
|
||||
|
||||
@@ -280,17 +287,31 @@ export async function render<T>(
|
||||
return next<T>(newObj, name, true);
|
||||
}
|
||||
|
||||
export async function getRenderContext(
|
||||
request: Request | GrpcRequest | null,
|
||||
environmentId: string | null,
|
||||
ancestors: BaseModel[] | null = null,
|
||||
purpose: RenderPurpose | null = null,
|
||||
extraInfo: ExtraRenderInfo | null = null,
|
||||
): Promise<Record<string, any>> {
|
||||
if (!ancestors) {
|
||||
ancestors = await _getRequestAncestors(request);
|
||||
}
|
||||
interface RenderRequest<T extends Request | GrpcRequest> {
|
||||
request: T;
|
||||
}
|
||||
|
||||
interface BaseRenderContextOptions {
|
||||
environmentId?: string;
|
||||
purpose?: RenderPurpose;
|
||||
extraInfo?: ExtraRenderInfo;
|
||||
}
|
||||
|
||||
interface RenderContextOptions extends BaseRenderContextOptions, Partial<RenderRequest<Request | GrpcRequest>> {
|
||||
ancestors?: RenderContextAncestor[];
|
||||
}
|
||||
export async function getRenderContext(
|
||||
{
|
||||
request,
|
||||
environmentId,
|
||||
ancestors: _ancestors,
|
||||
purpose,
|
||||
extraInfo,
|
||||
}: RenderContextOptions,
|
||||
): Promise<Record<string, any>> {
|
||||
const ancestors = _ancestors || await getRenderContextAncestors(request);
|
||||
|
||||
const space = ancestors.find(isSpace);
|
||||
const workspace = ancestors.find(isWorkspace);
|
||||
|
||||
if (!workspace) {
|
||||
@@ -373,24 +394,26 @@ export async function getRenderContext(
|
||||
|
||||
baseContext.getEnvironmentId = () => environmentId;
|
||||
|
||||
// It is possible for a space to not exist because this code path can be reached via Inso/insomnia-send-request which has no concept of a space.
|
||||
baseContext.getSpaceId = () => space?._id;
|
||||
|
||||
// Generate the context we need to render
|
||||
return buildRenderContext(ancestors, rootEnvironment, subEnvironment, baseContext);
|
||||
return buildRenderContext({ ancestors, rootEnvironment, subEnvironment: subEnvironment || undefined, baseContext });
|
||||
}
|
||||
|
||||
interface RenderGrpcRequestOptions extends BaseRenderContextOptions, RenderRequest<GrpcRequest> {
|
||||
skipBody?: boolean;
|
||||
}
|
||||
export async function getRenderedGrpcRequest(
|
||||
request: GrpcRequest,
|
||||
environmentId: string | null,
|
||||
purpose?: RenderPurpose,
|
||||
extraInfo?: ExtraRenderInfo,
|
||||
skipBody?: boolean,
|
||||
) {
|
||||
const renderContext = await getRenderContext(
|
||||
{
|
||||
purpose,
|
||||
extraInfo,
|
||||
request,
|
||||
environmentId,
|
||||
null,
|
||||
purpose,
|
||||
extraInfo || null,
|
||||
);
|
||||
skipBody,
|
||||
}: RenderGrpcRequestOptions,
|
||||
) {
|
||||
const renderContext = await getRenderContext({ request, environmentId, purpose, extraInfo });
|
||||
const description = request.description;
|
||||
// Render description separately because it's lower priority
|
||||
request.description = '';
|
||||
@@ -406,41 +429,35 @@ export async function getRenderedGrpcRequest(
|
||||
return renderedRequest;
|
||||
}
|
||||
|
||||
type RenderGrpcRequestMessageOptions = BaseRenderContextOptions & RenderRequest<GrpcRequest>;
|
||||
export async function getRenderedGrpcRequestMessage(
|
||||
request: GrpcRequest,
|
||||
environmentId: string | null,
|
||||
purpose?: RenderPurpose,
|
||||
extraInfo?: ExtraRenderInfo,
|
||||
) {
|
||||
const renderContext = await getRenderContext(
|
||||
request,
|
||||
{
|
||||
environmentId,
|
||||
null,
|
||||
request,
|
||||
extraInfo,
|
||||
purpose,
|
||||
extraInfo || null,
|
||||
);
|
||||
}: RenderGrpcRequestMessageOptions,
|
||||
) {
|
||||
const renderContext = await getRenderContext({ request, environmentId, purpose, extraInfo });
|
||||
// Render request body
|
||||
const renderedBody: RenderedGrpcRequestBody = await render(request.body, renderContext);
|
||||
return renderedBody;
|
||||
}
|
||||
|
||||
type RenderRequestOptions = BaseRenderContextOptions & RenderRequest<Request>;
|
||||
export async function getRenderedRequestAndContext(
|
||||
request: Request,
|
||||
environmentId?: string | null,
|
||||
purpose?: RenderPurpose,
|
||||
extraInfo?: ExtraRenderInfo,
|
||||
{
|
||||
request,
|
||||
environmentId,
|
||||
extraInfo,
|
||||
purpose,
|
||||
}: RenderRequestOptions,
|
||||
) {
|
||||
const ancestors = await _getRequestAncestors(request);
|
||||
const ancestors = await getRenderContextAncestors(request);
|
||||
const workspace = ancestors.find(isWorkspace);
|
||||
const parentId = workspace ? workspace._id : 'n/a';
|
||||
const cookieJar = await models.cookieJar.getOrCreateForParentId(parentId);
|
||||
const renderContext = await getRenderContext(
|
||||
request,
|
||||
environmentId || null,
|
||||
ancestors,
|
||||
purpose,
|
||||
extraInfo || null,
|
||||
);
|
||||
const renderContext = await getRenderContext({ request, environmentId, ancestors, purpose, extraInfo });
|
||||
|
||||
// HACK: Switch '#}' to '# }' to prevent Nunjucks from barfing
|
||||
// https://github.com/kong/insomnia/issues/895
|
||||
@@ -539,11 +556,13 @@ function _getOrderedEnvironmentKeys(finalRenderContext: Record<string, any>): st
|
||||
});
|
||||
}
|
||||
|
||||
async function _getRequestAncestors(request: Request | GrpcRequest | null): Promise<BaseModel[]> {
|
||||
return await db.withAncestors(request, [
|
||||
type RenderContextAncestor = Request | GrpcRequest | RequestGroup | Workspace | Space;
|
||||
export async function getRenderContextAncestors(base?: Request | GrpcRequest | Workspace): Promise<RenderContextAncestor[]> {
|
||||
return await db.withAncestors<RenderContextAncestor>(base || null, [
|
||||
models.request.type,
|
||||
models.grpcRequest.type,
|
||||
models.requestGroup.type,
|
||||
models.workspace.type,
|
||||
models.space.type,
|
||||
]);
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Schema } from '@develohpanda/fluent-builder';
|
||||
import clone from 'clone';
|
||||
|
||||
import { BaseModel, gitRepository, grpcRequest, request, requestGroup, workspace } from '..';
|
||||
import { BaseModel, environment, gitRepository, grpcRequest, request, requestGroup, workspace } from '..';
|
||||
import { Environment } from '../environment';
|
||||
import { GitRepository } from '../git-repository';
|
||||
import { GrpcRequest } from '../grpc-request';
|
||||
import { Request } from '../request';
|
||||
@@ -64,3 +65,9 @@ export const gitRepositorySchema: Schema<GitRepository> = {
|
||||
needsFullClone: () => false,
|
||||
uriNeedsMigration: () => true,
|
||||
};
|
||||
|
||||
export const environmentModelSchema: Schema<Environment> = {
|
||||
...baseModelSchema,
|
||||
...toSchema(environment.init()),
|
||||
type: () => environment.type,
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getAppName } from '../common/constants';
|
||||
import { database as db } from '../common/database';
|
||||
import { generateId } from '../common/misc';
|
||||
import type { BaseModel } from './index';
|
||||
@@ -8,15 +9,13 @@ export const prefix = 'sp';
|
||||
export const canDuplicate = false;
|
||||
export const canSync = false;
|
||||
|
||||
// A base space represents "no" space, when viewing this base space fetch all workspaces with no parent.
|
||||
// Using this instead of null.
|
||||
export const BASE_SPACE_ID = 'base-space';
|
||||
export const BASE_SPACE_ID = `${prefix}_base-space`;
|
||||
|
||||
export const isBaseSpace = ({ _id }: Pick<Space, '_id'>) => _id === BASE_SPACE_ID;
|
||||
export const isNotBaseSpace = (space: Pick<Space, '_id'>) => !isBaseSpace(space);
|
||||
export const isLocalSpace = ({ remoteId }: Pick<Space, 'remoteId'>) => remoteId === null;
|
||||
export const isRemoteSpace = (space: Pick<Space, 'remoteId'>) => !isLocalSpace(space);
|
||||
export const spaceHasSettings = (space: Pick<Space, 'remoteId' | '_id'>) => isLocalSpace(space) && !isBaseSpace(space);
|
||||
export const isBaseSpace = ({ _id }: Space) => _id === BASE_SPACE_ID;
|
||||
export const isNotBaseSpace = (space: Space) => !isBaseSpace(space);
|
||||
export const isLocalSpace = ({ remoteId }: Space) => remoteId === null;
|
||||
export const isRemoteSpace = (space: Space) => !isLocalSpace(space);
|
||||
export const spaceHasSettings = (space: Space) => isLocalSpace(space) && !isBaseSpace(space);
|
||||
|
||||
interface BaseSpace {
|
||||
name: string;
|
||||
@@ -64,6 +63,13 @@ export function update(space: Space, patch: Partial<Space>) {
|
||||
return db.docUpdate(space, patch);
|
||||
}
|
||||
|
||||
export function all() {
|
||||
return db.all<Space>(type);
|
||||
export async function all() {
|
||||
const spaces = await db.all<Space>(type);
|
||||
|
||||
if (!spaces.find(c => c._id === BASE_SPACE_ID)) {
|
||||
await create({ _id: BASE_SPACE_ID, name: getAppName(), remoteId: null });
|
||||
return db.all<Space>(type);
|
||||
}
|
||||
|
||||
return spaces;
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { database as db } from '../common/database';
|
||||
import { strings } from '../common/strings';
|
||||
import type { BaseModel } from './index';
|
||||
import * as models from './index';
|
||||
import { isSpaceId } from './space';
|
||||
import { BASE_SPACE_ID, isSpaceId } from './space';
|
||||
|
||||
export const name = 'Workspace';
|
||||
export const type = 'Workspace';
|
||||
@@ -57,6 +57,7 @@ export async function migrate(doc: Workspace) {
|
||||
fileName: doc.name,
|
||||
});
|
||||
doc = _migrateScope(doc);
|
||||
doc = _migrateIntoBaseSpace(doc);
|
||||
return doc;
|
||||
}
|
||||
|
||||
@@ -64,6 +65,10 @@ export function getById(id?: string) {
|
||||
return db.get<Workspace>(type, id);
|
||||
}
|
||||
|
||||
export function findByParentId(parentId: string) {
|
||||
return db.find<Workspace>(type, { parentId });
|
||||
}
|
||||
|
||||
export async function create(patch: Partial<Workspace> = {}) {
|
||||
expectParentToBeSpace(patch.parentId);
|
||||
return db.docCreate<Workspace>(type, patch);
|
||||
@@ -153,6 +158,14 @@ function _migrateScope(workspace: MigrationWorkspace) {
|
||||
return workspace as Workspace;
|
||||
}
|
||||
|
||||
function _migrateIntoBaseSpace(workspace: Workspace) {
|
||||
if (!workspace.parentId) {
|
||||
workspace.parentId = BASE_SPACE_ID;
|
||||
}
|
||||
|
||||
return workspace;
|
||||
}
|
||||
|
||||
export async function ensureChildren({ _id }: Workspace) {
|
||||
await models.environment.getOrCreateForParentId(_id);
|
||||
await models.cookieJar.getOrCreateForParentId(_id);
|
||||
|
||||
@@ -19,10 +19,13 @@ import { DEFAULT_BOUNDARY } from '../multipart';
|
||||
import * as networkUtils from '../network';
|
||||
const CONTEXT = {};
|
||||
|
||||
const getRenderedRequest = async (...args) => (await getRenderedRequestAndContext(...args)).request;
|
||||
const getRenderedRequest = async (args: Parameters<typeof getRenderedRequestAndContext>[0]) => (await getRenderedRequestAndContext(args)).request;
|
||||
|
||||
describe('actuallySend()', () => {
|
||||
beforeEach(globalBeforeEach);
|
||||
beforeEach(async () => {
|
||||
await globalBeforeEach();
|
||||
await models.space.all();
|
||||
});
|
||||
|
||||
it('sends a generic request', async () => {
|
||||
const workspace = await models.workspace.create();
|
||||
@@ -90,7 +93,7 @@ describe('actuallySend()', () => {
|
||||
password: 'pass',
|
||||
},
|
||||
});
|
||||
const renderedRequest = await getRenderedRequest(request);
|
||||
const renderedRequest = await getRenderedRequest({ request });
|
||||
const response = await networkUtils._actuallySend(
|
||||
renderedRequest,
|
||||
CONTEXT,
|
||||
@@ -165,7 +168,7 @@ describe('actuallySend()', () => {
|
||||
},
|
||||
url: 'http://localhost',
|
||||
});
|
||||
const renderedRequest = await getRenderedRequest(request);
|
||||
const renderedRequest = await getRenderedRequest({ request });
|
||||
const response = await networkUtils._actuallySend(
|
||||
renderedRequest,
|
||||
CONTEXT,
|
||||
@@ -265,7 +268,7 @@ describe('actuallySend()', () => {
|
||||
settingStoreCookies: false,
|
||||
settingSendCookies: false,
|
||||
});
|
||||
const renderedRequest = await getRenderedRequest(request);
|
||||
const renderedRequest = await getRenderedRequest({ request });
|
||||
const response = await networkUtils._actuallySend(
|
||||
renderedRequest,
|
||||
CONTEXT,
|
||||
@@ -325,7 +328,7 @@ describe('actuallySend()', () => {
|
||||
fileName,
|
||||
},
|
||||
});
|
||||
const renderedRequest = await getRenderedRequest(request);
|
||||
const renderedRequest = await getRenderedRequest({ request });
|
||||
const response = await networkUtils._actuallySend(
|
||||
renderedRequest,
|
||||
CONTEXT,
|
||||
@@ -405,7 +408,7 @@ describe('actuallySend()', () => {
|
||||
],
|
||||
},
|
||||
});
|
||||
const renderedRequest = await getRenderedRequest(request);
|
||||
const renderedRequest = await getRenderedRequest({ request });
|
||||
const response = await networkUtils._actuallySend(
|
||||
renderedRequest,
|
||||
CONTEXT,
|
||||
@@ -466,7 +469,7 @@ describe('actuallySend()', () => {
|
||||
url: 'http://unix:/my/socket:/my/path',
|
||||
method: 'GET',
|
||||
});
|
||||
const renderedRequest = await getRenderedRequest(request);
|
||||
const renderedRequest = await getRenderedRequest({ request });
|
||||
const response = await networkUtils._actuallySend(
|
||||
renderedRequest,
|
||||
CONTEXT,
|
||||
@@ -506,7 +509,7 @@ describe('actuallySend()', () => {
|
||||
url: 'http://localhost:3000/foo/bar',
|
||||
method: 'HEAD',
|
||||
});
|
||||
const renderedRequest = await getRenderedRequest(request);
|
||||
const renderedRequest = await getRenderedRequest({ request });
|
||||
const response = await networkUtils._actuallySend(
|
||||
renderedRequest,
|
||||
CONTEXT,
|
||||
@@ -545,7 +548,7 @@ describe('actuallySend()', () => {
|
||||
url: 'http://unix:3000/my/path',
|
||||
method: 'GET',
|
||||
});
|
||||
const renderedRequest = await getRenderedRequest(request);
|
||||
const renderedRequest = await getRenderedRequest({ request });
|
||||
const response = await networkUtils._actuallySend(
|
||||
renderedRequest,
|
||||
CONTEXT,
|
||||
@@ -585,7 +588,7 @@ describe('actuallySend()', () => {
|
||||
type: AUTH_NETRC,
|
||||
},
|
||||
});
|
||||
const renderedRequest = await getRenderedRequest(request);
|
||||
const renderedRequest = await getRenderedRequest({ request });
|
||||
const response = await networkUtils._actuallySend(
|
||||
renderedRequest,
|
||||
CONTEXT,
|
||||
@@ -623,7 +626,7 @@ describe('actuallySend()', () => {
|
||||
_id: 'req_123',
|
||||
parentId: workspace._id,
|
||||
});
|
||||
const renderedRequest = await getRenderedRequest(request);
|
||||
const renderedRequest = await getRenderedRequest({ request });
|
||||
const responseV1 = await networkUtils._actuallySend(renderedRequest, CONTEXT, workspace, {
|
||||
...settings,
|
||||
preferredHttpVersion: HttpVersions.V1_0,
|
||||
@@ -674,8 +677,8 @@ describe('actuallySend()', () => {
|
||||
url: 'http://unix:3000/requestB',
|
||||
method: 'GET',
|
||||
});
|
||||
const renderedRequest1 = await getRenderedRequest(request1);
|
||||
const renderedRequest2 = await getRenderedRequest(request2);
|
||||
const renderedRequest1 = await getRenderedRequest({ request: request1 });
|
||||
const renderedRequest2 = await getRenderedRequest({ request: request2 });
|
||||
|
||||
// WHEN
|
||||
const response1Promise = networkUtils._actuallySend(
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { mocked } from 'ts-jest/utils';
|
||||
|
||||
import { globalBeforeEach } from '../../../__jest__/before-each';
|
||||
import {
|
||||
getRenderedGrpcRequest,
|
||||
getRenderedGrpcRequestMessage,
|
||||
getRenderedGrpcRequest as _getRenderedGrpcRequest,
|
||||
getRenderedGrpcRequestMessage as _getRenderedGrpcRequestMessage,
|
||||
RENDER_PURPOSE_SEND,
|
||||
} from '../../../common/render';
|
||||
import * as models from '../../../models';
|
||||
@@ -10,6 +12,9 @@ import { prepareGrpcMessage, prepareGrpcRequest } from '../prepare';
|
||||
|
||||
jest.mock('../../../common/render');
|
||||
|
||||
const getRenderedGrpcRequest = mocked(_getRenderedGrpcRequest);
|
||||
const getRenderedGrpcRequestMessage = mocked(_getRenderedGrpcRequestMessage);
|
||||
|
||||
describe('prepareGrpcRequest', () => {
|
||||
beforeEach(globalBeforeEach);
|
||||
|
||||
@@ -26,11 +31,12 @@ describe('prepareGrpcRequest', () => {
|
||||
getRenderedGrpcRequest.mockResolvedValue(gr);
|
||||
const result = await prepareGrpcRequest(gr._id, env._id, methodType);
|
||||
expect(getRenderedGrpcRequest).toHaveBeenLastCalledWith(
|
||||
gr,
|
||||
env,
|
||||
RENDER_PURPOSE_SEND,
|
||||
{},
|
||||
false,
|
||||
{
|
||||
request: gr,
|
||||
environmentId: env._id,
|
||||
purpose: RENDER_PURPOSE_SEND,
|
||||
skipBody: false,
|
||||
},
|
||||
);
|
||||
expect(result).toEqual({
|
||||
request: gr,
|
||||
@@ -51,11 +57,12 @@ describe('prepareGrpcRequest', () => {
|
||||
getRenderedGrpcRequest.mockResolvedValue(gr);
|
||||
const result = await prepareGrpcRequest(gr._id, env._id, methodType);
|
||||
expect(getRenderedGrpcRequest).toHaveBeenLastCalledWith(
|
||||
gr,
|
||||
env,
|
||||
RENDER_PURPOSE_SEND,
|
||||
{},
|
||||
true,
|
||||
{
|
||||
request: gr,
|
||||
environmentId: env._id,
|
||||
purpose: RENDER_PURPOSE_SEND,
|
||||
skipBody: true,
|
||||
},
|
||||
);
|
||||
expect(result).toEqual({
|
||||
request: gr,
|
||||
@@ -78,10 +85,11 @@ describe('prepareGrpcMessage', () => {
|
||||
getRenderedGrpcRequestMessage.mockResolvedValue(gr.body);
|
||||
const result = await prepareGrpcMessage(gr._id, env._id);
|
||||
expect(getRenderedGrpcRequestMessage).toHaveBeenLastCalledWith(
|
||||
gr,
|
||||
env,
|
||||
RENDER_PURPOSE_SEND,
|
||||
{},
|
||||
{
|
||||
request: gr,
|
||||
environmentId: env._id,
|
||||
purpose: RENDER_PURPOSE_SEND,
|
||||
},
|
||||
);
|
||||
expect(result).toEqual({
|
||||
body: gr.body,
|
||||
|
||||
@@ -23,15 +23,16 @@ export const prepareGrpcRequest = async (
|
||||
methodType: GrpcMethodType,
|
||||
): Promise<GrpcIpcRequestParams> => {
|
||||
const req = await models.grpcRequest.getById(requestId);
|
||||
const environment = await models.environment.getById(environmentId || 'n/a');
|
||||
const request = await getRenderedGrpcRequest(
|
||||
// @ts-expect-error -- TSCONVERSION req can be null but should not try to render if it is null
|
||||
req,
|
||||
environment,
|
||||
RENDER_PURPOSE_SEND,
|
||||
{},
|
||||
canClientStream(methodType),
|
||||
{
|
||||
// @ts-expect-error -- TSCONVERSION req can be null but should not try to render if it is null
|
||||
request: req,
|
||||
environmentId,
|
||||
purpose: RENDER_PURPOSE_SEND,
|
||||
skipBody: canClientStream(methodType),
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
request,
|
||||
};
|
||||
@@ -42,13 +43,12 @@ export const prepareGrpcMessage = async (
|
||||
environmentId: string,
|
||||
): Promise<GrpcIpcMessageParams> => {
|
||||
const req = await models.grpcRequest.getById(requestId);
|
||||
const environment = await models.environment.getById(environmentId || 'n/a');
|
||||
const requestBody = await getRenderedGrpcRequestMessage(
|
||||
const requestBody = await getRenderedGrpcRequestMessage({
|
||||
// @ts-expect-error -- TSCONVERSION req can be null but should not try to render if it is null
|
||||
req,
|
||||
environment,
|
||||
RENDER_PURPOSE_SEND,
|
||||
{},
|
||||
request: req,
|
||||
environmentId,
|
||||
purpose: RENDER_PURPOSE_SEND,
|
||||
},
|
||||
);
|
||||
return {
|
||||
body: requestBody,
|
||||
|
||||
@@ -895,7 +895,7 @@ export async function sendWithSettings(
|
||||
};
|
||||
|
||||
try {
|
||||
renderResult = await getRenderedRequestAndContext(newRequest, environmentId);
|
||||
renderResult = await getRenderedRequestAndContext({ request: newRequest, environmentId });
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to render request: ${requestId}`);
|
||||
}
|
||||
@@ -946,10 +946,12 @@ export async function send(
|
||||
|
||||
const environment: Environment | null = await models.environment.getById(environmentId || 'n/a');
|
||||
const renderResult = await getRenderedRequestAndContext(
|
||||
request,
|
||||
environmentId || null,
|
||||
RENDER_PURPOSE_SEND,
|
||||
extraInfo,
|
||||
{
|
||||
request,
|
||||
environmentId,
|
||||
purpose: RENDER_PURPOSE_SEND,
|
||||
extraInfo,
|
||||
},
|
||||
);
|
||||
const renderedRequestBeforePlugins = renderResult.request;
|
||||
const renderedContextBeforePlugins = renderResult.context;
|
||||
@@ -1004,7 +1006,7 @@ async function _applyRequestPluginHooks(
|
||||
for (const { plugin, hook } of await plugins.getRequestHooks()) {
|
||||
const context = {
|
||||
...(pluginContexts.app.init(RENDER_PURPOSE_NO_RENDER) as Record<string, any>),
|
||||
...pluginContexts.data.init(),
|
||||
...pluginContexts.data.init(renderedContext.getSpaceId()),
|
||||
...(pluginContexts.store.init(plugin) as Record<string, any>),
|
||||
...(pluginContexts.request.init(newRenderedRequest, renderedContext) as Record<string, any>),
|
||||
...(pluginContexts.network.init(renderedContext.getEnvironmentId()) as Record<string, any>),
|
||||
@@ -1032,7 +1034,7 @@ async function _applyResponsePluginHooks(
|
||||
for (const { plugin, hook } of await plugins.getResponseHooks()) {
|
||||
const context = {
|
||||
...(pluginContexts.app.init(RENDER_PURPOSE_NO_RENDER) as Record<string, any>),
|
||||
...pluginContexts.data.init(),
|
||||
...pluginContexts.data.init(renderedContext.getSpaceId()),
|
||||
...(pluginContexts.store.init(plugin) as Record<string, any>),
|
||||
...(pluginContexts.response.init(newResponse) as Record<string, any>),
|
||||
...(pluginContexts.request.init(newRequest, renderedContext, true) as Record<string, any>),
|
||||
|
||||
@@ -5,6 +5,7 @@ import { globalBeforeEach } from '../../../__jest__/before-each';
|
||||
import { getAppVersion } from '../../../common/constants';
|
||||
import { database as db } from '../../../common/database';
|
||||
import * as models from '../../../models/index';
|
||||
import { BASE_SPACE_ID, Space } from '../../../models/space';
|
||||
import { WorkspaceScopeKeys } from '../../../models/workspace';
|
||||
import * as plugin from '../data';
|
||||
|
||||
@@ -14,7 +15,7 @@ describe('init()', () => {
|
||||
beforeEach(globalBeforeEach);
|
||||
|
||||
it('initializes correctly', async () => {
|
||||
const { data } = plugin.init();
|
||||
const { data } = plugin.init(BASE_SPACE_ID);
|
||||
expect(Object.keys(data)).toEqual(['import', 'export']);
|
||||
expect(Object.keys(data.export).sort()).toEqual(['har', 'insomnia']);
|
||||
expect(Object.keys(data.import).sort()).toEqual(['raw', 'uri']);
|
||||
@@ -22,12 +23,16 @@ describe('init()', () => {
|
||||
});
|
||||
|
||||
describe('app.import.*', () => {
|
||||
let space: Space;
|
||||
|
||||
beforeEach(async () => {
|
||||
await globalBeforeEach();
|
||||
space = await models.space.create();
|
||||
await models.workspace.create({
|
||||
_id: 'wrk_1',
|
||||
created: 111,
|
||||
modified: 222,
|
||||
parentId: space._id,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,7 +40,7 @@ describe('app.import.*', () => {
|
||||
const workspace = await models.workspace.getById('wrk_1');
|
||||
expect(await db.all(models.workspace.type)).toEqual([workspace]);
|
||||
expect(await db.count(models.request.type)).toBe(0);
|
||||
const { data } = plugin.init();
|
||||
const { data } = plugin.init(space._id);
|
||||
const filename = path.resolve(__dirname, '../__fixtures__/basic-import.json');
|
||||
await data.import.uri(`file://${filename}`);
|
||||
const allWorkspaces = await db.all(models.workspace.type);
|
||||
@@ -47,7 +52,7 @@ describe('app.import.*', () => {
|
||||
modified: 999,
|
||||
description: '',
|
||||
name: 'New',
|
||||
parentId: null,
|
||||
parentId: space._id,
|
||||
type: 'Workspace',
|
||||
scope: WorkspaceScopeKeys.collection,
|
||||
},
|
||||
@@ -83,7 +88,7 @@ describe('app.import.*', () => {
|
||||
const workspace = await models.workspace.getById('wrk_1');
|
||||
expect(await db.all(models.workspace.type)).toEqual([workspace]);
|
||||
expect(await db.count(models.request.type)).toBe(0);
|
||||
const { data } = plugin.init();
|
||||
const { data } = plugin.init(space._id);
|
||||
const filename = path.resolve(__dirname, '../__fixtures__/basic-import.json');
|
||||
await data.import.raw(fs.readFileSync(filename, 'utf8'));
|
||||
const allWorkspaces = await db.all(models.workspace.type);
|
||||
@@ -95,7 +100,7 @@ describe('app.import.*', () => {
|
||||
modified: 999,
|
||||
description: '',
|
||||
name: 'New',
|
||||
parentId: null,
|
||||
parentId: space._id,
|
||||
type: 'Workspace',
|
||||
scope: WorkspaceScopeKeys.collection,
|
||||
},
|
||||
@@ -129,12 +134,15 @@ describe('app.import.*', () => {
|
||||
});
|
||||
|
||||
describe('app.export.*', () => {
|
||||
let space: Space;
|
||||
beforeEach(async () => {
|
||||
await globalBeforeEach();
|
||||
space = await models.space.create();
|
||||
await models.workspace.create({
|
||||
_id: 'wrk_1',
|
||||
created: 111,
|
||||
modified: 222,
|
||||
parentId: space._id,
|
||||
});
|
||||
await models.request.create({
|
||||
_id: 'req_1',
|
||||
@@ -153,7 +161,7 @@ describe('app.export.*', () => {
|
||||
});
|
||||
|
||||
it('insomnia', async () => {
|
||||
const { data } = plugin.init();
|
||||
const { data } = plugin.init(space._id);
|
||||
const exported = await data.export.insomnia();
|
||||
const exportedData = JSON.parse(exported);
|
||||
expect(typeof exportedData.__export_date).toBe('string');
|
||||
@@ -202,7 +210,7 @@ describe('app.export.*', () => {
|
||||
});
|
||||
|
||||
it('har', async () => {
|
||||
const { data } = plugin.init();
|
||||
const { data } = plugin.init(space._id);
|
||||
const exported = await data.export.har();
|
||||
const exportedData = JSON.parse(exported);
|
||||
exportedData.log.entries[0].startedDateTime = '2017-11-24T18:12:12.849Z';
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { exportWorkspacesData, exportWorkspacesHAR } from '../../common/export';
|
||||
import type { ImportRawConfig } from '../../common/import';
|
||||
import { importRaw, importUri } from '../../common/import';
|
||||
import * as models from '../../models';
|
||||
import { BASE_SPACE_ID } from '../../models/space';
|
||||
import type { Workspace, WorkspaceScope } from '../../models/workspace';
|
||||
|
||||
interface PluginImportOptions {
|
||||
@@ -16,21 +18,29 @@ interface InsomniaExport {
|
||||
|
||||
type HarExport = Omit<InsomniaExport, 'format'>;
|
||||
|
||||
const buildImportRawConfig = (options: PluginImportOptions): ImportRawConfig => ({
|
||||
const buildImportRawConfig = (options: PluginImportOptions, activeSpaceId: string): ImportRawConfig => ({
|
||||
getWorkspaceId: () => Promise.resolve(options.workspaceId || null),
|
||||
getWorkspaceScope: options.scope && (() => (
|
||||
Promise.resolve<WorkspaceScope>(options.scope as WorkspaceScope))
|
||||
),
|
||||
getSpaceId: () => Promise.resolve(activeSpaceId),
|
||||
});
|
||||
|
||||
export const init = () => ({
|
||||
// TODO: add metrics here to track how frequently this fallback is being used
|
||||
const getWorkspaces = (activeSpaceId?: string) =>
|
||||
activeSpaceId
|
||||
? models.workspace.findByParentId(activeSpaceId)
|
||||
: models.workspace.all();
|
||||
|
||||
// Only in the case of running unit tests from Inso via send-request can activeSpaceId be undefined. This is because the concept of a space doesn't exist in git/insomnia sync or an export file
|
||||
export const init = (activeSpaceId?: string) => ({
|
||||
data: {
|
||||
import: {
|
||||
uri: async (uri: string, options: PluginImportOptions = {}) => {
|
||||
await importUri(uri, buildImportRawConfig(options));
|
||||
await importUri(uri, buildImportRawConfig(options, activeSpaceId || BASE_SPACE_ID));
|
||||
},
|
||||
raw: async (text: string, options: PluginImportOptions = {}) => {
|
||||
await importRaw(text, buildImportRawConfig(options));
|
||||
await importRaw(text, buildImportRawConfig(options, activeSpaceId || BASE_SPACE_ID));
|
||||
},
|
||||
},
|
||||
export: {
|
||||
@@ -39,7 +49,7 @@ export const init = () => ({
|
||||
includePrivate,
|
||||
format,
|
||||
}: InsomniaExport = {}) => exportWorkspacesData(
|
||||
workspace ? [workspace] : [],
|
||||
workspace ? [workspace] : await getWorkspaces(activeSpaceId),
|
||||
Boolean(includePrivate),
|
||||
format || 'json',
|
||||
),
|
||||
@@ -48,7 +58,7 @@ export const init = () => ({
|
||||
workspace,
|
||||
includePrivate,
|
||||
}: HarExport = {}) => exportWorkspacesHAR(
|
||||
workspace ? [workspace] : [],
|
||||
workspace ? [workspace] : await getWorkspaces(activeSpaceId),
|
||||
Boolean(includePrivate),
|
||||
),
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@ import { globalBeforeEach } from '../../../__jest__/before-each';
|
||||
import { database as db } from '../../../common/database';
|
||||
import * as models from '../../../models';
|
||||
import { workspaceModelSchema } from '../../../models/__schemas__/model-schemas';
|
||||
import { BASE_SPACE_ID } from '../../../models/space';
|
||||
import { GIT_CLONE_DIR, GIT_INSOMNIA_DIR, GIT_INSOMNIA_DIR_NAME } from '../git-vcs';
|
||||
import { NeDBClient } from '../ne-db-client';
|
||||
import { assertAsyncError, setupDateMocks } from './util';
|
||||
@@ -19,6 +20,7 @@ describe('NeDBClient', () => {
|
||||
workspaceBuilder.reset();
|
||||
setupDateMocks();
|
||||
// Create some sample models
|
||||
await models.space.all();
|
||||
await models.workspace.create({
|
||||
_id: 'wrk_1',
|
||||
});
|
||||
@@ -40,7 +42,7 @@ describe('NeDBClient', () => {
|
||||
|
||||
describe('readdir()', () => {
|
||||
it('reads model IDs from model type folders', async () => {
|
||||
const neDbClient = new NeDBClient('wrk_1');
|
||||
const neDbClient = new NeDBClient('wrk_1', BASE_SPACE_ID);
|
||||
const reqDir = path.join(GIT_INSOMNIA_DIR, models.request.type);
|
||||
const wrkDir = path.join(GIT_INSOMNIA_DIR, models.workspace.type);
|
||||
expect(await neDbClient.readdir(GIT_CLONE_DIR)).toEqual([GIT_INSOMNIA_DIR_NAME]);
|
||||
@@ -66,11 +68,11 @@ describe('NeDBClient', () => {
|
||||
const wrk1Yml = path.join(GIT_INSOMNIA_DIR, models.workspace.type, 'wrk_1.yml');
|
||||
const req1Yml = path.join(GIT_INSOMNIA_DIR, models.request.type, 'req_1.yml');
|
||||
const reqXYml = path.join(GIT_INSOMNIA_DIR, models.request.type, 'req_x.yml');
|
||||
const pNeDB = new NeDBClient('wrk_1');
|
||||
const pNeDB = new NeDBClient('wrk_1', BASE_SPACE_ID);
|
||||
expect(YAML.parse((await pNeDB.readFile(wrk1Yml, 'utf8')).toString())).toEqual(
|
||||
expect.objectContaining({
|
||||
_id: 'wrk_1',
|
||||
parentId: null,
|
||||
parentId: null, // should be reset to default value instead of the active space id
|
||||
}),
|
||||
);
|
||||
expect(YAML.parse((await pNeDB.readFile(req1Yml, 'utf8')).toString())).toEqual(
|
||||
@@ -95,7 +97,7 @@ describe('NeDBClient', () => {
|
||||
type: 'file',
|
||||
});
|
||||
// Act
|
||||
const neDbClient = new NeDBClient('wrk_1');
|
||||
const neDbClient = new NeDBClient('wrk_1', BASE_SPACE_ID);
|
||||
// Assert
|
||||
expect(await neDbClient.stat(GIT_CLONE_DIR)).toEqual(dirType);
|
||||
expect(await neDbClient.stat(GIT_INSOMNIA_DIR)).toEqual(dirType);
|
||||
@@ -110,7 +112,7 @@ describe('NeDBClient', () => {
|
||||
// Arrange
|
||||
const upsertSpy = jest.spyOn(db, 'upsert');
|
||||
const workspaceId = 'wrk_1';
|
||||
const neDbClient = new NeDBClient(workspaceId);
|
||||
const neDbClient = new NeDBClient(workspaceId, BASE_SPACE_ID);
|
||||
const env = {
|
||||
_id: 'env_1',
|
||||
type: models.environment.type,
|
||||
@@ -128,7 +130,7 @@ describe('NeDBClient', () => {
|
||||
it('should write files in GIT_INSOMNIA_DIR directory to db', async () => {
|
||||
// Arrange
|
||||
const workspaceId = 'wrk_1';
|
||||
const neDbClient = new NeDBClient(workspaceId);
|
||||
const neDbClient = new NeDBClient(workspaceId, BASE_SPACE_ID);
|
||||
const upsertSpy = jest.spyOn(db, 'upsert');
|
||||
const env = {
|
||||
_id: 'env_1',
|
||||
@@ -202,7 +204,7 @@ describe('NeDBClient', () => {
|
||||
it('should throw error if id does not match', async () => {
|
||||
// Arrange
|
||||
const workspaceId = 'wrk_1';
|
||||
const neDbClient = new NeDBClient(workspaceId);
|
||||
const neDbClient = new NeDBClient(workspaceId, BASE_SPACE_ID);
|
||||
const env = {
|
||||
_id: 'env_1',
|
||||
type: models.environment.type,
|
||||
@@ -220,7 +222,7 @@ describe('NeDBClient', () => {
|
||||
it('should throw error if type does not match', async () => {
|
||||
// Arrange
|
||||
const workspaceId = 'wrk_1';
|
||||
const neDbClient = new NeDBClient(workspaceId);
|
||||
const neDbClient = new NeDBClient(workspaceId, BASE_SPACE_ID);
|
||||
const env = {
|
||||
_id: 'env_1',
|
||||
type: models.environment.type,
|
||||
@@ -239,7 +241,7 @@ describe('NeDBClient', () => {
|
||||
describe('mkdir()', () => {
|
||||
it('should throw error', async () => {
|
||||
const workspaceId = 'wrk_1';
|
||||
const neDbClient = new NeDBClient(workspaceId);
|
||||
const neDbClient = new NeDBClient(workspaceId, BASE_SPACE_ID);
|
||||
const promiseResult = neDbClient.mkdir();
|
||||
await expect(promiseResult).rejects.toThrowError('NeDBClient is not writable');
|
||||
});
|
||||
|
||||
@@ -16,18 +16,18 @@ import { BufferEncoding } from './utils';
|
||||
|
||||
export class NeDBClient {
|
||||
_workspaceId: string;
|
||||
_spaceId: string | null;
|
||||
_spaceId: string;
|
||||
|
||||
constructor(workspaceId: string, spaceId?: string) {
|
||||
constructor(workspaceId: string, spaceId: string) {
|
||||
if (!workspaceId) {
|
||||
throw new Error('Cannot use NeDBClient without workspace ID');
|
||||
}
|
||||
|
||||
this._workspaceId = workspaceId;
|
||||
this._spaceId = spaceId || null;
|
||||
this._spaceId = spaceId;
|
||||
}
|
||||
|
||||
static createClient(workspaceId: string, spaceId?: string): PromiseFsClient {
|
||||
static createClient(workspaceId: string, spaceId: string): PromiseFsClient {
|
||||
return {
|
||||
promises: new NeDBClient(workspaceId, spaceId),
|
||||
};
|
||||
@@ -104,7 +104,6 @@ export class NeDBClient {
|
||||
// Whenever we write a workspace into nedb we should set the parentId to be that of the current space
|
||||
// This is because the parentId (or a space) is not synced into git, so it will be cleared whenever git writes the workspace into the db, thereby removing it from the space on the client
|
||||
// In order to reproduce this bug, comment out the following line, then clone a repository into a local space, then open the workspace, you'll notice it will have moved into the base space
|
||||
// @ts-expect-error parentId can be string or null for a workspace
|
||||
doc.parentId = this._spaceId;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,25 +39,6 @@ describe('pullProject()', () => {
|
||||
expect(vcs.pull).not.toHaveBeenCalledWith([], expect.anything());
|
||||
});
|
||||
|
||||
it('should insert a workspace with no parent', async () => {
|
||||
// Arrange
|
||||
const space = undefined;
|
||||
|
||||
// Act
|
||||
await pullProject({ vcs, project, space });
|
||||
|
||||
// Assert
|
||||
const workspaces = await models.workspace.all();
|
||||
expect(workspaces).toHaveLength(1);
|
||||
const workspace = workspaces[0];
|
||||
expect(workspace).toStrictEqual(expect.objectContaining<Partial<Workspace>>({
|
||||
_id: project.rootDocumentId,
|
||||
name: project.name,
|
||||
// @ts-expect-error parent id is optional for workspaces
|
||||
parentId: null,
|
||||
}));
|
||||
});
|
||||
|
||||
it('should insert a workspace with parent', async () => {
|
||||
// Arrange
|
||||
const space = await models.space.create();
|
||||
@@ -104,36 +85,6 @@ describe('pullProject()', () => {
|
||||
afterEach(() => {
|
||||
});
|
||||
|
||||
it('should overwrite the parentId only for a workspace with null', async () => {
|
||||
// Arrange
|
||||
const space = undefined;
|
||||
const existingWrk = await models.workspace.create({ _id: project.rootDocumentId, name: project.name });
|
||||
const existingReq = await models.request.create({ parentId: existingWrk._id });
|
||||
|
||||
vcs.allDocuments.mockResolvedValue([existingWrk, existingReq]);
|
||||
|
||||
// Act
|
||||
await pullProject({ vcs, project, space });
|
||||
|
||||
// Assert
|
||||
const workspaces = await models.workspace.all();
|
||||
expect(workspaces).toHaveLength(1);
|
||||
const workspace = workspaces[0];
|
||||
expect(workspace).toStrictEqual(expect.objectContaining<Partial<Workspace>>({
|
||||
_id: project.rootDocumentId,
|
||||
name: project.name,
|
||||
// @ts-expect-error parent id is optional for workspaces
|
||||
parentId: null,
|
||||
}));
|
||||
|
||||
const requests = await models.request.all();
|
||||
expect(requests).toHaveLength(1);
|
||||
const request = requests[0];
|
||||
expect(request).toStrictEqual(existingReq);
|
||||
|
||||
expect(vcs.pull).toHaveBeenCalledWith([], undefined);
|
||||
});
|
||||
|
||||
it('should overwrite the parentId only for a workspace with the space id', async () => {
|
||||
// Arrange
|
||||
const space = await models.space.create({ remoteId: 'abc' });
|
||||
|
||||
@@ -943,7 +943,7 @@ describe('util', () => {
|
||||
|
||||
it('shouldnt change the hash of a workspace after a parent id is added and ignored', () => {
|
||||
// Arrange
|
||||
// @ts-expect-error parent id should be nullable
|
||||
// @ts-expect-error parentId is intentionally null, because that's what the data was originally
|
||||
const originalSyncedWorkspace = workspaceModelBuilder.parentId(null).build();
|
||||
|
||||
// Existing synced workspaces do not have a modified field
|
||||
|
||||
@@ -10,7 +10,7 @@ import { VCS } from './vcs';
|
||||
interface Options {
|
||||
vcs: VCS;
|
||||
project: Project;
|
||||
space?: Space;
|
||||
space: Space;
|
||||
}
|
||||
|
||||
export const pullProject = async ({ vcs, project, space }: Options) => {
|
||||
@@ -20,7 +20,7 @@ export const pullProject = async ({ vcs, project, space }: Options) => {
|
||||
const remoteBranches = await vcs.getRemoteBranches();
|
||||
const defaultBranchMissing = !remoteBranches.includes(DEFAULT_BRANCH_NAME);
|
||||
|
||||
const workspaceParentId = space?._id || null;
|
||||
const workspaceParentId = space._id;
|
||||
|
||||
// The default branch does not exist, so we create it and the workspace locally
|
||||
if (defaultBranchMissing) {
|
||||
@@ -31,14 +31,13 @@ export const pullProject = async ({ vcs, project, space }: Options) => {
|
||||
});
|
||||
await database.upsert(workspace);
|
||||
} else {
|
||||
await vcs.pull([], space?.remoteId); // There won't be any existing docs since it's a new pull
|
||||
await vcs.pull([], space.remoteId); // There won't be any existing docs since it's a new pull
|
||||
|
||||
const flushId = await database.bufferChanges();
|
||||
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
for (const doc of (await vcs.allDocuments() || [])) {
|
||||
if (isWorkspace(doc)) {
|
||||
// @ts-expect-error parent id is optional for workspaces
|
||||
doc.parentId = workspaceParentId;
|
||||
}
|
||||
await database.upsert(doc);
|
||||
|
||||
@@ -12,6 +12,7 @@ import { GrpcRequest } from '../../../models/grpc-request';
|
||||
import * as requestOperations from '../../../models/helpers/request-operations';
|
||||
import { isRequest, Request } from '../../../models/request';
|
||||
import type { RequestGroup } from '../../../models/request-group';
|
||||
import { Space } from '../../../models/space';
|
||||
import { incrementDeletedRequests } from '../../../models/stats';
|
||||
// Plugin action related imports
|
||||
import type { RequestAction } from '../../../plugins';
|
||||
@@ -38,6 +39,7 @@ interface Props extends Partial<DropdownProps> {
|
||||
hotKeyRegistry: HotKeyRegistry;
|
||||
handleSetRequestPinned: Function;
|
||||
activeEnvironment?: Environment | null;
|
||||
activeSpace: Space;
|
||||
}
|
||||
|
||||
// Setup state for plugin actions
|
||||
@@ -101,11 +103,11 @@ class RequestActionsDropdown extends PureComponent<Props, State> {
|
||||
}));
|
||||
|
||||
try {
|
||||
const { activeEnvironment, request, requestGroup } = this.props;
|
||||
const { activeEnvironment, activeSpace, request, requestGroup } = this.props;
|
||||
const activeEnvironmentId = activeEnvironment ? activeEnvironment._id : null;
|
||||
const context = {
|
||||
...(pluginContexts.app.init(RENDER_PURPOSE_NO_RENDER) as Record<string, any>),
|
||||
...pluginContexts.data.init(),
|
||||
...pluginContexts.data.init(activeSpace._id),
|
||||
...(pluginContexts.store.init(p.plugin) as Record<string, any>),
|
||||
...(pluginContexts.network.init(activeEnvironmentId) as Record<string, any>),
|
||||
};
|
||||
|
||||
+4
-2
@@ -9,6 +9,7 @@ import { RENDER_PURPOSE_NO_RENDER } from '../../../common/render';
|
||||
import * as models from '../../../models';
|
||||
import type { Environment } from '../../../models/environment';
|
||||
import type { RequestGroup } from '../../../models/request-group';
|
||||
import { Space } from '../../../models/space';
|
||||
import type { Workspace } from '../../../models/workspace';
|
||||
import type { RequestGroupAction } from '../../../plugins';
|
||||
import { getRequestGroupActions } from '../../../plugins';
|
||||
@@ -25,6 +26,7 @@ import { showError, showModal } from '../modals';
|
||||
import EnvironmentEditModal from '../modals/environment-edit-modal';
|
||||
|
||||
interface Props {
|
||||
space: Space;
|
||||
workspace: Workspace;
|
||||
requestGroup: RequestGroup;
|
||||
hotKeyRegistry: HotKeyRegistry;
|
||||
@@ -91,11 +93,11 @@ class RequestGroupActionsDropdown extends PureComponent<Props, State> {
|
||||
}));
|
||||
|
||||
try {
|
||||
const { activeEnvironment, requestGroup } = this.props;
|
||||
const { activeEnvironment, requestGroup, space } = this.props;
|
||||
const activeEnvironmentId = activeEnvironment ? activeEnvironment._id : null;
|
||||
const context = {
|
||||
...(pluginContexts.app.init(RENDER_PURPOSE_NO_RENDER) as Record<string, any>),
|
||||
...pluginContexts.data.init(),
|
||||
...pluginContexts.data.init(space._id),
|
||||
...(pluginContexts.store.init(p.plugin) as Record<string, any>),
|
||||
...(pluginContexts.network.init(activeEnvironmentId) as Record<string, any>),
|
||||
};
|
||||
|
||||
@@ -3,9 +3,8 @@ import React, { FC, useCallback, useMemo } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { getAppName } from '../../../common/constants';
|
||||
import { strings } from '../../../common/strings';
|
||||
import { BASE_SPACE_ID, isBaseSpace, isRemoteSpace, Space, spaceHasSettings } from '../../../models/space';
|
||||
import { isBaseSpace, isNotBaseSpace, isRemoteSpace, Space, spaceHasSettings } from '../../../models/space';
|
||||
import { VCS } from '../../../sync/vcs/vcs';
|
||||
import { useRemoteSpaces } from '../../hooks/space';
|
||||
import { setActiveSpace } from '../../redux/modules/global';
|
||||
@@ -14,14 +13,6 @@ import { selectActiveSpace, selectSpaces } from '../../redux/selectors';
|
||||
import { showModal } from '../modals';
|
||||
import SpaceSettingsModal from '../modals/space-settings-modal';
|
||||
|
||||
type SpaceSubset = Pick<Space, '_id' | 'name' | 'remoteId'>;
|
||||
|
||||
const baseSpace: SpaceSubset = {
|
||||
_id: BASE_SPACE_ID,
|
||||
name: getAppName(),
|
||||
remoteId: null,
|
||||
};
|
||||
|
||||
const svgPlacementHack = {
|
||||
// This is a bit of a hack/workaround to avoid some larger changes that we'd need to do with dropdown item icons and tooltips.
|
||||
// Without this, the icon is too high with respect to the text because of Tooltip introducing some changes to the placement of the icon.
|
||||
@@ -67,7 +58,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const SpaceDropdownItem: FC<{
|
||||
space: SpaceSubset;
|
||||
space: Space;
|
||||
isActive: boolean;
|
||||
setActive: (spaceId: string) => void;
|
||||
}> = ({ isActive, space, setActive }) => {
|
||||
@@ -92,10 +83,9 @@ SpaceDropdownItem.displayName = DropdownItem.name; // This is required because t
|
||||
export const SpaceDropdown: FC<Props> = ({ vcs }) => {
|
||||
const { loading, refresh } = useRemoteSpaces(vcs);
|
||||
|
||||
// get list of spaces (which doesn't include the base space)
|
||||
const spaces = useSelector(selectSpaces);
|
||||
|
||||
const activeSpace = useSelector(selectActiveSpace) || baseSpace;
|
||||
const activeSpace = useSelector(selectActiveSpace);
|
||||
const dispatch = useDispatch();
|
||||
const setActive = useCallback((spaceId: string) => dispatch(setActiveSpace(spaceId)), [dispatch]);
|
||||
const createNew = useCallback(() => dispatch(createSpace()), [dispatch]);
|
||||
@@ -109,25 +99,20 @@ export const SpaceDropdown: FC<Props> = ({ vcs }) => {
|
||||
</button>
|
||||
), [activeSpace]);
|
||||
|
||||
const renderSpace = useCallback((space: Space) => (
|
||||
<SpaceDropdownItem
|
||||
key={space._id}
|
||||
isActive={space._id === activeSpace._id}
|
||||
setActive={setActive}
|
||||
space={space}
|
||||
/>
|
||||
), [setActive, activeSpace._id]);
|
||||
|
||||
return (
|
||||
<Dropdown renderButton={button} onOpen={refresh}>
|
||||
<SpaceDropdownItem
|
||||
isActive={baseSpace._id === activeSpace._id}
|
||||
setActive={setActive}
|
||||
space={baseSpace}
|
||||
/>
|
||||
|
||||
{spaces.filter(isBaseSpace).map(renderSpace)}
|
||||
<DropdownDivider>All spaces{' '}{loading && spinner}</DropdownDivider>
|
||||
|
||||
{spaces.map(space => (
|
||||
<SpaceDropdownItem
|
||||
isActive={space._id === activeSpace._id}
|
||||
key={space._id}
|
||||
setActive={setActive}
|
||||
space={space}
|
||||
/>
|
||||
))}
|
||||
|
||||
{spaces.filter(isNotBaseSpace).map(renderSpace)}
|
||||
{spaceHasSettings(activeSpace) && <>
|
||||
<DropdownDivider />
|
||||
<DropdownItem icon={<StyledSvgIcon icon="gear" />} onClick={showSettings}>
|
||||
@@ -142,3 +127,4 @@ export const SpaceDropdown: FC<Props> = ({ vcs }) => {
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ import * as models from '../../../models';
|
||||
import type { ApiSpec } from '../../../models/api-spec';
|
||||
import getWorkspaceName from '../../../models/helpers/get-workspace-name';
|
||||
import * as workspaceOperations from '../../../models/helpers/workspace-operations';
|
||||
import { Space } from '../../../models/space';
|
||||
import type { Workspace } from '../../../models/workspace';
|
||||
import { WorkspaceScopeKeys } from '../../../models/workspace';
|
||||
import type { DocumentAction } from '../../../plugins';
|
||||
@@ -23,11 +24,12 @@ import AskModal from '../modals/ask-modal';
|
||||
interface Props {
|
||||
workspace: Workspace;
|
||||
apiSpec: ApiSpec;
|
||||
space: Space;
|
||||
}
|
||||
|
||||
const spinner = <i className="fa fa-refresh fa-spin" />;
|
||||
|
||||
const useWorkspaceHandlers = ({ workspace, apiSpec }: { workspace: Workspace; apiSpec: ApiSpec; }) => {
|
||||
const useWorkspaceHandlers = ({ workspace, apiSpec }: Props) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleDuplicate = useCallback(() => {
|
||||
@@ -78,7 +80,7 @@ const useWorkspaceHandlers = ({ workspace, apiSpec }: { workspace: Workspace; ap
|
||||
return { handleDelete, handleDuplicate, handleRename };
|
||||
};
|
||||
|
||||
const useDocumentActionPlugins = ({ workspace, apiSpec }: { workspace: Workspace; apiSpec: ApiSpec; }) => {
|
||||
const useDocumentActionPlugins = ({ workspace, apiSpec, space }: Props) => {
|
||||
const [actionPlugins, setActionPlugins] = useState<DocumentAction[]>([]);
|
||||
const { startLoading, stopLoading, isLoading } = useLoadingRecord();
|
||||
|
||||
@@ -95,7 +97,7 @@ const useDocumentActionPlugins = ({ workspace, apiSpec }: { workspace: Workspace
|
||||
try {
|
||||
const context = {
|
||||
...pluginContexts.app.init(RENDER_PURPOSE_NO_RENDER),
|
||||
...pluginContexts.data.init(),
|
||||
...pluginContexts.data.init(space._id),
|
||||
...pluginContexts.store.init(p.plugin),
|
||||
};
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
@@ -108,7 +110,7 @@ const useDocumentActionPlugins = ({ workspace, apiSpec }: { workspace: Workspace
|
||||
} finally {
|
||||
stopLoading(p.label);
|
||||
}
|
||||
}, [apiSpec.contents, startLoading, stopLoading]);
|
||||
}, [apiSpec.contents, space._id, startLoading, stopLoading]);
|
||||
|
||||
const renderPluginDropdownItems = useCallback(() => actionPlugins.map(p => (
|
||||
<DropdownItem
|
||||
|
||||
@@ -13,6 +13,7 @@ import { ApiSpec } from '../../../models/api-spec';
|
||||
import type { Environment } from '../../../models/environment';
|
||||
import { isRequest } from '../../../models/request';
|
||||
import { isRequestGroup } from '../../../models/request-group';
|
||||
import { Space } from '../../../models/space';
|
||||
import { isDesign, Workspace } from '../../../models/workspace';
|
||||
import type { WorkspaceAction } from '../../../plugins';
|
||||
import { ConfigGenerator, getConfigGenerators, getWorkspaceActions } from '../../../plugins';
|
||||
@@ -33,6 +34,7 @@ interface Props {
|
||||
activeEnvironment: Environment | null;
|
||||
activeWorkspace: Workspace;
|
||||
activeApiSpec: ApiSpec;
|
||||
activeSpace: Space;
|
||||
hotKeyRegistry: HotKeyRegistry;
|
||||
isLoading: boolean;
|
||||
className?: string;
|
||||
@@ -57,13 +59,13 @@ class WorkspaceDropdown extends PureComponent<Props, State> {
|
||||
this.setState(state => ({
|
||||
loadingActions: { ...state.loadingActions, [p.label]: true },
|
||||
}));
|
||||
const { activeEnvironment, activeWorkspace } = this.props;
|
||||
const { activeEnvironment, activeWorkspace, activeSpace } = this.props;
|
||||
|
||||
try {
|
||||
const activeEnvironmentId = activeEnvironment ? activeEnvironment._id : null;
|
||||
const context = {
|
||||
...(pluginContexts.app.init(RENDER_PURPOSE_NO_RENDER) as Record<string, any>),
|
||||
...pluginContexts.data.init(),
|
||||
...pluginContexts.data.init(activeSpace._id),
|
||||
...(pluginContexts.store.init(p.plugin) as Record<string, any>),
|
||||
...(pluginContexts.network.init(activeEnvironmentId) as Record<string, any>),
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import React, { PureComponent } from 'react';
|
||||
import React, { PureComponent, ReactNode } from 'react';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import Modal from '../base/modal';
|
||||
@@ -7,13 +7,16 @@ import ModalBody from '../base/modal-body';
|
||||
import ModalFooter from '../base/modal-footer';
|
||||
import ModalHeader from '../base/modal-header';
|
||||
|
||||
interface State {
|
||||
title: string;
|
||||
message: string;
|
||||
addCancel: boolean;
|
||||
okLabel: string;
|
||||
export interface AlertModalOptions {
|
||||
title?: string;
|
||||
message?: ReactNode;
|
||||
addCancel?: boolean;
|
||||
okLabel?: string;
|
||||
onConfirm?: () => void | Promise<void>;
|
||||
}
|
||||
|
||||
type State = Omit<AlertModalOptions, 'onConfirm'>;
|
||||
|
||||
@autoBindMethodsForReact(AUTOBIND_CFG)
|
||||
class AlertModal extends PureComponent<{}, State> {
|
||||
state: State = {
|
||||
@@ -26,8 +29,9 @@ class AlertModal extends PureComponent<{}, State> {
|
||||
modal: Modal | null = null;
|
||||
_cancel: HTMLButtonElement | null = null;
|
||||
_ok: HTMLButtonElement | null = null;
|
||||
_okCallback: Function | null = null;
|
||||
_okCallback2: Function | null = null;
|
||||
|
||||
_okCallback: (value: void | PromiseLike<void>) => void;
|
||||
_okCallback2: AlertModalOptions['onConfirm'];
|
||||
|
||||
_setModalRef(m: Modal) {
|
||||
this.modal = m;
|
||||
@@ -36,7 +40,7 @@ class AlertModal extends PureComponent<{}, State> {
|
||||
_handleOk() {
|
||||
this.hide();
|
||||
|
||||
this._okCallback?.();
|
||||
this._okCallback();
|
||||
|
||||
if (typeof this._okCallback2 === 'function') {
|
||||
this._okCallback2();
|
||||
@@ -55,9 +59,7 @@ class AlertModal extends PureComponent<{}, State> {
|
||||
this._ok = n;
|
||||
}
|
||||
|
||||
show(options = {}) {
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
const { title, message, addCancel, onConfirm, okLabel } = options;
|
||||
show({ title, message, addCancel, onConfirm, okLabel }: AlertModalOptions) {
|
||||
this.setState({
|
||||
title,
|
||||
message,
|
||||
@@ -70,7 +72,7 @@ class AlertModal extends PureComponent<{}, State> {
|
||||
this._cancel && this._cancel.focus();
|
||||
}, 100);
|
||||
this._okCallback2 = onConfirm;
|
||||
return new Promise(resolve => {
|
||||
return new Promise<void>(resolve => {
|
||||
this._okCallback = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { trackEvent } from '../../../common/analytics';
|
||||
import AlertModal from './alert-modal';
|
||||
import type { ErrorModalOptions } from './error-modal';
|
||||
import ErrorModal from './error-modal';
|
||||
import AlertModal, { AlertModalOptions } from './alert-modal';
|
||||
import ErrorModal, { ErrorModalOptions } from './error-modal';
|
||||
import PromptModal, { PromptModalOptions } from './prompt-modal';
|
||||
|
||||
const modals = {};
|
||||
@@ -24,7 +23,7 @@ export function showPrompt(config: PromptModalOptions) {
|
||||
return showModal(PromptModal, config);
|
||||
}
|
||||
|
||||
export function showAlert(config) {
|
||||
export function showAlert(config: AlertModalOptions) {
|
||||
return showModal(AlertModal, config);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ import { bindActionCreators } from 'redux';
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import { strings } from '../../../common/strings';
|
||||
import * as models from '../../../models/index';
|
||||
import { Space } from '../../../models/space';
|
||||
import { isBaseSpace, isRemoteSpace } from '../../../models/space';
|
||||
import { RootState } from '../../redux/modules';
|
||||
import * as spaceActions from '../../redux/modules/space';
|
||||
import { selectActiveSpace } from '../../redux/selectors';
|
||||
import DebouncedInput from '../base/debounced-input';
|
||||
@@ -15,15 +16,9 @@ import ModalBody from '../base/modal-body';
|
||||
import ModalHeader from '../base/modal-header';
|
||||
import PromptButton from '../base/prompt-button';
|
||||
|
||||
interface ReduxStateProps {
|
||||
space?: Space;
|
||||
}
|
||||
export type ReduxProps = ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps>;
|
||||
|
||||
interface ReduxDispatchProps {
|
||||
handleRemoveSpace: (space: Space) => void;
|
||||
}
|
||||
|
||||
interface Props extends ReduxStateProps, ReduxDispatchProps { }
|
||||
type Props = ReduxProps
|
||||
|
||||
@autoBindMethodsForReact(AUTOBIND_CFG)
|
||||
class SpaceSettingsModal extends PureComponent<Props> {
|
||||
@@ -34,32 +29,26 @@ class SpaceSettingsModal extends PureComponent<Props> {
|
||||
}
|
||||
|
||||
_handleRemoveSpace() {
|
||||
if (this.props.space) {
|
||||
this.props.handleRemoveSpace(this.props.space);
|
||||
}
|
||||
|
||||
this.props.handleRemoveSpace(this.props.space);
|
||||
this.hide();
|
||||
}
|
||||
|
||||
async _handleRename(name: string) {
|
||||
const { space } = this.props;
|
||||
|
||||
if (space) {
|
||||
await models.space.update(space, { name });
|
||||
}
|
||||
await models.space.update(space, { name });
|
||||
}
|
||||
|
||||
show() {
|
||||
this.modal && this.modal.show();
|
||||
this.modal?.show();
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.modal && this.modal.hide();
|
||||
this.modal?.hide();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { space } = this.props;
|
||||
if (!space) {
|
||||
if (isBaseSpace(space) || isRemoteSpace(space)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -98,12 +87,12 @@ class SpaceSettingsModal extends PureComponent<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state): ReduxStateProps => {
|
||||
const mapStateToProps = (state: RootState) => {
|
||||
const space = selectActiveSpace(state);
|
||||
return { space };
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch): ReduxDispatchProps => {
|
||||
const mapDispatchToProps = dispatch => {
|
||||
const boundSpaceActions = bindActionCreators(spaceActions, dispatch);
|
||||
return {
|
||||
handleRemoveSpace: boundSpaceActions.removeSpace,
|
||||
|
||||
@@ -16,7 +16,7 @@ import SyncPullButton from '../sync-pull-button';
|
||||
|
||||
interface Props {
|
||||
workspace: Workspace;
|
||||
space?: Space;
|
||||
space: Space;
|
||||
syncItems: StatusCandidate[];
|
||||
vcs: VCS;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { Environment } from '../../../models/environment';
|
||||
import { GrpcRequest, isGrpcRequest } from '../../../models/grpc-request';
|
||||
import { isRequest, Request } from '../../../models/request';
|
||||
import type { RequestGroup } from '../../../models/request-group';
|
||||
import { Space } from '../../../models/space';
|
||||
import type { Workspace } from '../../../models/workspace';
|
||||
import SidebarCreateDropdown from './sidebar-create-dropdown';
|
||||
import SidebarRequestGroupRow from './sidebar-request-group-row';
|
||||
@@ -43,6 +44,7 @@ interface Props {
|
||||
hotKeyRegistry: HotKeyRegistry;
|
||||
activeEnvironment?: Environment | null;
|
||||
activeRequest?: Request | GrpcRequest | null;
|
||||
activeSpace: Space;
|
||||
}
|
||||
|
||||
@autoBindMethodsForReact(AUTOBIND_CFG)
|
||||
@@ -91,6 +93,7 @@ class SidebarChildren extends PureComponent<Props> {
|
||||
workspace,
|
||||
hotKeyRegistry,
|
||||
activeEnvironment,
|
||||
activeSpace,
|
||||
} = this.props;
|
||||
const activeRequestId = activeRequest ? activeRequest._id : 'n/a';
|
||||
return children.map(child => {
|
||||
@@ -117,6 +120,7 @@ class SidebarChildren extends PureComponent<Props> {
|
||||
request={child.doc}
|
||||
hotKeyRegistry={hotKeyRegistry} // Necessary for plugin actions on requests
|
||||
activeEnvironment={activeEnvironment}
|
||||
activeSpace={activeSpace}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -158,6 +162,7 @@ class SidebarChildren extends PureComponent<Props> {
|
||||
requestGroup={requestGroup}
|
||||
hotKeyRegistry={hotKeyRegistry}
|
||||
activeEnvironment={activeEnvironment}
|
||||
activeSpace={activeSpace}
|
||||
>
|
||||
{children}
|
||||
</SidebarRequestGroupRow>
|
||||
|
||||
@@ -10,6 +10,7 @@ import * as misc from '../../../common/misc';
|
||||
import { HandleRender } from '../../../common/render';
|
||||
import { Environment } from '../../../models/environment';
|
||||
import { RequestGroup } from '../../../models/request-group';
|
||||
import { Space } from '../../../models/space';
|
||||
import { Workspace } from '../../../models/workspace';
|
||||
import Highlight from '../base/highlight';
|
||||
import RequestGroupActionsDropdown from '../dropdowns/request-group-actions-dropdown';
|
||||
@@ -36,6 +37,7 @@ interface Props {
|
||||
connectDragSource?: Function;
|
||||
connectDropTarget?: Function;
|
||||
activeEnvironment?: Environment | null;
|
||||
activeSpace: Space;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@@ -107,6 +109,7 @@ class SidebarRequestGroupRow extends PureComponent<Props, State> {
|
||||
workspace,
|
||||
hotKeyRegistry,
|
||||
activeEnvironment,
|
||||
activeSpace,
|
||||
} = this.props;
|
||||
const { dragDirection } = this.state;
|
||||
let folderIconClass = 'fa-folder';
|
||||
@@ -192,6 +195,7 @@ class SidebarRequestGroupRow extends PureComponent<Props, State> {
|
||||
hotKeyRegistry={hotKeyRegistry}
|
||||
isPinned={false} // Necessary so that plugin actions work
|
||||
activeEnvironment={activeEnvironment}
|
||||
activeSpace={activeSpace}
|
||||
/>
|
||||
)}
|
||||
</ul>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { GrpcRequest, isGrpcRequest } from '../../../models/grpc-request';
|
||||
import * as requestOperations from '../../../models/helpers/request-operations';
|
||||
import { Request } from '../../../models/request';
|
||||
import { RequestGroup } from '../../../models/request-group';
|
||||
import { Space } from '../../../models/space';
|
||||
import Editable from '../base/editable';
|
||||
import Highlight from '../base/highlight';
|
||||
import RequestActionsDropdown from '../dropdowns/request-actions-dropdown';
|
||||
@@ -23,6 +24,7 @@ import GrpcTag from '../tags/grpc-tag';
|
||||
import MethodTag from '../tags/method-tag';
|
||||
|
||||
interface Props {
|
||||
activeSpace: Space;
|
||||
activeEnvironment?: Environment | null;
|
||||
handleActivateRequest: Function;
|
||||
handleSetRequestPinned: Function;
|
||||
@@ -212,6 +214,7 @@ class SidebarRequestRow extends PureComponent<Props, State> {
|
||||
isPinned,
|
||||
request,
|
||||
requestGroup,
|
||||
activeSpace,
|
||||
} = this.props;
|
||||
const { dragDirection } = this.state;
|
||||
let node;
|
||||
@@ -283,6 +286,7 @@ class SidebarRequestRow extends PureComponent<Props, State> {
|
||||
requestGroup={requestGroup}
|
||||
hotKeyRegistry={hotKeyRegistry} // Necessary for plugin actions to have network capabilities
|
||||
activeEnvironment={activeEnvironment}
|
||||
activeSpace={activeSpace}
|
||||
/>
|
||||
</div>
|
||||
{isPinned && (
|
||||
|
||||
@@ -9,7 +9,7 @@ import { showError } from './modals';
|
||||
interface Props {
|
||||
vcs: VCS;
|
||||
branch: string;
|
||||
space?: Space;
|
||||
space: Space;
|
||||
onPull: (...args: any[]) => any;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
@@ -40,7 +40,7 @@ class SyncPullButton extends PureComponent<Props, State> {
|
||||
try {
|
||||
// Clone old VCS so we don't mess anything up while working on other projects
|
||||
await newVCS.checkout([], branch);
|
||||
await newVCS.pull([], space?.remoteId);
|
||||
await newVCS.pull([], space.remoteId);
|
||||
} catch (err) {
|
||||
showError({
|
||||
title: 'Pull Error',
|
||||
|
||||
@@ -23,6 +23,7 @@ const WorkspacePageHeader: FunctionComponent<Props> = ({
|
||||
wrapperProps: {
|
||||
activeApiSpec,
|
||||
activeWorkspace,
|
||||
activeSpace,
|
||||
activeEnvironment,
|
||||
settings,
|
||||
activity,
|
||||
@@ -47,6 +48,7 @@ const WorkspacePageHeader: FunctionComponent<Props> = ({
|
||||
activeEnvironment={activeEnvironment}
|
||||
activeWorkspace={activeWorkspace}
|
||||
activeApiSpec={activeApiSpec}
|
||||
activeSpace={activeSpace}
|
||||
hotKeyRegistry={settings.hotKeyRegistry}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
|
||||
@@ -108,6 +108,7 @@ class WrapperDebug extends PureComponent<Props> {
|
||||
handleSidebarSort,
|
||||
} = this.props;
|
||||
const {
|
||||
activeSpace,
|
||||
activeEnvironment,
|
||||
activeRequest,
|
||||
activeWorkspace,
|
||||
@@ -185,6 +186,7 @@ class WrapperDebug extends PureComponent<Props> {
|
||||
filter={sidebarFilter || ''}
|
||||
hotKeyRegistry={settings.hotKeyRegistry}
|
||||
activeEnvironment={activeEnvironment}
|
||||
activeSpace={activeSpace}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
@@ -160,6 +160,7 @@ class WrapperHome extends PureComponent<Props, State> {
|
||||
|
||||
renderCard(workspace: Workspace) {
|
||||
const {
|
||||
activeSpace,
|
||||
apiSpecs,
|
||||
workspaceMetas,
|
||||
} = this.props.wrapperProps;
|
||||
@@ -232,7 +233,7 @@ class WrapperHome extends PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
const docMenu = <WorkspaceCardDropdown apiSpec={apiSpec} workspace={workspace} />;
|
||||
const docMenu = <WorkspaceCardDropdown apiSpec={apiSpec} workspace={workspace} space={activeSpace} />;
|
||||
const version = spec?.info?.version || '';
|
||||
let label: string = strings.collection.singular;
|
||||
let format = '';
|
||||
|
||||
@@ -529,13 +529,12 @@ class App extends PureComponent<AppProps, State> {
|
||||
|
||||
async _fetchRenderContext() {
|
||||
const { activeEnvironment, activeRequest, activeWorkspace } = this.props;
|
||||
const environmentId = activeEnvironment ? activeEnvironment._id : null;
|
||||
const ancestors = await db.withAncestors(activeRequest || activeWorkspace || null, [
|
||||
models.request.type,
|
||||
models.requestGroup.type,
|
||||
models.workspace.type,
|
||||
]);
|
||||
return render.getRenderContext(activeRequest, environmentId, ancestors);
|
||||
const ancestors = await render.getRenderContextAncestors(activeRequest || activeWorkspace);
|
||||
return render.getRenderContext({
|
||||
request: activeRequest || undefined,
|
||||
environmentId: activeEnvironment?._id,
|
||||
ancestors,
|
||||
});
|
||||
}
|
||||
|
||||
async _handleGetRenderContext(): Promise<RenderContextAndKeys> {
|
||||
@@ -1208,7 +1207,7 @@ class App extends PureComponent<AppProps, State> {
|
||||
);
|
||||
|
||||
/** All app data is stored within a namespaced GIT_INSOMNIA_DIR directory at the root of the repository and is read/written from the local NeDB database */
|
||||
const neDbClient = NeDBClient.createClient(activeWorkspace._id, activeSpace?._id);
|
||||
const neDbClient = NeDBClient.createClient(activeWorkspace._id, activeSpace._id);
|
||||
|
||||
/** All git metadata in the GIT_INTERNAL_DIR directory is stored in a git/ directory on the filesystem */
|
||||
const gitDataClient = fsClient(baseDir);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { mocked } from 'ts-jest/utils';
|
||||
import { globalBeforeEach } from '../../../__jest__/before-each';
|
||||
import { isLoggedIn as _isLoggedIn } from '../../../account/session';
|
||||
import * as models from '../../../models';
|
||||
import { Space } from '../../../models/space';
|
||||
import { BASE_SPACE_ID, Space } from '../../../models/space';
|
||||
import MemoryDriver from '../../../sync/store/drivers/memory-driver';
|
||||
import { VCS } from '../../../sync/vcs/vcs';
|
||||
import { useRemoteSpaces } from '../space';
|
||||
@@ -36,7 +36,7 @@ describe('useRemoteSpaces', () => {
|
||||
|
||||
expect(vcs.teams).not.toHaveBeenCalled();
|
||||
expect(result.current.loading).toBe(false);
|
||||
await expect(models.space.all()).resolves.toHaveLength(0);
|
||||
await expect(models.space.all()).resolves.toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should load teams each time VCS changes', async () => {
|
||||
@@ -66,8 +66,11 @@ describe('useRemoteSpaces', () => {
|
||||
expect(vcs2.teams).toHaveBeenCalledTimes(1);
|
||||
|
||||
const allSpaces = await models.space.all();
|
||||
expect(allSpaces).toHaveLength(2);
|
||||
expect(allSpaces).toHaveLength(3);
|
||||
expect(allSpaces).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining<Partial<Space>>({
|
||||
_id: BASE_SPACE_ID,
|
||||
}),
|
||||
expect.objectContaining<Partial<Space>>({
|
||||
remoteId: team1.id,
|
||||
name: team1.name,
|
||||
@@ -92,7 +95,7 @@ describe('useRemoteSpaces', () => {
|
||||
await waitFor(() => result.current.loading === false);
|
||||
|
||||
expect(vcs.teams).toHaveBeenCalledTimes(1);
|
||||
await expect(models.space.all()).resolves.toHaveLength(0);
|
||||
await expect(models.space.all()).resolves.toHaveLength(1);
|
||||
|
||||
const team1 = { id: 'id1', name: 'team1' };
|
||||
const team2 = { id: 'id2', name: 'team2' };
|
||||
@@ -105,8 +108,11 @@ describe('useRemoteSpaces', () => {
|
||||
expect(vcs.teams).toHaveBeenCalledTimes(3);
|
||||
|
||||
const allSpaces = await models.space.all();
|
||||
expect(allSpaces).toHaveLength(2);
|
||||
expect(allSpaces).toHaveLength(3);
|
||||
expect(allSpaces).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining<Partial<Space>>({
|
||||
_id: BASE_SPACE_ID,
|
||||
}),
|
||||
expect.objectContaining<Partial<Space>>({
|
||||
remoteId: team1.id,
|
||||
name: team1.name,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { globalBeforeEach } from '../../../__jest__/before-each';
|
||||
import { reduxStateForTest } from '../../../__jest__/redux-state-for-test';
|
||||
import * as models from '../../../models';
|
||||
import { BASE_SPACE_ID, Space } from '../../../models/space';
|
||||
import { selectActiveSpace } from '../selectors';
|
||||
|
||||
describe('selectors', () => {
|
||||
@@ -19,7 +20,7 @@ describe('selectors', () => {
|
||||
expect(space).toStrictEqual(spaceA);
|
||||
});
|
||||
|
||||
it('should return undefined if active space not found', async () => {
|
||||
it('should return base space if active space not found', async () => {
|
||||
// create two spaces
|
||||
await models.space.create();
|
||||
await models.space.create();
|
||||
@@ -28,19 +29,19 @@ describe('selectors', () => {
|
||||
const state = await reduxStateForTest({ activeSpaceId: 'some-other-space' });
|
||||
|
||||
const space = selectActiveSpace(state);
|
||||
expect(space).toBeUndefined();
|
||||
expect(space).toStrictEqual(expect.objectContaining<Partial<Space>>({ _id: BASE_SPACE_ID }));
|
||||
});
|
||||
|
||||
it('should return undefined if no active space', async () => {
|
||||
it('should return base space if no active space', async () => {
|
||||
// create two spaces
|
||||
await models.space.create();
|
||||
await models.space.create();
|
||||
|
||||
// set first as selected
|
||||
const state = await reduxStateForTest({ activeSpaceId: null });
|
||||
// set base as selected
|
||||
const state = await reduxStateForTest({ activeSpaceId: undefined });
|
||||
|
||||
const space = selectActiveSpace(state);
|
||||
expect(space).toBeUndefined();
|
||||
expect(space).toStrictEqual(expect.objectContaining<Partial<Space>>({ _id: BASE_SPACE_ID }));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -10,6 +10,7 @@ import * as models from '../../../../models';
|
||||
import { ApiSpec } from '../../../../models/api-spec';
|
||||
import { CookieJar } from '../../../../models/cookie-jar';
|
||||
import { Environment } from '../../../../models/environment';
|
||||
import { BASE_SPACE_ID } from '../../../../models/space';
|
||||
import { Workspace, WorkspaceScope, WorkspaceScopeKeys } from '../../../../models/workspace';
|
||||
import { WorkspaceMeta } from '../../../../models/workspace-meta';
|
||||
import { getAndClearShowPromptMockArgs } from '../../../../test-utils';
|
||||
@@ -31,7 +32,7 @@ const createStoreWithSpace = async () => {
|
||||
return { store, space };
|
||||
};
|
||||
|
||||
const expectedModelsCreated = async (name: string, scope: WorkspaceScope, parentId: string | null = null) => {
|
||||
const expectedModelsCreated = async (name: string, scope: WorkspaceScope, parentId: string) => {
|
||||
const workspaces = await models.workspace.all();
|
||||
expect(workspaces).toHaveLength(1);
|
||||
const workspace = workspaces[0];
|
||||
@@ -40,7 +41,6 @@ const expectedModelsCreated = async (name: string, scope: WorkspaceScope, parent
|
||||
|
||||
expect(descendents).toHaveLength(5);
|
||||
expect(descendents).toStrictEqual(expect.arrayContaining([
|
||||
// @ts-expect-error parentId is nullable for workspaces
|
||||
expect.objectContaining<Partial<Workspace>>({ name, scope, parentId, type: models.workspace.type }),
|
||||
expect.objectContaining<Partial<ApiSpec>>({ parentId: workspace._id, type: models.apiSpec.type }),
|
||||
expect.objectContaining<Partial<Environment>>({ parentId: workspace._id, type: models.environment.type }),
|
||||
@@ -125,7 +125,7 @@ describe('workspace', () => {
|
||||
const workspaceName = 'name';
|
||||
await onComplete?.(workspaceName);
|
||||
|
||||
const workspaceId = await expectedModelsCreated(workspaceName, WorkspaceScopeKeys.collection);
|
||||
const workspaceId = await expectedModelsCreated(workspaceName, WorkspaceScopeKeys.collection, BASE_SPACE_ID);
|
||||
|
||||
expect(trackSegmentEvent).toHaveBeenCalledWith('Collection Created');
|
||||
expect(trackEvent).toHaveBeenCalledWith('Workspace', 'Create');
|
||||
|
||||
@@ -39,7 +39,7 @@ import install from '../../../plugins/install';
|
||||
import { setTheme } from '../../../plugins/misc';
|
||||
import AskModal from '../../../ui/components/modals/ask-modal';
|
||||
import AlertModal from '../../components/modals/alert-modal';
|
||||
import { showError, showModal } from '../../components/modals/index';
|
||||
import { showAlert, showError, showModal } from '../../components/modals/index';
|
||||
import LoginModal from '../../components/modals/login-modal';
|
||||
import PaymentNotificationModal from '../../components/modals/payment-notification-modal';
|
||||
import { SelectModal } from '../../components/modals/select-modal';
|
||||
@@ -47,7 +47,7 @@ import SettingsModal, {
|
||||
TAB_INDEX_PLUGINS,
|
||||
TAB_INDEX_THEMES,
|
||||
} from '../../components/modals/settings-modal';
|
||||
import { selectSettings, selectWorkspacesForActiveSpace } from '../selectors';
|
||||
import { selectActiveSpaceName, selectSettings, selectWorkspacesForActiveSpace } from '../selectors';
|
||||
import { importUri } from './import';
|
||||
|
||||
export const LOCALSTORAGE_PREFIX = 'insomnia::meta';
|
||||
@@ -79,7 +79,7 @@ function activeActivityReducer(state: string | null = null, action) {
|
||||
}
|
||||
}
|
||||
|
||||
function activeSpaceReducer(state: string | null = BASE_SPACE_ID, action) {
|
||||
function activeSpaceReducer(state: string = BASE_SPACE_ID, action) {
|
||||
switch (action.type) {
|
||||
case SET_ACTIVE_SPACE:
|
||||
return action.spaceId;
|
||||
@@ -141,7 +141,7 @@ function loginStateChangeReducer(state = false, action) {
|
||||
|
||||
export interface GlobalState {
|
||||
isLoading: boolean;
|
||||
activeSpaceId: string | null;
|
||||
activeSpaceId: string;
|
||||
activeWorkspaceId: string | null;
|
||||
activeActivity: GlobalActivity | null,
|
||||
isLoggedIn: boolean;
|
||||
@@ -461,10 +461,22 @@ const writeExportedFileToFileSystem = (filename: string, jsonData: string, onDon
|
||||
|
||||
export const exportAllToFile = () => async (dispatch: Dispatch, getState) => {
|
||||
dispatch(loadStart());
|
||||
const state = getState();
|
||||
const activeSpaceName = selectActiveSpaceName(state);
|
||||
const workspaces = selectWorkspacesForActiveSpace(state);
|
||||
|
||||
if (!workspaces.length) {
|
||||
dispatch(loadStop());
|
||||
showAlert({
|
||||
title: 'Cannot export',
|
||||
message: <>There are no workspaces to export in the <strong>{activeSpaceName}</strong> space.</>,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
showSelectExportTypeModal({
|
||||
onCancel: () => { dispatch(loadStop()); },
|
||||
onDone: async selectedFormat => {
|
||||
const state = getState();
|
||||
// Check if we want to export private environments.
|
||||
const environments = await models.environment.all();
|
||||
|
||||
@@ -487,8 +499,6 @@ export const exportAllToFile = () => async (dispatch: Dispatch, getState) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const workspaces = selectWorkspacesForActiveSpace(state);
|
||||
|
||||
let stringifiedExport;
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { getAppName } from '../../../common/constants';
|
||||
import { BASE_SPACE_ID, Space } from '../../../models/space';
|
||||
import { Space } from '../../../models/space';
|
||||
import { WorkspaceScope, WorkspaceScopeKeys } from '../../../models/workspace';
|
||||
import { showModal } from '../../components/modals';
|
||||
import AskModal from '../../components/modals/ask-modal';
|
||||
@@ -64,18 +63,17 @@ export function askToSetWorkspaceScope(scope?: WorkspaceScope): SetWorkspaceScop
|
||||
};
|
||||
}
|
||||
|
||||
export type SetSpaceIdPrompt = () => Promise<string | null>;
|
||||
export function askToImportIntoSpace({ spaces, activeSpace }: { spaces: Space[]; activeSpace?: Space; }): SetSpaceIdPrompt {
|
||||
export type SetSpaceIdPrompt = () => Promise<string>;
|
||||
export function askToImportIntoSpace({ spaces, activeSpace }: { spaces: Space[]; activeSpace: Space; }): SetSpaceIdPrompt {
|
||||
return function() {
|
||||
return new Promise(resolve => {
|
||||
// If no spaces exist, return null (indicating no parent/space)
|
||||
if (spaces.length === 0) {
|
||||
return resolve(null);
|
||||
// If only one space exists, return that
|
||||
if (spaces.length === 1) {
|
||||
return resolve(spaces[0]._id);
|
||||
}
|
||||
|
||||
const options = [{ name: getAppName(), value: BASE_SPACE_ID }, ...spaces.map(space => ({ name: space.name, value: space._id }))];
|
||||
|
||||
const defaultValue = activeSpace?._id || options[0].value;
|
||||
const options = spaces.map(space => ({ name: space.name, value: space._id }));
|
||||
const defaultValue = activeSpace._id;
|
||||
|
||||
showSelectModal({
|
||||
title: 'Import',
|
||||
@@ -84,7 +82,8 @@ export function askToImportIntoSpace({ spaces, activeSpace }: { spaces: Space[];
|
||||
value: defaultValue,
|
||||
noEscape: true,
|
||||
onDone: selectedSpaceId => {
|
||||
resolve(selectedSpaceId === BASE_SPACE_ID ? null : selectedSpaceId);
|
||||
// @ts-expect-error onDone can send null as an argument; why/how?
|
||||
resolve(selectedSpaceId);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -50,14 +50,13 @@ const convertToRawConfig = ({
|
||||
}: ImportOptions,
|
||||
state: RootState): ImportRawConfig => {
|
||||
const activeSpace = selectActiveSpace(state);
|
||||
// WARNING: this selector does not include the base space
|
||||
const spaces = selectSpaces(state);
|
||||
|
||||
return ({
|
||||
getWorkspaceScope: askToSetWorkspaceScope(forceToScope),
|
||||
getWorkspaceId: askToImportIntoWorkspace({ workspaceId, forceToWorkspace }),
|
||||
// Currently, just return the active space instead of prompting for which space to import into
|
||||
getSpaceId: forceToSpace === 'prompt' ? askToImportIntoSpace({ spaces, activeSpace }) : () => Promise.resolve(activeSpace?._id || null),
|
||||
getSpaceId: forceToSpace === 'prompt' ? askToImportIntoSpace({ spaces, activeSpace }) : () => Promise.resolve(activeSpace._id),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ export const createWorkspace = ({ scope, onCreate }: {
|
||||
}) => {
|
||||
return (dispatch, getState) => {
|
||||
const activeSpace = selectActiveSpace(getState());
|
||||
const parentId = activeSpace?._id || null;
|
||||
|
||||
const design = isDesign({
|
||||
scope,
|
||||
@@ -61,8 +60,7 @@ export const createWorkspace = ({ scope, onCreate }: {
|
||||
{
|
||||
name,
|
||||
scope,
|
||||
// @ts-expect-error TSCONVERSION the common parentId isn't typed correctly
|
||||
parentId,
|
||||
parentId: activeSpace._id,
|
||||
},
|
||||
onCreate,
|
||||
),
|
||||
|
||||
@@ -7,6 +7,7 @@ import { BaseModel } from '../../models';
|
||||
import { getStatusCandidates } from '../../models/helpers/get-status-candidates';
|
||||
import { isRequest, Request } from '../../models/request';
|
||||
import { isRequestGroup, RequestGroup } from '../../models/request-group';
|
||||
import { BASE_SPACE_ID } from '../../models/space';
|
||||
import { UnitTestResult } from '../../models/unit-test-result';
|
||||
import { RootState } from './modules';
|
||||
|
||||
@@ -60,7 +61,6 @@ export const selectSettings = createSelector(
|
||||
selectEntitiesLists,
|
||||
entities => entities.settings[0] || models.settings.init());
|
||||
|
||||
/** WARNING: this does not include the base space */
|
||||
export const selectSpaces = createSelector(
|
||||
selectEntitiesLists,
|
||||
entities => entities.spaces,
|
||||
@@ -70,7 +70,7 @@ export const selectActiveSpace = createSelector(
|
||||
selectEntities,
|
||||
(state: RootState) => state.global.activeSpaceId,
|
||||
(entities, activeSpaceId) => {
|
||||
return activeSpaceId ? entities.spaces[activeSpaceId] : undefined;
|
||||
return entities.spaces[activeSpaceId] || entities.spaces[BASE_SPACE_ID];
|
||||
},
|
||||
);
|
||||
|
||||
@@ -82,10 +82,7 @@ export const selectAllWorkspaces = createSelector(
|
||||
export const selectWorkspacesForActiveSpace = createSelector(
|
||||
selectAllWorkspaces,
|
||||
selectActiveSpace,
|
||||
(workspaces, activeSpace) => {
|
||||
const parentId = activeSpace?._id || null;
|
||||
return workspaces.filter(w => w.parentId === parentId);
|
||||
},
|
||||
(workspaces, activeSpace) => workspaces.filter(w => w.parentId === activeSpace._id),
|
||||
);
|
||||
|
||||
export const selectActiveWorkspace = createSelector(
|
||||
@@ -366,7 +363,7 @@ export const selectActiveUnitTests = createSelector(
|
||||
|
||||
export const selectActiveSpaceName = createSelector(
|
||||
selectActiveSpace,
|
||||
activeSpace => activeSpace?.name,
|
||||
activeSpace => activeSpace.name,
|
||||
);
|
||||
|
||||
export const selectActiveUnitTestSuites = createSelector(
|
||||
|
||||
Reference in new issue
Block a user