diff --git a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx index ce9c4153f..bd835144d 100644 --- a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx +++ b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx @@ -181,39 +181,37 @@ function NetworkPanelCont({ panelHeight }: { panelHeight: number }) { const tabValues = Object.values(tabStates); const dataSource = uiPlayerStore.dataSource; const showSingleTab = dataSource === 'current'; - const { - fetchList = [], - resourceList = [], - fetchListNow = [], - resourceListNow = [], - websocketList = [], - websocketListNow = [], - } = React.useMemo(() => { - if (showSingleTab) { - return tabStates[currentTab] ?? {}; - } else { - const fetchList = tabValues.flatMap((tab) => tab.fetchList); - const resourceList = tabValues.flatMap((tab) => tab.resourceList); - const fetchListNow = tabValues - .flatMap((tab) => tab.fetchListNow) - .filter(Boolean); - const resourceListNow = tabValues - .flatMap((tab) => tab.resourceListNow) - .filter(Boolean); - const websocketList = tabValues.flatMap((tab) => tab.websocketList); - const websocketListNow = tabValues - .flatMap((tab) => tab.websocketListNow) - .filter(Boolean); - return { - fetchList, - resourceList, - fetchListNow, - resourceListNow, - websocketList, - websocketListNow, - }; - } - }, [currentTab, tabStates, dataSource, tabValues]); + + let fetchList = []; + let resourceList = []; + let fetchListNow = []; + let resourceListNow = []; + let websocketList = []; + let websocketListNow = []; + + if (showSingleTab) { + const state = tabStates[currentTab] ?? {}; + fetchList = state.fetchList ?? []; + resourceList = state.resourceList ?? []; + fetchListNow = state.fetchListNow ?? []; + resourceListNow = state.resourceListNow ?? []; + websocketList = state.websocketList ?? []; + websocketListNow = state.websocketListNow ?? []; + } else { + fetchList = tabValues.flatMap((tab) => tab.fetchList); + resourceList = tabValues.flatMap((tab) => tab.resourceList); + fetchListNow = tabValues + .flatMap((tab) => tab.fetchListNow) + .filter(Boolean); + resourceListNow = tabValues + .flatMap((tab) => tab.resourceListNow) + .filter(Boolean); + websocketList = tabValues.flatMap((tab) => tab.websocketList); + websocketListNow = tabValues + .flatMap((tab) => tab.websocketListNow) + .filter(Boolean); + } + const getTabNum = (tab: string) => tabsArr.findIndex((t) => t === tab) + 1; const getTabName = (tabId: string) => tabNames[tabId] return ( @@ -416,7 +414,7 @@ export const NetworkPanelComp = observer( : true ) .sort((a, b) => a.time - b.time), - [resourceList.length, fetchList.length, socketList] + [resourceList.length, fetchList.length, socketList.length] ); let filteredList = useMemo(() => { @@ -453,7 +451,7 @@ export const NetworkPanelComp = observer( activeIndex, (index) => devTools.update(INDEX_KEY, { index }) ); - const onMouseEnter = stopAutoscroll; + const onMouseEnter = () => stopAutoscroll; const onMouseLeave = () => { if (isDetailsModalActive) { return; diff --git a/frontend/app/components/shared/DevTools/TimeTable/BarRow.tsx b/frontend/app/components/shared/DevTools/TimeTable/BarRow.tsx index eaaebb754..9bbe8a84a 100644 --- a/frontend/app/components/shared/DevTools/TimeTable/BarRow.tsx +++ b/frontend/app/components/shared/DevTools/TimeTable/BarRow.tsx @@ -26,40 +26,7 @@ const BarRow = ({ }: Props) => { const timeOffset = time - timestart; ttfb = ttfb || 0; - // TODO fix the tooltip - const content = ( - - {ttfb != null && ( -
-
{'Waiting (TTFB)'}
-
-
-
-
{formatTime(ttfb)}
-
- )} -
-
{'Content Download'}
-
-
-
-
{formatTime(duration - ttfb)}
-
- - ); const trigger = (
React.Node -// } | { -// dataKey: string, -// } type RenderOrKey = | { render?: (row: Row) => React.ReactNode; @@ -79,12 +75,8 @@ type TimeLineInfo = { timewidth: number; }; -type State = TimeLineInfo & typeof initialState; - -//const TABLE_HEIGHT = 195; let _additionalHeight = 0; const ROW_HEIGHT = 24; -//const VISIBLE_COUNT = Math.ceil(TABLE_HEIGHT/ROW_HEIGHT); const TIME_SECTIONS_COUNT = 8; const ZERO_TIMEWIDTH = 1000; @@ -103,10 +95,13 @@ function computeTimeLine( firstVisibleRowIndex, firstVisibleRowIndex + visibleCount + _additionalHeight ); - let timestart = visibleRows.length > 0 ? Math.min(...visibleRows.map((r) => r.time)) : 0; + let timestart = + visibleRows.length > 0 ? Math.min(...visibleRows.map((r) => r.time)) : 0; // TODO: GraphQL requests do not have a duration, so their timeline is borked. Assume a duration of 0.2s for every GraphQL request const timeend = - visibleRows.length > 0 ? Math.max(...visibleRows.map((r) => r.time + (r.duration ?? 200))) : 0; + visibleRows.length > 0 + ? Math.max(...visibleRows.map((r) => r.time + (r.duration ?? 200))) + : 0; let timewidth = timeend - timestart; const offset = timewidth / 70; if (timestart >= offset) { @@ -122,245 +117,267 @@ function computeTimeLine( }; } -const initialState = { - firstVisibleRowIndex: 0, -}; +function TimeTable(props: Props) { + const tableHeight = props.tableHeight || 195; + const visibleCount = Math.ceil(tableHeight / ROW_HEIGHT); + const [timerange, setTimerange] = React.useState({ + timestart: 0, + timewidth: 0, + }); + const [firstVisibleRowIndex, setFirstVisibleRowIndex] = React.useState(0); + const scroller = React.createRef(); + const { timestart, timewidth } = timerange; -export default class TimeTable extends React.PureComponent { - state = { - ...computeTimeLine(this.props.rows, initialState.firstVisibleRowIndex, this.visibleCount), - ...initialState, - }; - - get tableHeight() { - return this.props.tableHeight || 195; - } - - get visibleCount() { - return Math.ceil(this.tableHeight / ROW_HEIGHT); - } - - scroller = React.createRef(); - autoScroll = true; - - adjustScroll(prevActiveIndex: number) { - if ( - this.props.activeIndex && - this.props.activeIndex >= 0 && - prevActiveIndex !== this.props.activeIndex && - this.scroller.current - ) { - this.scroller.current.scrollToIndex(this.props.activeIndex); + React.useEffect(() => { + const { timestart, timewidth } = computeTimeLine( + props.rows, + firstVisibleRowIndex, + visibleCount + ); + setTimerange({ timestart, timewidth }); + }, [ + props.rows.length, + visibleCount, + _additionalHeight, + firstVisibleRowIndex, + ]); + React.useEffect(() => { + if (props.activeIndex && props.activeIndex >= 0 && scroller.current) { + scroller.current.scrollToIndex(props.activeIndex); + setFirstVisibleRowIndex(props.activeIndex ?? 0); } - } + }, [props.activeIndex]); - componentDidUpdate(prevProps: any, prevState: any) { - if ( - prevState.firstVisibleRowIndex !== this.state.firstVisibleRowIndex || - (this.props.rows.length <= this.visibleCount + _additionalHeight && - prevProps.rows.length !== this.props.rows.length) - ) { - this.setState({ - ...computeTimeLine(this.props.rows, this.state.firstVisibleRowIndex, this.visibleCount), - }); - } - - // this.adjustScroll(prevProps.activeIndex); - } - - onScroll = ({ - scrollTop, - scrollHeight, - clientHeight, - }: { - scrollTop: number; - scrollHeight: number; - clientHeight: number; - }): void => { - const firstVisibleRowIndex = Math.floor(scrollTop / ROW_HEIGHT + 0.33); - - if (this.state.firstVisibleRowIndex !== firstVisibleRowIndex) { - this.autoScroll = scrollHeight - clientHeight - scrollTop < ROW_HEIGHT / 2; - this.setState({ firstVisibleRowIndex }); + const onJump = (index: any) => { + if (props.onJump) { + props.onJump(props.rows[index]); } }; - onJump = (index: any) => { - if (this.props.onJump) { - this.props.onJump(this.props.rows[index]); + const onPrevClick = () => { + let prevRedIndex = -1; + for (let i = firstVisibleRowIndex - 1; i >= 0; i--) { + if (props.rows[i].isRed) { + prevRedIndex = i; + break; + } + } + if (scroller.current != null) { + scroller.current.scrollToIndex(prevRedIndex); } }; - renderRow = (index: number) => { - const { activeIndex } = this.props; - const { children: columns, rows, renderPopup, hoverable, onRowClick } = this.props; - const { timestart, timewidth } = this.state; - const row = rows[index]; - return ( -
activeIndex, - } - )} - onClick={typeof onRowClick === 'function' ? () => onRowClick(row, index) : undefined} - id="table-row" - > - {columns - .filter((i: any) => !i.hidden) - .map(({ dataKey, render, width, label }) => ( -
- {render - ? render(row) - : row[dataKey || ''] || {'empty'}} + const onNextClick = () => { + let prevRedIndex = -1; + for (let i = firstVisibleRowIndex + 1; i < props.rows.length; i++) { + if (props.rows[i].isRed) { + prevRedIndex = i; + break; + } + } + if (scroller.current != null) { + scroller.current.scrollToIndex(prevRedIndex); + } + }; + + const { + className, + rows, + navigation = false, + referenceLines = [], + additionalHeight = 0, + renderPopup, + hoverable, + onRowClick, + activeIndex, + } = props; + const columns = props.children.filter((i: any) => !i.hidden); + + _additionalHeight = additionalHeight; + + const sectionDuration = Math.round(timewidth / TIME_SECTIONS_COUNT); + const timeColumns: number[] = []; + if (timewidth > 0) { + for (let i = 0; i < TIME_SECTIONS_COUNT; i++) { + timeColumns.push(timestart + i * sectionDuration); + } + } + + const visibleRefLines = referenceLines.filter( + ({ time }) => time > timestart && time < timestart + timewidth + ); + + const columnsSumWidth = columns.reduce((sum, { width }) => sum + width, 0); + + return ( +
+ {navigation && ( +
+
+ )} +
+
+ {columns.map(({ label, width, dataKey, onClick = null }) => ( +
onColumnClick(dataKey, onClick)} + > + {label}
))} -
-
- this.onJump(index)} /> -
- ); - }; - - onPrevClick = () => { - let prevRedIndex = -1; - for (let i = this.state.firstVisibleRowIndex - 1; i >= 0; i--) { - if (this.props.rows[i].isRed) { - prevRedIndex = i; - break; - } - } - if (this.scroller.current != null) { - this.scroller.current.scrollToIndex(prevRedIndex); - } - }; - - onNextClick = () => { - let prevRedIndex = -1; - for (let i = this.state.firstVisibleRowIndex + 1; i < this.props.rows.length; i++) { - if (this.props.rows[i].isRed) { - prevRedIndex = i; - break; - } - } - if (this.scroller.current != null) { - this.scroller.current.scrollToIndex(prevRedIndex); - } - }; - - onColumnClick = (dataKey: string, onClick: any) => { - if (typeof onClick === 'function') { - onClick(dataKey); - } - }; - - render() { - const { - className, - rows, - navigation = false, - referenceLines = [], - additionalHeight = 0, - activeIndex, - } = this.props; - const columns = this.props.children.filter((i: any) => !i.hidden); - const { timewidth, timestart } = this.state; - - _additionalHeight = additionalHeight; - - const sectionDuration = Math.round(timewidth / TIME_SECTIONS_COUNT); - const timeColumns: number[] = []; - if (timewidth > 0) { - for (let i = 0; i < TIME_SECTIONS_COUNT; i++) { - timeColumns.push(timestart + i * sectionDuration); - } - } - - const visibleRefLines = referenceLines.filter( - ({ time }) => time > timestart && time < timestart + timewidth - ); - - const columnsSumWidth = columns.reduce((sum, { width }) => sum + width, 0); - - return ( -
- {navigation && ( -
-
- )} -
-
- {columns.map(({ label, width, dataKey, onClick = null }) => ( -
this.onColumnClick(dataKey, onClick)} - > - {label} -
- ))} -
-
- {timeColumns.map((time, i) => ( -
- {formatTime(time)} -
- ))} -
-
- - -
-
- {timeColumns.map((_, index) => ( -
- ))} - {visibleRefLines.map(({ time, color, onClick }) => ( -
- ))} +
+ {timeColumns.map((time, i) => ( +
+ {formatTime(time)}
- - {this.props.rows.map((_, index) => this.renderRow(index))} - -
- + ))} +
- ); - } + + +
+
+ {timeColumns.map((_, index) => ( +
+ ))} + {visibleRefLines.map(({ time, color, onClick }) => ( +
+ ))} +
+ { + const firstVisibleRowIndex = Math.floor( + offset / ROW_HEIGHT + 0.33 + ); + setFirstVisibleRowIndex(firstVisibleRowIndex); + }} + > + {(index) => ( + + )} + +
+ +
+ ); } + +function RowRenderer({ + index, + row, + columns, + timestart, + timewidth, + renderPopup, + hoverable, + onRowClick, + activeIndex, + onJump, +}: any) { + return ( +
activeIndex, + } + )} + onClick={ + typeof onRowClick === 'function' + ? () => onRowClick(row, index) + : undefined + } + id="table-row" + > + +
+ +
+ onJump(index)} /> +
+ ); +} + +const RowColumns = React.memo(({ columns, row }: any) => { + return columns.map(({ dataKey, render, width, label }: any) => ( +
+ {render + ? render(row) + : row[dataKey || ''] || ( + {'empty'} + )} +
+ )) +}) + +export default observer(TimeTable); diff --git a/frontend/app/components/shared/DevTools/useAutoscroll.ts b/frontend/app/components/shared/DevTools/useAutoscroll.ts index 5225f59ea..4a65ad877 100644 --- a/frontend/app/components/shared/DevTools/useAutoscroll.ts +++ b/frontend/app/components/shared/DevTools/useAutoscroll.ts @@ -16,9 +16,9 @@ function useAutoupdate( updadteValue: (value: T) => void, ) { const [ autoupdate, setAutoupdate ] = useState(savedValue === resetValue) - + const [ timeoutStartAutoupdate, stopAutoupdate ] = useCancelableTimeout( - () => setAutoupdate(true), + () => setAutoupdate(true), () => setAutoupdate(false), TIMEOUT_DURATION, ) @@ -61,4 +61,4 @@ export default function useAutoscroll( }, [ time, filteredList ]) return useAutoupdate(savedIndex, filteredIndexNow, 0, updadteIndex) -} \ No newline at end of file +} diff --git a/frontend/app/player/web/types/resource.ts b/frontend/app/player/web/types/resource.ts index 45c0dfc64..7729b4d00 100644 --- a/frontend/app/player/web/types/resource.ts +++ b/frontend/app/player/web/types/resource.ts @@ -131,6 +131,7 @@ export function getResourceFromResourceTiming(msg: ResourceTiming, sessStart: nu // duration might be duration=0 when cached const failed = msg.duration === 0 && msg.ttfb === 0 && msg.headerSize === 0 && msg.encodedBodySize === 0 && msg.transferredSize === 0 const type = getResourceType(msg.initiator, msg.url) + console.log(msg.url, msg.timestamp - sessStart) return Resource({ ...msg, type, @@ -142,6 +143,7 @@ export function getResourceFromResourceTiming(msg: ResourceTiming, sessStart: nu } export function getResourceFromNetworkRequest(msg: NetworkRequest | Fetch | MobileNetworkCall, sessStart: number) { + console.log(msg.url, msg.timestamp - sessStart) return Resource({ ...msg, // @ts-ignore