change(ui) - events and console improvements

This commit is contained in:
Shekar Siri 2022-10-13 09:50:27 +02:00
parent c11e3b9069
commit c47d06ae43
20 changed files with 335 additions and 47 deletions

View file

@ -17,7 +17,7 @@ function ConsoleRow(props: Props) {
const canExpand = lines.length > 1;
return (
<div
className={cn(stl.line, 'flex py-2 px-4 overflow-hidden group relative', {
className={cn(stl.line, 'flex py-2 px-4 overflow-hidden group relative select-none', {
info: !log.isYellow() && !log.isRed(),
warn: log.isYellow(),
error: log.isRed(),
@ -33,7 +33,9 @@ function ConsoleRow(props: Props) {
</div> */}
<div key={log.key} className={cn('')} data-scroll-item={log.isRed()}>
<div className={cn(stl.message, 'flex items-center')}>
{canExpand && <Icon name="caret-right-fill" className="mr-2" />}
{canExpand && (
<Icon name={expanded ? 'caret-down-fill' : 'caret-right-fill'} className="mr-2" />
)}
<span>{renderWithNL(lines.pop())}</span>
</div>
{canExpand && expanded && lines.map((l: any) => <div className="ml-4 mb-1">{l}</div>)}

View file

@ -22,7 +22,7 @@ import BottomBlock from '../BottomBlock';
@connectPlayer((state) => ({
logs: state.logListNow,
exceptions: state.exceptionsList,
exceptionsNow: state.exceptionsListNow,
// exceptionsNow: state.exceptionsListNow,
}))
@connect(
(state) => ({

View file

@ -8,7 +8,7 @@ const ControlButton = ({
icon = '',
disabled = false,
onClick,
count = 0,
// count = 0,
hasErrors = false,
active = false,
size = 20,
@ -31,7 +31,7 @@ const ControlButton = ({
>
<div className={stl.labels}>
{hasErrors && <div className={stl.errorSymbol} />}
{count > 0 && <div className={stl.countLabel}>{count}</div>}
{/* {count > 0 && <div className={stl.countLabel}>{count}</div>} */}
</div>
{!noIcon && <Icon name={icon} size={size} color="gray-dark" />}
{!noLabel && (

View file

@ -95,23 +95,23 @@ function getStorageName(type) {
disabled: state.cssLoading || state.messagesLoading || state.inspectorMode || state.markedTargets,
inspectorMode: state.inspectorMode,
fullscreenDisabled: state.messagesLoading,
logCount: state.logListNow.length,
logRedCount: state.logRedCountNow,
resourceRedCount: state.resourceRedCountNow,
fetchRedCount: state.fetchRedCountNow,
logCount: state.logList.length,
logRedCount: state.logRedCount,
resourceRedCount: state.resourceRedCount,
fetchRedCount: state.fetchRedCount,
showStack: state.stackList.length > 0,
stackCount: state.stackListNow.length,
stackRedCount: state.stackRedCountNow,
profilesCount: state.profilesListNow.length,
stackCount: state.stackList.length,
stackRedCount: state.stackRedCount,
profilesCount: state.profilesList.length,
storageCount: selectStorageListNow(state).length,
storageType: selectStorageType(state),
showStorage: selectStorageType(state) !== STORAGE_TYPES.NONE,
showProfiler: state.profilesList.length > 0,
showGraphql: state.graphqlList.length > 0,
showFetch: state.fetchCount > 0,
fetchCount: state.fetchCountNow,
graphqlCount: state.graphqlListNow.length,
exceptionsCount: state.exceptionsListNow.length,
fetchCount: state.fetchCount,
graphqlCount: state.graphqlList.length,
exceptionsCount: state.exceptionsList.length,
showExceptions: state.exceptionsList.length > 0,
showLongtasks: state.longtasksList.length > 0,
liveTimeTravel: state.liveTimeTravel,
@ -380,7 +380,7 @@ export default class Controls extends React.Component {
label="CONSOLE"
noIcon
labelClassName="!text-base font-semibold"
count={logCount}
// count={logCount}
hasErrors={logRedCount > 0}
containerClassName="mx-2"
/>
@ -412,7 +412,7 @@ export default class Controls extends React.Component {
disabled={disabled && !inspectorMode}
onClick={() => toggleBottomTools(GRAPHQL)}
active={bottomBlock === GRAPHQL && !inspectorMode}
count={graphqlCount}
// count={graphqlCount}
label="GRAPHQL"
noIcon
labelClassName="!text-base font-semibold"
@ -424,7 +424,7 @@ export default class Controls extends React.Component {
disabled={disabled && !inspectorMode}
onClick={() => toggleBottomTools(STORAGE)}
active={bottomBlock === STORAGE && !inspectorMode}
count={storageCount}
// count={storageCount}
label={getStorageName(storageType)}
noIcon
labelClassName="!text-base font-semibold"
@ -440,7 +440,7 @@ export default class Controls extends React.Component {
noIcon
labelClassName="!text-base font-semibold"
containerClassName="mx-2"
count={exceptionsCount}
// count={exceptionsCount}
hasErrors={exceptionsCount > 0}
/>
)}
@ -453,7 +453,7 @@ export default class Controls extends React.Component {
noIcon
labelClassName="!text-base font-semibold"
containerClassName="mx-2"
count={stackCount}
// count={stackCount}
hasErrors={stackRedCount > 0}
/>
)}
@ -462,7 +462,7 @@ export default class Controls extends React.Component {
disabled={disabled && !inspectorMode}
onClick={() => toggleBottomTools(PROFILER)}
active={bottomBlock === PROFILER && !inspectorMode}
count={profilesCount}
// count={profilesCount}
label="PROFILER"
noIcon
labelClassName="!text-base font-semibold"

View file

@ -19,7 +19,7 @@ import {
INSPECTOR,
OVERVIEW,
} from 'Duck/components/player';
import NetworkPanel from 'Shared/DevTools/NetworkPanel/NetworkPanel';
import NetworkPanel from 'Shared/DevTools/NetworkPanel';
import Console from '../Console/Console';
import StackEvents from '../StackEvents/StackEvents';
import Storage from '../Storage';
@ -40,6 +40,7 @@ import Overlay from './Overlay';
import stl from './player.module.css';
import { updateLastPlayedSession } from 'Duck/sessions';
import OverviewPanel from '../OverviewPanel';
import ConsolePanel from 'Shared/DevTools/ConsolePanel';
@connectPlayer((state) => ({
live: state.live,
@ -108,7 +109,7 @@ export default class Player extends React.PureComponent {
{!fullscreen && !!bottomBlock && (
<div style={{ maxWidth, width: '100%' }}>
{bottomBlock === OVERVIEW && <OverviewPanel />}
{bottomBlock === CONSOLE && <Console />}
{bottomBlock === CONSOLE && <ConsolePanel />}
{bottomBlock === NETWORK && (
// <Network />
<NetworkPanel />

View file

@ -6,6 +6,7 @@ import withEnumToggle from 'HOCs/withEnumToggle';
import { connectPlayer, jump } from 'Player';
import React from 'react';
import { connect } from 'react-redux';
import StackEventRow from 'Shared/DevTools/StackEventRow';
import { DATADOG, SENTRY, STACKDRIVER, typeList } from 'Types/session/stackEvent';
import { NoContent, SlideModal, Tabs, Link } from 'UI';
import Autoscroll from '../Autoscroll';
@ -19,7 +20,7 @@ const TABS = [ALL, ...typeList].map((tab) => ({ text: tab, key: tab }));
@withEnumToggle('activeTab', 'setActiveTab', ALL)
@connectPlayer((state) => ({
stackEvents: state.stackList,
stackEventsNow: state.stackListNow,
// stackEventsNow: state.stackListNow,
}))
@connect(
(state) => ({
@ -156,14 +157,19 @@ export default class StackEvents extends React.PureComponent {
>
<Autoscroll>
{filteredStackEvents.map((userEvent, index) => (
<UserEvent
<StackEventRow
key={userEvent.key}
onDetailsClick={this.onDetailsClick.bind(this)}
// inactive={index > lastIndex}
// selected={lastIndex === index}
userEvent={userEvent}
event={userEvent}
onJump={() => jump(userEvent.time)}
/>
// <UserEvent
// key={userEvent.key}
// onDetailsClick={this.onDetailsClick.bind(this)}
// // inactive={index > lastIndex}
// // selected={lastIndex === index}
// userEvent={userEvent}
// onJump={() => jump(userEvent.time)}
// />
))}
</Autoscroll>
</NoContent>

View file

@ -18,7 +18,9 @@ export default class JsonViewer extends React.PureComponent {
{isObjectData && <JSONTree src={data} collapsed={false} />}
{!isObjectData && Array.isArray(data) && (
<div>
<div className="text-lg">{data[0]}</div>
<div className="code-font mb-2">
{typeof data[0] === 'string' ? data[0] : JSON.stringify(data[0])}
</div>
<JSONTree src={data[1]} collapsed={false} />
</div>
)}

View file

@ -35,12 +35,14 @@ export default class UserEvent extends React.PureComponent {
render() {
const { userEvent, inactive, selected } = this.props;
let message = userEvent.payload[0] || '';
message = typeof message === 'string' ? message : JSON.stringify(message);
return (
<div
data-scroll-item={userEvent.isRed()}
onClick={this.onClickDetails}
className={cn(
'group flex items-center py-2 px-4 border-b cursor-pointer',
'group flex items-center py-2 px-4 border-b cursor-pointer relative',
// stl.userEvent,
// this.getLevelClassname(),
// {
@ -52,10 +54,11 @@ export default class UserEvent extends React.PureComponent {
{/* <div className={'self-start pr-4'}>
{Duration.fromMillis(userEvent.time).toFormat('mm:ss.SSS')}
</div> */}
<div className={cn('mr-auto', stl.infoWrapper)}>
<div className={stl.title}>
<Icon {...this.getIconProps()} />
<span className="capitalize">{userEvent.name}</span>
<div className={cn('mr-auto flex items-start')}>
<Icon {...this.getIconProps()} />
<div>
<div className="capitalize font-medium mb-1">{userEvent.name}</div>
<div className="code-font text-xs">{message}</div>
</div>
</div>
<JumpButton onClick={this.props.onJump} />

View file

@ -0,0 +1,131 @@
import React, { useState } from 'react';
import { connectPlayer, jump } from 'Player';
// 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';
const ALL = 'ALL';
const INFO = 'INFO';
const WARNINGS = 'WARNINGS';
const ERRORS = 'ERRORS';
const LEVEL_TAB = {
[LEVEL.INFO]: INFO,
[LEVEL.LOG]: INFO,
[LEVEL.WARNING]: WARNINGS,
[LEVEL.ERROR]: ERRORS,
[LEVEL.EXCEPTION]: ERRORS,
};
const TABS = [ALL, ERRORS, WARNINGS, INFO].map((tab) => ({ text: tab, key: tab }));
function renderWithNL(s = '') {
if (typeof s !== 'string') return '';
return s.split('\n').map((line, i) => <div className={cn({ 'ml-20': i !== 0 })}>{line}</div>);
}
const getIconProps = (level: any) => {
switch (level) {
case LEVEL.INFO:
case LEVEL.LOG:
return {
name: 'console/info',
color: 'blue2',
};
case LEVEL.WARN:
case LEVEL.WARNING:
return {
name: 'console/warning',
color: 'red2',
};
case LEVEL.ERROR:
return {
name: 'console/error',
color: 'red',
};
}
return null;
};
interface Props {
logs: any;
exceptions: any;
}
function ConsolePanel(props: Props) {
const { logs } = props;
const additionalHeight = 0;
const [activeTab, setActiveTab] = useState(ALL);
const [filter, setFilter] = useState('');
let filtered = React.useMemo(() => {
const filterRE = getRE(filter, 'i');
let list = logs;
list = list.filter(
({ value, level }: any) =>
(!!filter ? filterRE.test(value) : true) &&
(activeTab === ALL || activeTab === LEVEL_TAB[level])
);
return list;
}, [filter, activeTab]);
const onTabClick = (activeTab: any) => setActiveTab(activeTab);
const onFilterChange = ({ target: { value } }: any) => setFilter(value);
return (
<BottomBlock style={{ height: 300 + additionalHeight + 'px' }}>
<BottomBlock.Header>
<div className="flex items-center">
<span className="font-semibold color-gray-medium mr-4">Console</span>
<Tabs tabs={TABS} active={activeTab} onClick={onTabClick} border={false} />
</div>
<Input
className="input-small h-8"
placeholder="Filter by keyword"
icon="search"
iconPosition="left"
name="filter"
height={28}
onChange={onFilterChange}
/>
</BottomBlock.Header>
<BottomBlock.Content className="overflow-y-auto">
<NoContent
title={
<div className="capitalize flex items-center mt-16">
<Icon name="info-circle" className="mr-2" size="18" />
No Data
</div>
}
size="small"
show={filtered.length === 0}
>
{/* <Autoscroll> */}
{filtered.map((l: any, index: any) => (
<ConsoleRow
key={index}
log={l}
jump={jump}
iconProps={getIconProps(l.level)}
renderWithNL={renderWithNL}
/>
))}
{/* </Autoscroll> */}
</NoContent>
</BottomBlock.Content>
</BottomBlock>
);
}
export default connectPlayer((state: any) => {
const logs = state.logList;
// const exceptions = state.exceptionsList; // TODO merge
return {
logs,
};
})(ConsolePanel);

View file

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

View file

@ -0,0 +1,45 @@
import React, { useState } from 'react';
import cn from 'classnames';
// import stl from '../console.module.css';
import { Icon } from 'UI';
import JumpButton from 'Shared/DevTools/JumpButton';
interface Props {
log: any;
iconProps: any;
jump?: any;
renderWithNL?: any;
}
function ConsoleRow(props: Props) {
const { log, iconProps, jump, renderWithNL } = props;
const [expanded, setExpanded] = useState(false);
const lines = log.value.split('\n').filter((l: any) => !!l);
const canExpand = lines.length > 1;
return (
<div
className={cn('border-b flex items-center py-2 px-4 overflow-hidden group relative select-none', {
info: !log.isYellow() && !log.isRed(),
warn: log.isYellow(),
error: log.isRed(),
'cursor-pointer': canExpand,
})}
onClick={() => setExpanded(!expanded)}
>
<div className="mr-2">
<Icon size="14" {...iconProps} />
</div>
<div key={log.key} data-scroll-item={log.isRed()}>
<div className={cn('flex items-center')}>
{canExpand && (
<Icon name={expanded ? 'caret-down-fill' : 'caret-right-fill'} className="mr-2" />
)}
<span>{renderWithNL(lines.pop())}</span>
</div>
{canExpand && expanded && lines.map((l: any) => <div className="ml-4 mb-1">{l}</div>)}
</div>
<JumpButton onClick={() => jump(log.time)} />
</div>
);
}
export default ConsoleRow;

View file

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

View file

@ -10,7 +10,7 @@ function JumpButton(props: Props) {
return (
<Popup content={tooltip} disabled={!!tooltip}>
<div
className="invisible group-hover:visible rounded-lg bg-active-blue text-xs flex items-center px-2 py-1 color-teal absolute right-0 top-0 bottom-0"
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 absolute right-0 top-0 bottom-0 hover:shadow h-6 my-auto"
onClick={(e: any) => {
e.stopPropagation();
props.onClick();

View file

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

View file

@ -0,0 +1,32 @@
import React from 'react';
import { DATADOG, SENTRY, STACKDRIVER, typeList } from 'Types/session/stackEvent';
import JsonViewer from 'Components/Session_/StackEvents/UserEvent/JsonViewer';
import Sentry from 'Components/Session_/StackEvents/UserEvent/Sentry';
interface Props {
event: any;
}
function StackEventModal(props: Props) {
const { event } = props;
const renderPopupContent = () => {
const { source, payload, name } = event;
switch (source) {
case SENTRY:
return <Sentry event={payload} />;
case DATADOG:
return <JsonViewer title={name} data={payload} icon="integrations/datadog" />;
case STACKDRIVER:
return <JsonViewer title={name} data={payload} icon="integrations/stackdriver" />;
default:
return <JsonViewer title={name} data={payload} icon={`integrations/${source}`} />;
}
};
return (
<div className="bg-white overflow-y-auto h-screen p-5" style={{ width: '500px' }}>
<h5 className="mb-2 text-2xl">Stack Event</h5>
{renderPopupContent()}
</div>
);
}
export default StackEventModal;

View file

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

View file

@ -0,0 +1,52 @@
import React from 'react';
import JumpButton from '../JumpButton';
import { Icon } from 'UI';
import cn from 'classnames';
import { OPENREPLAY, SENTRY, DATADOG, STACKDRIVER } from 'Types/session/stackEvent';
import { useModal } from 'App/components/Modal';
import StackEventModal from '../StackEventModal';
interface Props {
event: any;
onJump: any;
}
function StackEventRow(props: Props) {
const { event, onJump } = props;
let message = event.payload[0] || '';
message = typeof message === 'string' ? message : JSON.stringify(message);
const onClickDetails = () => {
showModal(<StackEventModal event={event} />, { right: true });
};
const { showModal } = useModal();
const iconProps: any = React.useMemo(() => {
const { source } = event;
return {
name: `integrations/${source}`,
size: 18,
marginRight: source === OPENREPLAY ? 11 : 10,
};
}, [event]);
return (
<div
data-scroll-item={event.isRed()}
onClick={onClickDetails}
className={cn(
'group flex items-center py-2 px-4 border-b cursor-pointer relative',
'hover:bg-active-blue'
)}
>
<div className={cn('mr-auto flex items-start')}>
<Icon {...iconProps} />
<div>
<div className="capitalize font-medium mb-1">{event.name}</div>
<div className="code-font text-xs">{message}</div>
</div>
</div>
<JumpButton onClick={onJump} />
</div>
);
}
export default StackEventRow;

View file

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

View file

@ -5,8 +5,15 @@ import stl from './errorItem.module.css';
import { Duration } from 'luxon';
import { useModal } from 'App/components/Modal';
import ErrorDetailsModal from 'App/components/Dashboard/components/Errors/ErrorDetailsModal';
import JumpButton from 'Shared/DevTools/JumpButton';
function ErrorItem({ error = {}, onJump, inactive, selected }) {
interface Props {
error: any;
onJump: any;
inactive?: Boolean;
selected?: Boolean;
}
function ErrorItem({ error = {}, onJump, inactive, selected }: Props) {
const { showModal } = useModal();
const onErrorClick = () => {
@ -14,25 +21,27 @@ function ErrorItem({ error = {}, onJump, inactive, selected }) {
};
return (
<div
className={cn(stl.wrapper, 'py-2 px-4 flex cursor-pointer', {
[stl.inactive]: inactive,
[stl.selected]: selected,
className={cn(stl.wrapper, 'py-2 px-4 flex cursor-pointer hover:bg-active-blue relative group', {
// [stl.inactive]: inactive,
// [stl.selected]: selected,
})}
onClick={onJump}
onClick={onErrorClick}
// onClick={onJump}
>
<div className={'self-start pr-4 color-red'}>
{/* <div className={'self-start pr-4 color-red'}>
{Duration.fromMillis(error.time).toFormat('mm:ss.SSS')}
</div>
<div className="mr-auto overflow-hidden">
</div> */}
<div className="overflow-hidden">
<div className="color-red mb-1 cursor-pointer code-font">
{error.name}
<span className="color-gray-darkest ml-2">{error.stack0InfoString}</span>
</div>
<div className="text-sm color-gray-medium">{error.message}</div>
<div className="text-xs code-font">{error.message}</div>
</div>
<div className="self-center">
{/* <div className="self-center">
<IconButton red onClick={onErrorClick} label="DETAILS" />
</div>
</div> */}
<JumpButton onClick={onJump} />
</div>
);
}