From 8cee7bad392f1342103ab58ea63cb644e4ae640a Mon Sep 17 00:00:00 2001
From: Shashank Shekhar <48152748+sha5git@users.noreply.github.com>
Date: Tue, 2 Dec 2025 14:53:51 +0530
Subject: [PATCH 1/2] added copy button to copy response (#5409)
Co-authored-by: Shashank Shekhar <48152748+sha5-git@users.noreply.github.com>
---
.../ResponseCopy/StyledWrapper.js | 8 +++++
.../ResponsePane/ResponseCopy/index.js | 33 +++++++++++++++++++
.../src/components/ResponsePane/index.js | 4 +++
3 files changed, 45 insertions(+)
create mode 100644 packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js
create mode 100644 packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js
diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js b/packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js
new file mode 100644
index 000000000..b2acb52b8
--- /dev/null
+++ b/packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js
@@ -0,0 +1,8 @@
+import styled from 'styled-components';
+
+const StyledWrapper = styled.div`
+ font-size: 0.8125rem;
+ color: ${(props) => props.theme.requestTabPanel.responseStatus};
+`;
+
+export default StyledWrapper;
\ No newline at end of file
diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js b/packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js
new file mode 100644
index 000000000..80763f131
--- /dev/null
+++ b/packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js
@@ -0,0 +1,33 @@
+import React from 'react';
+import StyledWrapper from './StyledWrapper';
+import toast from 'react-hot-toast';
+import { IconCopy } from '@tabler/icons';
+
+const ResponseCopy = ({ item }) => {
+ const response = item.response || {};
+
+ const copyResponse = () => {
+ try {
+ const textToCopy = typeof response.data === 'string'
+ ? response.data
+ : JSON.stringify(response.data, null, 2);
+
+ navigator.clipboard.writeText(textToCopy).then(() => {
+ toast.success('Response copied to clipboard');
+ }).catch(() => {
+ toast.error('Failed to copy response');
+ });
+ } catch (error) {
+ toast.error('Failed to copy response');
+ }
+ };
+
+ return (
+
+
+
+ );
+};
+export default ResponseCopy;
\ No newline at end of file
diff --git a/packages/bruno-app/src/components/ResponsePane/index.js b/packages/bruno-app/src/components/ResponsePane/index.js
index b098570e2..859196182 100644
--- a/packages/bruno-app/src/components/ResponsePane/index.js
+++ b/packages/bruno-app/src/components/ResponsePane/index.js
@@ -18,6 +18,7 @@ import ScriptErrorIcon from './ScriptErrorIcon';
import StyledWrapper from './StyledWrapper';
import ResponseSave from 'src/components/ResponsePane/ResponseSave';
import ResponseClear from 'src/components/ResponsePane/ResponseClear';
+import ResponseCopy from 'src/components/ResponsePane/ResponseCopy';
import ResponseBookmark from 'src/components/ResponsePane/ResponseBookmark';
import SkippedRequest from './SkippedRequest';
import ClearTimeline from './ClearTimeline/index';
@@ -189,6 +190,9 @@ const ResponsePane = ({ item, collection }) => {
<>
+
+
+
{item.response?.stream?.running
From 2a251b1a626d8faaa8d82c0615a12eb53d5d9a0f Mon Sep 17 00:00:00 2001
From: Pooja
Date: Tue, 2 Dec 2025 15:06:39 +0530
Subject: [PATCH 2/2] added copy button to copy response (#6131)
* added copy button to copy response
* add: response action component
* fix: lint error
* add: playwright test
---------
Co-authored-by: Shashank Shekhar <48152748+sha5-git@users.noreply.github.com>
Co-authored-by: Sid
---
.../ResponseActions/StyledWrapper.js | 29 +++++++++++
.../ResponsePane/ResponseActions/index.js | 35 +++++++++++++
.../ResponsePane/ResponseClear/index.js | 15 +++++-
.../ResponseCopy/StyledWrapper.js | 2 +-
.../ResponsePane/ResponseCopy/index.js | 39 +++++++++-----
.../ResponsePane/ResponseSave/index.js | 18 ++++++-
.../src/components/ResponsePane/index.js | 12 ++---
tests/response/response-actions.spec.ts | 51 +++++++++++++++++++
8 files changed, 176 insertions(+), 25 deletions(-)
create mode 100644 packages/bruno-app/src/components/ResponsePane/ResponseActions/StyledWrapper.js
create mode 100644 packages/bruno-app/src/components/ResponsePane/ResponseActions/index.js
create mode 100644 tests/response/response-actions.spec.ts
diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseActions/StyledWrapper.js b/packages/bruno-app/src/components/ResponsePane/ResponseActions/StyledWrapper.js
new file mode 100644
index 000000000..6ba69090e
--- /dev/null
+++ b/packages/bruno-app/src/components/ResponsePane/ResponseActions/StyledWrapper.js
@@ -0,0 +1,29 @@
+import styled from 'styled-components';
+
+const StyledWrapper = styled.div`
+ button {
+ color: var(--color-tab-inactive);
+ cursor: pointer;
+
+ &:hover {
+ color: var(--color-tab-active);
+ }
+
+ &:disabled {
+ cursor: not-allowed;
+ opacity: 0.5;
+ }
+ }
+
+ .cursor-pointer {
+ display: flex;
+ align-items: center;
+ color: var(--color-tab-inactive);
+
+ &:hover {
+ color: var(--color-tab-active);
+ }
+ }
+`;
+
+export default StyledWrapper;
diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseActions/index.js b/packages/bruno-app/src/components/ResponsePane/ResponseActions/index.js
new file mode 100644
index 000000000..a430153a0
--- /dev/null
+++ b/packages/bruno-app/src/components/ResponsePane/ResponseActions/index.js
@@ -0,0 +1,35 @@
+import React, { useRef, forwardRef } from 'react';
+import { IconDots } from '@tabler/icons';
+import Dropdown from 'components/Dropdown';
+import StyledWrapper from './StyledWrapper';
+import ResponseClear from 'src/components/ResponsePane/ResponseClear';
+import ResponseSave from 'src/components/ResponsePane/ResponseSave';
+
+const ResponseActions = ({ collection, item }) => {
+ const menuDropdownTippyRef = useRef();
+
+ const onMenuDropdownCreate = (ref) => (menuDropdownTippyRef.current = ref);
+
+ const MenuIcon = forwardRef((_props, ref) => {
+ return (
+
+
+
+ );
+ });
+
+ const handleClose = () => {
+ menuDropdownTippyRef.current.hide();
+ };
+
+ return (
+
+ } placement="bottom-end">
+
+
+
+
+ );
+};
+
+export default ResponseActions;
diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js b/packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js
index 747543347..b18418592 100644
--- a/packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js
+++ b/packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js
@@ -4,10 +4,11 @@ import { useDispatch } from 'react-redux';
import StyledWrapper from './StyledWrapper';
import { responseCleared } from 'providers/ReduxStore/slices/collections/index';
-const ResponseClear = ({ collection, item }) => {
+const ResponseClear = ({ collection, item, asDropdownItem, onClose }) => {
const dispatch = useDispatch();
- const clearResponse = () =>
+ const clearResponse = () => {
+ if (onClose) onClose();
dispatch(
responseCleared({
itemUid: item.uid,
@@ -15,6 +16,16 @@ const ResponseClear = ({ collection, item }) => {
response: null
})
);
+ };
+
+ if (asDropdownItem) {
+ return (
+
+
+ Clear
+
+ );
+ }
return (
diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js b/packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js
index b2acb52b8..8c32a8bab 100644
--- a/packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js
+++ b/packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js
@@ -5,4 +5,4 @@ const StyledWrapper = styled.div`
color: ${(props) => props.theme.requestTabPanel.responseStatus};
`;
-export default StyledWrapper;
\ No newline at end of file
+export default StyledWrapper;
diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js b/packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js
index 80763f131..7c59301ef 100644
--- a/packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js
+++ b/packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js
@@ -1,33 +1,46 @@
-import React from 'react';
+import React, { useState, useEffect } from 'react';
import StyledWrapper from './StyledWrapper';
import toast from 'react-hot-toast';
-import { IconCopy } from '@tabler/icons';
+import { IconCopy, IconCheck } from '@tabler/icons';
const ResponseCopy = ({ item }) => {
const response = item.response || {};
+ const [copied, setCopied] = useState(false);
- const copyResponse = () => {
+ useEffect(() => {
+ if (copied) {
+ const timer = setTimeout(() => {
+ setCopied(false);
+ }, 2000);
+ return () => clearTimeout(timer);
+ }
+ }, [copied]);
+
+ const copyResponse = async () => {
try {
- const textToCopy = typeof response.data === 'string'
- ? response.data
- : JSON.stringify(response.data, null, 2);
+ const textToCopy = typeof response.data === 'string'
+ ? response.data
+ : JSON.stringify(response.data, null, 2);
- navigator.clipboard.writeText(textToCopy).then(() => {
+ await navigator.clipboard.writeText(textToCopy);
toast.success('Response copied to clipboard');
- }).catch(() => {
- toast.error('Failed to copy response');
- });
+ setCopied(true);
} catch (error) {
- toast.error('Failed to copy response');
+ toast.error('Failed to copy response');
}
};
return (
);
};
-export default ResponseCopy;
\ No newline at end of file
+
+export default ResponseCopy;
diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js b/packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js
index 15eef651b..c791463cd 100644
--- a/packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js
+++ b/packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js
@@ -4,11 +4,13 @@ import toast from 'react-hot-toast';
import get from 'lodash/get';
import { IconDownload } from '@tabler/icons';
-const ResponseSave = ({ item }) => {
+const ResponseSave = ({ item, asDropdownItem, onClose }) => {
const { ipcRenderer } = window;
const response = item.response || {};
const saveResponseToFile = () => {
+ if (!response.dataBuffer) return;
+ if (onClose) onClose();
return new Promise((resolve, reject) => {
ipcRenderer
.invoke('renderer:save-response-to-file', response, item?.requestSent?.url, item.pathname)
@@ -20,6 +22,20 @@ const ResponseSave = ({ item }) => {
});
};
+ if (asDropdownItem) {
+ return (
+
+
+ Download
+
+ );
+ }
+
return (