diff --git a/src/components/RequestBreakdown/BreakdownTableHeader.tsx b/src/components/RequestBreakdown/BreakdownTableHeader.tsx index da52729..30fd5e2 100644 --- a/src/components/RequestBreakdown/BreakdownTableHeader.tsx +++ b/src/components/RequestBreakdown/BreakdownTableHeader.tsx @@ -1,21 +1,121 @@ import React from 'react' import styles from './RequestBreakdown.module.css' +export type SortColumn = 'name' | 'count' | 'percentage' | 'totalSize' | 'sizePercentage' | 'totalResponseTime' | 'responseTimePercentage' | 'averageResponseTime' +export type SortDirection = 'asc' | 'desc' + interface BreakdownTableHeaderProps { categoryLabel: string + sortColumn: SortColumn | null + sortDirection: SortDirection + onSort: (column: SortColumn) => void } -const BreakdownTableHeader: React.FC = ({ categoryLabel }) => { +const BreakdownTableHeader: React.FC = ({ + categoryLabel, + sortColumn, + sortDirection, + onSort +}) => { + const getSortIcon = (column: SortColumn) => { + if (sortColumn !== column) { + return + } + return sortDirection === 'asc' ? + : + + } + + const getTooltipText = (column: SortColumn, label: string) => { + if (sortColumn === column) { + const oppositeDirection = sortDirection === 'asc' ? 'descending' : 'ascending' + return `Click to sort ${label.toLowerCase()} ${oppositeDirection}` + } + return `Click to sort by ${label.toLowerCase()}` + } + return (
- {categoryLabel} - Request Count - Request Count % - Total Size - Total Size % - Total Response Time - Total Response Time % - Avg Response Time + + + + + + + +
) } diff --git a/src/components/RequestBreakdown/HostnameBreakdown.tsx b/src/components/RequestBreakdown/HostnameBreakdown.tsx index 33b2b57..50c7905 100644 --- a/src/components/RequestBreakdown/HostnameBreakdown.tsx +++ b/src/components/RequestBreakdown/HostnameBreakdown.tsx @@ -1,6 +1,6 @@ import React, { useMemo, useState } from 'react' import type { HTTPRequest } from '../httprequestviewer/types/httpRequest' -import BreakdownTableHeader from './BreakdownTableHeader' +import BreakdownTableHeader, { type SortColumn, type SortDirection } from './BreakdownTableHeader' import styles from './RequestBreakdown.module.css' interface CategoryBreakdown { @@ -20,6 +20,8 @@ interface HostnameBreakdownProps { const HostnameBreakdown: React.FC = ({ httpRequests }) => { const [showAllHostnames, setShowAllHostnames] = useState(false) + const [sortColumn, setSortColumn] = useState('count') + const [sortDirection, setSortDirection] = useState('desc') const formatSize = (bytes: number): string => { if (bytes < 1024) return `${bytes} B` @@ -33,7 +35,7 @@ const HostnameBreakdown: React.FC = ({ httpRequests }) = return `${(microseconds / 1000000).toFixed(2)} s` } - const hostnameBreakdown: CategoryBreakdown[] = useMemo(() => { + const baseHostnameBreakdown: CategoryBreakdown[] = useMemo(() => { const hostMap = new Map() httpRequests.forEach(req => { @@ -72,15 +74,83 @@ const HostnameBreakdown: React.FC = ({ httpRequests }) = responseTimePercentage: totalAllResponseTime > 0 ? (totalResponseTime / totalAllResponseTime) * 100 : 0, averageResponseTime: totalResponseTime / requests.length } - }).sort((a, b) => b.count - a.count) + }) }, [httpRequests]) + const handleSort = (column: SortColumn) => { + if (sortColumn === column) { + setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc') + } else { + setSortColumn(column) + setSortDirection('desc') + } + } + + const sortedHostnameBreakdown = useMemo(() => { + if (!sortColumn) return baseHostnameBreakdown + + return [...baseHostnameBreakdown].sort((a, b) => { + let aValue: string | number + let bValue: string | number + + switch (sortColumn) { + case 'name': + aValue = a.name + bValue = b.name + break + case 'count': + aValue = a.count + bValue = b.count + break + case 'percentage': + aValue = a.percentage + bValue = b.percentage + break + case 'totalSize': + aValue = a.totalSize + bValue = b.totalSize + break + case 'sizePercentage': + aValue = a.sizePercentage + bValue = b.sizePercentage + break + case 'totalResponseTime': + aValue = a.totalResponseTime + bValue = b.totalResponseTime + break + case 'responseTimePercentage': + aValue = a.responseTimePercentage + bValue = b.responseTimePercentage + break + case 'averageResponseTime': + aValue = a.averageResponseTime + bValue = b.averageResponseTime + break + default: + return 0 + } + + if (typeof aValue === 'string' && typeof bValue === 'string') { + const comparison = aValue.localeCompare(bValue) + return sortDirection === 'asc' ? comparison : -comparison + } else { + const comparison = (aValue as number) - (bValue as number) + return sortDirection === 'asc' ? comparison : -comparison + } + }) + }, [baseHostnameBreakdown, sortColumn, sortDirection]) + return (

By Hostname

- - {(showAllHostnames ? hostnameBreakdown : hostnameBreakdown.slice(0, 10)).map(item => ( + + {(showAllHostnames ? sortedHostnameBreakdown : sortedHostnameBreakdown.slice(0, 10)).map(item => (
{item.name} {item.count} @@ -117,11 +187,11 @@ const HostnameBreakdown: React.FC = ({ httpRequests }) =
))}
- {hostnameBreakdown.length > 10 && ( + {sortedHostnameBreakdown.length > 10 && (
{showAllHostnames ? ( <> - Showing all {hostnameBreakdown.length} hostnames + Showing all {sortedHostnameBreakdown.length} hostnames