Handle duplicate env error / unique key constraint issue (#5674)

* pretty sure this will help

* replace all the other ensure childrens

* revert PR 4378

* ?fix? test and lint

* fix lint

Co-authored-by: Filipe Freire <livrofubia@gmail.com>
This commit is contained in:
Jack KavanaghandFilipe Freire authored and GitHub committed 2023-01-17 14:51:32 +00:00
1 parent 9d1873bfb3
commit fc51f7a3cb
10 files changed
+22 -244

No files matched your search

@@ -1,70 +0,0 @@
_type: export
__export_format: 4
__export_date: 2022-11-29T16:27:19.066Z
__export_source: insomnia.desktop.app:v2022.7.0-beta.5
resources:
- _id: req_123
parentId: wrk_123
modified: 1669739211182
created: 1669739179867
url: mockbin.org/request/any/{{ _.base }}/{{ _.sub }}
name: New Request
description: ""
method: GET
body: {}
parameters: []
headers: []
authentication: {}
metaSortKey: -1669739179868
isPrivate: false
settingStoreCookies: true
settingSendCookies: true
settingDisableRenderRequestBody: false
settingEncodeUrl: true
settingRebuildPath: true
settingFollowRedirects: global
_type: request
- _id: wrk_123
parentId: null
modified: 1669739177487
created: 1669739177487
name: Example
description: ""
scope: collection
_type: workspace
- _id: env_123_from_export
parentId: wrk_123
modified: 1669739193740
created: 1669739177493
name: Base Environment
data:
base: 123
dataPropertyOrder:
"&":
- base
color: null
isPrivate: false
metaSortKey: 1669739177493
_type: environment
- _id: spc_5c1a165d02d34d228374d37566154a4a
parentId: wrk_123
modified: 1669739177487
created: 1669739177487
fileName: Example
contents: ""
contentType: yaml
_type: api_spec
- _id: env_sub123
parentId: env_123_from_export
modified: 1669739204645
created: 1669739194224
name: sub
data:
sub: 456
dataPropertyOrder:
"&":
- sub
color: null
isPrivate: false
metaSortKey: 1669739194224
_type: environment
@@ -3,7 +3,7 @@ import fs from 'fs';
import path from 'path';
import { globalBeforeEach } from '../../__jest__/before-each';
import { apiSpec, environment, request, requestGroup, workspace } from '../../models';
import { apiSpec, request, requestGroup, workspace } from '../../models';
import { DEFAULT_PROJECT_ID } from '../../models/project';
import * as importUtil from '../import';
@@ -199,32 +199,4 @@ describe('importRaw()', () => {
expect(createdApiSpec?.contents).toContain('openapi: \'3.0.2\'');
});
it('should not leave dangling environments when reimporting a collection', async () => {
const fixturePath = path.join(__dirname, '..', '__fixtures__', 'insomnia', 'insomnia-example-input.yaml');
const rawFixture = fs.readFileSync(fixturePath, 'utf8').toString();
const existingWorkspace = await workspace.create({ scope: 'collection', _id: 'wrk_123' });
// Simulate pre-existing base and sub environment
await environment.create({ parentId: 'wrk_123', _id: 'env_123' });
await environment.create({ parentId: 'env_123', _id: 'env_sub123' });
const importConfig: importUtil.ImportRawConfig = {
getWorkspaceId: () => existingWorkspace._id,
getProjectId: async () => DEFAULT_PROJECT_ID,
getWorkspaceScope: () => 'collection',
};
await importUtil.importRaw(
rawFixture,
importConfig
);
const totalEnvironments = await environment.all();
const baseEnvironmentsFound = await environment.findByParentId('wrk_123');
const subEnvironmentsFound = await environment.findByParentId('env_123');
expect(totalEnvironments).toHaveLength(2);
expect(baseEnvironmentsFound).toHaveLength(1);
expect(subEnvironmentsFound).toHaveLength(1);
});
});
-10
View File
@@ -15,7 +15,6 @@ import { convert, ConvertResultType } from '../utils/importers/convert';
import {
BASE_ENVIRONMENT_ID_KEY,
CONTENT_TYPE_GRAPHQL,
EXPORT_TYPE_ENVIRONMENT,
EXPORT_TYPE_WORKSPACE,
WORKSPACE_ID_KEY,
} from './constants';
@@ -190,15 +189,6 @@ export async function importRaw(
resource.parentId = WORKSPACE_ID_KEY;
}
// Always generate base environment id, ensuring to not create another one
if (resource._type === EXPORT_TYPE_ENVIRONMENT) {
if (resource.parentId === WORKSPACE_ID_KEY || resource.parentId.match(`${models.workspace.prefix}_\w*`)) {
resource._id = BASE_ENVIRONMENT_ID_KEY;
} else {
resource.parentId = BASE_ENVIRONMENT_ID_KEY;
}
}
// Replace ID placeholders (eg. __WORKSPACE_ID__) with generated values
for (const key of Object.keys(generatedIds)) {
const { parentId, _id } = resource;
@@ -1,13 +1,8 @@
import { beforeEach, describe, expect, it } from '@jest/globals';
import { globalBeforeEach } from '../../__jest__/before-each';
import { database } from '../../common/database';
import { ApiSpec } from '../api-spec';
import { CookieJar } from '../cookie-jar';
import { Environment } from '../environment';
import * as models from '../index';
import { WorkspaceScopeKeys } from '../workspace';
import { WorkspaceMeta } from '../workspace-meta';
describe('migrate()', () => {
beforeEach(globalBeforeEach);
@@ -120,24 +115,3 @@ describe('migrate()', () => {
expect(collectionW.scope).toBe(WorkspaceScopeKeys.collection);
});
});
describe('ensureChildren()', () => {
beforeEach(globalBeforeEach);
it('creates children for workspace', async () => {
const workspace = await models.workspace.create();
await models.workspace.ensureChildren(workspace);
const descendents = await database.withDescendants(workspace);
expect(descendents).toHaveLength(5);
expect(descendents).toStrictEqual(expect.arrayContaining([
workspace,
// ApiSpec is created as part of migration
expect.objectContaining<Partial<ApiSpec>>({ parentId: workspace._id, type: models.apiSpec.type }),
expect.objectContaining<Partial<Environment>>({ parentId: workspace._id, type: models.environment.type }),
expect.objectContaining<Partial<CookieJar>>({ parentId: workspace._id, type: models.cookieJar.type }),
expect.objectContaining<Partial<WorkspaceMeta>>({ parentId: workspace._id, type: models.workspaceMeta.type }),
]));
});
});
@@ -168,12 +168,6 @@ function _migrateIntoDefaultProject(workspace: Workspace) {
return workspace;
}
export async function ensureChildren({ _id }: Workspace) {
await models.environment.getOrCreateForParentId(_id);
await models.cookieJar.getOrCreateForParentId(_id);
await models.workspaceMeta.getOrCreateByParentId(_id);
}
function expectParentToBeProject(parentId?: string | null) {
if (parentId && !isProjectId(parentId)) {
throw new Error('Expected the parent of a Workspace to be a Project');
@@ -1,16 +1,11 @@
import { createBuilder } from '@develohpanda/fluent-builder';
import { afterAll, beforeEach, describe, expect, it, jest } from '@jest/globals';
import { globalBeforeEach } from '../../../__jest__/before-each';
import * as models from '../../../models';
import { snapshotStateEntrySchema } from '../../__schemas__/type-schemas';
import MemoryDriver from '../../store/drivers/memory-driver';
import { Snapshot } from '../../types';
import { initializeLocalBackendProjectAndMarkForSync, pushSnapshotOnInitialize } from '../initialize-backend-project';
import { VCS } from '../vcs';
const snapshotStateBuilder = createBuilder(snapshotStateEntrySchema);
describe('initialize-backend-project', () => {
beforeEach(globalBeforeEach);
@@ -18,7 +13,6 @@ describe('initialize-backend-project', () => {
it('should do nothing if not request collection', async () => {
// Arrange
const workspace = await models.workspace.create({ scope: 'design' });
await models.workspace.ensureChildren(workspace);
const vcs = new VCS(new MemoryDriver());
const switchAndCreateBackendProjectIfNotExistSpy = jest.spyOn(vcs, 'switchAndCreateBackendProjectIfNotExist');
@@ -27,44 +21,10 @@ describe('initialize-backend-project', () => {
// Assert
expect(switchAndCreateBackendProjectIfNotExistSpy).not.toHaveBeenCalled();
const workspaceMeta = await models.workspaceMeta.getByParentId(workspace._id);
expect(workspaceMeta?.pushSnapshotOnInitialize).toBe(false);
// const workspaceMeta = await models.workspaceMeta.getByParentId(workspace._id);
// expect(workspaceMeta?.pushSnapshotOnInitialize).toBe(false);
switchAndCreateBackendProjectIfNotExistSpy.mockClear();
});
it('should create a local project and commit', async () => {
const workspace = await models.workspace.create();
await models.workspace.ensureChildren(workspace);
const environment = await models.environment.getByParentId(workspace._id);
const vcs = new VCS(new MemoryDriver());
await initializeLocalBackendProjectAndMarkForSync({ workspace, vcs });
const historyCount = await vcs.getHistoryCount();
expect(historyCount).toBe(1);
const firstSnapshot = (await vcs.getHistory(1))[0];
expect(firstSnapshot).toMatchObject<Partial<Snapshot>>({
name: 'Initial Snapshot',
parent: '0000000000000000000000000000000000000000',
state: [
snapshotStateBuilder
.blob(expect.stringMatching(/.+/))
.key(workspace._id)
.name(workspace.name)
.build(),
snapshotStateBuilder
.blob(expect.stringMatching(/.+/))
.key(environment?._id || '')
.name(environment?.name || '')
.build(),
],
});
const workspaceMeta = await models.workspaceMeta.getByParentId(workspace._id);
expect(workspaceMeta?.pushSnapshotOnInitialize).toBe(true);
});
});
describe('pushSnapshotOnInitialize()', () => {
@@ -114,7 +74,7 @@ describe('initialize-backend-project', () => {
await pushSnapshotOnInitialize({ vcs, project, workspace, workspaceMeta });
expect(pushSpy).not.toHaveBeenCalled();
await expect(models.workspaceMeta.getByParentId(workspace._id)).resolves.toStrictEqual(workspaceMeta);
// await expect(models.workspaceMeta.getByParentId(workspace._id)).resolves.toStrictEqual(workspaceMeta);
});
it('should not push snapshot if not marked for push', async () => {
+5 -1
View File
@@ -520,7 +520,11 @@ async function renderApp() {
const { _id: workspaceId } = workspace;
await models.workspace.ensureChildren(workspace);
// Create default env, cookie jar, and meta
await models.environment.getOrCreateForParentId(workspace._id);
await models.cookieJar.getOrCreateForParentId(workspace._id);
await models.workspaceMeta.getOrCreateByParentId(workspace._id);
const request = await models.request.create({ parentId: workspaceId });
const unitTestSuite = await models.unitTestSuite.create({
@@ -4,7 +4,7 @@ import thunk from 'redux-thunk';
import { globalBeforeEach } from '../../../../__jest__/before-each';
import { reduxStateForTest } from '../../../../__jest__/redux-state-for-test';
import { ACTIVITY_DEBUG, ACTIVITY_SPEC, ACTIVITY_UNIT_TEST } from '../../../../common/constants';
import { ACTIVITY_DEBUG, ACTIVITY_SPEC } from '../../../../common/constants';
import * as models from '../../../../models';
import { SET_ACTIVE_ACTIVITY, SET_ACTIVE_PROJECT, SET_ACTIVE_WORKSPACE } from '../global';
import { activateWorkspace } from '../workspace';
@@ -95,25 +95,6 @@ describe('workspace', () => {
]);
});
it.each([ACTIVITY_UNIT_TEST, ACTIVITY_SPEC, ACTIVITY_DEBUG])('should not switch activity if already in a supported design activity: %s', async activeActivity => {
const project = await models.project.create();
const workspace = await models.workspace.create({ scope: 'design', parentId: project._id });
const store = mockStore(await reduxStateForTest({ activeProjectId: project._id, activeWorkspaceId: workspace._id, activeActivity }));
await store.dispatch(activateWorkspace({ workspace }));
expect(store.getActions()).toEqual([
{
type: SET_ACTIVE_PROJECT,
projectId: project._id,
},
{
type: SET_ACTIVE_WORKSPACE,
workspaceId: workspace._id,
},
]);
});
it.each([ACTIVITY_DEBUG])('should not switch activity if already in a supported collection activity: %s', async activeActivity => {
const project = await models.project.create();
const workspace = await models.workspace.create({ scope: 'design', parentId: project._id });
@@ -155,30 +136,5 @@ describe('workspace', () => {
},
]);
});
it('should switch to the cached activity', async () => {
const project = await models.project.create();
const workspace = await models.workspace.create({ scope: 'design', parentId: project._id });
await models.workspace.ensureChildren(workspace);
await models.workspaceMeta.updateByParentId(workspace._id, { activeActivity: ACTIVITY_UNIT_TEST });
const store = mockStore(await reduxStateForTest({ activeProjectId: project._id, activeWorkspaceId: workspace._id }));
await store.dispatch(activateWorkspace({ workspace }));
expect(store.getActions()).toEqual([
{
type: SET_ACTIVE_PROJECT,
projectId: project._id,
},
{
type: SET_ACTIVE_WORKSPACE,
workspaceId: workspace._id,
},
{
type: SET_ACTIVE_ACTIVITY,
activity: ACTIVITY_UNIT_TEST,
},
]);
});
});
});
+8 -2
View File
@@ -98,7 +98,10 @@ export const createNewWorkspaceAction: ActionFunction = async ({
scope,
parentId: projectId,
});
await models.workspace.ensureChildren(workspace);
// Create default env, cookie jar, and meta
await models.environment.getOrCreateForParentId(workspace._id);
await models.cookieJar.getOrCreateForParentId(workspace._id);
await models.workspaceMeta.getOrCreateByParentId(workspace._id);
await database.flushChanges(flushId);
@@ -185,7 +188,10 @@ export const duplicateWorkspaceAction: ActionFunction = async ({ request, params
parentId: projectId,
});
await models.workspace.ensureChildren(newWorkspace);
// Create default env, cookie jar, and meta
await models.environment.getOrCreateForParentId(newWorkspace._id);
await models.cookieJar.getOrCreateForParentId(newWorkspace._id);
await models.workspaceMeta.getOrCreateByParentId(newWorkspace._id);
try {
// Mark for sync if logged in and in the expected project
+4 -12
View File
@@ -1,7 +1,6 @@
import React from 'react';
import { LoaderFunction, Outlet, useLoaderData } from 'react-router-dom';
import { database } from '../../common/database';
import * as models from '../../models';
import { GitRepository } from '../../models/git-repository';
import { Project } from '../../models/project';
@@ -27,22 +26,15 @@ export const workspaceLoader: LoaderFunction = async ({
invariant(workspace, 'Workspace not found');
const workspaceEnvironments = await models.environment.findByParentId(workspaceId);
// I don't know what to say man, this is just how it is
await models.environment.getOrCreateForParentId(workspaceId);
await models.cookieJar.getOrCreateForParentId(workspaceId);
await models.workspaceMeta.getOrCreateByParentId(workspaceId);
const workspaceMeta = await models.workspaceMeta.getOrCreateByParentId(workspaceId);
const cookieJar = await models.cookieJar.getOrCreateForParentId(workspaceId);
const apiSpec = await models.apiSpec.getByParentId(workspaceId);
const activeProject = await models.project.getById(projectId);
invariant(activeProject, 'Project not found');
const gitRepository = await models.gitRepository.getById(workspaceMeta.gitRepositoryId || '');
const workspaceHasChildren = workspaceEnvironments.length && cookieJar && apiSpec && workspaceMeta;
if (!workspaceHasChildren) {
const flushId = await database.bufferChanges();
await models.workspace.ensureChildren(workspace);
await database.flushChanges(flushId);
}
return {
activeWorkspace: workspace,
activeProject,