perfViz/src/components/httprequestviewer/RequestRowDetails.tsx
Michael Mainguy 488d9a2650 Add JavaScript viewer and HTTP request initiator tracking
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>
2025-08-19 06:32:57 -05:00

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