mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-22 04:05:42 +00:00
feat: async parser workers (#3834)
This commit is contained in:
@@ -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 ?
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
table {
|
||||
td {
|
||||
&:first-child {
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
@@ -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)} MB
|
||||
</td>
|
||||
</tr>
|
||||
) : null
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default RequestsNotLoaded;
|
||||
@@ -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)} MB</td>
|
||||
</tr>
|
||||
: null
|
||||
}
|
||||
</>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
<RequestsNotLoaded collection={collection} />
|
||||
</div>
|
||||
<div className="col-span-3">
|
||||
<Docs collection={collection} />
|
||||
</div>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -45,10 +45,6 @@ const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.partial-request-overlay {
|
||||
background: ${(props) => props.theme.bg};
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user