* ui: fix performance bottlenecks, split data sources in devtools panes * ui: move xray warn * Player ux improvements (#2834) * Player UX improvements. DevTools (Including multi-tab) Actions panel (User events, Click maps, Tag Elements) * ui: remove unused imports, remove str templ classnames --------- Co-authored-by: Sudheer Salavadi <connect.uxmaster@gmail.com> --------- Co-authored-by: Sudheer Salavadi <connect.uxmaster@gmail.com>
207 lines
5 KiB
TypeScript
207 lines
5 KiB
TypeScript
import React from 'react';
|
|
import { useModal } from 'App/components/Modal';
|
|
import { Icon } from 'UI';
|
|
import { shortDurationFromMs } from "App/date";
|
|
import StackEventModal from '../StackEventModal';
|
|
import ErrorDetailsModal from 'App/components/Dashboard/components/Errors/ErrorDetailsModal';
|
|
import FetchDetails from 'Shared/FetchDetailsModal';
|
|
import GraphQLDetailsModal from 'Shared/GraphQLDetailsModal';
|
|
import { PlayerContext } from 'App/components/Session/playerContext';
|
|
import { Popover } from 'antd';
|
|
import {
|
|
shortenResourceName,
|
|
NetworkElement,
|
|
getFrustration,
|
|
FrustrationElement,
|
|
StackEventElement,
|
|
PerformanceElement,
|
|
ExceptionElement,
|
|
} from './Dots'
|
|
|
|
interface Props {
|
|
pointer: any;
|
|
type:
|
|
| 'ERRORS'
|
|
| 'EVENT'
|
|
| 'NETWORK'
|
|
| 'FRUSTRATIONS'
|
|
| 'EVENTS'
|
|
| 'PERFORMANCE'
|
|
noClick?: boolean;
|
|
fetchPresented?: boolean;
|
|
isGrouped?: boolean;
|
|
}
|
|
const TimelinePointer = React.memo((props: Props) => {
|
|
const { pointer, type, isGrouped } = props;
|
|
const { player } = React.useContext(PlayerContext);
|
|
const item = isGrouped ? pointer : pointer[0]
|
|
|
|
const { showModal } = useModal();
|
|
const createEventClickHandler = (pointer: any, type: any) => (e: any) => {
|
|
if (props.noClick) return;
|
|
e.stopPropagation();
|
|
player.jump(pointer.time);
|
|
if (!type) {
|
|
return;
|
|
}
|
|
|
|
if (type === 'ERRORS') {
|
|
showModal(<ErrorDetailsModal errorId={pointer.errorId} />, {
|
|
right: true,
|
|
width: 1200,
|
|
});
|
|
}
|
|
|
|
if (type === 'EVENT') {
|
|
showModal(<StackEventModal event={pointer} />, {
|
|
right: true,
|
|
width: 450,
|
|
});
|
|
}
|
|
|
|
if (type === 'NETWORK') {
|
|
if (pointer.tp === 'graph_ql') {
|
|
showModal(<GraphQLDetailsModal resource={pointer} />, {
|
|
right: true,
|
|
width: 500,
|
|
});
|
|
} else {
|
|
showModal(
|
|
<FetchDetails
|
|
resource={pointer}
|
|
fetchPresented={props.fetchPresented}
|
|
/>,
|
|
{ right: true, width: 500 }
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
if (isGrouped) {
|
|
const onClick = createEventClickHandler(item[0], type);
|
|
return <GroupedIssue type={type} items={item} onClick={onClick} createEventClickHandler={createEventClickHandler} />;
|
|
}
|
|
|
|
if (type === 'NETWORK') {
|
|
return (
|
|
<NetworkElement
|
|
item={item}
|
|
createEventClickHandler={createEventClickHandler}
|
|
/>
|
|
);
|
|
}
|
|
if (type === 'FRUSTRATIONS') {
|
|
return (
|
|
<FrustrationElement
|
|
item={item}
|
|
createEventClickHandler={createEventClickHandler}
|
|
/>
|
|
);
|
|
}
|
|
if (type === 'ERRORS') {
|
|
return (
|
|
<ExceptionElement
|
|
item={item}
|
|
createEventClickHandler={createEventClickHandler}
|
|
/>
|
|
);
|
|
}
|
|
if (type === 'EVENTS') {
|
|
return (
|
|
<StackEventElement
|
|
item={item}
|
|
createEventClickHandler={createEventClickHandler}
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (type === 'PERFORMANCE') {
|
|
return (
|
|
<PerformanceElement
|
|
item={item}
|
|
createEventClickHandler={createEventClickHandler}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return <div>unknown type</div>;
|
|
});
|
|
|
|
function GroupedIssue({
|
|
type,
|
|
items,
|
|
onClick,
|
|
createEventClickHandler,
|
|
}: {
|
|
type: string;
|
|
items: Record<string, any>[];
|
|
onClick: () => void;
|
|
createEventClickHandler: any;
|
|
}) {
|
|
const subStr = {
|
|
NETWORK: 'Network Issues',
|
|
ERRORS: 'Errors',
|
|
EVENTS: 'Events',
|
|
FRUSTRATIONS: 'Frustrations',
|
|
};
|
|
const title = `${items.length} ${subStr[type]} Observed`;
|
|
|
|
return (
|
|
<Popover
|
|
placement={'right'}
|
|
title={title}
|
|
content={
|
|
<div style={{ maxHeight: 160, overflowY: 'auto' }}>
|
|
{items.map((pointer) => (
|
|
<div
|
|
key={pointer.time}
|
|
onClick={createEventClickHandler(pointer, type)}
|
|
className={'flex items-center gap-2 mb-1 cursor-pointer border-b border-transparent hover:border-gray-lightest'}
|
|
>
|
|
<div className={'text-disabled-text'}>@{shortDurationFromMs(pointer.time)}</div>
|
|
<RenderLineData type={type} item={pointer} />
|
|
</div>
|
|
))}
|
|
</div>
|
|
}
|
|
>
|
|
<div
|
|
onClick={onClick}
|
|
className={
|
|
'h-5 w-5 cursor-pointer rounded-full bg-red text-white font-bold flex items-center justify-center text-xs'
|
|
}
|
|
>
|
|
{items.length}
|
|
</div>
|
|
</Popover>
|
|
);
|
|
}
|
|
|
|
function RenderLineData({ item, type }: any) {
|
|
if (type === 'FRUSTRATIONS') {
|
|
const elData = getFrustration(item);
|
|
return <>
|
|
<div><Icon name={elData.icon} color="black" size="16" /></div>
|
|
<div>{elData.name}</div>
|
|
</>
|
|
}
|
|
if (type === 'NETWORK') {
|
|
const name = item.success ? 'Slow resource' : '4xx/5xx Error';
|
|
return <>
|
|
<div>{name}</div>
|
|
<div>{shortenResourceName(item.name)}</div>
|
|
</>
|
|
}
|
|
if (type === 'EVENTS') {
|
|
return <div>{item.name || 'Stack Event'}</div>
|
|
}
|
|
if (type === 'PERFORMANCE') {
|
|
return <div>{item.type}</div>
|
|
}
|
|
if (type === 'ERRORS') {
|
|
return <div>{item.message}</div>
|
|
}
|
|
return <div>{JSON.stringify(item)}</div>
|
|
}
|
|
|
|
export default TimelinePointer;
|