diff --git a/packages/bruno-app/src/components/ResponsePane/RunnerTimeline/StyledWrapper.js b/packages/bruno-app/src/components/ResponsePane/RunnerTimeline/StyledWrapper.js index 020d5bd91..4b7cb28a7 100644 --- a/packages/bruno-app/src/components/ResponsePane/RunnerTimeline/StyledWrapper.js +++ b/packages/bruno-app/src/components/ResponsePane/RunnerTimeline/StyledWrapper.js @@ -1,11 +1,109 @@ import styled from 'styled-components'; const StyledWrapper = styled.div` + .timeline-event { + padding: 8px 0 0 0; + cursor: pointer; + } + + .timeline-event-content { + border-radius: 4px; + padding: 12px; + margin-top: 0.5rem; + } + + .timeline-event-header { + color: ${(props) => props.theme.text}; + } + + .method-label { + font-weight: 600; + } + + .status-code { + font-weight: 600; + } + + .url-text { + color: ${(props) => props.theme.colors.text.muted}; + font-size: 0.875rem; + margin-top: 0.25rem; + } + + .timestamp { + color: ${(props) => props.theme.colors.text.muted}; + font-size: 0.875rem; + } + + .meta-info { + color: ${(props) => props.theme.colors.text.muted}; + font-size: 0.875rem; + } + + .oauth-section { + .oauth-header { + display: flex; + align-items: center; + color: ${(props) => props.theme.text}; + font-weight: 600; + + span { + margin-left: 0.5rem; + } + } + } + + .tabs-switcher { + border-bottom: 1px solid ${(props) => props.theme.modal.input.border}; + margin-bottom: 16px; + + button { + position: relative; + padding: 8px 16px; + color: ${(props) => props.theme.colors.text.muted}; + + &.active { + color: ${(props) => props.theme.tabs.active.color}; + &:after { + content: ''; + position: absolute; + bottom: -1px; + left: 0; + right: 0; + height: 2px; + background: ${(props) => props.theme.tabs.active.border}; + } + } + } + } + + .network-logs { + background: ${(props) => props.theme.codemirror.bg}; + color: ${(props) => props.theme.text}; + border-radius: 4px; + } + + .oauth-request-item-content { + border-radius: 4px; + margin-top: 0.5rem; + } + + .collapsible-section { + margin-bottom: 12px; + + .section-header { + cursor: pointer; + &:hover { + opacity: 0.8; + } + } + } + .line { white-space: pre-line; word-wrap: break-word; word-break: break-all; - font-family: Inter, sans-serif !important; + font-family: ${(props) => props.theme.font || 'Inter, sans-serif'} !important; .arrow { opacity: 0.5; @@ -19,6 +117,35 @@ const StyledWrapper = styled.div` color: ${(props) => props.theme.colors.text.purple}; } } + + .request-label { + font-size: 0.75rem; + padding: 2px 6px; + border-radius: 3px; + margin-left: 8px; + background: ${(props) => props.theme.requestTabs.bg}; + } + + table { + width: 100%; + border-collapse: collapse; + font-weight: 600; + table-layout: fixed; + + thead, + td { + border: 1px solid ${(props) => props.theme.table.border}; + } + + thead { + color: ${(props) => props.theme.table.thead.color}; + font-size: 0.8125rem; + user-select: none; + } + td { + padding: 6px 10px; + } + } `; export default StyledWrapper; diff --git a/packages/bruno-app/src/components/ResponsePane/RunnerTimeline/index.js b/packages/bruno-app/src/components/ResponsePane/RunnerTimeline/index.js index 592e0641b..4fac7ed6d 100644 --- a/packages/bruno-app/src/components/ResponsePane/RunnerTimeline/index.js +++ b/packages/bruno-app/src/components/ResponsePane/RunnerTimeline/index.js @@ -1,14 +1,10 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import forOwn from 'lodash/forOwn'; -import { safeStringifyJSON } from 'utils/common'; import StyledWrapper from './StyledWrapper'; +import TimelineItem from '../Timeline/TimelineItem'; -const RunnerTimeline = ({ request, response }) => { +const RunnerTimeline = ({ request = {}, response = {}, item, collection }) => { const requestHeaders = []; - const responseHeaders = typeof response.headers === 'object' ? Object.entries(response.headers) : []; - - request = request || {}; - response = response || {}; forOwn(request.headers, (value, key) => { requestHeaders.push({ @@ -17,43 +13,56 @@ const RunnerTimeline = ({ request, response }) => { }); }); - let requestData = typeof request?.data === "string" ? request?.data : safeStringifyJSON(request?.data, true); + const oauth2Events = useMemo( + () => + collection?.timeline?.filter( + (event) => event.type === 'oauth2' && event.itemUid === item.uid + ) || [], + [collection?.timeline, item.uid] + ); return ( -
-
-          {'>'} {request.method} {request.url}
-        
- {requestHeaders.map((h) => { - return ( -
-              {'>'} {h.name}: {h.value}
-            
- ); - })} - - {requestData ? ( -
-            {'>'} data{' '}
-            
{requestData}
-
- ) : null} -
- -
-
-          {'<'} {response.status} - {response.statusText}
-        
- - {responseHeaders.map((h) => { - return ( -
-              {'<'} {h[0]}: {h[1]}
-            
- ); - })} -
+ {/* Show the main request/response timeline item */} + + + {oauth2Events.map((event, index) => { + const { data, timestamp } = event; + const { debugInfo } = data; + return ( +
+
+
+ OAuth2.0 Calls +
+
+
+ {debugInfo && debugInfo.length > 0 ? ( + debugInfo.map((data, idx) => ( +
+ +
+ )) + ) : ( +
No debug information available.
+ )} +
+
+ ); + })}
); }; diff --git a/packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/index.js b/packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/index.js index 46a5efad5..ff33e41ec 100644 --- a/packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/index.js +++ b/packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/index.js @@ -6,7 +6,7 @@ import Method from "./Common/Method/index"; import Status from "./Common/Status/index"; import { RelativeTime } from "./Common/Time/index"; -const TimelineItem = ({ timestamp, request, response, item, collection, isOauth2 }) => { +const TimelineItem = ({ timestamp, request, response, item, collection, isOauth2, hideTimestamp = false }) => { const [isCollapsed, _toggleCollapse] = useState(false); const [activeTab, setActiveTab] = useState('request'); const toggleCollapse = () => _toggleCollapse(prev => !prev); @@ -23,11 +23,15 @@ const TimelineItem = ({ timestamp, request, response, item, collection, isOauth2 {isOauth2 ?
[oauth2.0]
: null} -
[{new Date(timestamp).toISOString()}]
+ {!hideTimestamp && ( + <> +
[{new Date(timestamp).toISOString()}]
+ + + + + )} - - -
{url}
diff --git a/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js b/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js index 76a65d0cd..6475677a2 100644 --- a/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js +++ b/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js @@ -57,7 +57,14 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => { return ; } case 'timeline': { - return ; + return ( + + ); } case 'tests': { return { credentials: request?.oauth2Credentials?.credentials, url: request?.oauth2Credentials?.url, collectionUid, - credentialsId: request?.oauth2Credentials?.credentialsId + credentialsId: request?.oauth2Credentials?.credentialsId, + ...(request?.oauth2Credentials?.folderUid ? { folderUid: request.oauth2Credentials.folderUid } : { itemUid: item.uid }), + debugInfo: request?.oauth2Credentials?.debugInfo, }); } @@ -1170,6 +1172,7 @@ const registerNetworkIpc = (mainWindow) => { dataBuffer: dataBuffer.toString('base64'), size: Buffer.byteLength(dataBuffer), data: response.data, + timeline: response.timeline, responseTime: response.headers.get('request-duration') }, ...eventData @@ -1193,6 +1196,7 @@ const registerNetworkIpc = (mainWindow) => { dataBuffer: dataBuffer.toString('base64'), size: Buffer.byteLength(dataBuffer), data: error.response.data, + timeline: error.response.timeline, responseTime: error.response.headers.get('request-duration') };