Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/bruno-app/src/components/AppView/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ const AppView = ({ item, collection, code }) => {
statusText: result.statusText,
headers: result.headers,
data: result.data,
dataBuffer: result.dataBuffer,
bodyRef: result.bodyRef || null,
size: result.size,
duration: result.duration,
timeline: serializeTimeline(result.timeline)
Expand Down
2 changes: 1 addition & 1 deletion packages/bruno-app/src/components/CollectionApp/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ const CollectionApp = ({ item, collection }) => {
statusText: result.statusText,
headers: result.headers,
data: result.data,
dataBuffer: result.dataBuffer,
bodyRef: result.bodyRef || null,
size: result.size,
duration: result.duration,
timeline: serializeTimeline(result.timeline)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { useDispatch } from 'react-redux';
import StyledWrapper from './StyledWrapper';
import { clearRequestTimeline } from 'providers/ReduxStore/slices/collections/index';
import { clearRequestTimeline } from 'providers/ReduxStore/slices/collections';

const ClearTimeline = ({ collection, item }) => {
const dispatch = useDispatch();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,53 @@ import get from 'lodash/get';
import StyledWrapper from './StyledWrapper';
import { formatSize } from 'utils/common/index';
import Button from 'ui/Button/index';
import { getResponseBodyClient } from 'utils/response-body';

const LargeResponseWarning = ({ item, responseSize, onRevealResponse }) => {
/** Show inline below this size; warning UI above (bytes). */
export const SHOW_INLINE_BYTES = 10 * 1024 * 1024;

/** View-from-disk allowed at or below this; Download only above (bytes). */
export const VIEW_MAX_BYTES = 50 * 1024 * 1024;

const LargeResponseWarning = ({
item,
responseSize,
onRevealResponse,
canView = true,
revealLoading = false
}) => {
const { ipcRenderer } = window;
const response = item.response || {};
const canDownload = Boolean(response.bodyRef) && !response.stream?.running;
const canCopy = response.data != null;

const downloadResponseToFile = () => {
if (!canDownload) return;
return new Promise((resolve, reject) => {
ipcRenderer
.invoke('renderer:save-response-to-file', response, item.requestSent.url, item.pathname)
const savePromise = response.bodyRef
? getResponseBodyClient().save(response.bodyRef, {
url: item?.requestSent?.url,
pathname: item.pathname,
headers: response.headers
})
: ipcRenderer.invoke('renderer:save-response-to-file', response, item.requestSent.url, item.pathname);

savePromise
.then((result) => {
if (result && result.success) {
toast.success('Response downloaded to file');
}
resolve();
})
.catch((err) => {
toast.error(get(err, 'error.message') || 'Something went wrong!');
toast.error(get(err, 'error.message') || get(err, 'message') || 'Something went wrong!');
reject(err);
});
});
};

const copyResponse = () => {
if (!canCopy) return;
try {
const textToCopy = typeof response.data === 'string'
? response.data
Expand All @@ -54,9 +78,15 @@ const LargeResponseWarning = ({ item, responseSize, onRevealResponse }) => {
Large Response Warning
</div>
<div className="warning-description">
Handling responses over <span className="size-highlight supported-size">{formatSize(10 * 1024 * 1024)}</span> could degrade performance.
Handling responses over <span className="size-highlight supported-size">{formatSize(SHOW_INLINE_BYTES)}</span> could degrade performance.
<br />
Size of current response: <span className="size-highlight current-size">{formatSize(responseSize)}</span>
{!canView ? (
<>
<br />
Responses over <span className="size-highlight supported-size">{formatSize(VIEW_MAX_BYTES)}</span> can only be downloaded.
</>
) : null}
</div>
</div>
</div>
Expand All @@ -65,28 +95,30 @@ const LargeResponseWarning = ({ item, responseSize, onRevealResponse }) => {
icon={<IconEye size={18} strokeWidth={1.5} />}
iconPosition="left"
onClick={onRevealResponse}
title="Show response content"
disabled={!canView || revealLoading}
title={canView ? 'Show response content' : 'Response is too large to view in-app'}
color="secondary"
size="sm"
>
View
{revealLoading ? 'Loading…' : 'View'}
</Button>
<Button
icon={<IconDownload size={18} strokeWidth={1.5} />}
iconPosition="left"
onClick={downloadResponseToFile}
disabled={!response.dataBuffer}
disabled={!canDownload}
title="Download response to file"
color="secondary"
size="sm"
data-testid="large-response-download-btn"
>
Download
</Button>
<Button
icon={<IconCopy size={18} strokeWidth={1.5} />}
iconPosition="left"
onClick={copyResponse}
disabled={!response.data}
disabled={!canCopy}
title="Copy response to clipboard"
color="secondary"
size="sm"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,23 @@ const QueryResultFilter = ({ filter, filterExpanded, onChange, onExpandChange, m
};

const infotipText = useMemo(() => {
if (mode.includes('json')) {
if (mode?.includes('json')) {
return 'Filter with JSONPath';
}

if (mode.includes('xml')) {
if (mode?.includes('xml')) {
return 'Filter with XPath';
}

return null;
}, [mode]);

const placeholderText = useMemo(() => {
if (mode.includes('json')) {
if (mode?.includes('json')) {
return '$.store.books..author';
}

if (mode.includes('xml')) {
if (mode?.includes('xml')) {
return '/store/books//author';
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ const TextPreview = memo(({ data, onLinkClick }) => {
return String(data);
}
}
if (typeof data === 'string') {
// Historical preview for JSON bodies was JSON.stringify(parsedObject) (compact).
// Under bodyRef we may receive the raw UTF-8 string instead — normalize for preview.
try {
const parsed = JSON.parse(data);
if (parsed !== null && typeof parsed === 'object') {
return JSON.stringify(parsed);
}
} catch {
/* not JSON — show as-is */
}
return data;
}
return String(data);
}, [data]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ const QueryResultPreview = ({
previewMode,
disableRunEventListener,
displayedTheme,
docKey
docKey,
mediaSrc
}) => {
const preferences = useSelector((state) => state.app.preferences);
const dispatch = useDispatch();
Expand Down Expand Up @@ -84,12 +85,15 @@ const QueryResultPreview = ({
return <HtmlPreview data={data} baseUrl={baseUrl} />;
}
case 'preview-image': {
return <img src={`data:${contentType.replace(/\;(.*)/, '')};base64,${dataBuffer}`} />;
const src = mediaSrc || (dataBuffer ? `data:${contentType.replace(/\;(.*)/, '')};base64,${dataBuffer}` : null);
return src ? <img src={src} /> : null;
}
case 'preview-pdf': {
const file = mediaSrc || (dataBuffer ? `data:application/pdf;base64,${dataBuffer}` : null);
if (!file) return null;
return (
<div className="preview-pdf" style={{ height: '100%', overflow: 'auto', maxHeight: 'calc(100vh - 220px)' }}>
<Document file={`data:application/pdf;base64,${dataBuffer}`} onLoadSuccess={onDocumentLoadSuccess}>
<Document file={file} onLoadSuccess={onDocumentLoadSuccess}>
{Array.from(new Array(numPages), (el, index) => (
<Page key={`page_${index + 1}`} pageNumber={index + 1} renderAnnotationLayer={false} />
))}
Expand All @@ -98,11 +102,15 @@ const QueryResultPreview = ({
);
}
case 'preview-audio': {
return (
<audio controls src={`data:${contentType.replace(/\;(.*)/, '')};base64,${dataBuffer}`} className="mx-auto" />
);
const src = mediaSrc || (dataBuffer ? `data:${contentType.replace(/\;(.*)/, '')};base64,${dataBuffer}` : null);
return src ? (
<audio controls src={src} className="mx-auto" />
) : null;
}
case 'preview-video': {
if (mediaSrc) {
return <video controls src={mediaSrc} className="mx-auto max-w-full" />;
}
return <VideoPreview contentType={contentType} dataBuffer={dataBuffer} />;
}
case 'preview-json': {
Expand Down
Loading
Loading