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;