ui: fix log panel crashing

This commit is contained in:
nick-delirium 2024-12-10 13:31:18 +01:00
parent 37f00f4d73
commit 4eea15b053
No known key found for this signature in database
GPG key ID: 93ABD695DF5FDBA0
6 changed files with 112 additions and 96 deletions

View file

@ -1,7 +1,7 @@
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import React from 'react'; import React from 'react';
import { VList, VListHandle } from 'virtua'; import { VList, VListHandle } from 'virtua';
import { PlayerContext } from "App/components/Session/playerContext"; import { PlayerContext } from 'App/components/Session/playerContext';
import { processLog, UnifiedLog } from './utils'; import { processLog, UnifiedLog } from './utils';
import { observer } from 'mobx-react-lite'; import { observer } from 'mobx-react-lite';
import { useStore } from 'App/mstore'; import { useStore } from 'App/mstore';
@ -13,13 +13,10 @@ import BottomBlock from 'App/components/shared/DevTools/BottomBlock';
import { capitalize } from 'App/utils'; import { capitalize } from 'App/utils';
import { Icon } from 'UI'; import { Icon } from 'UI';
import { Segmented, Input, Tooltip } from 'antd'; import { Segmented, Input, Tooltip } from 'antd';
import {SearchOutlined} from '@ant-design/icons'; import { SearchOutlined } from '@ant-design/icons';
import { client } from 'App/mstore'; import { client } from 'App/mstore';
import { FailedFetch, LoadingFetch } from "./StatusMessages"; import { FailedFetch, LoadingFetch } from './StatusMessages';
import { import { TableHeader, LogRow } from './Table';
TableHeader,
LogRow
} from './Table'
async function fetchLogs( async function fetchLogs(
tab: string, tab: string,
@ -31,23 +28,24 @@ async function fetchLogs(
); );
const json = await data.json(); const json = await data.json();
try { try {
const logsResp = await fetch(json.url) const logsResp = await fetch(json.url);
if (logsResp.ok) { if (logsResp.ok) {
const logJson = await logsResp.json() const logJson = await logsResp.json();
if (logJson.length === 0) return [] if (logJson.length === 0) return [];
return processLog(logJson) return processLog(logJson);
} else { } else {
throw new Error('Failed to fetch logs') throw new Error('Failed to fetch logs');
} }
} catch (e) { } catch (e) {
console.log(e) console.log(e);
throw e throw e;
} }
} }
function BackendLogsPanel() { function BackendLogsPanel() {
const { projectsStore, sessionStore, integrationsStore } = useStore(); const { projectsStore, sessionStore, integrationsStore } = useStore();
const integratedServices = integrationsStore.integrations.backendLogIntegrations; const integratedServices =
integrationsStore.integrations.backendLogIntegrations;
const defaultTab = integratedServices[0]!.name; const defaultTab = integratedServices[0]!.name;
const sessionId = sessionStore.currentId; const sessionId = sessionStore.currentId;
const projectId = projectsStore.siteId!; const projectId = projectsStore.siteId!;
@ -83,59 +81,59 @@ function BackendLogsPanel() {
return ( return (
<BottomBlock style={{ height: '100%' }}> <BottomBlock style={{ height: '100%' }}>
<BottomBlock.Header> <BottomBlock.Header>
<div className='flex items-center justify-between w-full'> <div className="flex items-center justify-between w-full">
<div className={'flex gap-2 items-center'}> <div className={'flex gap-2 items-center'}>
<div className={'font-semibold'}>Traces</div> <div className={'font-semibold'}>Traces</div>
{tabs.length && tab ? ( {tabs.length && tab ? (
<div> <div>
<Segmented options={tabs} value={tab} onChange={setTab} size='small' /> <Segmented
</div> options={tabs}
) : null} value={tab}
</div> onChange={setTab}
size="small"
<div className='flex items-center gap-2'>
<Segmented
options={[
{ label: 'All Tabs', value: 'all', },
{ label: (
<Tooltip title="Backend logs are fetched for all tabs combined.">
<span>Current Tab</span>
</Tooltip>),
value: 'current', disabled: true},
]}
defaultValue="all"
size="small"
className="rounded-full font-medium"
/>
<Input
className="rounded-lg"
placeholder="Filter by keyword"
name="filter"
onChange={onFilterChange}
value={filter}
size='small'
prefix={<SearchOutlined className='text-neutral-400' />}
/> />
</div> </div>
) : null}
</div> </div>
<div className="flex items-center gap-2">
<Segmented
options={[
{ label: 'All Tabs', value: 'all' },
{
label: (
<Tooltip title="Backend logs are fetched for all tabs combined.">
<span>Current Tab</span>
</Tooltip>
),
value: 'current',
disabled: true,
},
]}
defaultValue="all"
size="small"
className="rounded-full font-medium"
/>
<Input
className="rounded-lg"
placeholder="Filter by keyword"
name="filter"
onChange={onFilterChange}
value={filter}
size="small"
prefix={<SearchOutlined className="text-neutral-400" />}
/>
</div>
</div>
</BottomBlock.Header> </BottomBlock.Header>
<BottomBlock.Content className="overflow-y-auto"> <BottomBlock.Content className="overflow-y-auto">
{isPending ? ( {isPending ? <LoadingFetch provider={capitalize(tab)} /> : null}
<LoadingFetch provider={capitalize(tab)} />
) : null}
{isError ? ( {isError ? (
<FailedFetch <FailedFetch provider={capitalize(tab)} onRetry={refetch} />
provider={capitalize(tab)}
onRetry={refetch}
/>
) : null}
{isSuccess ? (
<LogsTable data={data} />
) : null} ) : null}
{isSuccess ? <LogsTable data={data} /> : null}
</BottomBlock.Content> </BottomBlock.Content>
</BottomBlock> </BottomBlock>
); );
@ -148,8 +146,10 @@ const LogsTable = observer(({ data }: { data: UnifiedLog[] }) => {
const _list = React.useRef<VListHandle>(null); const _list = React.useRef<VListHandle>(null);
const activeIndex = React.useMemo(() => { const activeIndex = React.useMemo(() => {
const currTs = time + sessionStart; const currTs = time + sessionStart;
const index = data.findIndex( const index = data.findIndex((log) =>
(log) => log.timestamp !== 'N/A' ? new Date(log.timestamp).getTime() >= currTs : false log.timestamp !== 'N/A'
? new Date(log.timestamp).getTime() >= currTs
: false
); );
return index === -1 ? data.length - 1 : index; return index === -1 ? data.length - 1 : index;
}, [time, data.length]); }, [time, data.length]);
@ -161,17 +161,22 @@ const LogsTable = observer(({ data }: { data: UnifiedLog[] }) => {
const onJump = (ts: number) => { const onJump = (ts: number) => {
player.jump(ts - sessionStart); player.jump(ts - sessionStart);
} };
return ( return (
<> <>
<TableHeader size={data.length} /> <TableHeader size={data.length} />
<VList ref={_list} count={data.length}> <VList ref={_list} count={data.length}>
{data.map((log, index) => ( {data.map((log, index) => (
<LogRow key={index} isActive={index === activeIndex} log={log} onJump={onJump} /> <LogRow
key={index}
isActive={index === activeIndex}
log={log}
onJump={onJump}
/>
))} ))}
</VList> </VList>
</> </>
) );
}); });
export default observer(BackendLogsPanel); export default observer(BackendLogsPanel);

View file

@ -22,7 +22,6 @@ const Tabs = ({ tabs, active, onClick, border = true, className }: Props) => {
return ( return (
<div className={cn(stl.tabs, className, { [stl.bordered]: border })} role="tablist"> <div className={cn(stl.tabs, className, { [stl.bordered]: border })} role="tablist">
<Segmented <Segmented
className='w-full'
size="small" size="small"
value={active} value={active}
options={tabs.map(({ key, text, hidden = false, disabled = false, iconComp = null }) => ({ options={tabs.map(({ key, text, hidden = false, disabled = false, iconComp = null }) => ({

View file

@ -58,11 +58,12 @@ const PerformanceGraph = React.memo((props: Props) => {
{disabled ? ( {disabled ? (
<div <div
className={ className={
'flex justify-start' 'flex justify-center'
} }
> >
<div className={'text-xs text-neutral-400 ps-2'}> <div className={'text-xs text-neutral-400 ps-2'}>
Multi-tab performance overview is not available.</div> Multi-tab performance overview is not available.
</div>
</div> </div>
) : null} ) : null}
<ResponsiveContainer height={35}> <ResponsiveContainer height={35}>

View file

@ -1,10 +1,13 @@
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { LogLevel, ILog } from 'Player'; import { LogLevel, ILog } from 'Player';
import BottomBlock from '../BottomBlock'; import BottomBlock from '../BottomBlock';
import { Tabs, Input, Icon, NoContent } from 'UI'; import { Tabs, Input, NoContent } from 'UI';
import cn from 'classnames'; import cn from 'classnames';
import ConsoleRow from '../ConsoleRow'; import ConsoleRow from '../ConsoleRow';
import { IOSPlayerContext, MobilePlayerContext } from 'App/components/Session/playerContext'; import {
IOSPlayerContext,
MobilePlayerContext,
} from 'App/components/Session/playerContext';
import { observer } from 'mobx-react-lite'; import { observer } from 'mobx-react-lite';
import { VList, VListHandle } from 'virtua'; import { VList, VListHandle } from 'virtua';
import { useStore } from 'App/mstore'; import { useStore } from 'App/mstore';
@ -12,7 +15,7 @@ import ErrorDetailsModal from 'App/components/Dashboard/components/Errors/ErrorD
import { useModal } from 'App/components/Modal'; import { useModal } from 'App/components/Modal';
import useAutoscroll, { getLastItemTime } from '../useAutoscroll'; import useAutoscroll, { getLastItemTime } from '../useAutoscroll';
import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter'; import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter';
import {InfoCircleOutlined} from '@ant-design/icons' import { InfoCircleOutlined, SearchOutlined } from '@ant-design/icons';
const ALL = 'ALL'; const ALL = 'ALL';
const INFO = 'INFO'; const INFO = 'INFO';
@ -27,7 +30,10 @@ const LEVEL_TAB = {
[LogLevel.EXCEPTION]: ERRORS, [LogLevel.EXCEPTION]: ERRORS,
} as const; } as const;
const TABS = [ALL, ERRORS, WARNINGS, INFO].map((tab) => ({ text: tab, key: tab })); const TABS = [ALL, ERRORS, WARNINGS, INFO].map((tab) => ({
text: tab,
key: tab,
}));
function renderWithNL(s: string | null = '') { function renderWithNL(s: string | null = '') {
if (typeof s !== 'string') return ''; if (typeof s !== 'string') return '';
@ -74,20 +80,23 @@ function MobileConsolePanel() {
const [isDetailsModalActive, setIsDetailsModalActive] = useState(false); const [isDetailsModalActive, setIsDetailsModalActive] = useState(false);
const { showModal } = useModal(); const { showModal } = useModal();
const { player, store } = React.useContext<IOSPlayerContext>(MobilePlayerContext); const { player, store } =
React.useContext<IOSPlayerContext>(MobilePlayerContext);
const jump = (t: number) => player.jump(t); const jump = (t: number) => player.jump(t);
const { const { logList, logListNow, exceptionsListNow } = store.get();
logList,
logListNow,
exceptionsListNow,
} = store.get();
const list = logList as ILog[]; const list = logList as ILog[];
let filteredList = useRegExListFilterMemo(list, (l) => l.value, filter); let filteredList = useRegExListFilterMemo(list, (l) => l.value, filter);
filteredList = useTabListFilterMemo(filteredList, (l) => LEVEL_TAB[l.level], ALL, activeTab); filteredList = useTabListFilterMemo(
filteredList,
(l) => LEVEL_TAB[l.level],
ALL,
activeTab
);
const onTabClick = (activeTab: any) => devTools.update(INDEX_KEY, { activeTab }); const onTabClick = (activeTab: any) =>
devTools.update(INDEX_KEY, { activeTab });
const onFilterChange = ({ target: { value } }: any) => const onFilterChange = ({ target: { value } }: any) =>
devTools.update(INDEX_KEY, { filter: value }); devTools.update(INDEX_KEY, { filter: value });
@ -137,7 +146,12 @@ function MobileConsolePanel() {
<BottomBlock.Header> <BottomBlock.Header>
<div className="flex items-center"> <div className="flex items-center">
<span className="font-semibold color-gray-medium mr-4">Console</span> <span className="font-semibold color-gray-medium mr-4">Console</span>
<Tabs tabs={TABS} active={activeTab} onClick={onTabClick} border={false} /> <Tabs
tabs={TABS}
active={activeTab}
onClick={onTabClick}
border={false}
/>
</div> </div>
<Input <Input
className="rounded-lg" className="rounded-lg"
@ -145,8 +159,8 @@ function MobileConsolePanel() {
name="filter" name="filter"
onChange={onFilterChange} onChange={onFilterChange}
value={filter} value={filter}
size='small' size="small"
prefix={<SearchOutlined className='text-neutral-400' />} prefix={<SearchOutlined className="text-neutral-400" />}
/> />
</BottomBlock.Header> </BottomBlock.Header>
<BottomBlock.Content className="overflow-y-auto"> <BottomBlock.Content className="overflow-y-auto">
@ -160,11 +174,7 @@ function MobileConsolePanel() {
size="small" size="small"
show={filteredList.length === 0} show={filteredList.length === 0}
> >
<VList <VList ref={_list} itemSize={25} count={filteredList.length || 1}>
ref={_list}
itemSize={25}
count={filteredList.length || 1}
>
{filteredList.map((log, index) => ( {filteredList.map((log, index) => (
<ConsoleRow <ConsoleRow
key={log.time + index} key={log.time + index}

View file

@ -91,7 +91,7 @@
"@babel/preset-typescript": "^7.23.2", "@babel/preset-typescript": "^7.23.2",
"@babel/runtime": "^7.23.2", "@babel/runtime": "^7.23.2",
"@jest/globals": "^29.7.0", "@jest/globals": "^29.7.0",
"@openreplay/sourcemap-uploader": "^3.0.8", "@openreplay/sourcemap-uploader": "^3.0.10",
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/luxon": "^3.4.2", "@types/luxon": "^3.4.2",
"@types/node": "^22.7.8", "@types/node": "^22.7.8",

View file

@ -2471,15 +2471,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@openreplay/sourcemap-uploader@npm:^3.0.8": "@openreplay/sourcemap-uploader@npm:^3.0.10":
version: 3.0.10 version: 3.0.13
resolution: "@openreplay/sourcemap-uploader@npm:3.0.10" resolution: "@openreplay/sourcemap-uploader@npm:3.0.13"
dependencies: dependencies:
argparse: "npm:^2.0.1" argparse: "npm:^2.0.1"
glob: "npm:^8.0.3"
glob-promise: "npm:^6.0.7" glob-promise: "npm:^6.0.7"
bin: bin:
sourcemap-uploader: cli.js sourcemap-uploader: cli.js
checksum: 10c1/aa22a161020f55e96c3835cb719f4c530728ccb55e90d1a0bbc96c5243bffb900e204dd6275f1dee2927db3c4439b5d33c38c60774e5340ccb36acab95f30cc5 checksum: 10c1/1bab68b1a2f348973d9c027e00b9e027a833cec998e9a8a6c2872585d46fcdfb6f1492b98fd42f04ed2e6c4f913ec19e44a24be95460e1f7d1e08f6edf15eabe
languageName: node languageName: node
linkType: hard linkType: hard
@ -8058,7 +8059,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"glob@npm:^8.0.1": "glob@npm:^8.0.1, glob@npm:^8.0.3":
version: 8.1.0 version: 8.1.0
resolution: "glob@npm:8.1.0" resolution: "glob@npm:8.1.0"
dependencies: dependencies:
@ -11308,7 +11309,7 @@ __metadata:
"@floating-ui/react-dom-interactions": "npm:^0.10.3" "@floating-ui/react-dom-interactions": "npm:^0.10.3"
"@jest/globals": "npm:^29.7.0" "@jest/globals": "npm:^29.7.0"
"@medv/finder": "npm:^3.1.0" "@medv/finder": "npm:^3.1.0"
"@openreplay/sourcemap-uploader": "npm:^3.0.8" "@openreplay/sourcemap-uploader": "npm:^3.0.10"
"@sentry/browser": "npm:^5.21.1" "@sentry/browser": "npm:^5.21.1"
"@svg-maps/world": "npm:^1.0.1" "@svg-maps/world": "npm:^1.0.1"
"@tanstack/react-query": "npm:^5.56.2" "@tanstack/react-query": "npm:^5.56.2"