mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-07 14:08:38 +00:00
* #1884 - Add support for text/event-stream content-type * #1884 - Fix bugs with streaming Fix bug when streaming response is not ok Fix bug when clearing response of streaming request Show text signaling that the response is being streamed in the reponse status Update response size when new data is streamed in * #1884 - Fix multiple requests when spamming send button * #1884 - Add time counter for streamed response and fix final time * #1884 - Run post script only at end of streamed request * #1884 - add support for automatic "upgrade" to streaming data * #1884 - adjustments for stopwatch in stream implementation and remove unused imports * #1884 - fix imports indentation in useIpcEvents.js * #1884 - remove stream data ended export function from collections --------- Co-authored-by: Siddharth Gelera <ahoy@barelyhuman.dev>
This commit is contained in:
@@ -5,7 +5,7 @@ import { requestUrlChanged, updateRequestMethod } from 'providers/ReduxStore/sli
|
||||
import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import HttpMethodSelector from './HttpMethodSelector';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { IconDeviceFloppy, IconArrowRight, IconCode } from '@tabler/icons';
|
||||
import { IconDeviceFloppy, IconArrowRight, IconCode, IconX } from '@tabler/icons';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import { isMacOS } from 'utils/common/platform';
|
||||
import { hasRequestChanges } from 'utils/collections';
|
||||
@@ -87,7 +87,7 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
||||
<div className="flex items-center justify-center h-full w-16">
|
||||
<span className="text-xs text-indigo-500 font-bold">gRPC</span>
|
||||
</div>
|
||||
|
||||
|
||||
) : (
|
||||
<HttpMethodSelector method={method} onMethodSelect={onMethodSelect} />
|
||||
)}
|
||||
@@ -149,7 +149,13 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
||||
Save <span className="shortcut">({saveShortcut})</span>
|
||||
</span>
|
||||
</div>
|
||||
<IconArrowRight color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={22} data-testid="send-arrow-icon" />
|
||||
{
|
||||
item.response?.hasStreamRunning ? (
|
||||
<IconX color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={22} />
|
||||
) : (
|
||||
<IconArrowRight color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={22} data-testid="send-arrow-icon" />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{generateCodeItemModalOpen && (
|
||||
|
||||
@@ -10,7 +10,7 @@ import GrpcResponsePane from 'components/ResponsePane/GrpcResponsePane';
|
||||
import Welcome from 'components/Welcome';
|
||||
import { findItemInCollection } from 'utils/collections';
|
||||
import { updateRequestPaneTabWidth } from 'providers/ReduxStore/slices/tabs';
|
||||
import { sendRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { cancelRequest, sendRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import RequestNotFound from './RequestNotFound';
|
||||
import QueryUrl from 'components/RequestPane/QueryUrl/index';
|
||||
import GrpcQueryUrl from 'components/RequestPane/GrpcQueryUrl/index';
|
||||
@@ -74,8 +74,7 @@ const RequestTabPanel = () => {
|
||||
const screenWidth = useSelector((state) => state.app.screenWidth);
|
||||
let asideWidth = useSelector((state) => state.app.leftSidebarWidth);
|
||||
const [leftPaneWidth, setLeftPaneWidth] = useState(
|
||||
focusedTab && focusedTab.requestPaneWidth ? focusedTab.requestPaneWidth : (screenWidth - asideWidth) / 2.2
|
||||
); // 2.2 is intentional to make both panes appear to be of equal width
|
||||
focusedTab && focusedTab.requestPaneWidth ? focusedTab.requestPaneWidth : (screenWidth - asideWidth) / 2.2); // 2.2 is intentional to make both panes appear to be of equal width
|
||||
const [topPaneHeight, setTopPaneHeight] = useState(focusedTab?.requestPaneHeight || MIN_TOP_PANE_HEIGHT);
|
||||
const [dragging, setDragging] = useState(false);
|
||||
const dragOffset = useRef({ x: 0, y: 0 });
|
||||
@@ -141,12 +140,10 @@ const RequestTabPanel = () => {
|
||||
setDragging(false);
|
||||
if (!isVerticalLayout) {
|
||||
const mainRect = mainSectionRef.current.getBoundingClientRect();
|
||||
dispatch(
|
||||
updateRequestPaneTabWidth({
|
||||
uid: activeTabUid,
|
||||
requestPaneWidth: e.clientX - mainRect.left
|
||||
})
|
||||
);
|
||||
dispatch(updateRequestPaneTabWidth({
|
||||
uid: activeTabUid,
|
||||
requestPaneWidth: e.clientX - mainRect.left
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -263,11 +260,17 @@ const RequestTabPanel = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(sendRequest(item, collection.uid)).catch((err) =>
|
||||
toast.custom((t) => <NetworkError onClose={() => toast.dismiss(t.id)} />, {
|
||||
duration: 5000
|
||||
})
|
||||
);
|
||||
if (item.response?.hasStreamRunning) {
|
||||
dispatch(cancelRequest(item.cancelTokenUid, item, collection)).catch((err) =>
|
||||
toast.custom((t) => <NetworkError onClose={() => toast.dismiss(t.id)} />, {
|
||||
duration: 5000
|
||||
}));
|
||||
} else if (item.requestState !== 'sending' && item.requestState !== 'queued') {
|
||||
dispatch(sendRequest(item, collection.uid)).catch((err) =>
|
||||
toast.custom((t) => <NetworkError onClose={() => toast.dismiss(t.id)} />, {
|
||||
duration: 5000
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: reaper, improve selection of panes
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: ${(props) => props.theme.requestTabPanel.responseStatus};
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@@ -0,0 +1,27 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const ResponseStopWatch = ({ startMillis }) => {
|
||||
const [milliseconds, setMilliseconds] = useState(startMillis);
|
||||
|
||||
const tickInterval = 100;
|
||||
const tick = () => {
|
||||
setMilliseconds(_milliseconds => _milliseconds + tickInterval);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let timerID = setInterval(() => {
|
||||
tick()
|
||||
}, tickInterval);
|
||||
return () => {
|
||||
clearTimeout(timerID);
|
||||
};
|
||||
}, []);
|
||||
|
||||
let seconds = milliseconds / 1000;
|
||||
let secondsFormatted = `${seconds.toFixed(1)}s`;
|
||||
let width = secondsFormatted.length * 0.4; // Calculate width manually to stop parent layout from "flickering" by changing width too fast
|
||||
return <StyledWrapper className="ml-4" style={{width: `${width}rem`}}>{secondsFormatted}</StyledWrapper>;
|
||||
};
|
||||
|
||||
export default React.memo(ResponseStopWatch);
|
||||
@@ -4,7 +4,7 @@ import statusCodePhraseMap from './get-status-code-phrase';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
// Todo: text-error class is not getting pulled in for 500 errors
|
||||
const StatusCode = ({ status, statusText }) => {
|
||||
const StatusCode = ({ status, statusText, isStreaming }) => {
|
||||
const getTabClassname = (status) => {
|
||||
return classnames('ml-2', {
|
||||
'text-ok': status >= 100 && status < 200,
|
||||
@@ -17,7 +17,7 @@ const StatusCode = ({ status, statusText }) => {
|
||||
|
||||
return (
|
||||
<StyledWrapper className={`response-status-code ${getTabClassname(status)}`} data-testid="response-status-code">
|
||||
{status} {statusText || statusCodePhraseMap[status]}
|
||||
{status} {statusText || statusCodePhraseMap[status]} {isStreaming ? ' - STREAMING' : null}
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -21,6 +21,8 @@ import ResponseClear from 'src/components/ResponsePane/ResponseClear';
|
||||
import ResponseBookmark from 'src/components/ResponsePane/ResponseBookmark';
|
||||
import SkippedRequest from './SkippedRequest';
|
||||
import ClearTimeline from './ClearTimeline/index';
|
||||
import StopWatch from 'components/StopWatch';
|
||||
import ResponseStopWatch from 'components/ResponsePane/ResponseStopWatch';
|
||||
import ResponseLayoutToggle from './ResponseLayoutToggle';
|
||||
import HeightBoundContainer from 'ui/HeightBoundContainer';
|
||||
|
||||
@@ -87,15 +89,17 @@ const ResponsePane = ({ item, collection }) => {
|
||||
return <ResponseHeaders headers={response.headers} />;
|
||||
}
|
||||
case 'timeline': {
|
||||
return <Timeline collection={collection} item={item} />;
|
||||
return <Timeline collection={collection} item={item} />;
|
||||
}
|
||||
case 'tests': {
|
||||
return <TestResults
|
||||
results={item.testResults}
|
||||
assertionResults={item.assertionResults}
|
||||
preRequestTestResults={item.preRequestTestResults}
|
||||
postResponseTestResults={item.postResponseTestResults}
|
||||
/>;
|
||||
return (
|
||||
<TestResults
|
||||
results={item.testResults}
|
||||
assertionResults={item.assertionResults}
|
||||
preRequestTestResults={item.preRequestTestResults}
|
||||
postResponseTestResults={item.postResponseTestResults}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
default: {
|
||||
@@ -184,7 +188,10 @@ const ResponsePane = ({ item, collection }) => {
|
||||
<ResponseClear item={item} collection={collection} />
|
||||
<ResponseSave item={item} />
|
||||
<ResponseBookmark item={item} collection={collection} responseSize={responseSize} />
|
||||
<StatusCode status={response.status} />
|
||||
<StatusCode status={response.status} isStreaming={item.response?.hasStreamRunning} />
|
||||
{item.response?.hasStreamRunning ? (
|
||||
<ResponseStopWatch startMillis={response.duration} />
|
||||
) : <ResponseTime duration={response.duration} />}
|
||||
<ResponseTime duration={response.duration} />
|
||||
<ResponseSize size={responseSize} />
|
||||
</>
|
||||
@@ -193,7 +200,7 @@ const ResponsePane = ({ item, collection }) => {
|
||||
) : null}
|
||||
</div>
|
||||
<section
|
||||
className={`flex flex-col min-h-0 relative px-4 auto overflow-auto`}
|
||||
className="flex flex-col min-h-0 relative px-4 auto overflow-auto"
|
||||
style={{
|
||||
flex: '1 1 0',
|
||||
height: hasScriptError && showScriptErrorCard ? 'auto' : '100%'
|
||||
@@ -206,9 +213,9 @@ const ResponsePane = ({ item, collection }) => {
|
||||
onClose={() => setShowScriptErrorCard(false)}
|
||||
/>
|
||||
)}
|
||||
<div className='flex-1 overflow-y-auto'>
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{!item?.response ? (
|
||||
focusedTab?.responsePaneTab === "timeline" && requestTimeline?.length ? (
|
||||
focusedTab?.responsePaneTab === 'timeline' && requestTimeline?.length ? (
|
||||
<Timeline
|
||||
collection={collection}
|
||||
item={item}
|
||||
|
||||
Reference in New Issue
Block a user