add: new timeline in runner (#4927)

* add: new timeline in runner
This commit is contained in:
Pooja
2025-07-08 15:26:21 +05:30
committed by GitHub
parent 795b365df3
commit 63a8201290
6 changed files with 200 additions and 50 deletions

View File

@@ -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;

View File

@@ -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 (
<StyledWrapper className="pb-4 w-full">
<div>
<pre className="line request font-bold">
<span className="arrow">{'>'}</span> {request.method} {request.url}
</pre>
{requestHeaders.map((h) => {
return (
<pre className="line request" key={h.name}>
<span className="arrow">{'>'}</span> {h.name}: {h.value}
</pre>
);
})}
{requestData ? (
<pre className="line request">
<span className="arrow">{'>'}</span> data{' '}
<pre className="text-sm flex flex-wrap whitespace-break-spaces">{requestData}</pre>
</pre>
) : null}
</div>
<div className="mt-4">
<pre className="line response font-bold">
<span className="arrow">{'<'}</span> {response.status} - {response.statusText}
</pre>
{responseHeaders.map((h) => {
return (
<pre className="line response" key={h[0]}>
<span className="arrow">{'<'}</span> {h[0]}: {h[1]}
</pre>
);
})}
</div>
{/* Show the main request/response timeline item */}
<TimelineItem
request={request}
response={response}
item={item}
collection={collection}
hideTimestamp={true}
/>
{oauth2Events.map((event, index) => {
const { data, timestamp } = event;
const { debugInfo } = data;
return (
<div key={`oauth2-${index}`} className="timeline-event mt-4">
<div className="timeline-event-header cursor-pointer flex items-center">
<div className="flex items-center">
<span className="font-bold">OAuth2.0 Calls</span>
</div>
</div>
<div className="mt-2">
{debugInfo && debugInfo.length > 0 ? (
debugInfo.map((data, idx) => (
<div key={idx} className='ml-4'>
<TimelineItem
timestamp={timestamp}
request={data?.request}
response={data?.response}
item={item}
collection={collection}
isOauth2={true}
/>
</div>
))
) : (
<div>No debug information available.</div>
)}
</div>
</div>
);
})}
</StyledWrapper>
);
};

View File

@@ -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
<Method method={method} />
<Status statusCode={status || statusCode} statusText={statusText} />
{isOauth2 ? <pre className="opacity-50">[oauth2.0]</pre> : null}
<pre className="opacity-70">[{new Date(timestamp).toISOString()}]</pre>
{!hideTimestamp && (
<>
<pre className="opacity-70">[{new Date(timestamp).toISOString()}]</pre>
<span className="text-sm text-gray-400 flex-shrink-0 overflow-hidden text-ellipsis whitespace-nowrap">
<RelativeTime timestamp={timestamp} />
</span>
</>
)}
</div>
<span className="text-sm text-gray-400 flex-shrink-0 overflow-hidden text-ellipsis whitespace-nowrap">
<RelativeTime timestamp={timestamp} />
</span>
</div>
<div className="truncate text-sm mt-1">{url}</div>
</div>

View File

@@ -57,7 +57,14 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
return <ResponseHeaders headers={headers} />;
}
case 'timeline': {
return <RunnerTimeline request={requestSent} response={responseReceived} />;
return (
<RunnerTimeline
request={requestSent}
response={responseReceived}
item={item}
collection={collection}
/>
);
}
case 'tests': {
return <TestResults

View File

@@ -1084,4 +1084,3 @@ export const calculateDraggedItemNewPathname = ({ draggedItem, targetItem, dropT
};
// item sequence utils - END

View File

@@ -1123,7 +1123,9 @@ const registerNetworkIpc = (mainWindow) => {
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')
};