feat(ui) - overview - errordetails modal
This commit is contained in:
parent
fa314fed57
commit
6b7f66e949
8 changed files with 195 additions and 84 deletions
|
|
@ -4,10 +4,13 @@ import React from 'react';
|
|||
import BottomBlock from '../BottomBlock';
|
||||
import EventRow from './components/EventRow';
|
||||
import { TYPES } from 'Types/session/event';
|
||||
import { Icon } from 'UI';
|
||||
import { Icon, Checkbox, ErrorDetails } from 'UI';
|
||||
import { Tooltip } from 'react-tippy';
|
||||
import stl from './overviewPanel.module.css';
|
||||
import { connect } from 'react-redux';
|
||||
import TimelineScale from './components/TimelineScale';
|
||||
import FeatureSelection from './components/FeatureSelection/FeatureSelection';
|
||||
import { useModal } from 'App/components/Modal';
|
||||
|
||||
interface Props {
|
||||
resourceList: any[];
|
||||
|
|
@ -22,6 +25,8 @@ function OverviewPanel(props: Props) {
|
|||
return eventsList.filter((item: any) => item.type === TYPES.CLICKRAGE);
|
||||
}, [eventsList]);
|
||||
const scale = 100 / endTime;
|
||||
const selectedFeatures = React.useMemo(() => ['NETWORK', 'ERRORS', 'EVENTS'], []);
|
||||
const { showModal } = useModal();
|
||||
|
||||
const createEventClickHandler = (pointer: any, type: any) => (e: any) => {
|
||||
e.stopPropagation();
|
||||
|
|
@ -30,7 +35,10 @@ function OverviewPanel(props: Props) {
|
|||
return;
|
||||
}
|
||||
|
||||
props.toggleBottomBlock(type);
|
||||
if (type === EXCEPTIONS) {
|
||||
showModal(<ErrorDetails error={pointer} />, { right: true });
|
||||
}
|
||||
// props.toggleBottomBlock(type);
|
||||
};
|
||||
|
||||
const renderNetworkElement = (item: any) => {
|
||||
|
|
@ -92,12 +100,16 @@ function OverviewPanel(props: Props) {
|
|||
};
|
||||
|
||||
return (
|
||||
<BottomBlock style={{ height: '240px' }}>
|
||||
<BottomBlock style={{ height: '260px' }}>
|
||||
<BottomBlock.Header>
|
||||
<span className="font-semibold color-gray-medium mr-4">Overview</span>
|
||||
<div className="flex items-center">
|
||||
<FeatureSelection list={selectedFeatures} updateList={() => {}} />
|
||||
</div>
|
||||
</BottomBlock.Header>
|
||||
<BottomBlock.Content>
|
||||
<div className="overflow-x-auto overflow-y-hidden bg-gray-lightest px-4">
|
||||
<div className="overflow-x-auto overflow-y-hidden bg-gray-lightest">
|
||||
<TimelineScale />
|
||||
<div style={{ width: '100%' }} className="transition relative">
|
||||
<VerticalPointerLine />
|
||||
<EventRow title="Network" className="" list={resourceList} scale={scale} renderElement={renderNetworkElement} />
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ function EventRow(props: Props) {
|
|||
})
|
||||
}, [list]);
|
||||
return (
|
||||
<div className={cn('h-20 w-full flex flex-col py-2', className)}>
|
||||
<div className={cn('h-20 w-full flex flex-col py-2 px-4', className)}>
|
||||
<div className="uppercase color-gray-medium">{title}</div>
|
||||
<div className="relative w-full py-3">
|
||||
{_list.map((item: any, index: number) => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
import React from 'react';
|
||||
import { Checkbox } from 'UI';
|
||||
|
||||
const NETWORK = 'NETWORK';
|
||||
const ERRORS = 'ERRORS';
|
||||
const EVENTS = 'EVENTS';
|
||||
const CLICKRAGE = 'CLICK RAGE';
|
||||
const PERFORMANCE = 'PERFORMANCE';
|
||||
|
||||
interface Props {
|
||||
list: any[];
|
||||
updateList: any;
|
||||
}
|
||||
function FeatureSelection(props: Props) {
|
||||
const { list } = props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Checkbox
|
||||
name="slack"
|
||||
className="mr-8"
|
||||
type="checkbox"
|
||||
checked={list.includes(NETWORK)}
|
||||
onClick={(e: any) => {
|
||||
console.log(e);
|
||||
}}
|
||||
label={NETWORK}
|
||||
/>
|
||||
<Checkbox
|
||||
name="slack"
|
||||
className="mr-8"
|
||||
type="checkbox"
|
||||
checked={list.includes(ERRORS)}
|
||||
onClick={(e: any) => {
|
||||
console.log(e);
|
||||
}}
|
||||
label={ERRORS}
|
||||
/>
|
||||
<Checkbox
|
||||
name="slack"
|
||||
className="mr-8"
|
||||
type="checkbox"
|
||||
checked={list.includes(EVENTS)}
|
||||
onClick={(e: any) => {
|
||||
console.log(e);
|
||||
}}
|
||||
label={EVENTS}
|
||||
/>
|
||||
<Checkbox
|
||||
name="slack"
|
||||
className="mr-8"
|
||||
type="checkbox"
|
||||
checked={list.includes(CLICKRAGE)}
|
||||
onClick={(e: any) => {
|
||||
console.log(e);
|
||||
}}
|
||||
label={CLICKRAGE}
|
||||
/>
|
||||
<Checkbox
|
||||
name="slack"
|
||||
className="mr-8"
|
||||
type="checkbox"
|
||||
checked={list.includes(PERFORMANCE)}
|
||||
onClick={(e: any) => {
|
||||
console.log(e);
|
||||
}}
|
||||
label={PERFORMANCE}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default FeatureSelection;
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import React from 'react';
|
||||
|
||||
interface Props {
|
||||
|
||||
}
|
||||
function TimelineScale(props: Props) {
|
||||
return (
|
||||
<div className="h-6 bg-gray-darkest w-full">
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TimelineScale;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './TimelineScale';
|
||||
|
|
@ -2,19 +2,16 @@ import React from 'react';
|
|||
import cn from 'classnames';
|
||||
|
||||
interface Props {
|
||||
classNam?: string;
|
||||
label?: string;
|
||||
[x: string]: any;
|
||||
classNam?: string;
|
||||
label?: string;
|
||||
[x: string]: any;
|
||||
}
|
||||
export default (props: Props) => {
|
||||
const { className = '', label = '', ...rest } = props;
|
||||
return (
|
||||
<label className={ cn("flex items-center cursor-pointer", className)}>
|
||||
<input
|
||||
type="checkbox"
|
||||
{ ...rest }
|
||||
/>
|
||||
{label && <span className="ml-2 select-none mb-0">{label}</span>}
|
||||
</label>
|
||||
)
|
||||
};
|
||||
const { className = '', label = '', ...rest } = props;
|
||||
return (
|
||||
<label className={cn('flex items-center cursor-pointer', className)}>
|
||||
<input type="checkbox" {...rest} />
|
||||
{label && <span className="ml-2 select-none mb-0">{label}</span>}
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
import React, { useState } from 'react'
|
||||
import ErrorFrame from '../ErrorFrame/ErrorFrame'
|
||||
import cn from 'classnames';
|
||||
import { IconButton, Icon } from 'UI';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
const docLink = 'https://docs.openreplay.com/installation/upload-sourcemaps';
|
||||
|
||||
function ErrorDetails({ className, name = "Error", message, errorStack, sourcemapUploaded }) {
|
||||
const [showRaw, setShowRaw] = useState(false)
|
||||
const firstFunc = errorStack.first() && errorStack.first().function
|
||||
|
||||
const openDocs = () => {
|
||||
window.open(docLink, '_blank');
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className} >
|
||||
{ !sourcemapUploaded && (
|
||||
<div
|
||||
style={{ backgroundColor: 'rgba(204, 0, 0, 0.1)' }}
|
||||
className="font-normal flex items-center text-sm font-regular color-red border p-2 rounded"
|
||||
>
|
||||
<Icon name="info" size="16" color="red" />
|
||||
<div className="ml-2">Source maps must be uploaded to OpenReplay to be able to see stack traces. <a href="#" className="color-red font-medium underline" style={{ textDecoration: 'underline' }} onClick={openDocs}>Learn more.</a></div>
|
||||
</div>
|
||||
) }
|
||||
<div className="flex items-center my-3">
|
||||
<h3 className="text-xl mr-auto">
|
||||
Stacktrace
|
||||
</h3>
|
||||
<div className="flex justify-end mr-2">
|
||||
<IconButton
|
||||
onClick={() => setShowRaw(false) }
|
||||
label="FULL"
|
||||
plain={!showRaw}
|
||||
primaryText={!showRaw}
|
||||
/>
|
||||
<IconButton
|
||||
primaryText={showRaw}
|
||||
onClick={() => setShowRaw(true) }
|
||||
plain={showRaw}
|
||||
label="RAW"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-6 code-font" data-hidden={showRaw}>
|
||||
<div className="leading-relaxed font-weight-bold">{ name }</div>
|
||||
<div style={{ wordBreak: 'break-all'}}>{message}</div>
|
||||
</div>
|
||||
{ showRaw &&
|
||||
<div className="mb-3 code-font">{name} : {firstFunc ? firstFunc : '?' }</div>
|
||||
}
|
||||
{ errorStack.map((frame, i) => (
|
||||
<div className="mb-3" key={frame.key}>
|
||||
<ErrorFrame frame={frame} showRaw={showRaw} isFirst={i == 0} />
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ErrorDetails.displayName = "ErrorDetails";
|
||||
export default ErrorDetails;
|
||||
79
frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx
Normal file
79
frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import ErrorFrame from '../ErrorFrame/ErrorFrame';
|
||||
import { fetchErrorStackList } from 'Duck/sessions';
|
||||
import { IconButton, Icon } from 'UI';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
const docLink = 'https://docs.openreplay.com/installation/upload-sourcemaps';
|
||||
|
||||
interface Props {
|
||||
fetchErrorStackList: any;
|
||||
sourcemapUploaded?: boolean;
|
||||
errorStack?: any;
|
||||
message?: string;
|
||||
sessionId: string;
|
||||
error: any;
|
||||
}
|
||||
function ErrorDetails(props: Props) {
|
||||
const { error, sessionId, message = '', errorStack = [], sourcemapUploaded = false } = props;
|
||||
const [showRaw, setShowRaw] = useState(false);
|
||||
const firstFunc = errorStack.first() && errorStack.first().function;
|
||||
|
||||
const openDocs = () => {
|
||||
window.open(docLink, '_blank');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
props.fetchErrorStackList(sessionId, error.errorId);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="bg-white p-5 h-screen" style={{ width: '700px' }}>
|
||||
{!sourcemapUploaded && (
|
||||
<div
|
||||
style={{ backgroundColor: 'rgba(204, 0, 0, 0.1)' }}
|
||||
className="font-normal flex items-center text-sm font-regular color-red border p-2 rounded"
|
||||
>
|
||||
<Icon name="info" size="16" color="red" />
|
||||
<div className="ml-2">
|
||||
Source maps must be uploaded to OpenReplay to be able to see stack traces.{' '}
|
||||
<a href="#" className="color-red font-medium underline" style={{ textDecoration: 'underline' }} onClick={openDocs}>
|
||||
Learn more.
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center my-3">
|
||||
<h3 className="text-xl mr-auto">Stacktrace</h3>
|
||||
<div className="flex justify-end mr-2">
|
||||
<IconButton onClick={() => setShowRaw(false)} label="FULL" plain={!showRaw} primaryText={!showRaw} />
|
||||
<IconButton primaryText={showRaw} onClick={() => setShowRaw(true)} plain={showRaw} label="RAW" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-6 code-font" data-hidden={showRaw}>
|
||||
<div className="leading-relaxed font-weight-bold">{error.name}</div>
|
||||
<div style={{ wordBreak: 'break-all' }}>{message}</div>
|
||||
</div>
|
||||
{showRaw && (
|
||||
<div className="mb-3 code-font">
|
||||
{error.name} : {firstFunc ? firstFunc : '?'}
|
||||
</div>
|
||||
)}
|
||||
;
|
||||
{errorStack.map((frame: any, i: any) => (
|
||||
<div className="mb-3" key={frame.key}>
|
||||
<ErrorFrame frame={frame} showRaw={showRaw} isFirst={i == 0} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ErrorDetails.displayName = 'ErrorDetails';
|
||||
export default connect(
|
||||
(state: any) => ({
|
||||
errorStack: state.getIn(['sessions', 'errorStack']),
|
||||
sessionId: state.getIn(['sessions', 'current', 'sessionId']),
|
||||
}),
|
||||
{ fetchErrorStackList }
|
||||
)(ErrorDetails);
|
||||
Loading…
Add table
Reference in a new issue