ui: fix log panel crashing
This commit is contained in:
parent
37f00f4d73
commit
4eea15b053
6 changed files with 112 additions and 96 deletions
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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 }) => ({
|
||||||
|
|
|
||||||
|
|
@ -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}>
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue