import React, { useState } from 'react'; import { QuestionMarkHint, Tooltip, Tabs, Input, NoContent, Icon, Toggler, Button } from 'UI'; import { getRE } from 'App/utils'; import Resource, { TYPES } from 'Types/session/resource'; import { formatBytes } from 'App/utils'; import { formatMs } from 'App/date'; import TimeTable from '../TimeTable'; import BottomBlock from '../BottomBlock'; import InfoLine from '../BottomBlock/InfoLine'; import { Duration } from 'luxon'; import { connectPlayer, jump } from 'Player'; import { useModal } from 'App/components/Modal'; import FetchDetailsModal from 'Shared/FetchDetailsModal'; const ALL = 'ALL'; const XHR = 'xhr'; const JS = 'js'; const CSS = 'css'; const IMG = 'img'; const MEDIA = 'media'; const OTHER = 'other'; const TAB_TO_TYPE_MAP: any = { [XHR]: TYPES.XHR, [JS]: TYPES.JS, [CSS]: TYPES.CSS, [IMG]: TYPES.IMG, [MEDIA]: TYPES.MEDIA, [OTHER]: TYPES.OTHER, }; const TABS: any = [ALL, XHR, JS, CSS, IMG, MEDIA, OTHER].map((tab) => ({ text: tab === 'xhr' ? 'Fetch/XHR' : tab, key: tab, })); const DOM_LOADED_TIME_COLOR = 'teal'; const LOAD_TIME_COLOR = 'red'; function compare(a: any, b: any, key: string) { if (a[key] > b[key]) return 1; if (a[key] < b[key]) return -1; return 0; } export function renderType(r: any) { return ( {r.type}}>
{r.type}
); } export function renderName(r: any) { return ( {r.url}}>
{r.name}
); } export function renderStart(r: any) { return (
{Duration.fromMillis(r.time).toFormat('mm:ss.SSS')}
); } const renderXHRText = () => ( {XHR} Use our{' '} Fetch plugin {' to capture HTTP requests and responses, including status codes and bodies.'}
We also provide{' '} support for GraphQL {' for easy debugging of your queries.'} } className="ml-1" />
); function renderSize(r: any) { if (r.responseBodySize) return formatBytes(r.responseBodySize); let triggerText; let content; if (r.decodedBodySize == null) { triggerText = 'x'; content = 'Not captured'; } else { const headerSize = r.headerSize || 0; const encodedSize = r.encodedBodySize || 0; const transferred = headerSize + encodedSize; const showTransferred = r.headerSize != null; triggerText = formatBytes(r.decodedBodySize); content = ( ); } return (
{triggerText}
); } export function renderDuration(r: any) { if (!r.success) return 'x'; const text = `${Math.floor(r.duration)}ms`; if (!r.isRed() && !r.isYellow()) return text; let tooltipText; let className = 'w-full h-full flex items-center '; if (r.isYellow()) { tooltipText = 'Slower than average'; className += 'warn color-orange'; } else { tooltipText = 'Much slower than average'; className += 'error color-red'; } return (
{text}
); } interface Props { location: any; resources: any; fetchList: any; domContentLoadedTime: any; loadTime: any; playing: boolean; domBuildingTime: any; currentIndex: any; time: any; } function NetworkPanel(props: Props) { const { resources, time, currentIndex, domContentLoadedTime, loadTime, playing, domBuildingTime, fetchList, } = props; const { showModal, hideModal } = useModal(); const [activeTab, setActiveTab] = useState(ALL); const [sortBy, setSortBy] = useState('time'); const [sortAscending, setSortAscending] = useState(true); const [filter, setFilter] = useState(''); const [showOnlyErrors, setShowOnlyErrors] = useState(false); const onTabClick = (activeTab: any) => setActiveTab(activeTab); const onFilterChange = ({ target: { value } }: any) => setFilter(value); const additionalHeight = 0; const fetchPresented = fetchList.length > 0; const resourcesSize = resources.reduce( (sum: any, { decodedBodySize }: any) => sum + (decodedBodySize || 0), 0 ); const transferredSize = resources.reduce( (sum: any, { headerSize, encodedBodySize }: any) => sum + (headerSize || 0) + (encodedBodySize || 0), 0 ); const filterRE = getRE(filter, 'i'); let filtered = React.useMemo(() => { let list = resources; fetchList.forEach( (fetchCall: any) => (list = list.filter((networkCall: any) => networkCall.url !== fetchCall.url)) ); list = list.concat(fetchList); list = list.sort((a: any, b: any) => { return compare(a, b, sortBy); }); if (!sortAscending) { list = list.reverse(); } list = list.filter( ({ type, name, status, success }: any) => (!!filter ? filterRE.test(status) || filterRE.test(name) || filterRE.test(type) : true) && (activeTab === ALL || type === TAB_TO_TYPE_MAP[activeTab]) && (showOnlyErrors ? parseInt(status) >= 400 || !success : true) ); return list; }, [filter, sortBy, sortAscending, showOnlyErrors, activeTab]); // const lastIndex = currentIndex || filtered.filter((item: any) => item.time <= time).length - 1; const referenceLines = []; if (domContentLoadedTime != null) { referenceLines.push({ time: domContentLoadedTime.time, color: DOM_LOADED_TIME_COLOR, }); } if (loadTime != null) { referenceLines.push({ time: loadTime.time, color: LOAD_TIME_COLOR, }); } const onRowClick = (row: any) => { showModal(, { right: true, }); }; const handleSort = (sortKey: string) => { if (sortKey === sortBy) { setSortAscending(!sortAscending); // setSortBy('time'); } setSortBy(sortKey); }; return (
Network
setShowOnlyErrors(!showOnlyErrors)} label="4xx-5xx Only" />
0} /> 0} />
No Data } size="small" show={filtered.length === 0} > {[ // { // label: 'Start', // width: 120, // render: renderStart, // }, { label: 'Status', dataKey: 'status', width: 70, onClick: handleSort, }, { label: 'Type', dataKey: 'type', width: 90, render: renderType, onClick: handleSort, }, { label: 'Name', width: 240, dataKey: 'name', render: renderName, onClick: handleSort, }, { label: 'Size', width: 80, dataKey: 'decodedBodySize', render: renderSize, onClick: handleSort, hidden: activeTab === XHR, }, { label: 'Time', width: 80, dataKey: 'duration', render: renderDuration, onClick: handleSort, }, ]}
); } export default connectPlayer((state: any) => ({ location: state.location, resources: state.resourceList, fetchList: state.fetchList.map((i: any) => Resource({ ...i.toJS(), type: TYPES.XHR })), domContentLoadedTime: state.domContentLoadedTime, loadTime: state.loadTime, // time: state.time, playing: state.playing, domBuildingTime: state.domBuildingTime, }))(NetworkPanel);