diff --git a/packages/bruno-app/src/components/ResponsePane/ClearTimeline/StyledWrapper.js b/packages/bruno-app/src/components/ResponsePane/ClearTimeline/StyledWrapper.js
new file mode 100644
index 000000000..8c32a8bab
--- /dev/null
+++ b/packages/bruno-app/src/components/ResponsePane/ClearTimeline/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;
diff --git a/packages/bruno-app/src/components/ResponsePane/ClearTimeline/index.js b/packages/bruno-app/src/components/ResponsePane/ClearTimeline/index.js
new file mode 100644
index 000000000..d88a302af
--- /dev/null
+++ b/packages/bruno-app/src/components/ResponsePane/ClearTimeline/index.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import { useDispatch } from 'react-redux';
+import StyledWrapper from './StyledWrapper';
+import { clearRequestTimeline } from 'providers/ReduxStore/slices/collections/index';
+
+const ClearTimeline = ({ collection, item }) => {
+ const dispatch = useDispatch();
+
+ const clearResponse = () =>
+ dispatch(
+ clearRequestTimeline({
+ itemUid: item.uid,
+ collectionUid: collection.uid
+ })
+ );
+
+ return (
+
+
+
+ );
+};
+
+export default ClearTimeline;
diff --git a/packages/bruno-app/src/components/ResponsePane/index.js b/packages/bruno-app/src/components/ResponsePane/index.js
index 606c2043d..2d2a6ec25 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 ClearTimeline from './ClearTimeline/index';
const ResponsePane = ({ rightPaneWidth, item, collection }) => {
const dispatch = useDispatch();
@@ -26,6 +27,10 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
const isLoading = ['queued', 'sending'].includes(item.requestState);
const [showScriptErrorCard, setShowScriptErrorCard] = useState(false);
+ const requestTimeline = ([...(collection.timeline || [])]).filter(obj => {
+ if (obj.itemUid === item.uid) return true;
+ });
+
useEffect(() => {
if (item?.preRequestScriptErrorMessage || item?.postResponseScriptErrorMessage) {
setShowScriptErrorCard(true);
@@ -83,7 +88,7 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
);
}
- if (!item.response) {
+ if (!item.response && !requestTimeline?.length) {
return (
@@ -134,11 +139,17 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
onClick={() => setShowScriptErrorCard(true)}
/>
)}
-
-
-
-
-
+ {focusedTab?.responsePaneTab === "timeline" ? (
+
+ ) : item?.response ? (
+ <>
+
+
+
+
+
+ >
+ ) : null}
) : null}
@@ -152,7 +163,17 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
onClose={() => setShowScriptErrorCard(false)}
/>
)}
- {getTabPanel(focusedTab.responsePaneTab)}
+ {!item?.response ? (
+ focusedTab?.responsePaneTab === "timeline" && requestTimeline?.length ? (
+
+ ) : null
+ ) : (
+ <>{getTabPanel(focusedTab.responsePaneTab)}>
+ )}
);
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
index e2551e7c6..ace2beefd 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
@@ -1297,12 +1297,13 @@ export const refreshOauth2Credentials = (payload) => async (dispatch, getState)
return new Promise((resolve, reject) => {
window.ipcRenderer
.invoke('renderer:refresh-oauth2-credentials', { request, collection })
- .then(({ credentials, url, collectionUid, debugInfo }) => {
+ .then(({ credentials, url, collectionUid, debugInfo, credentialsId }) => {
dispatch(
collectionAddOauth2CredentialsByUrl({
credentials,
url,
collectionUid,
+ credentialsId,
debugInfo,
folderUid: folderUid || null,
itemId: !folderUid ? itemId : null
@@ -1320,8 +1321,12 @@ export const clearOauth2Cache = (payload) => async (dispatch, getState) => {
window.ipcRenderer
.invoke('clear-oauth2-cache', collectionUid, url, credentialsId)
.then(() => {
- // We do not dispatch any action to modify the Redux store,
- // since we are only clearing the session on the main process side.
+ dispatch(
+ collectionClearOauth2CredentialsByUrl({
+ url,
+ collectionUid,
+ })
+ );
resolve();
})
.catch(reject);
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
index 82691fd3e..d583fe261 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
@@ -324,6 +324,23 @@ export const collectionsSlice = createSlice({
}
}
},
+ clearTimeline: (state, action) => {
+ const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
+
+ if (collection) {
+ collection.timeline = [];
+ }
+ },
+ clearRequestTimeline: (state, action) => {
+ const { collectionUid, itemUid } = action.payload || {};
+ const collection = findCollectionByUid(state.collections, collectionUid);
+
+ if (collection) {
+ if (itemUid) {
+ collection.timeline = collection?.timeline?.filter(t => t?.itemUid !== itemUid);
+ }
+ }
+ },
saveRequest: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
@@ -2119,7 +2136,19 @@ export const collectionsSlice = createSlice({
},
collectionClearOauth2CredentialsByUrl: (state, action) => {
- // Since we don't want to remove tokens from oauth2Credentials or timeline,
+ const { collectionUid, url, credentialsId } = action.payload;
+ const collection = findCollectionByUid(state.collections, collectionUid);
+ if (!collection) return;
+
+ if (collection.oauth2Credentials) {
+ let collectionOauth2Credentials = cloneDeep(collection.oauth2Credentials);
+ const filteredOauth2Credentials = filter(
+ collectionOauth2Credentials,
+ (creds) =>
+ !(creds.url === url && creds.collectionUid === collectionUid)
+ );
+ collection.oauth2Credentials = filteredOauth2Credentials;
+ }
},
collectionGetOauth2CredentialsByUrl: (state, action) => {
@@ -2167,6 +2196,8 @@ export const {
requestCancelled,
responseReceived,
responseCleared,
+ clearTimeline,
+ clearRequestTimeline,
saveRequest,
deleteRequestDraft,
newEphemeralHttpRequest,
diff --git a/packages/bruno-electron/src/utils/oauth2.js b/packages/bruno-electron/src/utils/oauth2.js
index 92f1513c8..ed9a46856 100644
--- a/packages/bruno-electron/src/utils/oauth2.js
+++ b/packages/bruno-electron/src/utils/oauth2.js
@@ -25,8 +25,11 @@ const getStoredOauth2Credentials = ({ collectionUid, url, credentialsId }) => {
};
const isTokenExpired = (credentials) => {
- if (!credentials || !credentials.expires_in || !credentials.created_at) {
- return true; // Assume expired if missing data
+ if (!credentials?.access_token) {
+ return true;
+ }
+ if (!credentials?.expires_in || !credentials.created_at) {
+ return false;
}
const expiryTime = credentials.created_at + credentials.expires_in * 1000;
return Date.now() > expiryTime;