ui: drop deprecated TimeTable.tsx, point gql to correct one (#2567)

This commit is contained in:
Delirium 2024-09-12 15:54:49 +02:00 committed by GitHub
parent 8e81bdea7e
commit 9030806146
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 18 additions and 793 deletions

View file

@ -145,7 +145,7 @@ function Player(props: IProps) {
{bottomBlock === STORAGE && <Storage />}
{bottomBlock === PROFILER && <ProfilerPanel panelHeight={panelHeight} />}
{bottomBlock === PERFORMANCE && <ConnectedPerformance />}
{bottomBlock === GRAPHQL && <GraphQL />}
{bottomBlock === GRAPHQL && <GraphQL panelHeight={panelHeight} />}
{bottomBlock === EXCEPTIONS && <Exceptions />}
</div>
)}

View file

@ -3,7 +3,7 @@ import React, { useEffect } from 'react';
import { NoContent, Input, SlideModal, CloseButton, Button } from 'UI';
import { getRE } from 'App/utils';
import BottomBlock from '../BottomBlock';
import TimeTable from '../TimeTable';
import TimeTable from 'Components/shared/DevTools/TimeTable'
import GQLDetails from './GQLDetails';
import { PlayerContext } from 'App/components/Session/playerContext';
import { observer } from 'mobx-react-lite';
@ -31,7 +31,9 @@ function renderDefaultStatus() {
return '2xx-3xx';
}
function GraphQL() {
function GraphQL({
panelHeight
}: { panelHeight: number }) {
const { player, store } = React.useContext(PlayerContext);
const { time, livePlay, tabStates, currentTab } = store.get();
const { graphqlList: list = [], graphqlListNow: listNow = [] } = tabStates[currentTab]
@ -55,16 +57,6 @@ function GraphQL() {
return (
<div className="flex justify-between items-center grow-0 w-full">
<div>{r.operationName}</div>
<Button
variant="text"
className="right-0 text-xs uppercase p-2 color-gray-500 hover:color-teal"
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
player.jump(r.time);
}}
>
Jump
</Button>
</div>
);
}
@ -93,13 +85,16 @@ function GraphQL() {
};
const setCurrent = (item: any, index: number) => {
if (!livePlay) {
player.pause();
player.jump(item.time);
}
setState((prevState) => ({ ...prevState, current: item, currentIndex: index }));
};
const onJump = (time: number) => {
if (!livePlay) {
player.pause();
player.jump(time);
}
}
const closeModal = () =>
setState((prevState) => ({ ...prevState, current: null, showFetchDetails: false }));
@ -155,8 +150,10 @@ function GraphQL() {
<TimeTable
rows={filteredList}
onRowClick={setCurrent}
tableHeight={panelHeight - 102}
hoverable
activeIndex={lastActiveItem}
onJump={onJump}
>
{[
{
@ -164,19 +161,14 @@ function GraphQL() {
width: 90,
render: renderStart,
},
{
label: 'Status',
width: 70,
render: renderDefaultStatus,
},
{
label: 'Type',
dataKey: 'operationKind',
width: 60,
width: 80,
},
{
label: 'Name',
width: 240,
width: 300,
render: renderName,
},
]}

View file

@ -1,27 +0,0 @@
import React from 'react';
export default class ProfileInfo extends React.PureComponent {
render() {
const {
profile: {
name,
args,
result,
}
} = this.props;
return (
<div className="px-6" >
<h5 className="py-3">{"Arguments"}</h5>
<ul className="color-gray-medium">
{ args.split(',').map(arg => (
<li> { `${ arg }` } </li>
))}
</ul>
<h5 className="py-3" >{"Result"}</h5>
<div className="color-gray-medium" >
{ `${ result }` }
</div>
</div>
);
}
}

View file

@ -1,82 +0,0 @@
import React from 'react';
import { connectPlayer } from 'Player';
import { SlideModal, TextEllipsis, Input } from 'UI';
import { getRE } from 'App/utils';
import ProfileInfo from './ProfileInfo';
import TimeTable from '../TimeTable';
import BottomBlock from '../BottomBlock';
const renderDuration = p => `${ p.duration }ms`;
const renderName = p => <TextEllipsis text={ p.name } />;
@connectPlayer(state => ({
profiles: state.profilesList,
}))
export default class Profiler extends React.PureComponent {
state = {
filter: '',
modalProfile: null,
}
onFilterChange = ({ target: { value } }) => this.setState({ filter: value })
onProfileClick = resource => {
this.setState({ modalProfile: resource });
}
closeModal = () => this.setState({ modalProfile: null })
render() {
const { profiles } = this.props;
const { filter, modalProfile } = this.state;
const filterRE = getRE(filter, 'i');
const filteredProfiles = profiles.filter(({ name }) => filterRE.test(name));
return (
<React.Fragment>
<SlideModal
title={ modalProfile && modalProfile.name }
isDisplayed={ modalProfile !== null }
content={ modalProfile && <ProfileInfo profile={ modalProfile } />}
size="middle"
onClose={ this.closeModal }
/>
<BottomBlock>
<BottomBlock.Header>
<div className="flex items-center">
<span className="font-semibold color-gray-medium mr-4">Profiler</span>
</div>
<Input
// className="input-small"
placeholder="Filter by name"
icon="search"
name="filter"
onChange={ this.onFilterChange }
height={28}
/>
</BottomBlock.Header>
<BottomBlock.Content>
<TimeTable
rows={ filteredProfiles }
onRowClick={ this.onProfileClick }
hoverable
>
{[
{
label: "Name",
dataKey: 'name',
width: 200,
render: renderName,
}, {
label: "Time",
key: 'duration',
width: 80,
render: renderDuration,
}
]}
</TimeTable>
</BottomBlock.Content>
</BottomBlock>
</React.Fragment>
);
}
}

View file

@ -1 +0,0 @@
export { default } from './Profiler';

View file

@ -1,108 +0,0 @@
import { Tooltip } from 'UI';
import { percentOf } from 'App/utils';
import styles from './barRow.module.css';
import tableStyles from './timeTable.module.css';
import React from 'react';
const formatTime = (time) => (time < 1000 ? `${time.toFixed(2)}ms` : `${time / 1000}s`);
interface Props {
resource: {
time: number;
ttfb?: number;
duration?: number;
key: string;
};
popup?: boolean;
timestart: number;
timewidth: number;
}
// TODO: If request has no duration, set duration to 0.2s. Enforce existence of duration in the future.
const BarRow = ({
resource: { time, ttfb = 0, duration = 200, key },
popup = false,
timestart = 0,
timewidth,
}: Props) => {
const timeOffset = time - timestart;
ttfb = ttfb || 0;
const trigger = (
<div
className={styles.barWrapper}
style={{
left: `${percentOf(timeOffset, timewidth)}%`,
right: `${100 - percentOf(timeOffset + duration, timewidth)}%`,
minWidth: '5px',
}}
>
<div
className={styles.ttfbBar}
style={{
width: `${percentOf(ttfb, duration)}%`,
}}
/>
<div
className={styles.downloadBar}
style={{
width: `${percentOf(duration - ttfb, duration)}%`,
minWidth: '5px',
}}
/>
</div>
);
if (!popup)
return (
<div key={key} className={tableStyles.row}>
{' '}
{trigger}{' '}
</div>
);
return (
<div key={key} className={tableStyles.row}>
<Tooltip
title={
<React.Fragment>
{ttfb != null && (
<div className={styles.popupRow}>
<div className={styles.title}>{'Waiting (TTFB)'}</div>
<div className={styles.popupBarWrapper}>
<div
className={styles.ttfbBar}
style={{
left: 0,
width: `${percentOf(ttfb, duration)}%`,
}}
/>
</div>
<div className={styles.time}>{formatTime(ttfb)}</div>
</div>
)}
<div className={styles.popupRow}>
<div className={styles.title}>{'Content Download'}</div>
<div className={styles.popupBarWrapper}>
<div
className={styles.downloadBar}
style={{
left: `${percentOf(ttfb, duration)}%`,
width: `${percentOf(duration - ttfb, duration)}%`,
}}
/>
</div>
<div className={styles.time}>{formatTime(duration - ttfb)}</div>
</div>
</React.Fragment>
}
size="mini"
position="top center"
>
{trigger}
</Tooltip>
</div>
);
};
BarRow.displayName = 'BarRow';
export default BarRow;

View file

@ -1,390 +0,0 @@
import cn from 'classnames';
import { Duration } from 'luxon';
import React from 'react';
import { VList, VListHandle } from 'virtua';
import { percentOf } from 'App/utils';
import { Button, NoContent } from 'UI';
import autoscrollStl from '../autoscroll.module.css';
import BarRow from './BarRow';
import stl from './timeTable.module.css';
//aaa
type Timed = {
time: number;
};
type Durationed = {
duration: number;
};
type CanBeRed = {
//+isRed: boolean,
isRed: boolean;
};
interface Row extends Timed, Durationed, CanBeRed {
[key: string]: any;
key: string;
}
type Line = {
color: string; // Maybe use typescript?
hint?: string;
onClick?: any;
} & Timed;
type Column = {
label: string;
width: number;
dataKey?: string;
render?: (row: any) => void;
referenceLines?: Array<Line>;
style?: React.CSSProperties;
} & RenderOrKey;
// type RenderOrKey = { // Disjoint?
// render: Row => React.Node
// } | {
// dataKey: string,
// }
type RenderOrKey =
| {
render?: (row: Row) => React.ReactNode;
key?: string;
}
| {
dataKey: string;
};
type Props = {
className?: string;
rows: Array<Row>;
children: Array<Column>;
tableHeight?: number;
activeIndex?: number;
renderPopup?: boolean;
navigation?: boolean;
referenceLines?: any[];
additionalHeight?: number;
hoverable?: boolean;
onRowClick?: (row: any, index: number) => void;
};
type TimeLineInfo = {
timestart: number;
timewidth: number;
};
type State = TimeLineInfo & typeof initialState;
//const TABLE_HEIGHT = 195;
let _additionalHeight = 0;
const ROW_HEIGHT = 32;
//const VISIBLE_COUNT = Math.ceil(TABLE_HEIGHT/ROW_HEIGHT);
const TIME_SECTIONS_COUNT = 8;
const ZERO_TIMEWIDTH = 1000;
function formatTime(ms: number) {
if (ms < 0) return '';
if (ms < 1000) return Duration.fromMillis(ms).toFormat('0.SSS');
return Duration.fromMillis(ms).toFormat('mm:ss');
}
function computeTimeLine(
rows: Array<Row>,
firstVisibleRowIndex: number,
visibleCount: number
): TimeLineInfo {
const visibleRows = rows.slice(
firstVisibleRowIndex,
firstVisibleRowIndex + visibleCount + _additionalHeight
);
let timestart =
visibleRows.length > 0 ? Math.min(...visibleRows.map((r) => r.time)) : 0;
// TODO: GraphQL requests do not have a duration, so their timeline is borked. Assume a duration of 0.2s for every GraphQL request
const timeend =
visibleRows.length > 0
? Math.max(...visibleRows.map((r) => r.time + (r.duration ?? 200)))
: 0;
let timewidth = timeend - timestart;
const offset = timewidth / 70;
if (timestart >= offset) {
timestart -= offset;
}
timewidth *= 1.5; // += offset;
if (timewidth === 0) {
timewidth = ZERO_TIMEWIDTH;
}
return {
timestart,
timewidth,
};
}
const initialState = {
firstVisibleRowIndex: 0,
};
export default class TimeTable extends React.PureComponent<Props, State> {
state = {
...computeTimeLine(
this.props.rows,
initialState.firstVisibleRowIndex,
this.visibleCount
),
...initialState,
};
get tableHeight() {
return this.props.tableHeight || 195;
}
get visibleCount() {
return Math.ceil(this.tableHeight / ROW_HEIGHT);
}
scroller = React.createRef<VListHandle>();
autoScroll = true;
componentDidMount() {
if (this.scroller.current) {
this.scroller.current.scrollToIndex(this.props.activeIndex);
}
}
componentDidUpdate(prevProps: any, prevState: any) {
if (
prevState.firstVisibleRowIndex !== this.state.firstVisibleRowIndex ||
(this.props.rows.length <= this.visibleCount + _additionalHeight &&
prevProps.rows.length !== this.props.rows.length)
) {
this.setState({
...computeTimeLine(
this.props.rows,
this.state.firstVisibleRowIndex,
this.visibleCount
),
});
}
if (
this.props.activeIndex &&
this.props.activeIndex >= 0 &&
prevProps.activeIndex !== this.props.activeIndex &&
this.scroller.current
) {
this.scroller.current.scrollToIndex(this.props.activeIndex);
}
}
onScroll = ({
scrollTop,
scrollHeight,
clientHeight,
}: {
scrollTop: number;
scrollHeight: number;
clientHeight: number;
}): void => {
const firstVisibleRowIndex = Math.floor(scrollTop / ROW_HEIGHT + 0.33);
if (this.state.firstVisibleRowIndex !== firstVisibleRowIndex) {
this.autoScroll =
scrollHeight - clientHeight - scrollTop < ROW_HEIGHT / 2;
this.setState({ firstVisibleRowIndex });
}
};
renderRow = (index: number) => {
const { activeIndex } = this.props;
const {
children: columns,
rows,
renderPopup,
hoverable,
onRowClick,
} = this.props;
const { timestart, timewidth } = this.state;
const row = rows[index];
return (
<div
className={cn('border-b border-color-gray-light-shade', stl.row, {
[stl.hoverable]: hoverable,
'error color-red': !!row.isRed && row.isRed,
'cursor-pointer': typeof onRowClick === 'function',
[stl.activeRow]: activeIndex === index,
// [stl.inactiveRow]: !activeIndex || index > activeIndex,
})}
onClick={
typeof onRowClick === 'function'
? () => onRowClick(row, index)
: undefined
}
id="table-row"
>
{columns.map((column, key) => (
<div
key={column.label.replace(' ', '')}
className={stl.cell}
style={{ width: `${column.width}px` }}
>
{column.render
? column.render(row)
: row[column.dataKey || ''] || (
<i className="color-gray-light">{'empty'}</i>
)}
</div>
))}
<div className={cn('relative flex-1 flex', stl.timeBarWrapper)}>
<BarRow
resource={row}
timestart={timestart}
timewidth={timewidth}
popup={renderPopup}
/>
</div>
</div>
);
};
onPrevClick = () => {
let prevRedIndex = -1;
for (let i = this.state.firstVisibleRowIndex - 1; i >= 0; i--) {
if (this.props.rows[i].isRed) {
prevRedIndex = i;
break;
}
}
if (this.scroller.current != null) {
this.scroller.current.scrollToIndex(prevRedIndex);
}
};
onNextClick = () => {
let prevRedIndex = -1;
for (
let i = this.state.firstVisibleRowIndex + 1;
i < this.props.rows.length;
i++
) {
if (this.props.rows[i].isRed) {
prevRedIndex = i;
break;
}
}
if (this.scroller.current != null) {
this.scroller.current.scrollToIndex(prevRedIndex);
}
};
render() {
const {
className,
rows,
children: columns,
navigation = false,
referenceLines = [],
additionalHeight = 0,
activeIndex,
} = this.props;
const { timewidth, timestart } = this.state;
_additionalHeight = additionalHeight;
const sectionDuration = Math.round(timewidth / TIME_SECTIONS_COUNT);
const timeColumns: number[] = [];
if (timewidth > 0) {
for (let i = 0; i < TIME_SECTIONS_COUNT; i++) {
timeColumns.push(timestart + i * sectionDuration);
}
}
const visibleRefLines = referenceLines.filter(
({ time }) => time > timestart && time < timestart + timewidth
);
const columnsSumWidth = columns.reduce((sum, { width }) => sum + width, 0);
return (
<div className={cn(className, 'relative')}>
{navigation && (
<div className={cn(autoscrollStl.navButtons, 'flex items-center')}>
<Button
variant="text-primary"
icon="chevron-up"
tooltip={{
title: 'Previous Error',
delay: 0,
}}
onClick={this.onPrevClick}
/>
<Button
variant="text-primary"
icon="chevron-down"
tooltip={{
title: 'Next Error',
delay: 0,
}}
onClick={this.onNextClick}
/>
</div>
)}
<div className={stl.headers}>
<div className={stl.infoHeaders}>
{columns.map(({ label, width }) => (
<div
key={label.replace(' ', '')}
className={stl.headerCell}
style={{ width: `${width}px` }}
>
{label}
</div>
))}
</div>
<div className={stl.waterfallHeaders}>
{timeColumns.map((time, i) => (
<div className={stl.timeCell} key={`tc-${i}`}>
{formatTime(time)}
</div>
))}
</div>
</div>
<NoContent size="small" show={rows.length === 0}>
<div className="relative">
<div
className={stl.timePart}
style={{ left: `${columnsSumWidth}px` }}
>
{timeColumns.map((_, index) => (
<div key={`tc-${index}`} className={stl.timeCell} />
))}
{visibleRefLines.map((line, key) => (
<div
key={line.time + key}
className={cn(stl.refLine, `bg-${line.color}`)}
style={{
left: `${percentOf(line.time - timestart, timewidth)}%`,
cursor:
typeof line.onClick === 'function' ? 'click' : 'auto',
}}
onClick={line.onClick}
/>
))}
</div>
<VList
ref={this.scroller}
className={stl.list}
count={rows.length}
itemSize={ROW_HEIGHT}
>
{this.props.rows.map((_, i) => this.renderRow(i))}
</VList>
</div>
</NoContent>
</div>
);
}
}

View file

@ -1,45 +0,0 @@
.barWrapper {
display: flex;
position: absolute;
top: 35%;
bottom: 35%;
border-radius: 3px;
overflow: hidden;
}
.downloadBar, .ttfbBar {
/* box-shadow: inset 0px 0px 0px 1px $teal; */
height: 100%;
box-sizing: border-box;
position: relative;
}
.ttfbBar {
background-color: rgba(175, 226, 221, 0.8);
}
.downloadBar {
background-color: rgba(133, 200, 192, 0.8);
}
.popupRow {
color: $gray-medium;
display: flex;
align-items: center;
padding: 2px 0;
font-size: 12px;
}
.title {
width: 105px;
}
.time {
width: 60px;
padding-left: 10px;
}
.popupBarWrapper {
width: 220px;
height: 15px;
border-radius: 3px;
overflow: hidden;
}

View file

@ -1 +0,0 @@
export { default } from './TimeTable';

View file

@ -1,112 +0,0 @@
$offset: 10px;
.timeCell {
border-left: solid thin rgba(0, 0, 0, 0.05);
}
.headers {
box-shadow: 0 1px 2px 0 $gray-light;
background-color: $gray-lightest;
color: $gray-darkest;
font-size: 12px;
overflow-x: hidden;
white-space: nowrap;
width: 100%;
display: flex;
padding: 0 $offset;
}
.infoHeaders {
text-transform: uppercase;
display: flex;
& .headerCell {
padding: 4px 2px;
}
}
.waterfallHeaders {
display: flex;
flex: 1;
& .timeCell {
flex: 1;
overflow: hidden;
padding: 4px 0;
}
}
.list {
/* TODO hide the scrollbar track */
&::-webkit-scrollbar {
width: 1px;
}
scrollbar-width: thin;
font-size: 12px;
font-family: 'Menlo', 'monaco', 'consolas', monospace;
}
.row {
display: flex;
padding: 0 $offset;
&:hover {
background-color: $active-blue;
}
/*align-items: center;
cursor: pointer;
*/
/* &:nth-child(even) {
background-color: $gray-lightest;
} */
/* & > div:first-child {
padding-left: 5px;
}*/
}
.cell {
height: 100%;
display: flex;
align-items: center;
overflow: hidden;
padding: 0 2px;
}
.hoverable {
transition: all 0.3s;
cursor: pointer;
&:hover {
background-color: $active-blue;
transition: all 0.2s;
color: $gray-dark;
}
}
.timeBarWrapper{
overflow: hidden;
}
.timePart {
position: absolute;
top: 0;
bottom: 0;
/*left:0;*/
right: 0;
display: flex;
margin: 0 $offset;
& .timeCell {
height: 100%;
flex: 1;
z-index: 1;
pointer-events: none;
}
& .refLine {
position: absolute;
height: 100%;
width: 1px;
z-index: 1;
}
}
.activeRow {
background-color: $teal-light;
}
.inactiveRow {
opacity: 0.4;
}

View file

@ -71,7 +71,7 @@ type Props = {
additionalHeight?: number;
hoverable?: boolean;
onRowClick?: (row: any, index: number) => void;
onJump?: (time: any) => void;
onJump?: (time: number) => void;
};
type TimeLineInfo = {
@ -221,7 +221,7 @@ export default class TimeTable extends React.PureComponent<Props, State> {
: row[dataKey || ''] || <i className="color-gray-light">{'empty'}</i>}
</div>
))}
<div className={cn('relative flex-1 flex', stl.timeBarWrapper)}>
<div className={cn('relative flex-1 flex', stl.timeBarWrapper)} style={{ height: 15 }}>
<BarRow resource={row} timestart={timestart} timewidth={timewidth} popup={renderPopup} />
</div>
<JumpButton onClick={() => this.onJump(index)} />

View file

@ -38,7 +38,6 @@ export default class MessageLoader {
private isClickmap: boolean,
private uiErrorHandler?: { error: (msg: string) => void }
) {}
setSession(session: SessionFilesInfo) {
this.session = session
}