diff --git a/packages/insomnia/src/network/__tests__/network.test.ts b/packages/insomnia/src/network/__tests__/network.test.ts index 6dd51e7a9..03a33daff 100644 --- a/packages/insomnia/src/network/__tests__/network.test.ts +++ b/packages/insomnia/src/network/__tests__/network.test.ts @@ -208,7 +208,7 @@ describe('sendCurlAndWriteTimeline()', () => { PROXY: '', TIMEOUT_MS: 30000, URL: 'http://localhost/', - USERAGENT: `insomnia/${version}`, + USERAGENT: `insomnium/${version}`, VERBOSE: true, }, }); @@ -373,7 +373,7 @@ describe('sendCurlAndWriteTimeline()', () => { TIMEOUT_MS: 30000, UPLOAD: 1, URL: 'http://localhost/', - USERAGENT: `insomnia/${version}`, + USERAGENT: `insomnium/${version}`, VERBOSE: true, }, }); @@ -466,7 +466,7 @@ describe('sendCurlAndWriteTimeline()', () => { TIMEOUT_MS: 30000, URL: 'http://localhost/', UPLOAD: 1, - USERAGENT: `insomnia/${version}`, + USERAGENT: `insomnium/${version}`, VERBOSE: true, }, }); @@ -507,7 +507,7 @@ describe('sendCurlAndWriteTimeline()', () => { TIMEOUT_MS: 30000, URL: 'http://my/path', UNIX_SOCKET_PATH: '/my/socket', - USERAGENT: `insomnia/${version}`, + USERAGENT: `insomnium/${version}`, VERBOSE: true, }, }); @@ -547,7 +547,7 @@ describe('sendCurlAndWriteTimeline()', () => { PROXY: '', TIMEOUT_MS: 30000, URL: 'http://localhost:3000/foo/bar', - USERAGENT: `insomnia/${version}`, + USERAGENT: `insomnium/${version}`, VERBOSE: true, }, }); @@ -587,7 +587,7 @@ describe('sendCurlAndWriteTimeline()', () => { PROXY: '', TIMEOUT_MS: 30000, URL: 'http://unix:3000/my/path', - USERAGENT: `insomnia/${version}`, + USERAGENT: `insomnium/${version}`, VERBOSE: true, }, }); @@ -629,7 +629,7 @@ describe('sendCurlAndWriteTimeline()', () => { TIMEOUT_MS: 30000, NETRC: CurlNetrc.Required, URL: '', - USERAGENT: `insomnia/${version}`, + USERAGENT: `insomnium/${version}`, VERBOSE: true, }, }); diff --git a/packages/insomnia/src/network/grpc/proto-directory-loader.tsx b/packages/insomnia/src/network/grpc/proto-directory-loader.tsx deleted file mode 100644 index 587920e71..000000000 --- a/packages/insomnia/src/network/grpc/proto-directory-loader.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import fs from 'fs'; -import path from 'path'; - -import * as models from '../../models'; -import type { ProtoDirectory } from '../../models/proto-directory'; - -interface IngestResult { - createdDir?: ProtoDirectory | null; - createdIds: string[]; - error?: Error | null; -} - -export class ProtoDirectoryLoader { - createdIds: string[] = []; - rootDirPath: string; - workspaceId: string; - - constructor(rootDirPath: string, workspaceId: string) { - this.rootDirPath = rootDirPath; - this.workspaceId = workspaceId; - } - - async _parseDir(entryPath: string, parentId: string) { - const result = await this._ingest(entryPath, parentId); - return Boolean(result); - } - - async _parseFile(entryPath: string, parentId: string) { - const extension = path.extname(entryPath); - - // Ignore if not a .proto file - if (extension !== '.proto') { - return false; - } - - const contents = await fs.promises.readFile(entryPath, 'utf-8'); - const name = path.basename(entryPath); - const { _id } = await models.protoFile.create({ - name, - parentId, - protoText: contents, - }); - this.createdIds.push(_id); - return true; - } - - async _ingest(dirPath: string, parentId: string): Promise { - // Check exists - if (!fs.existsSync(dirPath)) { - return null; - } - - const newDirId = models.protoDirectory.createId(); - // Read contents - const entries = await fs.promises.readdir(dirPath, { - withFileTypes: true, - }); - // Loop and read all entries - let filesFound = false; - - for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) { - const fullEntryPath = path.resolve(dirPath, entry.name); - const result = await (entry.isDirectory() - ? this._parseDir(fullEntryPath, newDirId) - : this._parseFile(fullEntryPath, newDirId)); - filesFound = filesFound || result; - } - - // Only create the directory if a .proto file is found in the tree - if (filesFound) { - const createdProtoDir = await models.protoDirectory.create({ - _id: newDirId, - name: path.basename(dirPath), - parentId, - }); - this.createdIds.push(createdProtoDir._id); - return createdProtoDir; - } - - return null; - } - - async load() { - try { - const createdDir = await this._ingest(this.rootDirPath, this.workspaceId); - return { - createdDir, - createdIds: this.createdIds, - error: null, - } as IngestResult; - } catch (error) { - return { - createdDir: null, - createdIds: this.createdIds, - error, - } as IngestResult; - } - } -} diff --git a/packages/insomnia/src/network/grpc/proto-loader.tsx b/packages/insomnia/src/network/grpc/proto-loader.tsx new file mode 100644 index 000000000..dc2c170a2 --- /dev/null +++ b/packages/insomnia/src/network/grpc/proto-loader.tsx @@ -0,0 +1,179 @@ +import fs from "fs"; +import path from "path"; + +import * as protoLoader from "@grpc/proto-loader"; + +import * as models from "../../models"; +import { database as db } from "../../common/database"; +import { ProtoDirectory } from "../../models/proto-directory"; +import { ProtoFile } from "../../models/proto-file"; +import { writeProtoFile } from "./write-proto-file"; +import { Workspace } from "../../models/workspace"; + +export type ProtoLoadResult = { success: true; loaded: ProtoFile[]; errors: string[] } | { success: false; errors: string[] }; + +export async function addFileFromPath(filePath: string, parent: ProtoDirectory | Workspace): Promise { + // First validate the proto file + const validationResult = await validateProtoFile(filePath, parent); + + if (validationResult.success) { + // If proto file is valid, add it + const contents = await fs.promises.readFile(filePath, "utf-8"); + const newFile = await models.protoFile.create({ + name: path.basename(filePath), + parentId: parent._id, + protoText: contents, + }); + + return { success: true, loaded: [newFile], errors: [] }; + } else { + return { success: false, errors: validationResult.errors }; + } +} + +export async function updateFileFromPath(protoFile: ProtoFile, filePath: string): Promise { + // First validate the proto file + const parent = (await models.protoDirectory.getById(protoFile.parentId)) ?? (await models.workspace.getById(protoFile.parentId)); + const validationResult = await validateProtoFile(filePath, parent!); + + if (validationResult.success) { + // If file is valid, update it + const contents = await fs.promises.readFile(filePath, "utf-8"); + const contentsChanged = contents !== protoFile.protoText; + const updatedFile = await models.protoFile.update(protoFile, { + name: path.basename(filePath), + protoText: contents, + }); + // force update the proto file if the content changed + writeProtoFile(updatedFile, contentsChanged); + + return { success: true, loaded: [updatedFile], errors: [] }; + } else { + return { success: false, errors: validationResult.errors }; + } +} + +export async function addDirectoryFromPath(directoryPath: string, parent: ProtoDirectory | Workspace): Promise { + const entries = await fs.promises.readdir(directoryPath, { + withFileTypes: true, + }); + if (entries.length === 0) return { success: true, loaded: [], errors: [] }; + + const loaded: ProtoFile[] = []; + const errors: string[] = []; + + // Make temporary parent id without making the model yet + const newDirectory = await models.protoDirectory.create({ + name: path.basename(directoryPath), + parentId: parent._id, + }); + + for (const e of entries) { + const entryPath = path.join(directoryPath, e.name); + if (e.isDirectory()) { + const nestedResult = await addDirectoryFromPath(entryPath, newDirectory); + if (nestedResult.success) loaded.push(...nestedResult.loaded); + errors.push(...nestedResult.errors); + } else { + if (!e.name.endsWith(".proto")) { + continue; // Not a protobuf file, skip + } + + const addResult = await addFileFromPath(entryPath, newDirectory); + if (addResult.success) loaded.push(...addResult.loaded); + errors.push(...addResult.errors); + } + } + + if (loaded.length === 0) { + // If no files were created, remove the created directory again + models.protoDirectory.remove(newDirectory); + } + + return { success: true, loaded, errors }; +} + +export async function updateDirectoryFromPath(directoryPath: string, protoDir: ProtoDirectory): Promise { + const entries = await fs.promises.readdir(directoryPath, { + withFileTypes: true, + }); + if (entries.length === 0) return { success: true, loaded: [], errors: [] }; + + const loaded: ProtoFile[] = []; + const errors: string[] = []; + + const existingChildren = await findDirectoryChildren(protoDir); + for (const e of entries) { + const entryPath = path.join(directoryPath, e.name); + if (e.isDirectory()) { + // If the child is a known directory recurse the update into it, otherwise add it to db + const existingDirectory = existingChildren.find(c => models.protoDirectory.isProtoDirectory(c) && c.name === e.name); + if (existingDirectory) { + // Directory already known, udpate it + const updateResult = await updateDirectoryFromPath(entryPath, existingDirectory); + if (updateResult.success) loaded.push(...updateResult.loaded); + errors.push(...updateResult.errors); + } else { + // Directory unknown, add it + const addResult = await addDirectoryFromPath(entryPath, protoDir); + if (addResult.success) loaded.push(...addResult.loaded); + errors.push(...addResult.errors); + } + } else { + // If the child is a file, update if known, otherwise add it + const existingFile = existingChildren.find((c): c is ProtoFile => models.protoFile.isProtoFile(c) && c.name === e.name); + if (existingFile) { + // File already known, update it + const updateResult = await updateFileFromPath(existingFile, entryPath); + if (updateResult.success) loaded.push(...updateResult.loaded); + errors.push(...updateResult.errors); + } else { + // File is new, add it + const addResult = await addFileFromPath(entryPath, protoDir); + if (addResult.success) loaded.push(...addResult.loaded); + errors.push(...addResult.errors); + } + } + } + + return { success: true, loaded, errors }; +} + +async function findDirectoryChildren(protoDir: ProtoDirectory): Promise<(ProtoFile | ProtoDirectory)[]> { + const descendants = await db.withDescendants(protoDir); + return descendants.filter(d => (models.protoFile.isProtoFile(d) || models.protoDirectory.isProtoDirectory(d)) && d._id !== protoDir._id); +} + +async function findAncestorDirectories(filePath: string, context: ProtoDirectory | Workspace): Promise { + if (models.workspace.isWorkspace(context)) { + return [path.dirname(filePath)]; + } else { + /* Traverse up the file tree to gather all _real_ ancestor directories */ + let parent = await models.protoDirectory.getById(context._id); + let basePath = filePath; + const result = []; + while (parent !== null) { + basePath = path.dirname(basePath); + result.push(basePath); + parent = await models.protoDirectory.getById(parent.parentId); + } + return result; + } +} + +async function validateProtoFile(filePath: string, parent: ProtoDirectory | Workspace): Promise { + const includeDirs = await findAncestorDirectories(filePath, parent); + try { + await protoLoader.load(filePath, { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + includeDirs: includeDirs, + }); + return { success: true, loaded: [], errors: [] }; + } catch (e: any) { + return { success: false, errors: [`${filePath}: ${e.message}}`] }; + } +} diff --git a/packages/insomnia/src/sync/git/__tests__/git-vcs.test.ts b/packages/insomnia/src/sync/git/__tests__/git-vcs.test.ts index 97b6c4ed0..deaa34a53 100644 --- a/packages/insomnia/src/sync/git/__tests__/git-vcs.test.ts +++ b/packages/insomnia/src/sync/git/__tests__/git-vcs.test.ts @@ -112,10 +112,10 @@ describe('Git-VCS', () => { }, message: 'First commit!\n', parent: [], - tree: '14819d8019f05edb70a29850deb09a4314ad0afc', + tree: 'e2f917fbb2b671fd9a408124fde336a156f2b80c', }, - oid: '76f804a23eef9f52017bf93f4bc0bfde45ec8a93', - payload: `tree 14819d8019f05edb70a29850deb09a4314ad0afc + oid: '42c57adff2f3204f5551348414e930a44c47c88b', + payload: `tree e2f917fbb2b671fd9a408124fde336a156f2b80c author Karen Brown 1000000000 +0000 committer Karen Brown 1000000000 +0000 diff --git a/packages/insomnia/src/ui/components/dropdowns/grpc-method-dropdown/grpc-method-dropdown.tsx b/packages/insomnia/src/ui/components/dropdowns/grpc-method-dropdown/grpc-method-dropdown.tsx index 10d897399..65ec8b937 100644 --- a/packages/insomnia/src/ui/components/dropdowns/grpc-method-dropdown/grpc-method-dropdown.tsx +++ b/packages/insomnia/src/ui/components/dropdowns/grpc-method-dropdown/grpc-method-dropdown.tsx @@ -83,7 +83,7 @@ export const GrpcMethodDropdown: FunctionComponent = ({ style={{ maxWidth: '240px', display: 'flex', alignItems: 'center' }} > - {!selectedPath ? 'Select Method' : getShortGrpcPath(selectedPath)} + {selectedPath ? getShortGrpcPath(selectedPath) : methods.length > 0 ? 'Select Method' : 'No methods in proto'} diff --git a/packages/insomnia/src/ui/components/modals/proto-files-modal.tsx b/packages/insomnia/src/ui/components/modals/proto-files-modal.tsx index 58fb349cd..5d587d1b9 100644 --- a/packages/insomnia/src/ui/components/modals/proto-files-modal.tsx +++ b/packages/insomnia/src/ui/components/modals/proto-files-modal.tsx @@ -1,29 +1,29 @@ -import * as protoLoader from '@grpc/proto-loader'; -import fs from 'fs'; -import path from 'path'; -import React, { FC, useEffect, useRef, useState } from 'react'; -import { useParams } from 'react-router-dom'; +import React, { FC, useEffect, useRef, useState } from "react"; +import { useParams } from "react-router-dom"; + +import { ChangeBufferEvent, database as db } from "../../../common/database"; +import { selectFileOrFolder } from "../../../common/select-file-or-folder"; +import * as models from "../../../models"; +import { isProtoDirectory, ProtoDirectory } from "../../../models/proto-directory"; +import { isProtoFile, ProtoFile } from "../../../models/proto-file"; + +import { Modal, ModalHandle } from "../base/modal"; +import { ModalBody } from "../base/modal-body"; +import { ModalFooter } from "../base/modal-footer"; +import { ModalHeader } from "../base/modal-header"; +import { ExpandedProtoDirectory, ProtoFileList } from "../proto-file/proto-file-list"; +import { AsyncButton } from "../themed-button"; +import { showAlert, showError } from "."; +import * as protoLoader from "../../../network/grpc/proto-loader"; -import { ChangeBufferEvent, database as db } from '../../../common/database'; -import { selectFileOrFolder } from '../../../common/select-file-or-folder'; -import * as models from '../../../models'; -import { getById as getProtoDirectoryById, isProtoDirectory, ProtoDirectory } from '../../../models/proto-directory'; -import { isProtoFile, type ProtoFile } from '../../../models/proto-file'; -import { ProtoDirectoryLoader } from '../../../network/grpc/proto-directory-loader'; -import { writeProtoFile } from '../../../network/grpc/write-proto-file'; -import { Modal, type ModalHandle } from '../base/modal'; -import { ModalBody } from '../base/modal-body'; -import { ModalFooter } from '../base/modal-footer'; -import { ModalHeader } from '../base/modal-header'; -import { ExpandedProtoDirectory, ProtoFileList } from '../proto-file/proto-file-list'; -import { AsyncButton } from '../themed-button'; -import { showAlert, showError } from '.'; const tryToSelectFilePath = async () => { try { - const { filePath, canceled } = await selectFileOrFolder({ itemTypes: ['file'], extensions: ['proto'] }); + const { filePath, canceled } = await selectFileOrFolder({ + itemTypes: ["file"], + extensions: ["proto"], + }); if (!canceled && filePath) { return filePath; - } } catch (error) { showError({ error }); @@ -32,36 +32,18 @@ const tryToSelectFilePath = async () => { }; const tryToSelectFolderPath = async () => { try { - const { filePath, canceled } = await selectFileOrFolder({ itemTypes: ['directory'], extensions: ['proto'] }); + const { filePath, canceled } = await selectFileOrFolder({ + itemTypes: ["directory"], + extensions: ["proto"], + }); if (!canceled && filePath) { return filePath; - } } catch (error) { showError({ error }); } return; }; -const isProtofileValid = async (filePath: string, includeDirs: string[] = []) => { - try { - await protoLoader.load(filePath, { - keepCase: true, - longs: String, - enums: String, - defaults: true, - oneofs: true, - includeDirs: includeDirs, - }); - return true; - } catch (error) { - showError({ - title: 'Invalid Proto File', - message: `The file ${filePath} could not be parsed`, - error, - }); - return false; - } -}; const traverseDirectory = (dir: ProtoDirectory, files: ProtoFile[], directories: ProtoDirectory[]): ExpandedProtoDirectory => ({ dir, @@ -127,165 +109,123 @@ export const ProtoFilesModal: FC = ({ defaultId, onHide, onSave, reloadRe }); }, [workspaceId]); - const handleAddDirectory = async () => { - let rollback = false; - let createdIds: string[]; - const bufferId = await db.bufferChangesIndefinitely(); - const filePath = await tryToSelectFolderPath(); + const handleAddFile = async () => { + const filePath = await tryToSelectFilePath(); if (!filePath) { return; } - try { - const result = await new ProtoDirectoryLoader(filePath, workspaceId).load(); - createdIds = result.createdIds; - const { error, createdDir } = result; - if (error) { - showError({ - title: 'Failed to import', - message: `An unexpected error occurred when reading ${filePath}`, - error, - }); - rollback = true; - return; - } + const workspace = await models.workspace.getById(workspaceId); + const addResult = await protoLoader.addFileFromPath(filePath, workspace!); + if (addResult.success) { + setSelectedId(addResult.loaded[0]._id); + } - // Show warning if no files found - if (!createdDir) { - showAlert({ - title: 'No files found', - message: `No .proto files were found under ${filePath}.`, - }); - return; - } + if (addResult.errors.length > 0) { + showError({ + title: "Failed to add file", + message: `Could not add ${filePath}:\n${addResult.errors.join("\n")}`, + }); + } + }; - // Try parse all loaded proto files to make sure they are valid - const loadedEntities = await db.withDescendants(createdDir); - const loadedFiles = loadedEntities.filter(isProtoFile); + const handleAddDirectory = async () => { + const filePath = await tryToSelectFolderPath(); + if (!filePath) { + return; + } + const workspace = await models.workspace.getById(workspaceId); + const addResult = await protoLoader.addDirectoryFromPath(filePath, workspace!); - for (const protoFile of loadedFiles) { - try { - const { filePath, dirs } = await writeProtoFile(protoFile); - protoLoader.load(filePath, { - keepCase: true, - longs: String, - enums: String, - defaults: true, - oneofs: true, - includeDirs: dirs, - }); - } catch (error) { - showError({ - title: 'Invalid Proto File', - message: `The file ${protoFile.name} could not be parsed`, - error, - }); - rollback = true; - return; - } - } - } catch (error) { - rollback = true; - showError({ error }); - } finally { - // Fake flushing changes (or, rollback) only prevents change notifications being sent to the UI - // It does NOT revert changes written to the database, as is typical of a db transaction rollback - // As such, if rolling back, the created directory needs to be deleted manually - await db.flushChanges(bufferId, rollback); + if (addResult.errors.length > 0) { + showError({ + title: "Encountered some issues while adding directory", + message: `Some proto files could not be added:\n${addResult.errors.join("\n")}`, + }); + } + }; - if (rollback) { - // @ts-expect-error -- TSCONVERSION - await models.protoDirectory.batchRemoveIds(createdIds); - // @ts-expect-error -- TSCONVERSION - await models.protoFile.batchRemoveIds(createdIds); - } + const handleUpdate = async (protoFileOrDir: ProtoFile | ProtoDirectory) => { + if (isProtoFile(protoFileOrDir)) { + await handleUpdateProtoFile(protoFileOrDir); + } else { + await handleUpdateProtoDirectory(protoFileOrDir); } }; - const handleUpdate = async (protoFile: ProtoFile) => { + + const handleUpdateProtoFile = async (protoFile: ProtoFile) => { const filePath = await tryToSelectFilePath(); if (!filePath) { return; } - const includeDirs = await findAncestorDirectories(protoFile, filePath); - if (!await isProtofileValid(filePath, includeDirs)) { - return; + const updateResult = await protoLoader.updateFileFromPath(protoFile, filePath); + + if (updateResult.success) { + const updatedFile = updateResult.loaded[0]; + + const impacted = await models.grpcRequest.findByProtoFileId(updatedFile._id); + const requestIds = impacted.map(g => g._id); + if (requestIds?.length) { + requestIds.forEach(requestId => window.main.grpc.cancel(requestId)); + reloadRequests(requestIds); + } } - const contents = await fs.promises.readFile(filePath, 'utf-8'); - const contentsChanged = contents !== protoFile.protoText; - const updatedFile = await models.protoFile.update(protoFile, { - name: path.basename(filePath), - protoText: contents, - }); - // force update the proto file if the content changed - writeProtoFile(updatedFile, contentsChanged); - const impacted = await models.grpcRequest.findByProtoFileId(updatedFile._id); - const requestIds = impacted.map(g => g._id); - if (requestIds?.length) { - requestIds.forEach(requestId => window.main.grpc.cancel(requestId)); - reloadRequests(requestIds); + if (updateResult.errors.length > 0) { + showError({ + title: "Failed to update file", + message: `Could not update ${filePath}:\n${updateResult.errors.join("\n")}`, + }); } }; - const findAncestorDirectories = async (protoFile: ProtoFile, filePath: string): Promise => { - /* Traverse up the file tree to gather all _real_ ancestor directories */ - let parent = await getProtoDirectoryById(protoFile.parentId); - let basePath = path.dirname(filePath); - const result = []; - while (parent !== null) { - basePath = path.dirname(basePath); - result.push(path.join(basePath, parent.name)); - parent = await getProtoDirectoryById(parent.parentId); + const handleUpdateProtoDirectory = async (rootProtoDir: ProtoDirectory) => { + const dirPath = await tryToSelectFolderPath(); + if (!dirPath) return; + + const updateResult = await protoLoader.updateDirectoryFromPath(dirPath, rootProtoDir); + + if (updateResult.errors.length > 0) { + showError({ + title: "Encountered some issues while updating directory", + message: `Some proto files failed to update:\n${updateResult.errors.join("\n")}`, + }); } - return result; }; const handleDeleteDirectory = (protoDirectory: ProtoDirectory) => { showAlert({ title: `Delete ${protoDirectory.name}`, - message: ( - Really delete {protoDirectory.name} and all proto files contained within? - All requests that use these proto files will stop working. - ), + message: ( + + Really delete {protoDirectory.name} and all proto files contained within? All requests that use these proto files + will stop working. + + ), addCancel: true, onConfirm: async () => { models.protoDirectory.remove(protoDirectory); - setSelectedId(''); + setSelectedId(""); }, }); }; const handleDeleteFile = (protoFile: ProtoFile) => { showAlert({ title: `Delete ${protoFile.name}`, - message: ( - Really delete {protoFile.name}? All requests that use this proto file will - stop working. - ), + message: ( + + Really delete {protoFile.name}? All requests that use this proto file will stop working. + + ), addCancel: true, onConfirm: () => { models.protoFile.remove(protoFile); if (selectedId === protoFile._id) { - setSelectedId(''); + setSelectedId(""); } }, }); }; - const handleAddFile = async () => { - const filePath = await tryToSelectFilePath(); - if (!filePath) { - return; - } - if (!await isProtofileValid(filePath)) { - return; - } - const contents = await fs.promises.readFile(filePath, 'utf-8'); - const newFile = await models.protoFile.create({ - name: path.basename(filePath), - parentId: workspaceId, - protoText: contents, - }); - setSelectedId(newFile._id); - }; return ( @@ -294,17 +234,10 @@ export const ProtoFilesModal: FC = ({ defaultId, onHide, onSave, reloadRe
Files - } - > + }> Add Directory - } - > + }> Add Proto File @@ -324,7 +257,7 @@ export const ProtoFilesModal: FC = ({ defaultId, onHide, onSave, reloadRe className="btn" onClick={event => { event.preventDefault(); - if (typeof onSave === 'function' && selectedId) { + if (typeof onSave === "function" && selectedId) { onSave(selectedId); } }} @@ -334,6 +267,6 @@ export const ProtoFilesModal: FC = ({ defaultId, onHide, onSave, reloadRe
-
+ ); }; diff --git a/packages/insomnia/src/ui/components/panes/grpc-request-pane.tsx b/packages/insomnia/src/ui/components/panes/grpc-request-pane.tsx index 937e8cf48..fea6e0dfe 100644 --- a/packages/insomnia/src/ui/components/panes/grpc-request-pane.tsx +++ b/packages/insomnia/src/ui/components/panes/grpc-request-pane.tsx @@ -256,10 +256,10 @@ export const GrpcRequestPane: FunctionComponent = ({ className='btn btn--compact btn--clicky-small margin-left-sm bg-default' onClick={async () => { const requestBody = await getRenderedGrpcRequestMessage({ - request: activeRequest, - environmentId, - purpose: RENDER_PURPOSE_SEND, - }); + request: activeRequest, + environmentId, + purpose: RENDER_PURPOSE_SEND, + }); const preparedMessage = { body: requestBody, requestId, @@ -267,9 +267,9 @@ export const GrpcRequestPane: FunctionComponent = ({ window.main.grpc.sendMessage(preparedMessage); setGrpcState({ ...grpcState, requestMessages: [...requestMessages, { - id: generateId(), + id: generateId(), text: preparedMessage.body.text || '', - created: Date.now(), + created: Date.now(), }], }); }} @@ -298,17 +298,17 @@ export const GrpcRequestPane: FunctionComponent = ({ /> , ...requestMessages.sort((a, b) => a.created - b.created).map((m, index) => ( - - - - )), + + + + )), ]} @@ -341,20 +341,26 @@ export const GrpcRequestPane: FunctionComponent = ({ )} - {isProtoModalOpen && setIsProtoModalOpen(false)} - onSave={async (protoFileId: string) => { - if (activeRequest.protoFileId !== protoFileId) { - patchRequest(requestId, { protoFileId, protoMethodName: '' }); - const methods = await window.main.grpc.loadMethods(protoFileId); - setMethods(methods); + {isProtoModalOpen && ( + setIsProtoModalOpen(false)} + onSave={async (protoFileId: string) => { + if (activeRequest.protoFileId !== protoFileId) { + patchRequest(requestId, { + protoFileId, + protoMethodName: undefined, + }); + setGrpcState({ ...grpcState, method: undefined }); + const methods = await window.main.grpc.loadMethods(protoFileId); + setMethods(methods); + setIsProtoModalOpen(false); + } setIsProtoModalOpen(false); - } - setIsProtoModalOpen(false); - }} - />} + }} + /> + )} ); }; diff --git a/packages/insomnia/src/ui/components/proto-file/proto-file-list.tsx b/packages/insomnia/src/ui/components/proto-file/proto-file-list.tsx index 82b8cf6e1..40f21fd7a 100644 --- a/packages/insomnia/src/ui/components/proto-file/proto-file-list.tsx +++ b/packages/insomnia/src/ui/components/proto-file/proto-file-list.tsx @@ -9,7 +9,7 @@ import { Button } from '../themed-button'; export type SelectProtoFileHandler = (id: string) => void; export type DeleteProtoFileHandler = (protofile: ProtoFile) => void; export type DeleteProtoDirectoryHandler = (protoDirectory: ProtoDirectory) => void; -export type UpdateProtoFileHandler = (protofile: ProtoFile) => Promise; +export type UpdateProtoFileHandler = (protofile: ProtoFile | ProtoDirectory) => Promise; export type RenameProtoFileHandler = (protoFile: ProtoFile, name?: string) => Promise; export const ProtoListItem = styled(ListGroupItem).attrs(() => ({ className: 'row-spaced', @@ -54,17 +54,27 @@ const recursiveRender = (
)} + ), ...files.map(f => (