refactor(frontend):NetworkPanel

This commit is contained in:
Alex Kaminskii 2022-11-26 15:11:28 +01:00
parent 52c9edcc98
commit deee62e4db
7 changed files with 99 additions and 151 deletions

View file

@ -75,11 +75,11 @@ function ConsolePanel() {
const jump = (t: number) => player.jump(t)
const { logList, exceptionsList, logListNow, exceptionsListNow } = store.get()
const logs = useMemo(() =>
const list = useMemo(() =>
logList.concat(exceptionsList).sort((a, b) => a.time - b.time),
[ logList.length, exceptionsList.length ],
) as ILog[]
let filteredList = useRegExListFilterMemo(logs, l => l.value, filter)
let filteredList = useRegExListFilterMemo(list, l => l.value, filter)
filteredList = useTabListFilterMemo(filteredList, l => LEVEL_TAB[l.level], ALL, activeTab)
const onTabClick = (activeTab: any) => devTools.update(INDEX_KEY, { activeTab })
@ -95,7 +95,6 @@ function ConsolePanel() {
autoScrollIndex,
index => devTools.update(INDEX_KEY, { index })
)
const onMouseEnter = stopAutoscroll
const onMouseLeave = () => {
if (isDetailsModalActive) { return }

View file

@ -1,19 +1,21 @@
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { observer } from 'mobx-react-lite';
import { Duration } from 'luxon';
import { Tooltip, Tabs, Input, NoContent, Icon, Toggler } from 'UI';
import { getRE } from 'App/utils';
import Resource, { TYPES } from 'Types/session/resource';
import { TYPES } from 'Types/session/resource';
import { formatBytes } from 'App/utils';
import { formatMs } from 'App/date';
import { useModal } from 'App/components/Modal';
import FetchDetailsModal from 'Shared/FetchDetailsModal';
import { PlayerContext } from 'App/components/Session/playerContext';
import { useStore } from 'App/mstore';
import TimeTable from '../TimeTable';
import BottomBlock from '../BottomBlock';
import InfoLine from '../BottomBlock/InfoLine';
import { Duration } from 'luxon';
import { useModal } from 'App/components/Modal';
import FetchDetailsModal from 'Shared/FetchDetailsModal';
import { PlayerContext } from 'App/components/Session/playerContext';
import { observer } from 'mobx-react-lite';
import { useStore } from 'App/mstore';
import useAutoscroll from '../useAutoscroll';
import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter'
const INDEX_KEY = 'network';
@ -25,15 +27,17 @@ 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) => ({
const TYPE_TO_TAB = {
[TYPES.XHR]: XHR,
[TYPES.JS]: JS,
[TYPES.CSS]: CSS,
[TYPES.IMG]: IMG,
[TYPES.MEDIA]: MEDIA,
[TYPES.OTHER]: OTHER,
}
const TAP_KEYS = [ALL, XHR, JS, CSS, IMG, MEDIA, OTHER] as const;
const TABS = TAP_KEYS.map((tab) => ({
text: tab === 'xhr' ? 'Fetch/XHR' : tab,
key: tab,
}));
@ -123,133 +127,81 @@ export function renderDuration(r: any) {
);
}
interface Props {
location: any;
resources: any;
fetchList: any;
domContentLoadedTime: any;
loadTime: any;
playing: boolean;
domBuildingTime: any;
time: any;
}
let timeOut: any = null;
const TIMEOUT_DURATION = 5000;
function NetworkPanel() {
const { player, store } = React.useContext(PlayerContext)
const {
resourceList: resources,
domContentLoadedTime,
loadTime,
time,
domBuildingTime,
fetchList: fetchUnmap,
} = store.get();
const fetchList = fetchUnmap.map((i: any) => Resource({ ...i.toJS(), type: TYPES.XHR, time: i.time < 0 ? 0 : i.time }))
fetchList,
resourceList,
fetchListNow,
resourceListNow,
} = store.get()
const { showModal } = useModal();
const [sortBy, setSortBy] = useState('time');
const [sortAscending, setSortAscending] = useState(true);
const [showOnlyErrors, setShowOnlyErrors] = useState(false);
const [filteredList, setFilteredList] = useState([]);
const [isDetailsModalActive, setIsDetailsModalActive] = useState(false);
const additionalHeight = 0;
const fetchPresented = fetchList.length > 0;
const {
sessionStore: { devTools },
} = useStore();
const filter = devTools[INDEX_KEY].filter;
const activeTab = devTools[INDEX_KEY].activeTab;
const activeIndex = devTools[INDEX_KEY].index;
const [pauseSync, setPauseSync] = useState(activeIndex > 0);
const synRef: any = useRef({});
const onTabClick = (activeTab: any) => devTools.update(INDEX_KEY, { activeTab });
const onFilterChange = ({ target: { value } }: any) => {
devTools.update(INDEX_KEY, { filter: value });
};
const list = useMemo(() =>
resourceList.filter(res => !fetchList.some(ft => {
if (res.url !== ft.url) { return false }
if (Math.abs(res.time - ft.time) > 200) { return false } // TODO: find good epsilons
if (Math.abs(res.duration - ft.duration) > 100) { return false }
return true
}))
.concat(fetchList)
.sort((a, b) => a.time - b.time)
, [ resourceList.length, fetchList.length ])
let filteredList = useMemo(() => {
if (!showOnlyErrors) { return list }
return list.filter(it => parseInt(it.status) >= 400 || !it.success)
}, [ showOnlyErrors, list ])
filteredList = useRegExListFilterMemo(filteredList, it => it.status, filter)
filteredList = useRegExListFilterMemo(filteredList, it => it.name, filter)
filteredList = useRegExListFilterMemo(filteredList, it => it.type, filter)
filteredList = useTabListFilterMemo(filteredList, it => TYPE_TO_TAB[it.type], ALL, activeTab)
synRef.current = {
pauseSync,
const onTabClick = (activeTab: typeof TAP_KEYS[number]) => devTools.update(INDEX_KEY, { activeTab })
const onFilterChange = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => devTools.update(INDEX_KEY, { filter: value })
// AutoScroll
const autoScrollIndex = fetchListNow.length + resourceListNow.length
const {
timeoutStartAutoscroll,
stopAutoscroll,
} = useAutoscroll(
activeIndex,
};
const removePause = () => {
setIsDetailsModalActive(false);
clearTimeout(timeOut);
timeOut = setTimeout(() => {
devTools.update(INDEX_KEY, { index: getCurrentIndex() });
setPauseSync(false);
}, TIMEOUT_DURATION);
};
autoScrollIndex,
index => devTools.update(INDEX_KEY, { index })
)
const onMouseEnter = stopAutoscroll
const onMouseLeave = () => {
if (isDetailsModalActive) return;
removePause();
};
if (isDetailsModalActive) { return }
timeoutStartAutoscroll()
}
useEffect(() => {
if (pauseSync) {
removePause();
}
return () => {
clearTimeout(timeOut);
if (!synRef.current.pauseSync) {
devTools.update(INDEX_KEY, { index: 0 });
}
};
}, []);
const getCurrentIndex = () => {
return filteredList.filter((item: any) => item.time <= time).length - 1;
};
useEffect(() => {
const currentIndex = getCurrentIndex();
if (currentIndex !== activeIndex && !pauseSync) {
devTools.update(INDEX_KEY, { index: currentIndex });
}
}, [time]);
const { resourcesSize, transferredSize } = useMemo(() => {
const resourcesSize = resources.reduce(
(sum: any, { decodedBodySize }: any) => sum + (decodedBodySize || 0),
0
);
const transferredSize = resources.reduce(
(sum: any, { headerSize, encodedBodySize }: any) =>
const resourcesSize = useMemo(() =>
resourceList.reduce(
(sum, { decodedBodySize }) => sum + (decodedBodySize || 0),
0,
), [ resourceList.length ])
const transferredSize = useMemo(() =>
resourceList.reduce(
(sum, { headerSize, encodedBodySize }) =>
sum + (headerSize || 0) + (encodedBodySize || 0),
0
);
return {
resourcesSize,
transferredSize,
};
}, [resources]);
0,
), [ resourceList.length ])
useEffect(() => {
const filterRE = getRE(filter, 'i');
let list = resources;
fetchList.forEach(
(fetchCall: any) =>
(list = list.filter((networkCall: any) => networkCall.url !== fetchCall.url))
);
// @ts-ignore
list = list.concat(fetchList);
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)
);
setFilteredList(list);
}, [resources, filter, showOnlyErrors, activeTab]);
const referenceLines = useMemo(() => {
const arr = [];
@ -268,31 +220,30 @@ function NetworkPanel() {
}
return arr;
}, []);
}, [ domContentLoadedTime, loadTime ])
const showDetailsModal = (row: any) => {
setIsDetailsModalActive(true);
const showDetailsModal = (item: any) => {
setIsDetailsModalActive(true)
showModal(
<FetchDetailsModal resource={row} rows={filteredList} fetchPresented={fetchPresented} />,
{
<FetchDetailsModal resource={item} rows={filteredList} fetchPresented={fetchList.length > 0} />,
{
right: true,
onClose: removePause,
onClose: () => {
setIsDetailsModalActive(false)
timeoutStartAutoscroll()
}
}
);
devTools.update(INDEX_KEY, { index: filteredList.indexOf(row) });
setPauseSync(true);
};
useEffect(() => {
devTools.update(INDEX_KEY, { filter, activeTab });
}, [filter, activeTab]);
)
devTools.update(INDEX_KEY, { index: filteredList.indexOf(item) })
stopAutoscroll()
}
return (
<React.Fragment>
<BottomBlock
style={{ height: 300 + additionalHeight + 'px' }}
style={{ height: '300px' }}
className="border"
onMouseEnter={() => setPauseSync(true)}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
<BottomBlock.Header>
@ -373,7 +324,6 @@ function NetworkPanel() {
referenceLines={referenceLines}
renderPopup
onRowClick={showDetailsModal}
additionalHeight={additionalHeight}
sortBy={sortBy}
sortAscending={sortAscending}
onJump={(row: any) => {

View file

@ -29,14 +29,14 @@ function StackEventPanel() {
const { showModal } = useModal();
const [isDetailsModalActive, setIsDetailsModalActive] = useState(false) // TODO:embed that into useModal
const filter = devTools[INDEX_KEY].filter
const activeTab = devTools[INDEX_KEY].activeTab)
const activeTab = devTools[INDEX_KEY].activeTab
const activeIndex = devTools[INDEX_KEY].index
let filteredList = useRegExListFilterMemo(list, it => it.name, filter)
filteredList = useTabListFilterMemo(filteredList, it => it.source, ALL, activeTab)
const onTabClick = (activeTab: typeof TAB_KEYS[number]) => devTools.update(INDEX_KEY, { activeTab })
const onFilterChange = ({ target: { value } }: any) => devTools.update(INDEX_KEY, { filter: value })
const onFilterChange = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => devTools.update(INDEX_KEY, { filter: value })
const tabs = useMemo(() =>
TABS.filter(({ key }) => key === ALL || list.some(({ source }) => key === source)),

View file

@ -16,7 +16,7 @@ function FetchDetailsModal(props: Props) {
const [resource, setResource] = useState(props.resource);
const [first, setFirst] = useState(false);
const [last, setLast] = useState(false);
const isXHR = resource.type === TYPES.XHR || resource.type === TYPES.FETCH;
const isXHR = resource.type === TYPES.XHR
const {
sessionStore: { devTools },
} = useStore();

View file

@ -43,9 +43,9 @@ export interface State extends ScreenState, ListsState {
performanceChartTime?: number,
performanceAvaliability?: PerformanceTrackManager['avaliability']
domContentLoadedTime?: any,
domBuildingTime?: any,
loadTime?: any,
domContentLoadedTime?: { time: number, value: number },
domBuildingTime?: number,
loadTime?: { time: number, value: number },
error: boolean,
devtoolsLoading: boolean,
@ -437,10 +437,10 @@ export default class MessageManager {
response: msg.response,
status: msg.status,
duration: msg.duration,
type: TYPES.FETCH,
time: msg.timestamp - this.sessionStart, //~
type: TYPES.XHR,
time: Math.max(msg.timestamp - this.sessionStart, 0), // !!! doesn't look good. TODO: find solution to show negative timings
index,
}));
}) as Timed)
break;
case "redux":
decoded = this.decodeStateMessage(msg, ["state", "action"]);

View file

@ -37,7 +37,7 @@ export default class WebPlayer extends Player {
let initialLists = live ? {} : {
event: session.events.toJSON(),
stack: session.stackEvents.toJSON(),
resource: session.resources.toJSON(),
resource: session.resources.toJSON(), // MBTODO: put ResourceTiming in file
exceptions: session.errors.toJSON().map(({ time, errorId, name }: any) =>
Log({
level: LogLevel.ERROR,

View file

@ -8,8 +8,8 @@ const CSS = 'css';
const IMG = 'img';
const MEDIA = 'media';
const OTHER = 'other';
//
const FETCH = 'tracked_fetch';
//
// const IMG_EXTENTIONS = [ "png", "gif", "jpg", "jpeg", "svg" ];
// const MEDIA_EXTENTIONS = [ 'mp4', 'mkv', 'ogg', 'webm', 'avi', 'mp3' ];
@ -58,7 +58,6 @@ export const TYPES = {
IMG,
MEDIA,
OTHER,
FETCH,
}
const YELLOW_BOUND = 10;