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
This commit is contained in:
Opender Singh 2020-11-14 12:31:18 +13:00 committed by GitHub
parent 8f8acf83f2
commit ccd270a9fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 118 additions and 27 deletions

View File

@ -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 }),

View File

@ -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<string> {
const docs: Array<BaseModel> = await getDocWithDescendants(parentDoc, includePrivateDocs);
const requests: Array<BaseModel> = docs.filter(doc => doc.type === models.request.type);
const requests: Array<BaseModel> = docs.filter(isRequest);
return exportRequestsHAR(requests, includePrivateDocs);
}
@ -342,7 +347,7 @@ export async function exportWorkspacesData(
format: 'json' | 'yaml',
): Promise<string> {
const docs: Array<BaseModel> = await getDocWithDescendants(parentDoc, includePrivateDocs);
const requests: Array<BaseModel> = docs.filter(doc => doc.type === models.request.type);
const requests: Array<BaseModel> = 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;
}

View File

@ -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);
});
});

View File

@ -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;
}

View File

@ -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<Request | GrpcRequest | null> {
return isGrpcRequestId(requestId)
? models.grpcRequest.getById(requestId)
: models.request.getById(requestId);
}
export function remove<T>(request: T): Promise<void> {
return isGrpcRequest(request)

View File

@ -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<Props> {
render() {
const { request, isSelected } = this.props;
const isGrpc = isGrpcRequest(request);
return (
<li className="tree__row">
<div className="tree__item tree__item--request">
@ -28,7 +33,7 @@ class RequestRow extends PureComponent<Props> {
<input type="checkbox" checked={isSelected} onChange={this.handleSelect} />
</div>
<button className="wide">
<MethodTag method={request.method} />
{isGrpc ? <GrpcTag /> : <MethodTag method={request.method} />}
<span className="inline-block">{request.name}</span>
</button>
</div>

View File

@ -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<Props> {
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 (
<RequestRow
key={node.doc._id}

View File

@ -9,9 +9,11 @@ import Tree from '../export-requests/tree';
import type { Request } from '../../../models/request';
import type { RequestGroup } from '../../../models/request-group';
import * as models from '../../../models';
import type { GrpcRequest } from '../../../models/grpc-request';
import { isGrpcRequest, isRequest, isRequestGroup } from '../../../models/helpers/is-model';
export type Node = {
doc: Request | RequestGroup,
doc: Request | GrpcRequest | RequestGroup,
children: Array<Node>,
collapsed: boolean,
totalRequests: number,
@ -64,7 +66,8 @@ class ExportRequestsModal extends PureComponent<Props, State> {
}
getSelectedRequestIds(node: Node): Array<string> {
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<string> = [];
@ -105,7 +108,9 @@ class ExportRequestsModal extends PureComponent<Props, State> {
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<Props, State> {
}
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) {

View File

@ -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;
}