From a20ea65c4c14bc1b8388dc1bb2b400757d2645e7 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Wed, 13 Sep 2017 08:11:49 +0200 Subject: [PATCH] Async model migrations --- app/common/__tests__/har.test.js | 3 +- app/common/__tests__/import.test.js | 9 ++--- app/common/database.js | 34 ++++++++++--------- app/models/__tests__/request.test.js | 4 +-- app/models/__tests__/response.test.js | 8 ++--- app/models/environment.js | 10 ++++-- app/models/index.js | 10 +++--- app/models/workspace.js | 17 ++++------ app/network/__tests__/network.test.js | 3 +- app/sync/__tests__/sync.test.js | 16 ++++----- .../editors/body/graph-ql-editor.js | 15 ++++---- .../components/modals/request-create-modal.js | 2 +- app/ui/css/layout/base.less | 4 +++ 13 files changed, 71 insertions(+), 64 deletions(-) diff --git a/app/common/__tests__/har.test.js b/app/common/__tests__/har.test.js index b09eb22b87..8b535f16a4 100644 --- a/app/common/__tests__/har.test.js +++ b/app/common/__tests__/har.test.js @@ -19,7 +19,8 @@ describe('exportHarWithRequest()', () => { lastAccessed: new Date('2096-10-05T04:40:49.505Z') }]; - await models.cookieJar.create({ + const cookieJar = await models.cookieJar.getOrCreateForParentId(workspace._id); + await models.cookieJar.update(cookieJar, { parentId: workspace._id, cookies }); diff --git a/app/common/__tests__/import.test.js b/app/common/__tests__/import.test.js index e5f49439f7..6cd9615bc0 100644 --- a/app/common/__tests__/import.test.js +++ b/app/common/__tests__/import.test.js @@ -8,7 +8,7 @@ describe('export()', () => { it('succeed with username and password', async () => { const w = await models.workspace.create({name: 'Workspace'}); const r1 = await models.request.create({name: 'Request', parentId: w._id}); - const eBase = await models.environment.create({name: 'Base', parentId: w._id}); + const eBase = await models.environment.getOrCreateForWorkspace(w); const ePub = await models.environment.create({name: 'Public', parentId: eBase._id}); await models.environment.create({name: 'Private', isPrivate: true, parentId: eBase._id}); @@ -21,8 +21,9 @@ describe('export()', () => { expect(data.__export_source).toBe(`insomnia.desktop.app:v${getAppVersion()}`); expect(data.resources[0]._id).toBe(w._id); expect(data.resources[1]._id).toBe(eBase._id); - expect(data.resources[2]._id).toBe(r1._id); - expect(data.resources[3]._id).toBe(ePub._id); - expect(data.resources.length).toBe(4); + expect(data.resources[2]._id).toBe('jar_dd2ccc1a2745477a881a9e8ef9d42403'); + expect(data.resources[3]._id).toBe(r1._id); + expect(data.resources[4]._id).toBe(ePub._id); + expect(data.resources.length).toBe(5); }); }); diff --git a/app/common/database.js b/app/common/database.js index 5599363869..ab0caa4f23 100644 --- a/app/common/database.js +++ b/app/common/database.js @@ -155,16 +155,17 @@ export function findMostRecentlyModified ( limit: number | null = null ): Promise> { return new Promise(resolve => { - db[type].find(query).sort({modified: -1}).limit(limit).exec((err, rawDocs) => { + db[type].find(query).sort({modified: -1}).limit(limit).exec(async (err, rawDocs) => { if (err) { console.warn('[db] Failed to find docs', err); resolve([]); return; } - const docs = rawDocs.map(rawDoc => { - return initModel(type, rawDoc); - }); + const docs = []; + for (const rawDoc of rawDocs) { + docs.push(await initModel(type, rawDoc)); + } resolve(docs); }); @@ -177,14 +178,15 @@ export function find ( sort: Object = {created: 1} ): Promise> { return new Promise((resolve, reject) => { - db[type].find(query).sort(sort).exec((err, rawDocs) => { + db[type].find(query).sort(sort).exec(async (err, rawDocs) => { if (err) { return reject(err); } - const docs = rawDocs.map(rawDoc => { - return initModel(type, rawDoc); - }); + const docs = []; + for (const rawDoc of rawDocs) { + docs.push(await initModel(type, rawDoc)); + } resolve(docs); }); @@ -231,8 +233,8 @@ export async function upsert (doc: BaseModel, fromSync: boolean = false): Promis } export function insert (doc: T, fromSync: boolean = false): Promise { - return new Promise((resolve, reject) => { - const docWithDefaults = initModel(doc.type, doc); + return new Promise(async (resolve, reject) => { + const docWithDefaults = await initModel(doc.type, doc); db[doc.type].insert(docWithDefaults, (err, newDoc) => { if (err) { return reject(err); @@ -245,8 +247,8 @@ export function insert (doc: T, fromSync: boolean = false): Promis } export function update (doc: T, fromSync: boolean = false): Promise { - return new Promise((resolve, reject) => { - const docWithDefaults = initModel(doc.type, doc); + return new Promise(async (resolve, reject) => { + const docWithDefaults = await initModel(doc.type, doc); db[doc.type].update({_id: docWithDefaults._id}, docWithDefaults, err => { if (err) { return reject(err); @@ -295,8 +297,8 @@ export async function removeWhere (type: string, query: Object): Promise { // DEFAULT MODEL STUFF // // ~~~~~~~~~~~~~~~~~~~ // -export function docUpdate (originalDoc: T, patch: Object = {}): Promise { - const doc = initModel( +export async function docUpdate (originalDoc: T, patch: Object = {}): Promise { + const doc = await initModel( originalDoc.type, originalDoc, @@ -309,8 +311,8 @@ export function docUpdate (originalDoc: T, patch: Object = {}): Pr return update(doc); } -export function docCreate (type: string, ...patches: Array): Promise { - const doc = initModel( +export async function docCreate (type: string, ...patches: Array): Promise { + const doc = await initModel( type, ...patches, diff --git a/app/models/__tests__/request.test.js b/app/models/__tests__/request.test.js index 44709b142e..cf0152ff39 100644 --- a/app/models/__tests__/request.test.js +++ b/app/models/__tests__/request.test.js @@ -253,7 +253,7 @@ describe('migrate()', () => { expect(models.request.migrate(noBody)).toEqual(expected2); }); - it('migrates from initModel()', () => { + it('migrates from initModel()', async () => { Date.now = jest.fn().mockReturnValue(1478795580200); const original = { @@ -283,7 +283,7 @@ describe('migrate()', () => { settingEncodeUrl: true }; - const migrated = models.initModel(models.request.type, original); + const migrated = await models.initModel(models.request.type, original); expect(migrated).toEqual(expected); }); }); diff --git a/app/models/__tests__/response.test.js b/app/models/__tests__/response.test.js index c086942f6b..c8f6a030bb 100644 --- a/app/models/__tests__/response.test.js +++ b/app/models/__tests__/response.test.js @@ -13,7 +13,7 @@ describe('migrate()', () => { it('migrates utf8 body correctly', async () => { const initialModel = {body: 'hello world!', encoding: 'utf8'}; - const newModel = models.initModel(models.response.type, initialModel); + const newModel = await models.initModel(models.response.type, initialModel); const expectedBodyPath = path.join( electron.remote.app.getPath('userData'), `responses/fc3ff98e8c6a0d3087d515c0473f8677.zip` @@ -32,7 +32,7 @@ describe('migrate()', () => { it('migrates base64 body correctly', async () => { const initialModel = {body: 'aGVsbG8gd29ybGQh', encoding: 'base64'}; - const newModel = models.initModel(models.response.type, initialModel); + const newModel = await models.initModel(models.response.type, initialModel); jest.runAllTimers(); const expectedBodyPath = path.join( electron.remote.app.getPath('userData'), @@ -52,7 +52,7 @@ describe('migrate()', () => { it('migrates empty body', async () => { const initialModel = {body: ''}; - const newModel = models.initModel(models.response.type, initialModel); + const newModel = await models.initModel(models.response.type, initialModel); jest.runAllTimers(); jest.runAllTimers(); @@ -74,7 +74,7 @@ describe('migrate()', () => { it('does not migrate body again', async () => { const initialModel = {bodyPath: '/foo/bar'}; - const newModel = models.initModel(models.response.type, initialModel); + const newModel = await models.initModel(models.response.type, initialModel); // Should have stripped these expect(newModel.body).toBeUndefined(); diff --git a/app/models/environment.js b/app/models/environment.js index 2cb0d17632..f3b645003a 100644 --- a/app/models/environment.js +++ b/app/models/environment.js @@ -33,14 +33,14 @@ export function findByParentId (parentId) { return db.find(type, {parentId}); } -export async function getOrCreateForWorkspace (workspace) { +export async function getOrCreateForWorkspaceId (workspaceId) { let environment = await db.getWhere(type, { - parentId: workspace._id + parentId: workspaceId }); if (!environment) { environment = await create({ - parentId: workspace._id, + parentId: workspaceId, name: 'Base Environment' }); } @@ -48,6 +48,10 @@ export async function getOrCreateForWorkspace (workspace) { return environment; } +export async function getOrCreateForWorkspace (workspace) { + return getOrCreateForWorkspaceId(workspace._id); +} + export function getById (id) { return db.get(type, id); } diff --git a/app/models/index.js b/app/models/index.js index 29891dbf5b..713fb9dfab 100644 --- a/app/models/index.js +++ b/app/models/index.js @@ -82,7 +82,7 @@ export function getModelName (type: string, count: number = 1) { } } -export function initModel (type: string, ...sources: Array) { +export async function initModel (type: string, ...sources: Array): Promise { const model = getModel(type); if (!model) { @@ -108,14 +108,14 @@ export function initModel (type: string, ...sources: Array) { // Migrate the model // NOTE: Do migration before pruning because we might need to look at those fields - model.migrate(fullObject); + const migratedDoc = await model.migrate(fullObject); // Prune extra keys from doc - for (const key of Object.keys(fullObject)) { + for (const key of Object.keys(migratedDoc)) { if (!objectDefaults.hasOwnProperty(key)) { - delete fullObject[key]; + delete migratedDoc[key]; } } - return fullObject; + return migratedDoc; } diff --git a/app/models/workspace.js b/app/models/workspace.js index 916a7280d2..1b6f5ff8bd 100644 --- a/app/models/workspace.js +++ b/app/models/workspace.js @@ -32,7 +32,7 @@ export function init () { }; } -export function migrate (doc: Object) { +export async function migrate (doc: Object) { // There was a bug on import that would set this to the current workspace ID. // Let's remove it here so that nothing bad happens. if (doc.parentId !== null) { @@ -40,6 +40,10 @@ export function migrate (doc: Object) { process.nextTick(() => update(doc, {parentId: null})); } + // Ensure child dependencies exist + await models.cookieJar.getOrCreateForParentId(doc._id); + await models.environment.getOrCreateForWorkspaceId(doc._id); + return doc; } @@ -47,15 +51,8 @@ export function getById (id: string): Promise { return db.get(type, id); } -export async function create (patch: Object = {}): Promise { - const workspace = await db.docCreate(type, patch); - - // Make sure CookieJars and environments exist for all workspaces - // TODO: Find a better place to do this - await models.cookieJar.getOrCreateForParentId(workspace._id); - await models.environment.getOrCreateForWorkspace(workspace); - - return workspace; +export function create (patch: Object = {}): Promise { + return db.docCreate(type, patch); } export async function all (): Promise> { diff --git a/app/network/__tests__/network.test.js b/app/network/__tests__/network.test.js index 9d3fb0a0b5..84116f3b0c 100644 --- a/app/network/__tests__/network.test.js +++ b/app/network/__tests__/network.test.js @@ -31,7 +31,8 @@ describe('actuallySend()', () => { lastAccessed: new Date('2096-10-05T04:40:49.505Z') }]; - await models.cookieJar.create({ + const cookieJar = await models.cookieJar.getOrCreateForParentId(workspace._id); + await models.cookieJar.update(cookieJar, { parentId: workspace._id, cookies }); diff --git a/app/sync/__tests__/sync.test.js b/app/sync/__tests__/sync.test.js index 3d5d7e27d4..3c63d09507 100644 --- a/app/sync/__tests__/sync.test.js +++ b/app/sync/__tests__/sync.test.js @@ -248,8 +248,6 @@ describe('Integration tests for creating Resources and pushing', () => { await models.request.create({_id: 'req_1', name: 'Request 1', parentId: 'wrk_1'}); await models.request.create({_id: 'req_2', name: 'Request 2', parentId: 'wrk_1'}); await models.request.create({_id: 'req_3', name: 'Request 3', parentId: 'wrk_1'}); - await models.cookieJar.create({_id: 'jar_1', name: 'Jar 1', parentId: 'wrk_1'}); - await models.environment.create({_id: 'env_1', name: 'Env 1', parentId: 'wrk_1'}); await models.environment.create({_id: 'env_2', name: 'Env Prv', parentId: 'wrk_1', isPrivate: true}); // Flush changes just to be sure they won't affect our tests @@ -259,8 +257,8 @@ describe('Integration tests for creating Resources and pushing', () => { // Assert that all our new models were created expect((await models.workspace.all()).length).toBe(2); expect((await models.request.all()).length).toBe(3); - expect((await models.environment.all()).length).toBe(2); - expect((await models.cookieJar.all()).length).toBe(1); + expect((await models.environment.all()).length).toBe(3); + expect((await models.cookieJar.all()).length).toBe(2); // Assert that initializing sync will create the initial resources expect((await syncStorage.allConfigs()).length).toBe(0); @@ -269,7 +267,7 @@ describe('Integration tests for creating Resources and pushing', () => { jest.runOnlyPendingTimers(); await promise; expect((await syncStorage.allConfigs()).length).toBe(2); - expect((await syncStorage.allResources()).length).toBe(7); + expect((await syncStorage.allResources()).length).toBe(9); // Mark all configs as auto sync const configs = await syncStorage.allConfigs(); @@ -304,12 +302,12 @@ describe('Integration tests for creating Resources and pushing', () => { // Assert expect((await syncStorage.allConfigs()).length).toBe(2); - expect((await syncStorage.allResources()).length).toBe(8); + expect((await syncStorage.allResources()).length).toBe(10); expect(_decryptResource(resource).url).toBe('https://google.com'); expect(resource.removed).toBe(false); expect(session.syncPush.mock.calls.length).toBe(1); - expect(session.syncPush.mock.calls[0][0].length).toBe(8); + expect(session.syncPush.mock.calls[0][0].length).toBe(10); expect(session.syncPull.mock.calls).toEqual([]); }); @@ -368,7 +366,7 @@ describe('Integration tests for creating Resources and pushing', () => { expect(resource.removed).toBe(false); expect(session.syncPush.mock.calls.length).toBe(1); - expect(session.syncPush.mock.calls[0][0].length).toBe(7); + expect(session.syncPush.mock.calls[0][0].length).toBe(9); expect(session.syncPull.mock.calls).toEqual([]); }); @@ -391,7 +389,7 @@ describe('Integration tests for creating Resources and pushing', () => { expect(updatedResource.removed).toBe(true); expect(session.syncPush.mock.calls.length).toBe(1); - expect(session.syncPush.mock.calls[0][0].length).toBe(7); + expect(session.syncPush.mock.calls[0][0].length).toBe(9); expect(session.syncPull.mock.calls).toEqual([]); }); diff --git a/app/ui/components/editors/body/graph-ql-editor.js b/app/ui/components/editors/body/graph-ql-editor.js index 99bea0c942..62736d3522 100644 --- a/app/ui/components/editors/body/graph-ql-editor.js +++ b/app/ui/components/editors/body/graph-ql-editor.js @@ -1,5 +1,6 @@ // @flow import type {Request} from '../../../../models/request'; +import classnames from 'classnames'; import {newBodyRaw} from '../../../../models/request'; import React from 'react'; import autobind from 'autobind-decorator'; @@ -228,7 +229,7 @@ class GraphQLEditor extends React.PureComponent { } return ( -
+
{message}
); @@ -251,7 +252,8 @@ class GraphQLEditor extends React.PureComponent { schemaFetchError, hideSchemaFetchErrors, variablesSyntaxError, - forceRefreshKey + forceRefreshKey, + schemaIsFetching } = this.state; const { @@ -294,13 +296,10 @@ class GraphQLEditor extends React.PureComponent { )}
- {this.renderSchemaFetchMessage()} +

Query Variables Variables to use in GraphQL query
(JSON diff --git a/app/ui/components/modals/request-create-modal.js b/app/ui/components/modals/request-create-modal.js index d52c8d5547..5a982601ac 100644 --- a/app/ui/components/modals/request-create-modal.js +++ b/app/ui/components/modals/request-create-modal.js @@ -37,7 +37,7 @@ class RequestCreateModal extends PureComponent { e.preventDefault(); const {parentId, selectedContentType, selectedMethod} = this.state; - const request = models.initModel(models.request.type, { + const request = await models.initModel(models.request.type, { parentId, name: this._input.value, method: selectedMethod diff --git a/app/ui/css/layout/base.less b/app/ui/css/layout/base.less index e70c7bcdc6..4a3598c210 100644 --- a/app/ui/css/layout/base.less +++ b/app/ui/css/layout/base.less @@ -526,6 +526,10 @@ i.fa:not(.fa--skinny) { padding-bottom: 0; } +.no-pad-right { + padding-right: 0; +} + .no-pad-left { padding-left: 0; }