Introduces comprehensive request initiator visualization and JavaScript performance analysis capabilities. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
237 lines
13 KiB
TypeScript
237 lines
13 KiB
TypeScript
import React from 'react'
|
|
import type { HTTPRequest } from './types/httpRequest'
|
|
import InitiatorView from './InitiatorView'
|
|
import styles from './RequestRowDetails.module.css'
|
|
|
|
// Import utility functions
|
|
import { formatDuration, formatSize } from './lib/formatUtils'
|
|
import {
|
|
getTotalResponseTimeClass,
|
|
getQueueAnalysisIcon,
|
|
getCDNIcon,
|
|
getCDNDisplayName,
|
|
getPriorityIcon
|
|
} from './lib/colorUtils'
|
|
|
|
interface RequestRowDetailsProps {
|
|
request: HTTPRequest
|
|
visibleColumns: Record<string, boolean>
|
|
}
|
|
|
|
const RequestRowDetails: React.FC<RequestRowDetailsProps> = ({ request, visibleColumns }) => {
|
|
// Calculate the number of visible columns for colSpan
|
|
const visibleColumnCount = Object.values(visibleColumns).filter(Boolean).length
|
|
return (
|
|
<tr key={`${request.requestId}-expanded`} className={styles.expandedRow}>
|
|
<td colSpan={visibleColumnCount}>
|
|
<div className={styles.expandedContent}>
|
|
|
|
{/* Request Details */}
|
|
<div className={styles.detailCard}>
|
|
<h4 className={styles.detailCardTitle}>Request Details</h4>
|
|
<div className={styles.detailList}>
|
|
<div className={styles.detailListItem}><strong>Request ID:</strong> {request.requestId}</div>
|
|
<div className={styles.detailListItem}><strong>Method:</strong> {request.method}</div>
|
|
<div className={styles.detailListItem}>
|
|
<strong>Priority:</strong>
|
|
<span style={{ display: 'inline-flex', alignItems: 'center', gap: '4px', marginLeft: '4px' }}>
|
|
{getPriorityIcon(request.priority)} {request.priority || '-'}
|
|
</span>
|
|
</div>
|
|
<div className={styles.detailListItem}><strong>MIME Type:</strong> {request.mimeType || '-'}</div>
|
|
<div className={styles.detailListItem}><strong>Content-Length:</strong> {request.contentLength ? formatSize(request.contentLength) : '-'}</div>
|
|
<div className={styles.detailListItem}><strong>From Cache:</strong> {request.fromCache ? 'Yes' : 'No'}</div>
|
|
<div className={styles.detailListItem}><strong>Connection Reused:</strong> {request.connectionReused ? 'Yes' : 'No'}</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Initiator Information */}
|
|
<InitiatorView request={request} />
|
|
|
|
{/* Network Timing */}
|
|
<div className={styles.detailCard}>
|
|
<h4 className={styles.detailCardTitle}>Network Timing</h4>
|
|
<div className={styles.detailList}>
|
|
<div className={styles.detailListItem}><strong>Start Time:</strong> {formatDuration(request.timing.startOffset)}</div>
|
|
<div className={`${styles.detailListItem} ${styles.queueTimeContainer}`}>
|
|
<strong>Queue Time:</strong> {formatDuration(request.timing.queueTime)}
|
|
{request.queueAnalysis && (
|
|
<span title={request.queueAnalysis.description} className={styles.queueAnalysisIcon}>
|
|
{getQueueAnalysisIcon(request.queueAnalysis)}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<div className={styles.detailListItem}><strong>Server Latency:</strong> {formatDuration(request.timing.serverLatency)}</div>
|
|
<div className={styles.detailListItem}><strong>Network Duration:</strong> {formatDuration(request.timing.duration)}</div>
|
|
<div className={`${styles.detailListItem} ${styles.timingHighlighted} ${getTotalResponseTimeClass(request.timing.totalResponseTime)}`}>
|
|
<strong>Total Response Time:</strong> {formatDuration(request.timing.totalResponseTime)}
|
|
</div>
|
|
<div className={styles.detailListItem}><strong>Network Duration Only:</strong> {formatDuration(request.timing.networkDuration)}</div>
|
|
|
|
{/* DNS Timing */}
|
|
<div className={styles.detailListItem}>
|
|
<strong>DNS:</strong> {
|
|
request.timing.dnsStart !== undefined && request.timing.dnsEnd !== undefined && request.timing.dnsStart >= 0 && request.timing.dnsEnd >= 0
|
|
? formatDuration((request.timing.dnsEnd || 0) - (request.timing.dnsStart || 0))
|
|
: <span className={styles.connectionCached}>cached</span>
|
|
}
|
|
</div>
|
|
|
|
{/* Connection Timing */}
|
|
<div className={styles.detailListItem}>
|
|
<strong>Connection:</strong> {
|
|
request.timing.connectStart !== undefined && request.timing.connectEnd !== undefined && request.timing.connectStart >= 0 && request.timing.connectEnd >= 0
|
|
? formatDuration((request.timing.connectEnd || 0) - (request.timing.connectStart || 0))
|
|
: <span className={styles.connectionReused}>
|
|
{request.connectionReused ? 'reused' : 'cached'}
|
|
</span>
|
|
}
|
|
</div>
|
|
|
|
{/* SSL Timing (only show if HTTPS and timing available) */}
|
|
{(request.url.startsWith('https://') || request.protocol === 'h2') && (
|
|
<div className={styles.detailListItem}>
|
|
<strong>SSL:</strong> {
|
|
request.timing.sslStart !== undefined && request.timing.sslEnd !== undefined && request.timing.sslStart >= 0 && request.timing.sslEnd >= 0
|
|
? formatDuration((request.timing.sslEnd || 0) - (request.timing.sslStart || 0))
|
|
: <span className={styles.connectionReused}>reused</span>
|
|
}
|
|
</div>
|
|
)}
|
|
|
|
{/* Send Timing */}
|
|
{request.timing.sendStart !== undefined && request.timing.sendEnd !== undefined && request.timing.sendStart >= 0 && request.timing.sendEnd >= 0 && (
|
|
<div className={styles.detailListItem}><strong>Send:</strong> {formatDuration((request.timing.sendEnd || 0) - (request.timing.sendStart || 0))}</div>
|
|
)}
|
|
|
|
{/* Receive Headers Timing */}
|
|
{request.timing.receiveHeadersStart !== undefined && request.timing.receiveHeadersEnd !== undefined && request.timing.receiveHeadersStart >= 0 && request.timing.receiveHeadersEnd >= 0 && (
|
|
<div className={styles.detailListItem}><strong>Receive Headers:</strong> {formatDuration((request.timing.receiveHeadersEnd || 0) - (request.timing.receiveHeadersStart || 0))}</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Queue Analysis */}
|
|
{request.queueAnalysis && request.queueAnalysis.reason !== 'unknown' && (
|
|
<div className={`${styles.detailCard} ${styles.queueAnalysisCard}`}>
|
|
<h4 className={styles.detailCardTitle}>
|
|
Queue Analysis {getQueueAnalysisIcon(request.queueAnalysis)}
|
|
</h4>
|
|
<div className={styles.detailList}>
|
|
<div className={styles.detailListItem}><strong>Reason:</strong> {request.queueAnalysis.description}</div>
|
|
<div className={styles.detailListItem}><strong>Concurrent Requests:</strong> {request.queueAnalysis.concurrentRequests}</div>
|
|
{request.queueAnalysis.relatedRequests && request.queueAnalysis.relatedRequests.length > 0 && (
|
|
<div className={styles.detailListItem}>
|
|
<strong>Related Request IDs:</strong>{' '}
|
|
<span className={styles.relatedRequestIds}>
|
|
{request.queueAnalysis.relatedRequests.join(', ')}
|
|
{request.queueAnalysis.concurrentRequests > request.queueAnalysis.relatedRequests.length &&
|
|
` (+${request.queueAnalysis.concurrentRequests - request.queueAnalysis.relatedRequests.length} more)`}
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* CDN Analysis */}
|
|
{request.cdnAnalysis && request.cdnAnalysis.provider !== 'unknown' && (
|
|
<div className={`${styles.detailCard} ${styles.cdnAnalysisCard}`}>
|
|
<h4 className={styles.detailCardTitle}>
|
|
CDN Analysis {getCDNIcon(request.cdnAnalysis)}
|
|
</h4>
|
|
<div className={styles.detailList}>
|
|
<div className={styles.detailListItem}><strong>Provider:</strong> {getCDNDisplayName(request.cdnAnalysis.provider)}</div>
|
|
<div className={styles.detailListItem}><strong>Source:</strong> {request.cdnAnalysis.isEdge ? 'Edge Server' : 'Origin Server'}</div>
|
|
{request.cdnAnalysis.cacheStatus && request.cdnAnalysis.cacheStatus !== 'unknown' && (
|
|
<div className={styles.detailListItem}><strong>Cache Status:</strong> {request.cdnAnalysis.cacheStatus.toUpperCase()}</div>
|
|
)}
|
|
{request.cdnAnalysis.edgeLocation && (
|
|
<div className={styles.detailListItem}><strong>Edge Location:</strong> {request.cdnAnalysis.edgeLocation}</div>
|
|
)}
|
|
<div className={styles.detailListItem}><strong>Confidence:</strong> {(request.cdnAnalysis.confidence * 100).toFixed(0)}%</div>
|
|
<div className={styles.detailListItem}><strong>Detection Method:</strong> {request.cdnAnalysis.detectionMethod}</div>
|
|
|
|
{/* Debug info for canadiantire.ca requests */}
|
|
{(
|
|
<div className={styles.debugSection}>
|
|
<div className={styles.debugTitle}>
|
|
Debug - CDN Detection Analysis:
|
|
</div>
|
|
<div className={styles.debugInfo}>
|
|
<strong>Current Detection:</strong> {request.cdnAnalysis?.provider}
|
|
(confidence: {((request.cdnAnalysis?.confidence || 0) * 100).toFixed(0)}%)
|
|
</div>
|
|
<div className={styles.debugHeaders}>
|
|
All CDN-Related Headers:
|
|
</div>
|
|
{request.responseHeaders && request.responseHeaders.map((header, idx) => {
|
|
const headerName = header.name.toLowerCase()
|
|
// Show all potentially CDN-related headers
|
|
if (headerName.includes('akamai') ||
|
|
headerName.includes('fastly') ||
|
|
headerName.includes('server') ||
|
|
headerName.includes('via') ||
|
|
headerName.includes('x-cache') ||
|
|
headerName.includes('x-serial') ||
|
|
headerName.includes('x-served-by') ||
|
|
headerName.includes('cf-') ||
|
|
headerName.includes('x-amz-cf') ||
|
|
headerName.includes('azure') ||
|
|
headerName.includes('x-goog') ||
|
|
headerName.includes('cdn') ||
|
|
headerName.includes('edge') ||
|
|
headerName.includes('cache')) {
|
|
const isAkamaiIndicator = headerName.includes('akamai') ||
|
|
headerName.includes('x-serial') ||
|
|
(headerName === 'x-cache' && header.value.includes('tcp_')) ||
|
|
(headerName === 'x-served-by' && header.value.includes('cache-'))
|
|
return (
|
|
<div key={idx} className={`${styles.headerLine} ${isAkamaiIndicator ? styles.akamaiIndicator : ''}`}>
|
|
<span className={`${styles.headerName} ${isAkamaiIndicator ? styles.akamaiIndicator : ''}`}>
|
|
{header.name}:
|
|
</span> {header.value}
|
|
{isAkamaiIndicator && (
|
|
<span className={styles.akamaiLabel}>
|
|
← AKAMAI
|
|
</span>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
return null
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Response Headers */}
|
|
{request.responseHeaders && request.responseHeaders.length > 0 && (
|
|
<div className={`${styles.detailCard} ${styles.fullWidth}`}>
|
|
<h4 className={styles.detailCardTitle}>
|
|
Response Headers ({request.responseHeaders.length})
|
|
</h4>
|
|
<div className={styles.headersContainer}>
|
|
{request.responseHeaders.map((header, index) => (
|
|
<div key={index} className={styles.headerItem}>
|
|
<div className={styles.headerItemName}>
|
|
{header.name}:
|
|
</div>
|
|
<div className={styles.headerItemValue} title={header.value}>
|
|
{header.value}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
)
|
|
}
|
|
|
|
export default RequestRowDetails |