change(ui) - wip

This commit is contained in:
Shekar Siri 2022-11-22 19:10:17 +01:00
parent 5a1cd27ebc
commit a2a956b4d4
9 changed files with 267 additions and 189 deletions

View file

@ -1,17 +1,29 @@
import React from 'react';
import React, { useEffect } from 'react';
import cn from 'classnames';
import stl from './bottomBlock.module.css';
let timer = null;
const BottomBlock = ({
children = null,
className = '',
additionalHeight = 0,
onMouseEnter = () => {},
onMouseLeave = () => {},
...props
}) => (
<div className={ cn(stl.wrapper, "flex flex-col mb-2") } { ...props } >
{ children }
</div>
);
}) => {
useEffect(() => {}, []);
return (
<div
className={cn(stl.wrapper, 'flex flex-col mb-2')}
{...props}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{children}
</div>
);
};
BottomBlock.displayName = 'BottomBlock';

View file

@ -4,7 +4,6 @@ import Log from 'Types/session/log';
import BottomBlock from '../BottomBlock';
import { LEVEL } from 'Types/session/log';
import { Tabs, Input, Icon, NoContent } from 'UI';
// import Autoscroll from 'App/components/Session_/Autoscroll';
import cn from 'classnames';
import ConsoleRow from '../ConsoleRow';
import { getRE } from 'App/utils';

View file

@ -6,10 +6,10 @@ interface Props {
tooltip?: string;
}
function JumpButton(props: Props) {
const { tooltip = '' } = props;
const { tooltip } = props;
return (
<div className="absolute right-0 top-0 bottom-0 my-auto flex items-center">
<Tooltip title={tooltip} disabled={!!tooltip}>
<Tooltip title={tooltip} disabled={!tooltip}>
<div
className="mr-2 border cursor-pointer invisible group-hover:visible rounded-lg bg-active-blue text-xs flex items-center px-2 py-1 color-teal hover:shadow h-6"
onClick={(e: any) => {

View file

@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { QuestionMarkHint, Tooltip, Tabs, Input, NoContent, Icon, Toggler, Button } from 'UI';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { Tooltip, Tabs, Input, NoContent, Icon, Toggler } from 'UI';
import { getRE } from 'App/utils';
import Resource, { TYPES } from 'Types/session/resource';
import { formatBytes } from 'App/utils';
@ -12,6 +12,11 @@ import { Duration } from 'luxon';
import { connectPlayer, jump } from 'Player';
import { useModal } from 'App/components/Modal';
import FetchDetailsModal from 'Shared/FetchDetailsModal';
import { useStore } from 'App/mstore';
import { useObserver } from 'mobx-react-lite';
const INDEX_KEY = 'networkIndex';
const INDEX_KEY_ACTIVE = 'networkActive';
const ALL = 'ALL';
const XHR = 'xhr';
@ -67,37 +72,6 @@ export function renderStart(r: any) {
);
}
// const renderXHRText = () => (
// <span className="flex items-center">
// {XHR}
// <QuestionMarkHint
// content={
// <>
// Use our{' '}
// <a
// className="color-teal underline"
// target="_blank"
// href="https://docs.openreplay.com/plugins/fetch"
// >
// Fetch plugin
// </a>
// {' to capture HTTP requests and responses, including status codes and bodies.'} <br />
// We also provide{' '}
// <a
// className="color-teal underline"
// target="_blank"
// href="https://docs.openreplay.com/plugins/graphql"
// >
// support for GraphQL
// </a>
// {' for easy debugging of your queries.'}
// </>
// }
// className="ml-1"
// />
// </span>
// );
function renderSize(r: any) {
if (r.responseBodySize) return formatBytes(r.responseBodySize);
let triggerText;
@ -160,45 +134,76 @@ interface Props {
loadTime: any;
playing: boolean;
domBuildingTime: any;
currentIndex: any;
time: any;
}
function NetworkPanel(props: Props) {
const {
resources,
time,
currentIndex,
domContentLoadedTime,
loadTime,
playing,
domBuildingTime,
fetchList,
} = props;
const { showModal, hideModal } = useModal();
const { resources, time, domContentLoadedTime, loadTime, domBuildingTime, fetchList } = props;
const { showModal } = useModal();
const [activeTab, setActiveTab] = useState(ALL);
const [sortBy, setSortBy] = useState('time');
const [sortAscending, setSortAscending] = useState(true);
const [filter, setFilter] = useState('');
const [filteredList, setFilteredList] = useState([]);
const [showOnlyErrors, setShowOnlyErrors] = useState(false);
const [activeRequest, setActiveRequest] = useState(false )
const onTabClick = (activeTab: any) => setActiveTab(activeTab);
const onFilterChange = ({ target: { value } }: any) => setFilter(value);
const additionalHeight = 0;
const fetchPresented = fetchList.length > 0;
const {
sessionStore: { devTools },
} = useStore();
const resourcesSize = resources.reduce(
(sum: any, { decodedBodySize }: any) => sum + (decodedBodySize || 0),
0
);
const activeIndex = useObserver(() => devTools[INDEX_KEY]);
const activeClick = useObserver(() => devTools[INDEX_KEY_ACTIVE]);
const [pauseSync, setPauseSync] = useState(!!activeClick);
const synRef: any = useRef({});
const transferredSize = resources.reduce(
(sum: any, { headerSize, encodedBodySize }: any) =>
sum + (headerSize || 0) + (encodedBodySize || 0),
0
);
synRef.current = {
pauseSync,
activeIndex,
activeClick,
};
const filterRE = getRE(filter, 'i');
let filtered = React.useMemo(() => {
useEffect(() => {
if (!!activeClick) {
setPauseSync(true);
devTools.update(INDEX_KEY, activeClick);
console.log('mounting at: ', activeClick);
}
return () => {
if (synRef.current.pauseSync) {
console.log('unmouting at: ', synRef.current.activeIndex);
devTools.update(INDEX_KEY_ACTIVE, synRef.current.activeIndex);
}
};
}, []);
useEffect(() => {
const lastIndex = filteredList.filter((item: any) => item.time <= time).length - 1;
if (lastIndex !== activeIndex && !pauseSync) {
devTools.update(INDEX_KEY, lastIndex);
}
}, [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) =>
sum + (headerSize || 0) + (encodedBodySize || 0),
0
);
return {
resourcesSize,
transferredSize,
};
}, [resources]);
useEffect(() => {
const filterRE = getRE(filter, 'i');
let list = resources;
fetchList.forEach(
(fetchCall: any) =>
@ -209,9 +214,9 @@ function NetworkPanel(props: Props) {
return compare(a, b, sortBy);
});
if (!sortAscending) {
list = list.reverse();
}
// if (!sortAscending) {
// list = list.reverse();
// }
list = list.filter(
({ type, name, status, success }: any) =>
@ -219,41 +224,53 @@ function NetworkPanel(props: Props) {
(activeTab === ALL || type === TAB_TO_TYPE_MAP[activeTab]) &&
(showOnlyErrors ? parseInt(status) >= 400 || !success : true)
);
return list;
}, [filter, sortBy, sortAscending, showOnlyErrors, activeTab]);
setFilteredList(list);
}, [resources, filter, sortBy, sortAscending, showOnlyErrors, activeTab]);
// const lastIndex = currentIndex || filtered.filter((item: any) => item.time <= time).length - 1;
const referenceLines = [];
if (domContentLoadedTime != null) {
referenceLines.push({
time: domContentLoadedTime.time,
color: DOM_LOADED_TIME_COLOR,
});
}
if (loadTime != null) {
referenceLines.push({
time: loadTime.time,
color: LOAD_TIME_COLOR,
});
}
const referenceLines = useMemo(() => {
const arr = [];
if (domContentLoadedTime != null) {
arr.push({
time: domContentLoadedTime.time,
color: DOM_LOADED_TIME_COLOR,
});
}
if (loadTime != null) {
arr.push({
time: loadTime.time,
color: LOAD_TIME_COLOR,
});
}
return arr;
}, []);
const onRowClick = (row: any) => {
showModal(<FetchDetailsModal resource={row} rows={filtered} fetchPresented={fetchPresented} />, {
right: true,
});
showModal(
<FetchDetailsModal resource={row} rows={filteredList} fetchPresented={fetchPresented} />,
{
right: true,
}
);
devTools.update(INDEX_KEY, filteredList.indexOf(row));
setPauseSync(true);
};
const handleSort = (sortKey: string) => {
if (sortKey === sortBy) {
setSortAscending(!sortAscending);
// setSortBy('time');
}
setSortBy(sortKey);
};
return (
<React.Fragment>
<BottomBlock style={{ height: 300 + additionalHeight + 'px' }} className="border">
<BottomBlock
style={{ height: 300 + additionalHeight + 'px' }}
className="border"
onMouseEnter={() => setPauseSync(true)}
>
<BottomBlock.Header>
<div className="flex items-center">
<span className="font-semibold color-gray-medium mr-4">Network</span>
@ -287,7 +304,7 @@ function NetworkPanel(props: Props) {
/>
</div>
<InfoLine>
<InfoLine.Point label={filtered.length} value=" requests" />
<InfoLine.Point label={filteredList.length + ''} value=" requests" />
<InfoLine.Point
label={formatBytes(transferredSize)}
value="transferred"
@ -325,18 +342,22 @@ function NetworkPanel(props: Props) {
</div>
}
size="small"
show={filtered.length === 0}
show={filteredList.length === 0}
>
<TimeTable
rows={filtered}
rows={filteredList}
referenceLines={referenceLines}
renderPopup
onRowClick={onRowClick}
additionalHeight={additionalHeight}
onJump={jump}
onJump={(row: any) => {
setPauseSync(true);
devTools.update(INDEX_KEY, filteredList.indexOf(row));
jump(row.time);
}}
sortBy={sortBy}
sortAscending={sortAscending}
// activeIndex={lastIndex}
activeIndex={activeIndex}
>
{[
// {
@ -348,28 +369,28 @@ function NetworkPanel(props: Props) {
label: 'Status',
dataKey: 'status',
width: 70,
onClick: handleSort,
// onClick: handleSort,
},
{
label: 'Type',
dataKey: 'type',
width: 90,
render: renderType,
onClick: handleSort,
// onClick: handleSort,
},
{
label: 'Name',
width: 240,
dataKey: 'name',
render: renderName,
onClick: handleSort,
// onClick: handleSort,
},
{
label: 'Size',
width: 80,
dataKey: 'decodedBodySize',
render: renderSize,
onClick: handleSort,
// onClick: handleSort,
hidden: activeTab === XHR,
},
{
@ -377,7 +398,7 @@ function NetworkPanel(props: Props) {
width: 80,
dataKey: 'duration',
render: renderDuration,
onClick: handleSort,
// onClick: handleSort,
},
]}
</TimeTable>
@ -391,9 +412,12 @@ function NetworkPanel(props: Props) {
export default connectPlayer((state: any) => ({
location: state.location,
resources: state.resourceList,
fetchList: state.fetchList.map((i: any) => Resource({ ...i.toJS(), type: TYPES.XHR })),
fetchList: state.fetchList.map((i: any) =>
Resource({ ...i.toJS(), type: TYPES.XHR, time: i.time < 0 ? 0 : i.time })
),
domContentLoadedTime: state.domContentLoadedTime,
loadTime: state.loadTime,
time: state.time,
playing: state.playing,
domBuildingTime: state.domBuildingTime,
}))(NetworkPanel);

View file

@ -145,8 +145,19 @@ export default class TimeTable extends React.PureComponent<Props, State> {
scroller = React.createRef<List>();
autoScroll = true;
componentDidMount() {
if (this.scroller.current) {
// componentDidMount() {
// if (this.scroller.current) {
// this.scroller.current.scrollToRow(this.props.activeIndex);
// }
// }
adjustScroll(prevActiveIndex: number) {
if (
this.props.activeIndex &&
this.props.activeIndex >= 0 &&
prevActiveIndex !== this.props.activeIndex &&
this.scroller.current
) {
this.scroller.current.scrollToRow(this.props.activeIndex);
}
}
@ -161,14 +172,8 @@ export default class TimeTable extends React.PureComponent<Props, State> {
...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.scrollToRow(this.props.activeIndex);
}
// this.adjustScroll(prevProps.activeIndex);
}
onScroll = ({
@ -190,7 +195,7 @@ export default class TimeTable extends React.PureComponent<Props, State> {
onJump = (index: any) => {
if (this.props.onJump) {
this.props.onJump(this.props.rows[index].time);
this.props.onJump(this.props.rows[index]);
}
};
@ -203,23 +208,29 @@ export default class TimeTable extends React.PureComponent<Props, State> {
<div
style={rowStyle}
key={key}
className={cn('border-b border-color-gray-light-shade group items-center', 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,
})}
className={cn(
'dev-row border-b border-color-gray-light-shade group items-center',
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.filter((i: any) => !i.hidden).map(({ dataKey, render, width }) => (
<div className={stl.cell} style={{ width: `${width}px` }}>
{render
? render(row)
: row[dataKey || ''] || <i className="color-gray-light">{'empty'}</i>}
</div>
))}
{columns
.filter((i: any) => !i.hidden)
.map(({ dataKey, render, width }) => (
<div className={stl.cell} style={{ width: `${width}px` }}>
{render
? render(row)
: row[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>
@ -324,10 +335,15 @@ export default class TimeTable extends React.PureComponent<Props, State> {
'cursor-pointer': typeof onClick === 'function',
})}
style={{ width: `${width}px` }}
onClick={() => this.onColumnClick(dataKey, onClick)}
// onClick={() => this.onColumnClick(dataKey, onClick)}
>
<span>{label}</span>
{!!sortBy && sortBy === dataKey && <Icon name={ sortAscending ? "caret-down-fill" : "caret-up-fill" } className="ml-1" />}
{!!sortBy && sortBy === dataKey && (
<Icon
name={sortAscending ? 'caret-down-fill' : 'caret-up-fill'}
className="ml-1"
/>
)}
</div>
))}
</div>
@ -360,6 +376,7 @@ export default class TimeTable extends React.PureComponent<Props, State> {
<AutoSizer disableHeight>
{({ width }: { width: number }) => (
<List
scrollToIndex={this.props.activeIndex || 0}
ref={this.scroller}
className={stl.list}
height={this.tableHeight + additionalHeight}
@ -369,7 +386,7 @@ export default class TimeTable extends React.PureComponent<Props, State> {
rowHeight={ROW_HEIGHT}
rowRenderer={this.renderRow}
onScroll={this.onScroll}
scrollToAlignment="start"
scrollToAlignment="center"
forceUpdateProp={timestart | timewidth | (activeIndex || 0)}
/>
)}

View file

@ -5,75 +5,97 @@ import Session from './types/session';
import Record, { LAST_7_DAYS } from 'Types/app/period';
class UserFilter {
endDate: number = new Date().getTime();
startDate: number = new Date().getTime() - 24 * 60 * 60 * 1000;
rangeName: string = LAST_7_DAYS;
filters: any = [];
page: number = 1;
limit: number = 10;
period: any = Record({ rangeName: LAST_7_DAYS });
endDate: number = new Date().getTime();
startDate: number = new Date().getTime() - 24 * 60 * 60 * 1000;
rangeName: string = LAST_7_DAYS;
filters: any = [];
page: number = 1;
limit: number = 10;
period: any = Record({ rangeName: LAST_7_DAYS });
constructor() {
makeAutoObservable(this, {
page: observable,
update: action,
});
constructor() {
makeAutoObservable(this, {
page: observable,
update: action,
});
}
update(key: string, value: any) {
// @ts-ignore
this[key] = value;
if (key === 'period') {
this.startDate = this.period.start;
this.endDate = this.period.end;
}
}
update(key: string, value: any) {
this[key] = value;
setFilters(filters: any[]) {
this.filters = filters;
}
if (key === 'period') {
this.startDate = this.period.start;
this.endDate = this.period.end;
}
}
setPage(page: number) {
this.page = page;
}
setFilters(filters: any[]) {
this.filters = filters;
}
toJson() {
return {
endDate: this.period.end,
startDate: this.period.start,
filters: this.filters.map(filterMap),
page: this.page,
limit: this.limit,
};
}
}
setPage(page: number) {
this.page = page;
}
class DevTools {
networkIndex: 0;
consoleIndex: 0;
eventsIndex: 0;
networkActive: null;
consoleActive: null;
eventsActive: null;
constructor() {
makeAutoObservable(this, {
update: action,
});
}
toJson() {
return {
endDate: this.period.end,
startDate: this.period.start,
filters: this.filters.map(filterMap),
page: this.page,
limit: this.limit,
};
}
update(key: string, value: any) {
// @ts-ignore
this[key] = value;
}
}
export default class SessionStore {
userFilter: UserFilter = new UserFilter();
userFilter: UserFilter = new UserFilter();
devTools: DevTools = new DevTools();
constructor() {
makeAutoObservable(this, {
userFilter: observable,
constructor() {
makeAutoObservable(this, {
userFilter: observable,
devTools: observable,
});
}
resetUserFilter() {
this.userFilter = new UserFilter();
}
getSessions(filter: any): Promise<any> {
return new Promise((resolve, reject) => {
sessionService
.getSessions(filter.toJson())
.then((response: any) => {
resolve({
sessions: response.sessions.map((session: any) => new Session().fromJson(session)),
total: response.total,
});
})
.catch((error: any) => {
reject(error);
});
}
resetUserFilter() {
this.userFilter = new UserFilter();
}
getSessions(filter: any): Promise<any> {
return new Promise((resolve, reject) => {
sessionService
.getSessions(filter.toJson())
.then((response: any) => {
resolve({
sessions: response.sessions.map((session: any) => new Session().fromJson(session)),
total: response.total,
});
})
.catch((error: any) => {
reject(error);
});
});
}
});
}
}

View file

@ -132,7 +132,6 @@ export default class MessageDistributor extends StatedScreen {
exceptions: session.errors.toJSON(),
})
/* === */
this.loadMessages();
}

View file

@ -355,4 +355,8 @@ p {
width: 80px;
height: 80px;
transform: rotate(45deg);
}
.dev-row {
transition: all 0.5s;
}

View file

@ -91,6 +91,7 @@
"@types/react-dom": "^18.0.4",
"@types/react-redux": "^7.1.24",
"@types/react-router-dom": "^5.3.3",
"@types/react-virtualized": "^9.21.21",
"@typescript-eslint/eslint-plugin": "^5.24.0",
"@typescript-eslint/parser": "^5.24.0",
"autoprefixer": "^10.4.7",