From ccd270a9fb6c86863fe5e5003a791ffe3485a0d0 Mon Sep 17 00:00:00 2001 From: Opender Singh Date: Sat, 14 Nov 2020 12:31:18 +1300 Subject: [PATCH] gRPC with Insomnia export format V4 (#2830) * feat: initial commit to export grpc entities * feat: grpc requests and protofiles in v4 export format * test(export): add grpc requests and protofiles in export tests * test(models): add isProtoFile tests --- .../app/common/__tests__/import.test.js | 52 +++++++++++++++++-- packages/insomnia-app/app/common/import.js | 30 +++++++---- .../models/helpers/__tests__/is-model.test.js | 14 +++++ .../app/models/helpers/is-model.js | 6 ++- .../app/models/helpers/request-operations.js | 9 +++- .../components/export-requests/request-row.js | 9 +++- .../app/ui/components/export-requests/tree.js | 9 ++-- .../modals/export-requests-modal.js | 13 +++-- .../app/ui/redux/modules/global.js | 3 +- 9 files changed, 118 insertions(+), 27 deletions(-) diff --git a/packages/insomnia-app/app/common/__tests__/import.test.js b/packages/insomnia-app/app/common/__tests__/import.test.js index 83a651768..0e104c744 100644 --- a/packages/insomnia-app/app/common/__tests__/import.test.js +++ b/packages/insomnia-app/app/common/__tests__/import.test.js @@ -187,6 +187,24 @@ describe('export', () => { name: 'Request 1', parentId: w._id, }); + const pf1 = await models.protoFile.create({ + name: 'ProtoFile 1', + parentId: w._id, + }); + const gr1 = await models.grpcRequest.create({ + name: 'Grpc Request 1', + parentId: w._id, + protoFileId: pf1._id, + }); + const pf2 = await models.protoFile.create({ + name: 'ProtoFile 2', + parentId: w._id, + }); + const gr2 = await models.grpcRequest.create({ + name: 'Grpc Request 2', + parentId: w._id, + protoFileId: pf2._id, + }); const f2 = await models.requestGroup.create({ name: 'Folder 2', parentId: w._id, @@ -229,13 +247,17 @@ describe('export', () => { expect.objectContaining({ _id: f2._id }), expect.objectContaining({ _id: r2._id }), expect.objectContaining({ _id: ePub._id }), + expect.objectContaining({ _id: gr1._id }), + expect.objectContaining({ _id: pf1._id }), + expect.objectContaining({ _id: gr2._id }), + expect.objectContaining({ _id: pf2._id }), ]), }); - expect(exportWorkspacesDataJson.resources.length).toBe(8); + expect(exportWorkspacesDataJson.resources.length).toBe(12); // Test export some requests only. - const exportRequestsJson = await importUtil.exportRequestsData([r1], false, 'json'); - const exportRequestsYaml = await importUtil.exportRequestsData([r1], false, 'yaml'); + const exportRequestsJson = await importUtil.exportRequestsData([r1, gr1], false, 'json'); + const exportRequestsYaml = await importUtil.exportRequestsData([r1, gr1], false, 'yaml'); const exportRequestsDataJSON = JSON.parse(exportRequestsJson); const exportRequestsDataYAML = YAML.parse(exportRequestsYaml); @@ -250,11 +272,14 @@ describe('export', () => { expect.objectContaining({ _id: jar._id }), expect.objectContaining({ _id: r1._id }), expect.objectContaining({ _id: ePub._id }), + expect.objectContaining({ _id: gr1._id }), + expect.objectContaining({ _id: pf1._id }), + expect.objectContaining({ _id: pf2._id }), ]), }); - expect(exportRequestsDataJSON.resources.length).toBe(6); - expect(exportRequestsDataYAML.resources.length).toBe(6); + expect(exportRequestsDataJSON.resources.length).toBe(9); + expect(exportRequestsDataYAML.resources.length).toBe(9); // Ensure JSON and YAML are the same expect(exportRequestsDataJSON.resources).toEqual(exportRequestsDataYAML.resources); @@ -266,10 +291,19 @@ describe('export', () => { contents: 'openapi: "3.0.0"', }); const jar = await models.cookieJar.getOrCreateForParentId(w._id); + const pf1 = await models.protoFile.create({ + name: 'ProtoFile 1', + parentId: w._id, + }); const r1 = await models.request.create({ name: 'Request 1', parentId: w._id, }); + const gr1 = await models.grpcRequest.create({ + name: 'Grpc Request 1', + parentId: w._id, + protoFileId: pf1._id, + }); const f2 = await models.requestGroup.create({ name: 'Folder 2', parentId: w._id, @@ -278,6 +312,11 @@ describe('export', () => { name: 'Request 2', parentId: f2._id, }); + const gr2 = await models.grpcRequest.create({ + name: 'Grpc Request 2', + parentId: f2._id, + protoFileId: pf1._id, + }); const uts1 = await models.unitTestSuite.create({ name: 'Unit Test Suite One', parentId: w._id, @@ -308,8 +347,11 @@ describe('export', () => { expect.objectContaining({ _id: w._id }), expect.objectContaining({ _id: eBase._id }), expect.objectContaining({ _id: jar._id }), + expect.objectContaining({ _id: pf1._id }), expect.objectContaining({ _id: r1._id }), expect.objectContaining({ _id: r2._id }), + expect.objectContaining({ _id: gr1._id }), + expect.objectContaining({ _id: gr2._id }), expect.objectContaining({ _id: uts1._id }), expect.objectContaining({ _id: ut1._id }), expect.objectContaining({ _id: ePub._id }), diff --git a/packages/insomnia-app/app/common/import.js b/packages/insomnia-app/app/common/import.js index 018bd39ee..25b43e110 100644 --- a/packages/insomnia-app/app/common/import.js +++ b/packages/insomnia-app/app/common/import.js @@ -12,6 +12,7 @@ import fs from 'fs'; import { fnOrString, generateId } from './misc'; import YAML from 'yaml'; import { trackEvent } from './analytics'; +import { isGrpcRequest, isProtoFile, isRequest, isRequestGroup } from '../models/helpers/is-model'; const WORKSPACE_ID_KEY = '__WORKSPACE_ID__'; const BASE_ENVIRONMENT_ID_KEY = '__BASE_ENVIRONMENT_ID__'; @@ -19,6 +20,7 @@ const BASE_ENVIRONMENT_ID_KEY = '__BASE_ENVIRONMENT_ID__'; const EXPORT_FORMAT = 4; const EXPORT_TYPE_REQUEST = 'request'; +const EXPORT_TYPE_GRPC_REQUEST = 'grpc_request'; const EXPORT_TYPE_REQUEST_GROUP = 'request_group'; const EXPORT_TYPE_UNIT_TEST_SUITE = 'unit_test_suite'; const EXPORT_TYPE_UNIT_TEST = 'unit_test'; @@ -26,12 +28,14 @@ const EXPORT_TYPE_WORKSPACE = 'workspace'; const EXPORT_TYPE_COOKIE_JAR = 'cookie_jar'; const EXPORT_TYPE_ENVIRONMENT = 'environment'; const EXPORT_TYPE_API_SPEC = 'api_spec'; +const EXPORT_TYPE_PROTO_FILE = 'proto_file'; // If we come across an ID of this form, we will replace it with a new one const REPLACE_ID_REGEX = /__\w+_\d+__/g; const MODELS = { [EXPORT_TYPE_REQUEST]: models.request, + [EXPORT_TYPE_GRPC_REQUEST]: models.grpcRequest, [EXPORT_TYPE_REQUEST_GROUP]: models.requestGroup, [EXPORT_TYPE_UNIT_TEST_SUITE]: models.unitTestSuite, [EXPORT_TYPE_UNIT_TEST]: models.unitTest, @@ -39,6 +43,7 @@ const MODELS = { [EXPORT_TYPE_COOKIE_JAR]: models.cookieJar, [EXPORT_TYPE_ENVIRONMENT]: models.environment, [EXPORT_TYPE_API_SPEC]: models.apiSpec, + [EXPORT_TYPE_PROTO_FILE]: models.protoFile, }; export type ImportResult = { @@ -197,7 +202,7 @@ export async function importRaw( // Hack to switch to GraphQL based on finding `graphql` in the URL path // TODO: Support this in a better way if ( - model.type === models.request.type && + isRequest(model) && resource.body && typeof resource.body.text === 'string' && typeof resource.url === 'string' && @@ -209,7 +214,7 @@ export async function importRaw( // Try adding Content-Type JSON if no Content-Type exists if ( - model.type === models.request.type && + isRequest(model) && resource.body && typeof resource.body.text === 'string' && Array.isArray(resource.headers) && @@ -278,7 +283,7 @@ export async function exportWorkspacesHAR( includePrivateDocs: boolean = false, ): Promise { const docs: Array = await getDocWithDescendants(parentDoc, includePrivateDocs); - const requests: Array = docs.filter(doc => doc.type === models.request.type); + const requests: Array = docs.filter(isRequest); return exportRequestsHAR(requests, includePrivateDocs); } @@ -342,7 +347,7 @@ export async function exportWorkspacesData( format: 'json' | 'yaml', ): Promise { const docs: Array = await getDocWithDescendants(parentDoc, includePrivateDocs); - const requests: Array = docs.filter(doc => doc.type === models.request.type); + const requests: Array = docs.filter(doc => isRequest(doc) || isGrpcRequest(doc)); return exportRequestsData(requests, includePrivateDocs, format); } @@ -385,7 +390,8 @@ export async function exportRequestsData( d.type === models.environment.type || d.type === models.apiSpec.type || d.type === models.unitTestSuite.type || - d.type === models.unitTest.type + d.type === models.unitTest.type || + isProtoFile(d) ); }); docs.push(...descendants); @@ -398,8 +404,10 @@ export async function exportRequestsData( !( d.type === models.unitTestSuite.type || d.type === models.unitTest.type || - d.type === models.request.type || - d.type === models.requestGroup.type || + isRequest(d) || + isGrpcRequest(d) || + isRequestGroup(d) || + isProtoFile(d) || d.type === models.workspace.type || d.type === models.cookieJar.type || d.type === models.environment.type || @@ -422,10 +430,14 @@ export async function exportRequestsData( d._type = EXPORT_TYPE_UNIT_TEST_SUITE; } else if (d.type === models.unitTest.type) { d._type = EXPORT_TYPE_UNIT_TEST; - } else if (d.type === models.requestGroup.type) { + } else if (isRequestGroup(d)) { d._type = EXPORT_TYPE_REQUEST_GROUP; - } else if (d.type === models.request.type) { + } else if (isRequest(d)) { d._type = EXPORT_TYPE_REQUEST; + } else if (isGrpcRequest(d)) { + d._type = EXPORT_TYPE_GRPC_REQUEST; + } else if (isProtoFile(d)) { + d._type = EXPORT_TYPE_PROTO_FILE; } else if (d.type === models.apiSpec.type) { d._type = EXPORT_TYPE_API_SPEC; } diff --git a/packages/insomnia-app/app/models/helpers/__tests__/is-model.test.js b/packages/insomnia-app/app/models/helpers/__tests__/is-model.test.js index fb1da856a..882d5d6d1 100644 --- a/packages/insomnia-app/app/models/helpers/__tests__/is-model.test.js +++ b/packages/insomnia-app/app/models/helpers/__tests__/is-model.test.js @@ -3,6 +3,7 @@ import { difference } from 'lodash'; import { isGrpcRequest, isGrpcRequestId, + isProtoFile, isRequest, isRequestGroup, isRequestId, @@ -76,3 +77,16 @@ describe('isRequestGroup', () => { expect(isRequestGroup({ type })).toBe(false); }); }); + +describe('isProtoFile', () => { + const supported = [models.protoFile.type]; + const unsupported = difference(allTypes, supported); + + it.each(supported)('should return true: "%s"', type => { + expect(isProtoFile({ type })).toBe(true); + }); + + it.each(unsupported)('should return false: "%s"', type => { + expect(isProtoFile({ type })).toBe(false); + }); +}); diff --git a/packages/insomnia-app/app/models/helpers/is-model.js b/packages/insomnia-app/app/models/helpers/is-model.js index dfb410b33..c72f8ad31 100644 --- a/packages/insomnia-app/app/models/helpers/is-model.js +++ b/packages/insomnia-app/app/models/helpers/is-model.js @@ -1,6 +1,6 @@ // @flow import type { BaseModel } from '../index'; -import { grpcRequest, request, requestGroup } from '../index'; +import { grpcRequest, request, requestGroup, protoFile } from '../index'; export function isGrpcRequest(obj: BaseModel): boolean { return obj.type === grpcRequest.type; @@ -21,3 +21,7 @@ export function isRequestId(id: string): boolean { export function isRequestGroup(obj: BaseModel): boolean { return obj.type === requestGroup.type; } + +export function isProtoFile(obj: BaseModel): boolean { + return obj.type === protoFile.type; +} diff --git a/packages/insomnia-app/app/models/helpers/request-operations.js b/packages/insomnia-app/app/models/helpers/request-operations.js index e37db102f..aae94e455 100644 --- a/packages/insomnia-app/app/models/helpers/request-operations.js +++ b/packages/insomnia-app/app/models/helpers/request-operations.js @@ -1,6 +1,13 @@ // @flow import * as models from '../index'; -import { isGrpcRequest } from './is-model'; +import { isGrpcRequest, isGrpcRequestId } from './is-model'; +import type { GrpcRequest } from '../grpc-request'; + +export function getById(requestId: string): Promise { + return isGrpcRequestId(requestId) + ? models.grpcRequest.getById(requestId) + : models.request.getById(requestId); +} export function remove(request: T): Promise { return isGrpcRequest(request) diff --git a/packages/insomnia-app/app/ui/components/export-requests/request-row.js b/packages/insomnia-app/app/ui/components/export-requests/request-row.js index 0ffdc6bb4..705e7ef5b 100644 --- a/packages/insomnia-app/app/ui/components/export-requests/request-row.js +++ b/packages/insomnia-app/app/ui/components/export-requests/request-row.js @@ -3,11 +3,14 @@ import React, { PureComponent } from 'react'; import autobind from 'autobind-decorator'; import MethodTag from '../tags/method-tag'; import type { Request } from '../../../models/request'; +import type { GrpcRequest } from '../../../models/grpc-request'; +import { isGrpcRequest } from '../../../models/helpers/is-model'; +import GrpcTag from '../tags/grpc-tag'; type Props = { handleSetItemSelected: Function, isSelected: boolean, - request: Request, + request: Request | GrpcRequest, }; @autobind @@ -21,6 +24,8 @@ class RequestRow extends PureComponent { render() { const { request, isSelected } = this.props; + const isGrpc = isGrpcRequest(request); + return (
  • @@ -28,7 +33,7 @@ class RequestRow extends PureComponent {
    diff --git a/packages/insomnia-app/app/ui/components/export-requests/tree.js b/packages/insomnia-app/app/ui/components/export-requests/tree.js index 0e0dbf9ca..634052026 100644 --- a/packages/insomnia-app/app/ui/components/export-requests/tree.js +++ b/packages/insomnia-app/app/ui/components/export-requests/tree.js @@ -2,10 +2,11 @@ import * as React from 'react'; import RequestRow from './request-row'; import RequestGroupRow from './request-group-row'; -import * as models from '../../../models/index'; import type { Node } from '../modals/export-requests-modal'; import type { Request } from '../../../models/request'; import type { RequestGroup } from '../../../models/request-group'; +import { isGrpcRequest, isRequest } from '../../../models/helpers/is-model'; +import type { GrpcRequest } from '../../../models/grpc-request'; type Props = { root: ?Node, @@ -19,9 +20,9 @@ class Tree extends React.PureComponent { return null; } - if (node.doc.type === models.request.type) { - // Directly cast to Request will result in error, so cast it to any first. - const request: Request = ((node.doc: any): Request); + if (isRequest(node.doc) || isGrpcRequest(node.doc)) { + // Directly casting will result in error, so cast it to any first. + const request: Request | GrpcRequest = ((node.doc: any): Request | GrpcRequest); return ( , collapsed: boolean, totalRequests: number, @@ -64,7 +66,8 @@ class ExportRequestsModal extends PureComponent { } getSelectedRequestIds(node: Node): Array { - if (node.doc.type === models.request.type && node.selectedRequests === node.totalRequests) { + const docIsRequest = isRequest(node.doc) || isGrpcRequest(node.doc); + if (docIsRequest && node.selectedRequests === node.totalRequests) { return [node.doc._id]; } const requestIds: Array = []; @@ -105,7 +108,9 @@ class ExportRequestsModal extends PureComponent { let totalRequests = children .map(child => child.totalRequests) .reduce((acc, totalRequests) => acc + totalRequests, 0); - if (item.doc.type === models.request.type) { + + const docIsRequest = isRequest(item.doc) || isGrpcRequest(item.doc); + if (docIsRequest) { totalRequests++; } return { @@ -146,7 +151,7 @@ class ExportRequestsModal extends PureComponent { } setRequestGroupCollapsed(node: Node, isCollapsed: boolean, requestGroupId: string): boolean { - if (node.doc.type !== models.requestGroup.type) { + if (!isRequestGroup(node.doc)) { return false; } if (node.doc._id === requestGroupId) { diff --git a/packages/insomnia-app/app/ui/redux/modules/global.js b/packages/insomnia-app/app/ui/redux/modules/global.js index e81f655b2..58f48c4ce 100644 --- a/packages/insomnia-app/app/ui/redux/modules/global.js +++ b/packages/insomnia-app/app/ui/redux/modules/global.js @@ -13,6 +13,7 @@ import AlertModal from '../../components/modals/alert-modal'; import PaymentNotificationModal from '../../components/modals/payment-notification-modal'; import LoginModal from '../../components/modals/login-modal'; import * as models from '../../../models'; +import * as requestOperations from '../../../models/helpers/request-operations'; import SelectModal from '../../components/modals/select-modal'; import { showError, showModal } from '../../components/modals/index'; import * as db from '../../../common/database'; @@ -535,7 +536,7 @@ export function exportRequestsToFile(requestIds) { const privateEnvironments = []; const workspaceLookup = {}; for (const requestId of requestIds) { - const request = await models.request.getById(requestId); + const request = await requestOperations.getById(requestId); if (request == null) { continue; }