diff --git a/frontend/app/components/Session/Player/MobilePlayer/MobileControls.tsx b/frontend/app/components/Session/Player/MobilePlayer/MobileControls.tsx index 78f9d47d4..275f3e6e2 100644 --- a/frontend/app/components/Session/Player/MobilePlayer/MobileControls.tsx +++ b/frontend/app/components/Session/Player/MobilePlayer/MobileControls.tsx @@ -28,6 +28,7 @@ import { import { useStore } from 'App/mstore'; import { session as sessionRoute, withSiteId } from 'App/routes'; import { SummaryButton } from 'Components/Session_/Player/Controls/Controls'; +import { MobEventsList, WebEventsList } from "../../../Session_/Player/Controls/EventsList"; import useShortcuts from '../ReplayPlayer/useShortcuts'; export const SKIP_INTERVALS = { diff --git a/frontend/app/components/Session/Player/SharedComponents/BackendLogs/BackendLogsPanel.tsx b/frontend/app/components/Session/Player/SharedComponents/BackendLogs/BackendLogsPanel.tsx index bc3880dc7..45807f5b2 100644 --- a/frontend/app/components/Session/Player/SharedComponents/BackendLogs/BackendLogsPanel.tsx +++ b/frontend/app/components/Session/Player/SharedComponents/BackendLogs/BackendLogsPanel.tsx @@ -91,6 +91,7 @@ function BackendLogsPanel() { ) : null}
+ { + if (showSingleTab) { + const stackEventList = tabStates[currentTab].stackList; + const frustrationsList = tabStates[currentTab].frustrationsList; + const exceptionsList = tabStates[currentTab].exceptionsList; + const resourceListUnmap = tabStates[currentTab].resourceList; + const fetchList = tabStates[currentTab].fetchList; + const graphqlList = tabStates[currentTab].graphqlList; + const performanceChartData = + tabStates[currentTab].performanceChartData; + + return { + stackEventList, + frustrationsList, + exceptionsList, + resourceListUnmap, + fetchList, + graphqlList, + performanceChartData, + } + } else { + const stackEventList = tabValues.flatMap((tab) => tab.stackList); + // these two are global + const frustrationsList = tabValues[0].frustrationsList; + const exceptionsList = tabValues[0].exceptionsList; + // we can't compute global chart data because some tabs coexist + const performanceChartData: any = []; + const resourceListUnmap = tabValues.flatMap((tab) => tab.resourceList); + const fetchList = tabValues.flatMap((tab) => tab.fetchList); + const graphqlList = tabValues.flatMap((tab) => tab.graphqlList); + + return { + stackEventList, + frustrationsList, + exceptionsList, + resourceListUnmap, + fetchList, + graphqlList, + performanceChartData, + } + } + }, [tabStates, currentTab, dataSource, tabValues]); + + console.log(showSingleTab, frustrationsList, performanceChartData); const fetchPresented = fetchList.length > 0; const resourceList = resourceListUnmap @@ -168,7 +218,18 @@ function WebOverviewPanelCont() { PERFORMANCE: checkInZoomRange(performanceChartData), FRUSTRATIONS: checkInZoomRange(frustrationsList), }; - }, [tabStates, currentTab, zoomEnabled, zoomStartTs, zoomEndTs]); + }, [ + tabStates, + currentTab, + zoomEnabled, + zoomStartTs, + zoomEndTs, + resourceList.length, + exceptionsList.length, + stackEventList.length, + performanceChartData.length, + frustrationsList.length, + ]); const originStr = window.env.ORIGIN || window.location.origin; const isSaas = /app\.openreplay\.com/.test(originStr); @@ -187,6 +248,7 @@ function WebOverviewPanelCont() { sessionId={sessionId} setZoomTab={setZoomTab} zoomTab={zoomTab} + showSingleTab={showSingleTab} /> ); } @@ -238,6 +300,7 @@ function PanelComponent({ spotTime, spotEndTime, onClose, + showSingleTab, }: any) { return ( @@ -281,6 +344,7 @@ function PanelComponent({
{isSpot ? null : (
+ ( { - const { title, className, list = [], endTime = 0, isGraph = false, message = '' } = props; + const { title, className, list = [], endTime = 0, isGraph = false, message = '', disabled } = props; const scale = 100 / endTime; const _list = isGraph ? [] : @@ -82,7 +83,7 @@ const EventRow = React.memo((props: Props) => { } return groupedItems; - }, [list]); + }, [list.length]); return (
{
{isGraph ? ( - + ) : _list.length > 0 ? ( _list.map((item: { items: any[], left: number, isGrouped: boolean }, index: number) => { const left = item.left diff --git a/frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/PerformanceGraph.tsx b/frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/PerformanceGraph.tsx index 2e719f377..f46d89d60 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/PerformanceGraph.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/PerformanceGraph.tsx @@ -2,81 +2,103 @@ import React from 'react'; import { AreaChart, Area, ResponsiveContainer } from 'recharts'; interface Props { - list: any; + list: any; + disabled?: boolean; } const PerformanceGraph = React.memo((props: Props) => { - const { list } = props; + const { list, disabled } = props; - const finalValues = React.useMemo(() => { - const cpuMax = list.reduce((acc: number, item: any) => { - return Math.max(acc, item.cpu); - }, 0); - const cpuMin = list.reduce((acc: number, item: any) => { - return Math.min(acc, item.cpu); - }, Infinity); + const finalValues = React.useMemo(() => { + const cpuMax = list.reduce((acc: number, item: any) => { + return Math.max(acc, item.cpu); + }, 0); + const cpuMin = list.reduce((acc: number, item: any) => { + return Math.min(acc, item.cpu); + }, Infinity); - const memoryMin = list.reduce((acc: number, item: any) => { - return Math.min(acc, item.usedHeap); - }, Infinity); - const memoryMax = list.reduce((acc: number, item: any) => { - return Math.max(acc, item.usedHeap); - }, 0); + const memoryMin = list.reduce((acc: number, item: any) => { + return Math.min(acc, item.usedHeap); + }, Infinity); + const memoryMax = list.reduce((acc: number, item: any) => { + return Math.max(acc, item.usedHeap); + }, 0); - const convertToPercentage = (val: number, max: number, min: number) => { - return ((val - min) / (max - min)) * 100; - }; - const cpuValues = list.map((item: any) => convertToPercentage(item.cpu, cpuMax, cpuMin)); - const memoryValues = list.map((item: any) => convertToPercentage(item.usedHeap, memoryMax, memoryMin)); - const mergeArraysWithMaxNumber = (arr1: any[], arr2: any[]) => { - const maxLength = Math.max(arr1.length, arr2.length); - const result = []; - for (let i = 0; i < maxLength; i++) { - const num = Math.round(Math.max(arr1[i] || 0, arr2[i] || 0)); - result.push(num > 60 ? num : 1); - } - return result; - }; - const finalValues = mergeArraysWithMaxNumber(cpuValues, memoryValues); - return finalValues; - }, []); - - const data = list.map((item: any, index: number) => { - return { - time: item.time, - cpu: finalValues[index], - }; - }); - - return ( - - - - - - - - - {/* */} - - - + const convertToPercentage = (val: number, max: number, min: number) => { + return ((val - min) / (max - min)) * 100; + }; + const cpuValues = list.map((item: any) => + convertToPercentage(item.cpu, cpuMax, cpuMin) ); + const memoryValues = list.map((item: any) => + convertToPercentage(item.usedHeap, memoryMax, memoryMin) + ); + const mergeArraysWithMaxNumber = (arr1: any[], arr2: any[]) => { + const maxLength = Math.max(arr1.length, arr2.length); + const result = []; + for (let i = 0; i < maxLength; i++) { + const num = Math.round(Math.max(arr1[i] || 0, arr2[i] || 0)); + result.push(num > 60 ? num : 1); + } + return result; + }; + const finalValues = mergeArraysWithMaxNumber(cpuValues, memoryValues); + return finalValues; + }, [list.length]); + + const data = list.map((item: any, index: number) => { + return { + time: item.time, + cpu: finalValues[index], + }; + }); + + return ( +
+ {disabled ? ( +
+
Disabled for all tabs
+
+ ) : null} + + + + + + + + + {/* */} + + + +
+ ); }); export default PerformanceGraph; diff --git a/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx b/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx index cc7f13f63..4518ddf1c 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx @@ -168,7 +168,7 @@ function GroupedIssue({
{items.length} diff --git a/frontend/app/components/Session_/Performance/Performance.tsx b/frontend/app/components/Session_/Performance/Performance.tsx index c0cdceb39..4227c234f 100644 --- a/frontend/app/components/Session_/Performance/Performance.tsx +++ b/frontend/app/components/Session_/Performance/Performance.tsx @@ -23,6 +23,7 @@ import stl from './performance.module.css'; import BottomBlock from '../BottomBlock'; import InfoLine from '../BottomBlock/InfoLine'; import { useStore } from 'App/mstore' +import { Segmented } from 'antd' const CPU_VISUAL_OFFSET = 10; @@ -459,13 +460,16 @@ function Performance() {
Performance
- - - +
+ + + + +
diff --git a/frontend/app/components/Session_/Player/Controls/Controls.tsx b/frontend/app/components/Session_/Player/Controls/Controls.tsx index 0bddb0a23..d2dd042c0 100644 --- a/frontend/app/components/Session_/Player/Controls/Controls.tsx +++ b/frontend/app/components/Session_/Player/Controls/Controls.tsx @@ -34,6 +34,7 @@ import { Icon } from 'UI'; import LogsButton from 'App/components/Session/Player/SharedComponents/BackendLogs/LogsButton'; import ControlButton from './ControlButton'; +import { WebEventsList } from "./EventsList"; import Timeline from './Timeline'; import PlayerControls from './components/PlayerControls'; import styles from './controls.module.css'; diff --git a/frontend/app/components/Session_/Player/Controls/EventsList.tsx b/frontend/app/components/Session_/Player/Controls/EventsList.tsx index 41010c766..e9994771a 100644 --- a/frontend/app/components/Session_/Player/Controls/EventsList.tsx +++ b/frontend/app/components/Session_/Player/Controls/EventsList.tsx @@ -4,10 +4,12 @@ import { PlayerContext, MobilePlayerContext } from 'Components/Session/playerCon import { observer } from 'mobx-react-lite'; import { getTimelinePosition } from './getTimelinePosition' -function EventsList({ scale }: { scale: number }) { +function EventsList() { const { store } = useContext(PlayerContext); - const { tabStates, eventCount } = store.get(); + const { eventCount, endTime } = store.get(); + const tabStates = store.get().tabStates; + const scale = 100 / endTime; const events = React.useMemo(() => { return Object.values(tabStates)[0]?.eventList.filter(e => e.time) || []; }, [eventCount]); @@ -34,11 +36,12 @@ function EventsList({ scale }: { scale: number }) { ); } -function MobileEventsList({ scale }: { scale: number }) { +function MobileEventsList() { const { store } = useContext(MobilePlayerContext); - const { eventList } = store.get(); + const { eventList, endTime } = store.get(); const events = eventList.filter(e => e.type !== 'SWIPE') + const scale = 100/endTime; return ( <> {events.map((e) => ( diff --git a/frontend/app/components/Session_/Player/Controls/Timeline.tsx b/frontend/app/components/Session_/Player/Controls/Timeline.tsx index 0888df62b..1d5d56467 100644 --- a/frontend/app/components/Session_/Player/Controls/Timeline.tsx +++ b/frontend/app/components/Session_/Player/Controls/Timeline.tsx @@ -13,11 +13,7 @@ import NotesList from './NotesList'; import SkipIntervalsList from './SkipIntervalsList'; import TimelineTracker from 'Components/Session_/Player/Controls/TimelineTracker'; -interface IProps { - isMobile?: boolean; -} - -function Timeline(props: IProps) { +function Timeline({ isMobile }: { isMobile: boolean }) { const { player, store } = useContext(PlayerContext); const [wasPlaying, setWasPlaying] = useState(false); const [maxWidth, setMaxWidth] = useState(0); @@ -126,6 +122,7 @@ function Timeline(props: IProps) { return Math.max(Math.round(p * targetTime), 0); }; + console.log(devtoolsLoading , domLoading, !ready) return (
: null}
- {props.isMobile ? : } + {isMobile ? : } diff --git a/frontend/app/components/Session_/Storage/Storage.tsx b/frontend/app/components/Session_/Storage/Storage.tsx index 2b138728e..43c5fd851 100644 --- a/frontend/app/components/Session_/Storage/Storage.tsx +++ b/frontend/app/components/Session_/Storage/Storage.tsx @@ -1,18 +1,24 @@ import React from 'react'; -import { useStore } from 'App/mstore' +import { useStore } from 'App/mstore'; import { PlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; import { JSONTree, NoContent, Tooltip } from 'UI'; import { formatMs } from 'App/date'; -import diff from 'microdiff' -import { STORAGE_TYPES, selectStorageList, selectStorageListNow, selectStorageType } from 'Player'; +import diff from 'microdiff'; +import { + STORAGE_TYPES, + selectStorageList, + selectStorageListNow, + selectStorageType, +} from 'Player'; import Autoscroll from '../Autoscroll'; import BottomBlock from '../BottomBlock/index'; import DiffRow from './DiffRow'; import cn from 'classnames'; import stl from './storage.module.css'; -import logger from "App/logger"; -import ReduxViewer from './ReduxViewer' +import logger from 'App/logger'; +import ReduxViewer from './ReduxViewer'; +import { Segmented } from 'antd' function getActionsName(type: string) { switch (type) { @@ -31,7 +37,7 @@ const storageDecodeKeys = { [STORAGE_TYPES.ZUSTAND]: ['state', 'mutation'], [STORAGE_TYPES.MOBX]: ['payload'], [STORAGE_TYPES.NONE]: ['state, action', 'payload', 'mutation'], -} +}; function Storage() { const { uiPlayerStore } = useStore(); @@ -42,49 +48,48 @@ function Storage() { const [stateObject, setState] = React.useState({}); const { player, store } = React.useContext(PlayerContext); - const { tabStates, currentTab } = store.get() - const state = tabStates[currentTab] || {} + const { tabStates, currentTab } = store.get(); + const state = tabStates[currentTab] || {}; const listNow = selectStorageListNow(state) || []; const list = selectStorageList(state) || []; - const type = selectStorageType(state) || STORAGE_TYPES.NONE + const type = selectStorageType(state) || STORAGE_TYPES.NONE; React.useEffect(() => { let currentState; if (listNow.length === 0) { - currentState = decodeMessage(list[0]) + currentState = decodeMessage(list[0]); } else { - currentState = decodeMessage(listNow[listNow.length - 1]) + currentState = decodeMessage(listNow[listNow.length - 1]); } - const stateObj = currentState?.state || currentState?.payload?.state || {} + const stateObj = currentState?.state || currentState?.payload?.state || {}; const newState = Object.assign(stateObject, stateObj); setState(newState); - }, [listNow.length]); const decodeMessage = (msg: any) => { const decoded = {}; - const pureMSG = { ...msg } + const pureMSG = { ...msg }; const keys = storageDecodeKeys[type]; try { - keys.forEach(key => { + keys.forEach((key) => { if (pureMSG[key]) { // @ts-ignore TODO: types for decoder decoded[key] = player.decodeMessage(pureMSG[key]); } }); } catch (e) { - logger.error("Error on message decoding: ", e, pureMSG); + logger.error('Error on message decoding: ', e, pureMSG); return null; } return { ...pureMSG, ...decoded }; - } + }; const decodedList = React.useMemo(() => { - return listNow.map(msg => { - return decodeMessage(msg) - }) - }, [listNow.length]) + return listNow.map((msg) => { + return decodeMessage(msg); + }); + }, [listNow.length]); const focusNextButton = () => { if (lastBtnRef.current) { @@ -99,7 +104,10 @@ function Storage() { focusNextButton(); }, [listNow]); - const renderDiff = (item: Record, prevItem?: Record) => { + const renderDiff = ( + item: Record, + prevItem?: Record + ) => { if (!showDiffs) { return; } @@ -113,7 +121,10 @@ function Storage() { if (!stateDiff) { return ( -
+
No diff
); @@ -121,13 +132,15 @@ function Storage() { return (
- {stateDiff.map((d: Record, i: number) => renderDiffs(d, i))} + {stateDiff.map((d: Record, i: number) => + renderDiffs(d, i) + )}
); }; const renderDiffs = (diff: Record, i: number) => { - const path = diff.path.join('.') + const path = diff.path.join('.'); return ( @@ -145,12 +158,16 @@ function Storage() { player.jump(list[listNow.length].time); }; - const renderItem = (item: Record, i: number, prevItem?: Record) => { + const renderItem = ( + item: Record, + i: number, + prevItem?: Record + ) => { let src; let name; - const itemD = item - const prevItemD = prevItem ? prevItem : undefined + const itemD = item; + const prevItemD = prevItem ? prevItem : undefined; switch (type) { case STORAGE_TYPES.REDUX: @@ -177,7 +194,10 @@ function Storage() { return (
{src === null ? ( @@ -187,7 +207,10 @@ function Storage() { ) : ( <> {renderDiff(itemD, prevItemD)} -
+
{typeof item?.duration === 'number' && ( -
{formatMs(itemD.duration)}
+
+ {formatMs(itemD.duration)} +
)}
{i + 1 < listNow.length && ( - )} @@ -222,31 +250,36 @@ function Storage() { }; if (type === STORAGE_TYPES.REDUX) { - return + return ; } return ( {/*@ts-ignore*/} <> - {list.length > 0 && ( -
-

- {'STATE'} -

- {showDiffs ? ( -

- DIFFS -

- ) : null} -

- {getActionsName(type)} -

-

- TTE -

+
+
+

{'STATE'}

- )} + {showDiffs ? ( +

+ DIFFS +

+ ) : null} +

+ {getActionsName(type)} +

+

+ TTE +

+ +

- @@ -322,8 +358,7 @@ function Storage() { {'Empty state.'}
) : ( - + )}
@@ -342,7 +377,6 @@ function Storage() { export default observer(Storage); - /** * TODO: compute diff and only decode the required parts * WIP example @@ -384,4 +418,4 @@ export default observer(Storage); * }, [list.length]) * } * - * */ \ No newline at end of file + * */ diff --git a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx index b96b94e07..193c7556f 100644 --- a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx +++ b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx @@ -9,6 +9,7 @@ import { observer } from 'mobx-react-lite'; import { useStore } from 'App/mstore'; import ErrorDetailsModal from 'App/components/Dashboard/components/Errors/ErrorDetailsModal'; import { useModal } from 'App/components/Modal'; +import TabSelector from "../TabSelector"; import useAutoscroll, { getLastItemTime } from '../useAutoscroll'; import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter'; import { VList, VListHandle } from "virtua"; @@ -93,6 +94,7 @@ function ConsolePanel({ sessionStore: { devTools }, uiPlayerStore, } = useStore(); + const zoomEnabled = uiPlayerStore.timelineZoom.enabled; const zoomStartTs = uiPlayerStore.timelineZoom.startTs; const zoomEndTs = uiPlayerStore.timelineZoom.endTs; @@ -109,12 +111,22 @@ function ConsolePanel({ const jump = (t: number) => player.jump(t); const { currentTab, tabStates } = store.get(); - const { - logList = [], - exceptionsList = [], - logListNow = [], - exceptionsListNow = [], - } = tabStates[currentTab] ?? {}; + const tabsArr = Object.keys(tabStates); + const tabValues = Object.values(tabStates); + const dataSource = uiPlayerStore.dataSource; + const showSingleTab = dataSource === 'current'; + const { logList = [], exceptionsList = [], logListNow = [], exceptionsListNow = [] } = React.useMemo(() => { + if (showSingleTab) { + return tabStates[currentTab] ?? {}; + } else { + const logList = tabValues.flatMap(tab => tab.logList); + const exceptionsList = tabValues.flatMap(tab => tab.exceptionsList); + const logListNow = isLive ? tabValues.flatMap(tab => tab.logListNow) : []; + const exceptionsListNow = isLive ? tabValues.flatMap(tab => tab.exceptionsListNow) : []; + return { logList, exceptionsList, logListNow, exceptionsListNow } + } + }, [currentTab, tabStates, dataSource, tabValues, isLive]) + const getTabNum = (tab: string) => (tabsArr.findIndex((t) => t === tab) + 1); const list = isLive ? (useMemo( @@ -180,15 +192,18 @@ function ConsolePanel({ Console
- +
+ + +
{/* @ts-ignore */} {/* @ts-ignore */} @@ -211,6 +226,8 @@ function ConsolePanel({ iconProps={getIconProps(log.level)} renderWithNL={renderWithNL} onClick={() => showDetails(log)} + showSingleTab={showSingleTab} + getTabNum={getTabNum} /> ))} diff --git a/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx b/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx index f61e04a9c..9581dd87f 100644 --- a/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx +++ b/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx @@ -2,6 +2,8 @@ import React, { useState } from 'react'; import cn from 'classnames'; import { Icon } from 'UI'; import JumpButton from 'Shared/DevTools/JumpButton'; +import { Tag } from 'antd'; +import TabTag from "../TabTag"; interface Props { log: any; @@ -10,6 +12,8 @@ interface Props { renderWithNL?: any; style?: any; onClick?: () => void; + getTabNum: (tab: string) => number; + showSingleTab: boolean; } function ConsoleRow(props: Props) { const { log, iconProps, jump, renderWithNL, style } = props; @@ -41,11 +45,12 @@ function ConsoleRow(props: Props) { const titleLine = lines[0]; const restLines = lines.slice(1); + const logSource = props.showSingleTab ? -1 : props.getTabNum(log.tabId); return (
(!!log.errorId ? props.onClick?.() : toggleExpand()) : undefined} > -
- -
+ {logSource !== -1 && } +
-
+
{canExpand && ( diff --git a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx index bb00633e6..8d15b713c 100644 --- a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx +++ b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx @@ -1,7 +1,7 @@ import { ResourceType, Timed } from 'Player'; import MobilePlayer from 'Player/mobile/IOSPlayer'; import WebPlayer from 'Player/web/WebPlayer'; -import { Duration } from 'luxon'; +import TabTag from "../TabTag"; import { observer } from 'mobx-react-lite'; import React, { useMemo, useState } from 'react'; @@ -20,10 +20,10 @@ import { WsChannel } from "App/player/web/messages"; import BottomBlock from '../BottomBlock'; import InfoLine from '../BottomBlock/InfoLine'; +import TabSelector from "../TabSelector"; import TimeTable from '../TimeTable'; import useAutoscroll, { getLastItemTime } from '../useAutoscroll'; import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter'; -import WSModal from './WSModal'; import WSPanel from './WSPanel'; const INDEX_KEY = 'network'; @@ -57,12 +57,6 @@ export const NETWORK_TABS = TAP_KEYS.map((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}
}> @@ -79,14 +73,6 @@ export function renderName(r: any) { ); } -export function renderStart(r: any) { - return ( -
- {Duration.fromMillis(r.time).toFormat('mm:ss.SSS')} -
- ); -} - function renderSize(r: any) { if (r.responseBodySize) return formatBytes(r.responseBodySize); let triggerText; @@ -125,13 +111,10 @@ export function renderDuration(r: any) { 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 ( @@ -184,7 +167,8 @@ function NetworkPanelCont({ panelHeight: number; }) { const { player, store } = React.useContext(PlayerContext); - const { sessionStore } = useStore(); + const { sessionStore, uiPlayerStore } = useStore(); + const startedAt = sessionStore.current.startedAt; const { domContentLoadedTime, @@ -193,6 +177,10 @@ function NetworkPanelCont({ tabStates, currentTab, } = store.get(); + const tabsArr = Object.keys(tabStates); + const tabValues = Object.values(tabStates); + const dataSource = uiPlayerStore.dataSource; + const showSingleTab = dataSource === 'current'; const { fetchList = [], resourceList = [], @@ -200,7 +188,20 @@ function NetworkPanelCont({ resourceListNow = [], websocketList = [], websocketListNow = [], - } = tabStates[currentTab]; + } = 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]); + const getTabNum = (tab: string) => (tabsArr.findIndex((t) => t === tab) + 1); return ( ); } @@ -301,6 +304,8 @@ interface Props { onClose?: () => void; activeOutsideIndex?: number; isSpot?: boolean; + getTabNum?: (tab: string) => number; + showSingleTab?: boolean; } export const NetworkPanelComp = observer( @@ -323,6 +328,8 @@ export const NetworkPanelComp = observer( onClose, activeOutsideIndex, isSpot, + getTabNum, + showSingleTab, }: Props) => { const [selectedWsChannel, setSelectedWsChannel] = React.useState(null) const { showModal } = useModal(); @@ -507,6 +514,55 @@ export const NetworkPanelComp = observer( stopAutoscroll(); }; + const tableCols = React.useMemo(() => { + const cols: any[] = [ + { + label: 'Status', + dataKey: 'status', + width: 90, + render: renderStatus, + }, + { + label: 'Type', + dataKey: 'type', + width: 90, + render: renderType, + }, + { + label: 'Method', + width: 80, + dataKey: 'method', + }, + { + label: 'Name', + width: 240, + dataKey: 'name', + render: renderName, + }, + { + label: 'Size', + width: 80, + dataKey: 'decodedBodySize', + render: renderSize, + hidden: activeTab === XHR, + }, + { + label: 'Duration', + width: 80, + dataKey: 'duration', + render: renderDuration, + }, + ] + if (!showSingleTab) { + cols.unshift({ + label: 'Source', + width: 64, + render: (r: Record) =>
Tab {getTabNum?.(r.tabId) ?? 0}
, + }) + } + return cols + }, [showSingleTab]) + return ( )}
- +
+ + +
@@ -613,49 +672,7 @@ export const NetworkPanelComp = observer( }} activeIndex={activeIndex} > - {[ - // { - // label: 'Start', - // width: 120, - // render: renderStart, - // }, - { - label: 'Status', - dataKey: 'status', - width: 90, - render: renderStatus, - }, - { - label: 'Type', - dataKey: 'type', - width: 90, - render: renderType, - }, - { - label: 'Method', - width: 80, - dataKey: 'method', - }, - { - label: 'Name', - width: 240, - dataKey: 'name', - render: renderName, - }, - { - label: 'Size', - width: 80, - dataKey: 'decodedBodySize', - render: renderSize, - hidden: activeTab === XHR, - }, - { - label: 'Duration', - width: 80, - dataKey: 'duration', - render: renderDuration, - }, - ]} + {tableCols} {selectedWsChannel ? ( setSelectedWsChannel(null)} /> diff --git a/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx b/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx index 1ee227ae1..711504672 100644 --- a/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx +++ b/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx @@ -10,6 +10,7 @@ import { typeList } from 'Types/session/stackEvent'; import StackEventRow from 'Shared/DevTools/StackEventRow'; import StackEventModal from '../StackEventModal'; +import { Segmented } from 'antd' import useAutoscroll, { getLastItemTime } from '../useAutoscroll'; import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter'; import { VList, VListHandle } from 'virtua'; @@ -175,15 +176,18 @@ const EventsPanel = observer(({ border={false} />
- +
+ + +
{ + uiPlayerStore.changeDataSource(value) + } + return ( + + ) +} + +export default observer(TabSelector) \ No newline at end of file diff --git a/frontend/app/components/shared/DevTools/TabTag.tsx b/frontend/app/components/shared/DevTools/TabTag.tsx new file mode 100644 index 000000000..a2e6f33b1 --- /dev/null +++ b/frontend/app/components/shared/DevTools/TabTag.tsx @@ -0,0 +1,11 @@ +import React from 'react' + +function TabTag({ tabNum }: { tabNum?: React.ReactNode }) { + return ( +
+ {tabNum} +
+ ) +} + +export default TabTag \ No newline at end of file diff --git a/frontend/app/mstore/uiPlayerStore.ts b/frontend/app/mstore/uiPlayerStore.ts index dd642f047..d79e13c45 100644 --- a/frontend/app/mstore/uiPlayerStore.ts +++ b/frontend/app/mstore/uiPlayerStore.ts @@ -66,11 +66,16 @@ export default class UiPlayerStore { endTs: 0, } zoomTab: 'overview' | 'journey' | 'issues' | 'errors' = 'overview' + dataSource: 'all' | 'current' = 'all' constructor() { makeAutoObservable(this); } + changeDataSource = (source: 'all' | 'current') => { + this.dataSource = source; + } + toggleFullscreen = (val?: boolean) => { this.fullscreen = val ?? !this.fullscreen; } diff --git a/frontend/app/player/common/SimpleStore.ts b/frontend/app/player/common/SimpleStore.ts index 82ddd3433..c1194df96 100644 --- a/frontend/app/player/common/SimpleStore.ts +++ b/frontend/app/player/common/SimpleStore.ts @@ -1,13 +1,18 @@ import { Store } from './types' -export default class SimpleSore implements Store { +export default class SimpleStore, S extends Record = G> implements Store { constructor(private state: G){} get(): G { return this.state } - update(newState: Partial) { + update = (newState: Partial) => { Object.assign(this.state, newState) } + updateTabStates = (id: string, newState: Partial) => { + try { + Object.assign(this.state.tabStates[id], newState) + } catch (e) { + console.log('Error updating tab state', e, id, newState, this.state, this) + } + } } - - diff --git a/frontend/app/player/common/types.ts b/frontend/app/player/common/types.ts index ad34097cb..e3f06f1dc 100644 --- a/frontend/app/player/common/types.ts +++ b/frontend/app/player/common/types.ts @@ -27,6 +27,7 @@ export interface Interval { export interface Store { get(): G update(state: Partial): void + updateTabStates(id: string, state: Partial): void } diff --git a/frontend/app/player/web/MessageLoader.ts b/frontend/app/player/web/MessageLoader.ts index aa4bf58ad..4ff59cbf6 100644 --- a/frontend/app/player/web/MessageLoader.ts +++ b/frontend/app/player/web/MessageLoader.ts @@ -236,6 +236,7 @@ export default class MessageLoader { try { await this.loadMobs(); } catch (sessionLoadError) { + console.info('!', sessionLoadError); try { await this.loadEFSMobs(); } catch (unprocessedLoadError) { diff --git a/frontend/app/player/web/MessageManager.ts b/frontend/app/player/web/MessageManager.ts index d6ed2ed92..ba074a1cc 100644 --- a/frontend/app/player/web/MessageManager.ts +++ b/frontend/app/player/web/MessageManager.ts @@ -99,7 +99,7 @@ export default class MessageManager { closedTabs: [], sessionStart: 0, tabNames: {}, - }; +}; private clickManager: ListWalker = new ListWalker(); private mouseThrashingManager: ListWalker = new ListWalker(); @@ -179,6 +179,7 @@ export default class MessageManager { this.activityManager.end(); this.state.update({ skipIntervals: this.activityManager.list }); } + Object.values(this.tabs).forEach((tab) => tab.onFileReadSuccess?.()); }; @@ -317,6 +318,7 @@ export default class MessageManager { if (msg.tp === 9999) return; if (!this.tabs[msg.tabId]) { this.tabsAmount++; + this.state.update({ tabStates: { ...this.state.get().tabStates, [msg.tabId]: TabSessionManager.INITIAL_STATE } }); this.tabs[msg.tabId] = new TabSessionManager( this.session, this.state, diff --git a/frontend/app/player/web/TabManager.ts b/frontend/app/player/web/TabManager.ts index baa0176fb..200d5f9b6 100644 --- a/frontend/app/player/web/TabManager.ts +++ b/frontend/app/player/web/TabManager.ts @@ -163,15 +163,7 @@ export default class TabSessionManager { * Because we use main state (from messageManager), we have to update it this way * */ updateLocalState(state: Partial) { - this.state.update({ - tabStates: { - ...this.state.get().tabStates, - [this.id]: { - ...this.state.get().tabStates[this.id], - ...state, - }, - }, - }); + this.state.updateTabStates(this.id, state); } private setCSSLoading = (cssLoading: boolean) => { @@ -414,8 +406,9 @@ export default class TabSessionManager { } Object.assign(stateToUpdate, this.lists.moveGetState(t)); - Object.keys(stateToUpdate).length > 0 && + if (Object.keys(stateToUpdate).length > 0) { this.updateLocalState(stateToUpdate); + } /* Sequence of the managers is important here */ // Preparing the size of "screen" const lastResize = this.resizeManager.moveGetLast(t, index);