Refactor Request Breakdown into modular components

Extract breakdown sections into separate components for better maintainability:
- ResourceTypeBreakdown: handles resource type analysis and visualization
- StatusCodeBreakdown: handles status code analysis and visualization
- HostnameBreakdown: handles hostname analysis with toggle functionality
- RequestDataSummary: statistics cards component (previously extracted)
- BreakdownTableHeader: shared table header component (previously extracted)

Main RequestBreakdown component reduced from ~420 to ~90 lines with improved separation of concerns.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Michael Mainguy 2025-08-19 08:29:03 -05:00
parent a2e161bf2a
commit 52543a5d04
6 changed files with 488 additions and 355 deletions

View File

@ -1,11 +1,14 @@
import { useMemo, useState } from 'react'
import { useMemo } from 'react'
import { useDatabaseTraceData } from '../hooks/useDatabaseTraceData'
import { processHTTPRequests } from './httprequestviewer/lib/httpRequestProcessor'
import { addRequestPostProcessing } from './httprequestviewer/lib/requestPostProcessor'
import { analyzeCDN, analyzeQueueReason } from './httprequestviewer/lib/analysisUtils'
import { assignConnectionNumbers } from './httprequestviewer/lib/connectionUtils'
import sortRequests from './httprequestviewer/lib/sortRequests'
import type { HTTPRequest } from './httprequestviewer/types/httpRequest'
import RequestDataSummary from './RequestBreakdown/RequestDataSummary'
import ResourceTypeBreakdown from './RequestBreakdown/ResourceTypeBreakdown'
import StatusCodeBreakdown from './RequestBreakdown/StatusCodeBreakdown'
import HostnameBreakdown from './RequestBreakdown/HostnameBreakdown'
import styles from './RequestBreakdown.module.css'
interface RequestBreakdownProps {
@ -21,20 +24,9 @@ interface BreakdownStats {
cacheHitRate: number
}
interface CategoryBreakdown {
name: string
count: number
percentage: number
totalSize: number
sizePercentage: number
totalResponseTime: number
responseTimePercentage: number
averageResponseTime: number
}
const RequestBreakdown: React.FC<RequestBreakdownProps> = ({ traceId }) => {
const { traceData, loading, error } = useDatabaseTraceData(traceId)
const [showAllHostnames, setShowAllHostnames] = useState(false)
const httpRequests = useMemo(() => {
if (!traceData) return []
@ -85,143 +77,6 @@ const RequestBreakdown: React.FC<RequestBreakdownProps> = ({ traceId }) => {
}
}, [httpRequests])
const resourceTypeBreakdown: CategoryBreakdown[] = useMemo(() => {
const typeMap = new Map<string, HTTPRequest[]>()
httpRequests.forEach(req => {
const type = req.resourceType || 'unknown'
if (!typeMap.has(type)) {
typeMap.set(type, [])
}
typeMap.get(type)!.push(req)
})
// Calculate total size and response time across all requests for percentage calculations
const totalAllSize = httpRequests.reduce((sum, req) => {
return sum + (req.contentLength || req.encodedDataLength || 0)
}, 0)
const totalAllResponseTime = httpRequests.reduce((sum, req) => {
return sum + (req.timing.totalResponseTime || 0)
}, 0)
return Array.from(typeMap.entries()).map(([type, requests]) => {
const totalSize = requests.reduce((sum, req) => {
return sum + (req.contentLength || req.encodedDataLength || 0)
}, 0)
const totalResponseTime = requests.reduce((sum, req) => {
return sum + (req.timing.totalResponseTime || 0)
}, 0)
return {
name: type,
count: requests.length,
percentage: (requests.length / httpRequests.length) * 100,
totalSize,
sizePercentage: totalAllSize > 0 ? (totalSize / totalAllSize) * 100 : 0,
totalResponseTime,
responseTimePercentage: totalAllResponseTime > 0 ? (totalResponseTime / totalAllResponseTime) * 100 : 0,
averageResponseTime: totalResponseTime / requests.length
}
}).sort((a, b) => b.count - a.count)
}, [httpRequests])
const statusCodeBreakdown: CategoryBreakdown[] = useMemo(() => {
const statusMap = new Map<string, HTTPRequest[]>()
httpRequests.forEach(req => {
const status = req.statusCode ? Math.floor(req.statusCode / 100) + 'xx' : 'Unknown'
if (!statusMap.has(status)) {
statusMap.set(status, [])
}
statusMap.get(status)!.push(req)
})
// Calculate total size and response time across all requests for percentage calculations
const totalAllSize = httpRequests.reduce((sum, req) => {
return sum + (req.contentLength || req.encodedDataLength || 0)
}, 0)
const totalAllResponseTime = httpRequests.reduce((sum, req) => {
return sum + (req.timing.totalResponseTime || 0)
}, 0)
return Array.from(statusMap.entries()).map(([status, requests]) => {
const totalSize = requests.reduce((sum, req) => {
return sum + (req.contentLength || req.encodedDataLength || 0)
}, 0)
const totalResponseTime = requests.reduce((sum, req) => {
return sum + (req.timing.totalResponseTime || 0)
}, 0)
return {
name: status,
count: requests.length,
percentage: (requests.length / httpRequests.length) * 100,
totalSize,
sizePercentage: totalAllSize > 0 ? (totalSize / totalAllSize) * 100 : 0,
totalResponseTime,
responseTimePercentage: totalAllResponseTime > 0 ? (totalResponseTime / totalAllResponseTime) * 100 : 0,
averageResponseTime: totalResponseTime / requests.length
}
}).sort((a, b) => b.count - a.count)
}, [httpRequests])
const hostnameBreakdown: CategoryBreakdown[] = useMemo(() => {
const hostMap = new Map<string, HTTPRequest[]>()
httpRequests.forEach(req => {
const hostname = req.hostname || 'unknown'
if (!hostMap.has(hostname)) {
hostMap.set(hostname, [])
}
hostMap.get(hostname)!.push(req)
})
// Calculate total size and response time across all requests for percentage calculations
const totalAllSize = httpRequests.reduce((sum, req) => {
return sum + (req.contentLength || req.encodedDataLength || 0)
}, 0)
const totalAllResponseTime = httpRequests.reduce((sum, req) => {
return sum + (req.timing.totalResponseTime || 0)
}, 0)
return Array.from(hostMap.entries()).map(([hostname, requests]) => {
const totalSize = requests.reduce((sum, req) => {
return sum + (req.contentLength || req.encodedDataLength || 0)
}, 0)
const totalResponseTime = requests.reduce((sum, req) => {
return sum + (req.timing.totalResponseTime || 0)
}, 0)
return {
name: hostname,
count: requests.length,
percentage: (requests.length / httpRequests.length) * 100,
totalSize,
sizePercentage: totalAllSize > 0 ? (totalSize / totalAllSize) * 100 : 0,
totalResponseTime,
responseTimePercentage: totalAllResponseTime > 0 ? (totalResponseTime / totalAllResponseTime) * 100 : 0,
averageResponseTime: totalResponseTime / requests.length
}
}).sort((a, b) => b.count - a.count)
}, [httpRequests])
const formatSize = (bytes: number): string => {
if (bytes < 1024) return `${bytes} B`
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
return `${(bytes / 1024 / 1024).toFixed(1)} MB`
}
const formatDuration = (microseconds: number): string => {
if (microseconds < 1000) return `${microseconds.toFixed(0)} μs`
if (microseconds < 1000000) return `${(microseconds / 1000).toFixed(1)} ms`
return `${(microseconds / 1000000).toFixed(2)} s`
}
if (loading) {
return (
@ -247,212 +102,17 @@ const RequestBreakdown: React.FC<RequestBreakdownProps> = ({ traceId }) => {
<h2>Request Data Breakdown</h2>
{/* Overall Stats */}
<div className={styles.statsGrid}>
<div className={styles.statCard}>
<h3>Total Requests</h3>
<div className={styles.statValue}>{overallStats.totalRequests}</div>
</div>
<div className={styles.statCard}>
<h3>Total Size</h3>
<div className={styles.statValue}>{formatSize(overallStats.totalSize)}</div>
</div>
<div className={styles.statCard}>
<h3>Average Response Time</h3>
<div className={styles.statValue}>{formatDuration(overallStats.averageResponseTime)}</div>
</div>
<div className={styles.statCard}>
<h3>Success Rate</h3>
<div className={styles.statValue}>{overallStats.successRate.toFixed(1)}%</div>
</div>
<div className={styles.statCard}>
<h3>Cache Hit Rate</h3>
<div className={styles.statValue}>{overallStats.cacheHitRate.toFixed(1)}%</div>
</div>
</div>
<RequestDataSummary
totalRequests={overallStats.totalRequests}
totalSize={overallStats.totalSize}
averageResponseTime={overallStats.averageResponseTime}
successRate={overallStats.successRate}
cacheHitRate={overallStats.cacheHitRate}
/>
{/* Resource Type Breakdown */}
<div className={styles.breakdownSection}>
<h3>By Resource Type</h3>
<div className={styles.breakdownTable}>
<div className={styles.tableHeader}>
<span>Type</span>
<span>Count</span>
<span>Count %</span>
<span>Total Size</span>
<span>Size %</span>
<span>Total Response Time</span>
<span>Response Time %</span>
<span>Avg Response Time</span>
</div>
{resourceTypeBreakdown.map(item => (
<div key={item.name} className={styles.tableRow}>
<span className={styles.categoryName}>{item.name}</span>
<span>{item.count}</span>
<div className={styles.percentageCell}>
<span>{item.percentage.toFixed(1)}%</span>
<div className={styles.progressBar}>
<div
className={styles.progressFill}
style={{ width: `${item.percentage}%` }}
></div>
</div>
</div>
<span>{formatSize(item.totalSize)}</span>
<div className={styles.percentageCell}>
<span>{item.sizePercentage.toFixed(1)}%</span>
<div className={styles.progressBar}>
<div
className={styles.progressFill}
style={{ width: `${item.sizePercentage}%` }}
></div>
</div>
</div>
<span>{formatDuration(item.totalResponseTime)}</span>
<div className={styles.percentageCell}>
<span>{item.responseTimePercentage.toFixed(1)}%</span>
<div className={styles.progressBar}>
<div
className={styles.progressFill}
style={{ width: `${item.responseTimePercentage}%` }}
></div>
</div>
</div>
<span>{formatDuration(item.averageResponseTime)}</span>
</div>
))}
</div>
</div>
{/* Status Code Breakdown */}
<div className={styles.breakdownSection}>
<h3>By Status Code</h3>
<div className={styles.breakdownTable}>
<div className={styles.tableHeader}>
<span>Status</span>
<span>Count</span>
<span>Count %</span>
<span>Total Size</span>
<span>Size %</span>
<span>Total Response Time</span>
<span>Response Time %</span>
<span>Avg Response Time</span>
</div>
{statusCodeBreakdown.map(item => (
<div key={item.name} className={styles.tableRow}>
<span className={styles.categoryName}>{item.name}</span>
<span>{item.count}</span>
<div className={styles.percentageCell}>
<span>{item.percentage.toFixed(1)}%</span>
<div className={styles.progressBar}>
<div
className={styles.progressFill}
style={{ width: `${item.percentage}%` }}
></div>
</div>
</div>
<span>{formatSize(item.totalSize)}</span>
<div className={styles.percentageCell}>
<span>{item.sizePercentage.toFixed(1)}%</span>
<div className={styles.progressBar}>
<div
className={styles.progressFill}
style={{ width: `${item.sizePercentage}%` }}
></div>
</div>
</div>
<span>{formatDuration(item.totalResponseTime)}</span>
<div className={styles.percentageCell}>
<span>{item.responseTimePercentage.toFixed(1)}%</span>
<div className={styles.progressBar}>
<div
className={styles.progressFill}
style={{ width: `${item.responseTimePercentage}%` }}
></div>
</div>
</div>
<span>{formatDuration(item.averageResponseTime)}</span>
</div>
))}
</div>
</div>
{/* Hostname Breakdown */}
<div className={styles.breakdownSection}>
<h3>By Hostname</h3>
<div className={styles.breakdownTable}>
<div className={styles.tableHeader}>
<span>Hostname</span>
<span>Count</span>
<span>Count %</span>
<span>Total Size</span>
<span>Size %</span>
<span>Total Response Time</span>
<span>Response Time %</span>
<span>Avg Response Time</span>
</div>
{(showAllHostnames ? hostnameBreakdown : hostnameBreakdown.slice(0, 10)).map(item => (
<div key={item.name} className={styles.tableRow}>
<span className={styles.categoryName}>{item.name}</span>
<span>{item.count}</span>
<div className={styles.percentageCell}>
<span>{item.percentage.toFixed(1)}%</span>
<div className={styles.progressBar}>
<div
className={styles.progressFill}
style={{ width: `${item.percentage}%` }}
></div>
</div>
</div>
<span>{formatSize(item.totalSize)}</span>
<div className={styles.percentageCell}>
<span>{item.sizePercentage.toFixed(1)}%</span>
<div className={styles.progressBar}>
<div
className={styles.progressFill}
style={{ width: `${item.sizePercentage}%` }}
></div>
</div>
</div>
<span>{formatDuration(item.totalResponseTime)}</span>
<div className={styles.percentageCell}>
<span>{item.responseTimePercentage.toFixed(1)}%</span>
<div className={styles.progressBar}>
<div
className={styles.progressFill}
style={{ width: `${item.responseTimePercentage}%` }}
></div>
</div>
</div>
<span>{formatDuration(item.averageResponseTime)}</span>
</div>
))}
</div>
{hostnameBreakdown.length > 10 && (
<div className={styles.moreInfo}>
{showAllHostnames ? (
<>
Showing all {hostnameBreakdown.length} hostnames
<button
onClick={() => setShowAllHostnames(false)}
className={styles.toggleButton}
>
Show Top 10 Only
</button>
</>
) : (
<>
Showing top 10 of {hostnameBreakdown.length} hostnames
<button
onClick={() => setShowAllHostnames(true)}
className={styles.toggleButton}
>
Show All Hostnames
</button>
</>
)}
</div>
)}
</div>
<ResourceTypeBreakdown httpRequests={httpRequests} />
<StatusCodeBreakdown httpRequests={httpRequests} />
<HostnameBreakdown httpRequests={httpRequests} />
</div>
)
}

View File

@ -0,0 +1,23 @@
import React from 'react'
import styles from '../RequestBreakdown.module.css'
interface BreakdownTableHeaderProps {
categoryLabel: string
}
const BreakdownTableHeader: React.FC<BreakdownTableHeaderProps> = ({ categoryLabel }) => {
return (
<div className={styles.tableHeader}>
<span>{categoryLabel}</span>
<span>Request Count</span>
<span>Request Count %</span>
<span>Total Size</span>
<span>Total Size %</span>
<span>Total Response Time</span>
<span>Total Response Time %</span>
<span>Avg Response Time</span>
</div>
)
}
export default BreakdownTableHeader

View File

@ -0,0 +1,149 @@
import React, { useMemo, useState } from 'react'
import type { HTTPRequest } from '../httprequestviewer/types/httpRequest'
import BreakdownTableHeader from './BreakdownTableHeader'
import styles from '../RequestBreakdown.module.css'
interface CategoryBreakdown {
name: string
count: number
percentage: number
totalSize: number
sizePercentage: number
totalResponseTime: number
responseTimePercentage: number
averageResponseTime: number
}
interface HostnameBreakdownProps {
httpRequests: HTTPRequest[]
}
const HostnameBreakdown: React.FC<HostnameBreakdownProps> = ({ httpRequests }) => {
const [showAllHostnames, setShowAllHostnames] = useState(false)
const formatSize = (bytes: number): string => {
if (bytes < 1024) return `${bytes} B`
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
return `${(bytes / 1024 / 1024).toFixed(1)} MB`
}
const formatDuration = (microseconds: number): string => {
if (microseconds < 1000) return `${microseconds.toFixed(0)} μs`
if (microseconds < 1000000) return `${(microseconds / 1000).toFixed(1)} ms`
return `${(microseconds / 1000000).toFixed(2)} s`
}
const hostnameBreakdown: CategoryBreakdown[] = useMemo(() => {
const hostMap = new Map<string, HTTPRequest[]>()
httpRequests.forEach(req => {
const hostname = req.hostname || 'unknown'
if (!hostMap.has(hostname)) {
hostMap.set(hostname, [])
}
hostMap.get(hostname)!.push(req)
})
// Calculate total size and response time across all requests for percentage calculations
const totalAllSize = httpRequests.reduce((sum, req) => {
return sum + (req.contentLength || req.encodedDataLength || 0)
}, 0)
const totalAllResponseTime = httpRequests.reduce((sum, req) => {
return sum + (req.timing.totalResponseTime || 0)
}, 0)
return Array.from(hostMap.entries()).map(([hostname, requests]) => {
const totalSize = requests.reduce((sum, req) => {
return sum + (req.contentLength || req.encodedDataLength || 0)
}, 0)
const totalResponseTime = requests.reduce((sum, req) => {
return sum + (req.timing.totalResponseTime || 0)
}, 0)
return {
name: hostname,
count: requests.length,
percentage: (requests.length / httpRequests.length) * 100,
totalSize,
sizePercentage: totalAllSize > 0 ? (totalSize / totalAllSize) * 100 : 0,
totalResponseTime,
responseTimePercentage: totalAllResponseTime > 0 ? (totalResponseTime / totalAllResponseTime) * 100 : 0,
averageResponseTime: totalResponseTime / requests.length
}
}).sort((a, b) => b.count - a.count)
}, [httpRequests])
return (
<div className={styles.breakdownSection}>
<h3>By Hostname</h3>
<div className={styles.breakdownTable}>
<BreakdownTableHeader categoryLabel="Hostname" />
{(showAllHostnames ? hostnameBreakdown : hostnameBreakdown.slice(0, 10)).map(item => (
<div key={item.name} className={styles.tableRow}>
<span className={styles.categoryName}>{item.name}</span>
<span>{item.count}</span>
<div className={styles.percentageCell}>
<span>{item.percentage.toFixed(1)}%</span>
<div className={styles.progressBar}>
<div
className={styles.progressFill}
style={{ width: `${item.percentage}%` }}
></div>
</div>
</div>
<span>{formatSize(item.totalSize)}</span>
<div className={styles.percentageCell}>
<span>{item.sizePercentage.toFixed(1)}%</span>
<div className={styles.progressBar}>
<div
className={styles.progressFill}
style={{ width: `${item.sizePercentage}%` }}
></div>
</div>
</div>
<span>{formatDuration(item.totalResponseTime)}</span>
<div className={styles.percentageCell}>
<span>{item.responseTimePercentage.toFixed(1)}%</span>
<div className={styles.progressBar}>
<div
className={styles.progressFill}
style={{ width: `${item.responseTimePercentage}%` }}
></div>
</div>
</div>
<span>{formatDuration(item.averageResponseTime)}</span>
</div>
))}
</div>
{hostnameBreakdown.length > 10 && (
<div className={styles.moreInfo}>
{showAllHostnames ? (
<>
Showing all {hostnameBreakdown.length} hostnames
<button
onClick={() => setShowAllHostnames(false)}
className={styles.toggleButton}
>
Show Top 10 Only
</button>
</>
) : (
<>
Showing top 10 of {hostnameBreakdown.length} hostnames
<button
onClick={() => setShowAllHostnames(true)}
className={styles.toggleButton}
>
Show All Hostnames
</button>
</>
)}
</div>
)}
</div>
)
}
export default HostnameBreakdown

View File

@ -0,0 +1,57 @@
import React from 'react'
import styles from '../RequestBreakdown.module.css'
interface RequestDataSummaryProps {
totalRequests: number
totalSize: number
averageResponseTime: number
successRate: number
cacheHitRate: number
}
const RequestDataSummary: React.FC<RequestDataSummaryProps> = ({
totalRequests,
totalSize,
averageResponseTime,
successRate,
cacheHitRate
}) => {
const formatSize = (bytes: number): string => {
if (bytes < 1024) return `${bytes} B`
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
return `${(bytes / 1024 / 1024).toFixed(1)} MB`
}
const formatDuration = (microseconds: number): string => {
if (microseconds < 1000) return `${microseconds.toFixed(0)} μs`
if (microseconds < 1000000) return `${(microseconds / 1000).toFixed(1)} ms`
return `${(microseconds / 1000000).toFixed(2)} s`
}
return (
<div className={styles.statsGrid}>
<div className={styles.statCard}>
<h3>Total Requests</h3>
<div className={styles.statValue}>{totalRequests}</div>
</div>
<div className={styles.statCard}>
<h3>Total Size</h3>
<div className={styles.statValue}>{formatSize(totalSize)}</div>
</div>
<div className={styles.statCard}>
<h3>Average Response Time</h3>
<div className={styles.statValue}>{formatDuration(averageResponseTime)}</div>
</div>
<div className={styles.statCard}>
<h3>Success Rate</h3>
<div className={styles.statValue}>{successRate.toFixed(1)}%</div>
</div>
<div className={styles.statCard}>
<h3>Cache Hit Rate</h3>
<div className={styles.statValue}>{cacheHitRate.toFixed(1)}%</div>
</div>
</div>
)
}
export default RequestDataSummary

View File

@ -0,0 +1,122 @@
import React, { useMemo } from 'react'
import type { HTTPRequest } from '../httprequestviewer/types/httpRequest'
import BreakdownTableHeader from './BreakdownTableHeader'
import styles from '../RequestBreakdown.module.css'
interface CategoryBreakdown {
name: string
count: number
percentage: number
totalSize: number
sizePercentage: number
totalResponseTime: number
responseTimePercentage: number
averageResponseTime: number
}
interface ResourceTypeBreakdownProps {
httpRequests: HTTPRequest[]
}
const ResourceTypeBreakdown: React.FC<ResourceTypeBreakdownProps> = ({ httpRequests }) => {
const formatSize = (bytes: number): string => {
if (bytes < 1024) return `${bytes} B`
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
return `${(bytes / 1024 / 1024).toFixed(1)} MB`
}
const formatDuration = (microseconds: number): string => {
if (microseconds < 1000) return `${microseconds.toFixed(0)} μs`
if (microseconds < 1000000) return `${(microseconds / 1000).toFixed(1)} ms`
return `${(microseconds / 1000000).toFixed(2)} s`
}
const resourceTypeBreakdown: CategoryBreakdown[] = useMemo(() => {
const typeMap = new Map<string, HTTPRequest[]>()
httpRequests.forEach(req => {
const type = req.resourceType || 'unknown'
if (!typeMap.has(type)) {
typeMap.set(type, [])
}
typeMap.get(type)!.push(req)
})
// Calculate total size and response time across all requests for percentage calculations
const totalAllSize = httpRequests.reduce((sum, req) => {
return sum + (req.contentLength || req.encodedDataLength || 0)
}, 0)
const totalAllResponseTime = httpRequests.reduce((sum, req) => {
return sum + (req.timing.totalResponseTime || 0)
}, 0)
return Array.from(typeMap.entries()).map(([type, requests]) => {
const totalSize = requests.reduce((sum, req) => {
return sum + (req.contentLength || req.encodedDataLength || 0)
}, 0)
const totalResponseTime = requests.reduce((sum, req) => {
return sum + (req.timing.totalResponseTime || 0)
}, 0)
return {
name: type,
count: requests.length,
percentage: (requests.length / httpRequests.length) * 100,
totalSize,
sizePercentage: totalAllSize > 0 ? (totalSize / totalAllSize) * 100 : 0,
totalResponseTime,
responseTimePercentage: totalAllResponseTime > 0 ? (totalResponseTime / totalAllResponseTime) * 100 : 0,
averageResponseTime: totalResponseTime / requests.length
}
}).sort((a, b) => b.count - a.count)
}, [httpRequests])
return (
<div className={styles.breakdownSection}>
<h3>By Resource Type</h3>
<div className={styles.breakdownTable}>
<BreakdownTableHeader categoryLabel="Resource Type" />
{resourceTypeBreakdown.map(item => (
<div key={item.name} className={styles.tableRow}>
<span className={styles.categoryName}>{item.name}</span>
<span>{item.count}</span>
<div className={styles.percentageCell}>
<span>{item.percentage.toFixed(1)}%</span>
<div className={styles.progressBar}>
<div
className={styles.progressFill}
style={{ width: `${item.percentage}%` }}
></div>
</div>
</div>
<span>{formatSize(item.totalSize)}</span>
<div className={styles.percentageCell}>
<span>{item.sizePercentage.toFixed(1)}%</span>
<div className={styles.progressBar}>
<div
className={styles.progressFill}
style={{ width: `${item.sizePercentage}%` }}
></div>
</div>
</div>
<span>{formatDuration(item.totalResponseTime)}</span>
<div className={styles.percentageCell}>
<span>{item.responseTimePercentage.toFixed(1)}%</span>
<div className={styles.progressBar}>
<div
className={styles.progressFill}
style={{ width: `${item.responseTimePercentage}%` }}
></div>
</div>
</div>
<span>{formatDuration(item.averageResponseTime)}</span>
</div>
))}
</div>
</div>
)
}
export default ResourceTypeBreakdown

View File

@ -0,0 +1,122 @@
import React, { useMemo } from 'react'
import type { HTTPRequest } from '../httprequestviewer/types/httpRequest'
import BreakdownTableHeader from './BreakdownTableHeader'
import styles from '../RequestBreakdown.module.css'
interface CategoryBreakdown {
name: string
count: number
percentage: number
totalSize: number
sizePercentage: number
totalResponseTime: number
responseTimePercentage: number
averageResponseTime: number
}
interface StatusCodeBreakdownProps {
httpRequests: HTTPRequest[]
}
const StatusCodeBreakdown: React.FC<StatusCodeBreakdownProps> = ({ httpRequests }) => {
const formatSize = (bytes: number): string => {
if (bytes < 1024) return `${bytes} B`
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
return `${(bytes / 1024 / 1024).toFixed(1)} MB`
}
const formatDuration = (microseconds: number): string => {
if (microseconds < 1000) return `${microseconds.toFixed(0)} μs`
if (microseconds < 1000000) return `${(microseconds / 1000).toFixed(1)} ms`
return `${(microseconds / 1000000).toFixed(2)} s`
}
const statusCodeBreakdown: CategoryBreakdown[] = useMemo(() => {
const statusMap = new Map<string, HTTPRequest[]>()
httpRequests.forEach(req => {
const status = req.statusCode ? Math.floor(req.statusCode / 100) + 'xx' : 'Unknown'
if (!statusMap.has(status)) {
statusMap.set(status, [])
}
statusMap.get(status)!.push(req)
})
// Calculate total size and response time across all requests for percentage calculations
const totalAllSize = httpRequests.reduce((sum, req) => {
return sum + (req.contentLength || req.encodedDataLength || 0)
}, 0)
const totalAllResponseTime = httpRequests.reduce((sum, req) => {
return sum + (req.timing.totalResponseTime || 0)
}, 0)
return Array.from(statusMap.entries()).map(([status, requests]) => {
const totalSize = requests.reduce((sum, req) => {
return sum + (req.contentLength || req.encodedDataLength || 0)
}, 0)
const totalResponseTime = requests.reduce((sum, req) => {
return sum + (req.timing.totalResponseTime || 0)
}, 0)
return {
name: status,
count: requests.length,
percentage: (requests.length / httpRequests.length) * 100,
totalSize,
sizePercentage: totalAllSize > 0 ? (totalSize / totalAllSize) * 100 : 0,
totalResponseTime,
responseTimePercentage: totalAllResponseTime > 0 ? (totalResponseTime / totalAllResponseTime) * 100 : 0,
averageResponseTime: totalResponseTime / requests.length
}
}).sort((a, b) => b.count - a.count)
}, [httpRequests])
return (
<div className={styles.breakdownSection}>
<h3>By Status Code</h3>
<div className={styles.breakdownTable}>
<BreakdownTableHeader categoryLabel="Status Code" />
{statusCodeBreakdown.map(item => (
<div key={item.name} className={styles.tableRow}>
<span className={styles.categoryName}>{item.name}</span>
<span>{item.count}</span>
<div className={styles.percentageCell}>
<span>{item.percentage.toFixed(1)}%</span>
<div className={styles.progressBar}>
<div
className={styles.progressFill}
style={{ width: `${item.percentage}%` }}
></div>
</div>
</div>
<span>{formatSize(item.totalSize)}</span>
<div className={styles.percentageCell}>
<span>{item.sizePercentage.toFixed(1)}%</span>
<div className={styles.progressBar}>
<div
className={styles.progressFill}
style={{ width: `${item.sizePercentage}%` }}
></div>
</div>
</div>
<span>{formatDuration(item.totalResponseTime)}</span>
<div className={styles.percentageCell}>
<span>{item.responseTimePercentage.toFixed(1)}%</span>
<div className={styles.progressBar}>
<div
className={styles.progressFill}
style={{ width: `${item.responseTimePercentage}%` }}
></div>
</div>
</div>
<span>{formatDuration(item.averageResponseTime)}</span>
</div>
))}
</div>
</div>
)
}
export default StatusCodeBreakdown