feat: async parser workers (#3834)

This commit is contained in:
Anoop M D
2025-01-29 02:44:32 +05:30
parent 98f3a524dc
commit ced9d38abc
25 changed files with 365 additions and 230 deletions

View File

@@ -8,7 +8,7 @@ import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/acti
import Markdown from 'components/MarkDown';
import CodeEditor from 'components/CodeEditor';
import StyledWrapper from './StyledWrapper';
import { IconEdit, IconTrash, IconFileText } from '@tabler/icons';
import { IconEdit, IconX, IconFileText } from '@tabler/icons';
const Docs = ({ collection }) => {
const dispatch = useDispatch();
@@ -42,6 +42,7 @@ const Docs = ({ collection }) => {
const onSave = () => {
dispatch(saveCollectionRoot(collection.uid));
toggleViewMode();
}
return (
@@ -52,15 +53,20 @@ const Docs = ({ collection }) => {
Documentation
</div>
<div className='flex flex-row gap-2 items-center justify-center'>
<div className="editing-mode" role="tab" onClick={handleDiscardChanges}>
{isEditing ? <IconTrash className="cursor-pointer" size={20} strokeWidth={1.5} /> : <IconEdit className="cursor-pointer" size={20} strokeWidth={1.5} />}
</div>
{/* <div className="editing-mode" role="tab" onClick={toggleViewMode}>
<IconEdit className="cursor-pointer" size={20} strokeWidth={1.5} />
</div> */}
{/* <button type="submit" className="submit btn btn-sm btn-secondary" onClick={onSave}>
Save
</button> */}
{isEditing ? (
<>
<div className="editing-mode" role="tab" onClick={handleDiscardChanges}>
<IconX className="cursor-pointer" size={20} strokeWidth={1.5} />
</div>
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={onSave}>
Save
</button>
</>
) : (
<div className="editing-mode" role="tab" onClick={toggleViewMode}>
<IconEdit className="cursor-pointer" size={20} strokeWidth={1.5} />
</div>
)}
</div>
</div>
{isEditing ? (
@@ -75,7 +81,7 @@ const Docs = ({ collection }) => {
fontSize={get(preferences, 'font.codeFontSize')}
/>
) : (
<div className='h-full overflow-auto'>
<div className='h-full overflow-auto pl-1'>
<div className='h-[1px] min-h-[500px]'>
{
docs?.length > 0 ?

View File

@@ -1,13 +0,0 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
table {
td {
&:first-child {
width: 120px;
}
}
}
`;
export default StyledWrapper;

View File

@@ -1,5 +1,4 @@
import React from 'react';
import StyledWrapper from './StyledWrapper';
import { getTotalRequestCountInCollection } from 'utils/collections/';
import { IconFolder, IconFileOff, IconWorld, IconApi } from '@tabler/icons';
@@ -7,8 +6,8 @@ const Info = ({ collection }) => {
const totalRequestsInCollection = getTotalRequestCountInCollection(collection);
return (
<StyledWrapper className="w-full flex flex-col h-fit mt-2">
<div className="bg-white dark:bg-gray-800 rounded-lg py-6">
<div className="w-full flex flex-col h-fit">
<div className="rounded-lg py-6">
<div className="grid gap-6">
{/* Location Row */}
<div className="flex items-start">
@@ -16,8 +15,8 @@ const Info = ({ collection }) => {
<IconFolder className="w-5 h-5 text-blue-500" stroke={1.5} />
</div>
<div className="ml-4">
<div className="font-semibold text-sm text-gray-900 dark:text-gray-100">Location</div>
<div className="mt-1 text-xs text-gray-600 dark:text-gray-300 break-all">
<div className="font-semibold text-sm">Location</div>
<div className="mt-1 text-sm text-muted break-all">
{collection.pathname}
</div>
</div>
@@ -29,8 +28,8 @@ const Info = ({ collection }) => {
<IconWorld className="w-5 h-5 text-green-500" stroke={1.5} />
</div>
<div className="ml-4">
<div className="font-semibold text-sm text-gray-900 dark:text-gray-100">Environments</div>
<div className="mt-1 text-sm text-gray-600 dark:text-gray-300">
<div className="font-semibold text-sm">Environments</div>
<div className="mt-1 text-sm text-muted">
{collection.environments?.length || 0} environment{collection.environments?.length !== 1 ? 's' : ''} configured
</div>
</div>
@@ -42,15 +41,15 @@ const Info = ({ collection }) => {
<IconApi className="w-5 h-5 text-purple-500" stroke={1.5} />
</div>
<div className="ml-4">
<div className="font-semibold text-sm text-gray-900 dark:text-gray-100">Requests</div>
<div className="mt-1 text-sm text-gray-600 dark:text-gray-300">
<div className="font-semibold text-sm">Requests</div>
<div className="mt-1 text-sm text-muted">
{totalRequestsInCollection} request{totalRequestsInCollection !== 1 ? 's' : ''} in collection
</div>
</div>
</div>
</div>
</div>
</StyledWrapper>
</div>
);
};

View File

@@ -0,0 +1,25 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
&.card {
background-color: ${(props) => props.theme.requestTabPanel.card.bg};
.title {
border-top: 1px solid ${(props) => props.theme.requestTabPanel.cardTable.border};
border-left: 1px solid ${(props) => props.theme.requestTabPanel.cardTable.border};
border-right: 1px solid ${(props) => props.theme.requestTabPanel.cardTable.border};
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
.table {
thead {
background-color: ${(props) => props.theme.requestTabPanel.cardTable.table.thead.bg};
color: ${(props) => props.theme.requestTabPanel.cardTable.table.thead.color};
}
}
}
`;
export default StyledWrapper;

View File

@@ -0,0 +1,50 @@
import React from 'react';
import { flattenItems } from "utils/collections";
import { IconAlertTriangle } from '@tabler/icons';
import StyledWrapper from "./StyledWrapper";
const RequestsNotLoaded = ({ collection }) => {
const flattenedItems = flattenItems(collection.items);
const itemsFailedLoading = flattenedItems?.filter(item => item?.partial && !item?.loading);
if (!itemsFailedLoading?.length) {
return null;
}
return (
<StyledWrapper className="w-full card my-2">
<div className="flex items-center gap-2 px-3 py-2 title bg-yellow-50 dark:bg-yellow-900/20">
<IconAlertTriangle size={16} className="text-yellow-500" />
<span className="font-medium">Following requests were not loaded</span>
</div>
<table className="w-full border-collapse">
<thead>
<tr>
<th className="py-2 px-3 text-left font-medium">
Pathname
</th>
<th className="py-2 px-3 text-left font-medium">
Size
</th>
</tr>
</thead>
<tbody>
{flattenedItems?.map((item, index) => (
item?.partial && !item?.loading ? (
<tr key={index}>
<td className="py-1.5 px-3">
{item?.pathname?.split(`${collection?.pathname}/`)?.[1]}
</td>
<td className="py-1.5 px-3">
{item?.size?.toFixed?.(2)}&nbsp;MB
</td>
</tr>
) : null
))}
</tbody>
</table>
</StyledWrapper>
);
};
export default RequestsNotLoaded;

View File

@@ -1,67 +1,26 @@
import { flattenItems } from "utils/collections/index";
import StyledWrapper from "./StyledWrapper";
import Docs from "../Docs/index";
import Info from "../Info/index";
import { IconBox, IconAlertTriangle } from '@tabler/icons';
import Docs from "../Docs";
import Info from "./Info";
import { IconBox } from '@tabler/icons';
import RequestsNotLoaded from "./RequestsNotLoaded";
const Overview = ({ collection }) => {
const flattenedItems = flattenItems(collection.items);
const itemsFailedLoading = flattenedItems?.filter(item => item?.partial && !item?.loading);
return (
<StyledWrapper className="flex flex-col h-full relative py-2 gap-4">
<div className="flex flex-row grid grid-cols-5 w-full gap-8">
<div className="col-span-2 flex flex-col">
<div className="h-full">
<div className="grid grid-cols-5 gap-4 h-full">
<div className="col-span-2">
<div className="text-xl font-semibold flex items-center gap-2">
<IconBox size={24} />
<IconBox size={24} stroke={1.5} />
{collection?.name}
</div>
<Info collection={collection} />
{
itemsFailedLoading?.length ?
<div className="w-full border border-opacity-50 border-yellow-500 rounded-md">
<div className="my-2 mx-2 pb-2 font-medium">
Following requests were not loaded
</div>
<table className="w-full border-collapse mt-2">
<thead>
<tr>
<td>
<div className="ml-2">
Pathname
</div>
</td>
<td>
<div className="ml-2">
Size
</div>
</td>
</tr>
</thead>
<tbody>
{flattenedItems?.map(item => (
<>
{
item?.partial && !item?.loading ?
<tr className="">
<td className="py-2 px-2">{item?.pathname?.split(`${collection?.pathname}/`)?.[1]}</td>
<td className="py-2 px-2 text-left">{item?.size?.toFixed?.(2)}&nbsp;MB</td>
</tr>
: null
}
</>
))}
</tbody>
</table>
</div>
:
null
}
<RequestsNotLoaded collection={collection} />
</div>
<div className="col-span-3">
<Docs collection={collection} />
</div>
</div>
</StyledWrapper>
</div>
);
}

View File

@@ -12,9 +12,7 @@ import Headers from './Headers';
import Auth from './Auth';
import Script from './Script';
import Test from './Tests';
import Docs from './Docs';
import Presets from './Presets';
import Info from './Info';
import StyledWrapper from './StyledWrapper';
import Vars from './Vars/index';
import DotIcon from 'components/Icons/Dot';
@@ -132,12 +130,6 @@ const CollectionSettings = ({ collection }) => {
/>
);
}
case 'docs': {
return <Docs collection={collection} />;
}
case 'info': {
return <Info collection={collection} />;
}
}
};
@@ -184,13 +176,6 @@ const CollectionSettings = ({ collection }) => {
Client Certificates
{clientCertConfig.length > 0 && <ContentIndicator />}
</div>
{/* <div className={getTabClassname('docs')} role="tab" onClick={() => setTab('docs')}>
Docs
{hasDocs && <ContentIndicator />}
</div>
<div className={getTabClassname('info')} role="tab" onClick={() => setTab('info')}>
Info
</div> */}
</div>
<section className="mt-4 h-full">{getTabPanel(tab)}</section>
</StyledWrapper>

View File

@@ -9,7 +9,6 @@ const StyledMarkdownBodyWrapper = styled.div`
box-sizing: border-box;
height: 100%;
margin: 0 auto;
padding-top: 0.5rem;
font-size: 0.875rem;
h1 {
@@ -80,12 +79,6 @@ const StyledMarkdownBodyWrapper = styled.div`
}
}
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
`;
export default StyledMarkdownBodyWrapper;

View File

@@ -0,0 +1,19 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
div.card {
background: ${(props) => props.theme.requestTabPanel.card.bg};
border: 1px solid ${(props) => props.theme.requestTabPanel.card.border};
div.hr {
border-bottom: 1px solid ${(props) => props.theme.requestTabPanel.card.hr};
height: 1px;
}
div.border-top {
border-top: 1px solid ${(props) => props.theme.requestTabPanel.card.border};
}
}
`;
export default StyledWrapper;

View File

@@ -1,27 +1,47 @@
import { IconLoader2 } from '@tabler/icons';
import { IconLoader2, IconFile } from '@tabler/icons';
import StyledWrapper from './StyledWrapper';
const RequestIsLoading = ({ item }) => {
return <>
<div className='flex flex-col gap-6 w-fit pt-4 pb-3 px-4'>
<div className='flex flex-col gap-1'>
<div className='flex flex-row gap-1'>
<div className='opacity-70 min-w-[50px]'>Name</div>
<div>{item?.name}</div>
</div>
<div className='flex flex-row gap-1'>
<div className='opacity-70 min-w-[50px]'>Size</div>
<div>{item?.size?.toFixed?.(2)} MB</div>
</div>
<div className='flex flex-row gap-1'>
<div className='opacity-70 min-w-[50px]'>Path</div>
<div>{item?.pathname}</div>
</div>
<div className='flex flex-col gap-6 w-fit justify-start'>
<IconLoader2 className="animate-spin" size={18} strokeWidth={1.5} />
return <StyledWrapper>
<div className='flex flex-col p-4'>
<div className='card shadow-sm rounded-md p-4 w-[600px]'>
<div>
<div className='font-medium flex items-center gap-2 pb-4'>
<IconFile size={16} strokeWidth={1.5} className="text-gray-400" />
File Info
</div>
<div className='hr'/>
<div className='flex items-center mt-2'>
<span className='w-12 mr-2 text-muted'>Name:</span>
<div>
{item?.name}
</div>
</div>
<div className='flex items-center mt-1'>
<span className='w-12 mr-2 text-muted'>Path:</span>
<div className='break-all'>
{item?.pathname}
</div>
</div>
<div className='flex items-center mt-1 pb-4'>
<span className='w-12 mr-2 text-muted'>Size:</span>
<div>
{item?.size?.toFixed?.(2)} MB
</div>
</div>
<div className='hr'/>
<div className='flex items-center gap-2 mt-4'>
<IconLoader2 className="animate-spin" size={16} strokeWidth={2} />
<span>Loading...</span>
</div>
</div>
</div>
</div>
</>
</StyledWrapper>
}
export default RequestIsLoading;

View File

@@ -0,0 +1,19 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
div.card {
background: ${(props) => props.theme.requestTabPanel.card.bg};
border: 1px solid ${(props) => props.theme.requestTabPanel.card.border};
div.hr {
border-bottom: 1px solid ${(props) => props.theme.requestTabPanel.card.hr};
height: 1px;
}
div.border-top {
border-top: 1px solid ${(props) => props.theme.requestTabPanel.card.border};
}
}
`;
export default StyledWrapper;

View File

@@ -1,57 +1,89 @@
import { IconLoader2 } from '@tabler/icons';
import { loadRequest, loadRequestSync } from 'providers/ReduxStore/slices/collections/actions';
import { IconLoader2, IconFile } from '@tabler/icons';
import { loadRequest, loadRequestViaWorker } from 'providers/ReduxStore/slices/collections/actions';
import { useDispatch } from 'react-redux';
import StyledWrapper from './StyledWrapper';
const RequestNotLoaded = ({ collection, item }) => {
const dispatch = useDispatch();
const handleLoadRequestViaWorker = () => {
!item?.loading && dispatch(loadRequestViaWorker({ collectionUid: collection?.uid, pathname: item?.pathname }));
}
const handleLoadRequest = () => {
!item?.loading && dispatch(loadRequest({ collectionUid: collection?.uid, pathname: item?.pathname }));
}
const handleLoadRequestSync = () => {
!item?.loading && dispatch(loadRequestSync({ collectionUid: collection?.uid, pathname: item?.pathname }));
}
return <StyledWrapper>
<div className='flex flex-col p-4'>
<div className='card shadow-sm rounded-md p-4 w-[600px]'>
<div>
<div className='font-medium flex items-center gap-2 pb-4'>
<IconFile size={16} strokeWidth={1.5} className="text-gray-400" />
File Info
</div>
<div className='hr'/>
return <>
<div className='flex flex-col gap-6 w-fit pt-4 pb-3 px-4'>
<div className='flex flex-col gap-1'>
<div className='flex flex-row gap-1'>
<div className='opacity-70 min-w-[50px]'>Name</div>
<div>{item?.name}</div>
</div>
<div className='flex flex-row gap-1'>
<div className='opacity-70 min-w-[50px]'>Size</div>
<div>{item?.size?.toFixed?.(2)} MB</div>
</div>
<div className='flex flex-row gap-1'>
<div className='opacity-70 min-w-[50px]'>Path</div>
<div>{item?.pathname}</div>
<div className='flex items-center mt-2'>
<span className='w-12 mr-2 text-muted'>Name:</span>
<div>{item?.name}</div>
</div>
<div className='flex items-center mt-1'>
<span className='w-12 mr-2 text-muted'>Path:</span>
<div className='break-all'>{item?.pathname}</div>
</div>
<div className='flex items-center mt-1 pb-4'>
<span className='w-12 mr-2 text-muted'>Size:</span>
<div>{item?.size?.toFixed?.(2)} MB</div>
</div>
{!item?.error && (
<>
<div className='hr'/>
<div className='text-muted text-xs mt-4 mb-2'>
Due to its large size, this request wasn't loaded automatically.
</div>
<div className='flex flex-col gap-6 mt-4'>
<div className='flex flex-col'>
<button
className={`submit btn btn-sm btn-secondary w-fit h-fit flex flex-row gap-2 ${item?.loading? 'opacity-50 cursor-blocked': ''}`}
onClick={handleLoadRequest}
>
Load Request
</button>
<small className='text-muted mt-1'>
May cause the app to freeze temporarily while it runs.
</small>
</div>
<div className='flex flex-col'>
<button
className={`submit btn btn-sm btn-secondary w-fit h-fit flex flex-row gap-2 ${item?.loading? 'opacity-50 cursor-blocked': ''}`}
onClick={handleLoadRequestViaWorker}
>
Load Request in Background
</button>
<small className='text-muted mt-1'>
Runs in background.
</small>
</div>
</div>
</>
)}
{item?.loading && (
<>
<div className='hr mt-4'/>
<div className='flex items-center gap-2 mt-4'>
<IconLoader2 className="animate-spin" size={16} strokeWidth={2} />
<span>Loading...</span>
</div>
</>
)}
</div>
</div>
{!item?.error ?
<div className='flex flex-col gap-6 w-fit justify-start'>
<div className='flex flex-col'>
<button className={`submit btn btn-sm btn-secondary w-fit h-fit flex flex-row gap-2 ${item?.loading? 'opacity-50 cursor-blocked': ''}`} onClick={handleLoadRequestSync}>
{item?.loading ? `Loading Request` : `Load Request`}
{item?.loading ? <IconLoader2 className="animate-spin" size={18} strokeWidth={1.5} /> : null}
</button>
<small className='text-muted mt-1'>
May cause the app to freeze temporarily while it runs.
</small>
</div>
<div className='flex flex-col'>
<button className={`submit btn btn-sm btn-secondary w-fit h-fit flex flex-row gap-2 ${item?.loading? 'opacity-50 cursor-blocked': ''}`} onClick={handleLoadRequest}>
{item?.loading ? `Loading Request` : `Load Request in Background`}
{item?.loading ? <IconLoader2 className="animate-spin" size={18} strokeWidth={1.5} /> : null}
</button>
<small className='text-muted mt-1'>
Runs in background.
</small>
</div>
</div>
: null}
</div>
</>
</StyledWrapper>
}
export default RequestNotLoaded;

View File

@@ -45,10 +45,6 @@ const StyledWrapper = styled.div`
display: flex;
}
}
.partial-request-overlay {
background: ${(props) => props.theme.bg};
}
`;
export default StyledWrapper;

View File

@@ -9,7 +9,7 @@ import { IconRefresh, IconCircleCheck, IconCircleX, IconCheck, IconX, IconRun }
import slash from 'utils/common/slash';
import ResponsePane from './ResponsePane';
import StyledWrapper from './StyledWrapper';
import { areItemsLoading } from 'utils/collections/index';
import { areItemsLoading } from 'utils/collections';
const getRelativePath = (fullPath, pathname) => {
// convert to unix style path

View File

@@ -7,7 +7,7 @@ import { addTab } from 'providers/ReduxStore/slices/tabs';
import { runCollectionFolder } from 'providers/ReduxStore/slices/collections/actions';
import { flattenItems } from 'utils/collections';
import StyledWrapper from './StyledWrapper';
import { areItemsLoading } from 'utils/collections/index';
import { areItemsLoading } from 'utils/collections';
const RunCollectionItem = ({ collection, item, onClose }) => {
const dispatch = useDispatch();
@@ -34,6 +34,8 @@ const RunCollectionItem = ({ collection, item, onClose }) => {
const recursiveRunLength = getRequestsCount(flattenedItems);
const isFolderLoading = areItemsLoading(item);
console.log(item);
console.log(isFolderLoading);
return (
<StyledWrapper>

View File

@@ -19,8 +19,8 @@ import { isItemAFolder, isItemARequest } from 'utils/collections';
import RenameCollection from './RenameCollection';
import StyledWrapper from './StyledWrapper';
import CloneCollection from './CloneCollection/index';
import { areItemsLoading } from 'utils/collections/index';
import CloneCollection from './CloneCollection';
import { areItemsLoading } from 'utils/collections';
const Collection = ({ collection, searchText }) => {
const [showNewFolderModal, setShowNewFolderModal] = useState(false);
@@ -63,7 +63,6 @@ const Collection = ({ collection, searchText }) => {
// Check if the click came from the chevron icon
const isChevronClick = event.target.closest('svg')?.classList.contains('chevron-icon');
console.log('handleClick', collection.mountStatus);
if (collection.mountStatus === 'unmounted') {
dispatch(mountCollection({
collectionUid: collection.uid,

View File

@@ -1193,6 +1193,13 @@ export const hydrateCollectionWithUiStateSnapshot = (payload) => (dispatch, getS
});
};
export const loadRequestViaWorker = ({ collectionUid, pathname }) => (dispatch, getState) => {
return new Promise(async (resolve, reject) => {
const { ipcRenderer } = window;
ipcRenderer.invoke('renderer:load-request-via-worker', { collectionUid, pathname }).then(resolve).catch(reject);
});
};
export const loadRequest = ({ collectionUid, pathname }) => (dispatch, getState) => {
return new Promise(async (resolve, reject) => {
const { ipcRenderer } = window;
@@ -1200,13 +1207,6 @@ export const loadRequest = ({ collectionUid, pathname }) => (dispatch, getState)
});
};
export const loadRequestSync = ({ collectionUid, pathname }) => (dispatch, getState) => {
return new Promise(async (resolve, reject) => {
const { ipcRenderer } = window;
ipcRenderer.invoke('renderer:load-request-sync', { collectionUid, pathname }).then(resolve).catch(reject);
});
};
export const mountCollection = ({ collectionUid, collectionPathname, brunoConfig }) => (dispatch, getState) => {
dispatch(updateCollectionMountStatus({ collectionUid, mountStatus: 'mounting' }));
return new Promise(async (resolve, reject) => {

View File

@@ -114,7 +114,25 @@ const darkTheme = {
responseStatus: '#ccc',
responseOk: '#8cd656',
responseError: '#f06f57',
responseOverlayBg: 'rgba(30, 30, 30, 0.6)'
responseOverlayBg: 'rgba(30, 30, 30, 0.6)',
card: {
bg: '#252526',
border: 'transparent',
borderDark: '#8cd656',
hr: '#424242'
},
cardTable: {
border: '#333',
bg: '#252526',
table: {
thead: {
bg: '#3D3D3D',
color: '#ccc'
}
}
}
},
collection: {

View File

@@ -114,7 +114,22 @@ const lightTheme = {
responseStatus: 'rgb(117 117 117)',
responseOk: '#047857',
responseError: 'rgb(185, 28, 28)',
responseOverlayBg: 'rgba(255, 255, 255, 0.6)'
responseOverlayBg: 'rgba(255, 255, 255, 0.6)',
card: {
bg: '#fff',
border: '#f4f4f4',
hr: '#f4f4f4'
},
cardTable: {
border: '#efefef',
bg: '#fff',
table: {
thead: {
bg: 'rgb(249, 250, 251)',
color: 'rgb(75 85 99)'
}
}
}
},
collection: {

View File

@@ -2,7 +2,7 @@ const fs = require('fs');
const path = require('path');
const { dialog, ipcMain } = require('electron');
const Yup = require('yup');
const { isDirectory, normalizeAndResolvePath, addCollectionStatsToBrunoConfig } = require('../utils/filesystem');
const { isDirectory, normalizeAndResolvePath, getCollectionStats } = require('../utils/filesystem');
const { generateUidBasedOnHash } = require('../utils/common');
// todo: bruno.json config schema validation errors must be propagated to the UI
@@ -69,7 +69,10 @@ const openCollection = async (win, watcher, collectionPath, options = {}) => {
// this is to maintain backwards compatibility with older collections
brunoConfig.ignore = ['node_modules', '.git'];
}
brunoConfig = await addCollectionStatsToBrunoConfig({ brunoConfig, collectionPath });
const { size, filesCount } = await getCollectionStats(collectionPath);
brunoConfig.size = size;
brunoConfig.filesCount = filesCount;
win.webContents.send('main:collection-opened', collectionPath, uid, brunoConfig);
ipcMain.emit('main:collection-opened', win, collectionPath, uid, brunoConfig);

View File

@@ -13,7 +13,7 @@ const { setDotEnvVars } = require('../store/process-env');
const { setBrunoConfig } = require('../store/bruno-config');
const EnvironmentSecretsStore = require('../store/env-secrets');
const UiStateSnapshot = require('../store/ui-state-snapshot');
const { getBruFileMeta, hydrateRequestWithUuid } = require('../utils/collection');
const { parseBruFileMeta, hydrateRequestWithUuid } = require('../utils/collection');
const MAX_FILE_SIZE = 2.5 * 1024 * 1024;
@@ -166,7 +166,7 @@ const add = async (win, pathname, collectionUid, collectionPath, useWorkerThread
if (isBrunoConfigFile(pathname, collectionPath)) {
try {
const content = fs.readFileSync(pathname, 'utf8');
let brunoConfig = JSON.parse(content);
const brunoConfig = JSON.parse(content);
setBrunoConfig(collectionUid, brunoConfig);
} catch (err) {
@@ -279,21 +279,23 @@ const add = async (win, pathname, collectionUid, collectionPath, useWorkerThread
type: 'http-request'
};
const metaJson = await bruToJson(getBruFileMeta(bruContent), true);
const metaJson = await bruToJson(parseBruFileMeta(bruContent), true);
file.data = metaJson;
file.partial = true;
file.loading = false;
file.size = sizeInMB(fileStats?.size);
hydrateRequestWithUuid(file.data, pathname);
win.webContents.send('main:collection-tree-updated', 'addFile', file);
// If the file is smaller than the max file size, we can parse the file
// and send the full file info to the UI
if (fileStats.size < MAX_FILE_SIZE) {
// This is to update the loading indicator in the UI
file.data = metaJson;
file.partial = false;
file.loading = true;
hydrateRequestWithUuid(file.data, pathname);
win.webContents.send('main:collection-tree-updated', 'addFile', file);
// This is to update the file info in the UI
file.data = await bruToJsonViaWorker(bruContent);
file.partial = false;
file.loading = false;

View File

@@ -25,8 +25,7 @@ const {
isValidFilename,
hasSubDirectories,
getCollectionStats,
sizeInMB,
addCollectionStatsToBrunoConfig
sizeInMB
} = require('../utils/filesystem');
const { openCollectionDialog } = require('../app/collections');
const { generateUidBasedOnHash, stringifyJson, safeParseJSON, safeStringifyJSON } = require('../utils/common');
@@ -35,7 +34,7 @@ const { deleteCookiesForDomain, getDomainsWithCookies } = require('../utils/cook
const EnvironmentSecretsStore = require('../store/env-secrets');
const CollectionSecurityStore = require('../store/collection-security');
const UiStateSnapshotStore = require('../store/ui-state-snapshot');
const { getBruFileMeta, hydrateRequestWithUuid } = require('../utils/collection');
const { parseBruFileMeta, hydrateRequestWithUuid } = require('../utils/collection');
const environmentSecretsStore = new EnvironmentSecretsStore();
const collectionSecurityStore = new CollectionSecurityStore();
@@ -97,7 +96,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
}
const uid = generateUidBasedOnHash(dirPath);
let brunoConfig = {
const brunoConfig = {
version: '1',
name: collectionName,
type: 'collection',
@@ -106,7 +105,9 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
const content = await stringifyJson(brunoConfig);
await writeFile(path.join(dirPath, 'bruno.json'), content);
brunoConfig = await addCollectionStatsToBrunoConfig({ brunoConfig, collectionPath: dirPath });
const { size, filesCount } = await getCollectionStats(dirPath);
brunoConfig.size = size;
brunoConfig.filesCount = filesCount;
mainWindow.webContents.send('main:collection-opened', dirPath, uid, brunoConfig);
ipcMain.emit('main:collection-opened', mainWindow, dirPath, uid, brunoConfig);
@@ -158,7 +159,9 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
fs.copyFileSync(sourceFilePath, newFilePath);
}
brunoConfig = await addCollectionStatsToBrunoConfig({ brunoConfig, collectionPath: dirPath });
const { size, filesCount } = await getCollectionStats(dirPath);
brunoConfig.size = size;
brunoConfig.filesCount = filesCount;
mainWindow.webContents.send('main:collection-opened', dirPath, uid, brunoConfig);
ipcMain.emit('main:collection-opened', mainWindow, dirPath, uid);
@@ -601,7 +604,9 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
const collectionContent = await jsonToCollectionBru(collection.root);
await writeFile(path.join(collectionPath, 'collection.bru'), collectionContent);
brunoConfig = await addCollectionStatsToBrunoConfig({ brunoConfig, collectionPath });
const { size, filesCount } = await getCollectionStats(collectionPath);
brunoConfig.size = size;
brunoConfig.filesCount = filesCount;
mainWindow.webContents.send('main:collection-opened', collectionPath, uid, brunoConfig);
ipcMain.emit('main:collection-opened', mainWindow, collectionPath, uid, brunoConfig);
@@ -792,7 +797,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
}
});
ipcMain.handle('renderer:load-request', async (event, { collectionUid, pathname }) => {
ipcMain.handle('renderer:load-request-via-worker', async (event, { collectionUid, pathname }) => {
let fileStats;
try {
fileStats = fs.statSync(pathname);
@@ -805,7 +810,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
}
};
let bruContent = fs.readFileSync(pathname, 'utf8');
const metaJson = await bruToJson(getBruFileMeta(bruContent), true);
const metaJson = await bruToJson(parseBruFileMeta(bruContent), true);
file.data = metaJson;
file.loading = true;
file.partial = true;
@@ -829,7 +834,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
}
};
let bruContent = fs.readFileSync(pathname, 'utf8');
const metaJson = await bruToJson(getBruFileMeta(bruContent), true);
const metaJson = await bruToJson(parseBruFileMeta(bruContent), true);
file.data = metaJson;
file.partial = true;
file.loading = false;
@@ -841,7 +846,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
}
});
ipcMain.handle('renderer:load-request-sync', async (event, { collectionUid, pathname }) => {
ipcMain.handle('renderer:load-request', async (event, { collectionUid, pathname }) => {
let fileStats;
try {
fileStats = fs.statSync(pathname);
@@ -854,7 +859,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
}
};
let bruContent = fs.readFileSync(pathname, 'utf8');
const metaJson = await bruToJson(getBruFileMeta(bruContent), true);
const metaJson = await bruToJson(parseBruFileMeta(bruContent), true);
file.data = metaJson;
file.loading = true;
file.partial = true;
@@ -878,7 +883,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
}
};
let bruContent = fs.readFileSync(pathname, 'utf8');
const metaJson = await bruToJson(getBruFileMeta(bruContent), true);
const metaJson = await bruToJson(parseBruFileMeta(bruContent), true);
file.data = metaJson;
file.partial = true;
file.loading = false;
@@ -891,8 +896,17 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
});
ipcMain.handle('renderer:mount-collection', async (event, { collectionUid, collectionPathname, brunoConfig }) => {
const { size: collectionSize, filesCount: collectionBruFilesCount, maxFileSize: maxSingleBruFileSize } = await getCollectionStats(collectionPathname);
const shouldLoadCollectionAsync = (collectionSize > MAX_COLLECTION_SIZE_IN_MB) || (collectionBruFilesCount > MAX_COLLECTION_FILES_COUNT) || (maxSingleBruFileSize > MAX_SINGLE_FILE_SIZE_IN_COLLECTION_IN_MB);
const {
size,
filesCount,
maxFileSize
} = await getCollectionStats(collectionPathname);
const shouldLoadCollectionAsync =
(size > MAX_COLLECTION_SIZE_IN_MB) ||
(filesCount > MAX_COLLECTION_FILES_COUNT) ||
(maxFileSize > MAX_SINGLE_FILE_SIZE_IN_COLLECTION_IN_MB);
watcher.addWatcher(mainWindow, collectionPathname, collectionUid, brunoConfig, false, shouldLoadCollectionAsync);
});
};

View File

@@ -207,7 +207,7 @@ const getTreePathFromCollectionToItem = (collection, _item) => {
return path;
};
const getBruFileMeta = (data) => {
const parseBruFileMeta = (data) => {
try {
const metaRegex = /meta\s*{\s*([\s\S]*?)\s*}/;
const match = data?.match?.(metaRegex);
@@ -282,6 +282,6 @@ module.exports = {
findItemByPathname,
findItemInCollectionByPathname,
findParentItemInCollection,
getBruFileMeta,
parseBruFileMeta,
hydrateRequestWithUuid
};

View File

@@ -255,13 +255,6 @@ const sizeInMB = (size) => {
return size / (1024 * 1024);
}
const addCollectionStatsToBrunoConfig = async ({ brunoConfig, collectionPath }) => {
const { size: collectionSize, filesCount: collectionBruFilesCount } = await getCollectionStats(collectionPath);
brunoConfig.size = collectionSize;
brunoConfig.filesCount = collectionBruFilesCount;
return brunoConfig;
}
module.exports = {
isValidPathname,
exists,
@@ -288,6 +281,5 @@ module.exports = {
isValidFilename,
hasSubDirectories,
getCollectionStats,
sizeInMB,
addCollectionStatsToBrunoConfig
sizeInMB
};

View File

@@ -1,6 +1,6 @@
const { getBruFileMeta } = require("../../src/utils/collection");
const { parseBruFileMeta } = require("../../src/utils/collection");
describe('getBruFileMeta', () => {
describe('parseBruFileMeta', () => {
test('parses valid meta block correctly', () => {
const data = `meta {
name: 0.2_mb
@@ -8,7 +8,7 @@ describe('getBruFileMeta', () => {
seq: 1
}`;
const result = getBruFileMeta(data);
const result = parseBruFileMeta(data);
expect(result).toEqual({
meta: {
@@ -24,7 +24,7 @@ describe('getBruFileMeta', () => {
key: value
}`;
const result = getBruFileMeta(data);
const result = parseBruFileMeta(data);
expect(result).toBeUndefined();
});
@@ -32,7 +32,7 @@ describe('getBruFileMeta', () => {
test('handles empty meta block gracefully', () => {
const data = `meta {}`;
const result = getBruFileMeta(data);
const result = parseBruFileMeta(data);
expect(result).toEqual({ meta: {} });
});
@@ -44,7 +44,7 @@ describe('getBruFileMeta', () => {
seq: 1
}`;
const result = getBruFileMeta(data);
const result = parseBruFileMeta(data);
expect(result).toEqual({
meta: {
@@ -57,7 +57,7 @@ describe('getBruFileMeta', () => {
test('handles unexpected input gracefully', () => {
const data = null;
const result = getBruFileMeta(data);
const result = parseBruFileMeta(data);
expect(result).toBeUndefined();
});
@@ -68,7 +68,7 @@ describe('getBruFileMeta', () => {
seq: 1
}`;
const result = getBruFileMeta(data);
const result = parseBruFileMeta(data);
expect(result).toEqual({
meta: {
@@ -84,7 +84,7 @@ describe('getBruFileMeta', () => {
strValue: some_text
}`;
const result = getBruFileMeta(data);
const result = parseBruFileMeta(data);
expect(result).toEqual({
meta: {
@@ -102,7 +102,7 @@ describe('getBruFileMeta', () => {
seq: 1
}`;
const result = getBruFileMeta(data);
const result = parseBruFileMeta(data);
expect(result).toBeUndefined();
});
@@ -114,7 +114,7 @@ describe('getBruFileMeta', () => {
seq: 1
`;
const result = getBruFileMeta(data);
const result = parseBruFileMeta(data);
expect(result).toBeUndefined();
});