openreplay/frontend/app/components/Session_/StackEvents/StackEvents.js
Bart Riepe af4160eb20
feat(ui): add consistent timestamps to (almost) all items in the player ui
This also tries to make the autoscroll functionality a bit more consistent, where all items are always shown in the list, but items which have not yet occurred will be partially transparent until they happen.

Due to that change, autoscroll behavior which previously always went all the way to the bottom of a list didn't make sense anymore, so now it scrolls to the current item.
2022-08-23 09:34:55 +09:00

176 lines
5.8 KiB
JavaScript

import { error as errorRoute } from 'App/routes';
import JsonViewer from 'Components/Session_/StackEvents/UserEvent/JsonViewer';
import Sentry from 'Components/Session_/StackEvents/UserEvent/Sentry';
import { hideHint } from 'Duck/components/player';
import withEnumToggle from 'HOCs/withEnumToggle';
import { connectPlayer, jump } from 'Player';
import React from 'react';
import { connect } from 'react-redux';
import { DATADOG, SENTRY, STACKDRIVER, typeList } from 'Types/session/stackEvent';
import { NoContent, SlideModal, Tabs, Link } from 'UI';
import Autoscroll from '../Autoscroll';
import BottomBlock from '../BottomBlock';
import UserEvent from './UserEvent';
const ALL = 'ALL';
const TABS = [ALL, ...typeList].map((tab) => ({ text: tab, key: tab }));
@withEnumToggle('activeTab', 'setActiveTab', ALL)
@connectPlayer((state) => ({
stackEvents: state.stackList,
stackEventsNow: state.stackListNow,
}))
@connect(
(state) => ({
hintIsHidden:
state.getIn(['components', 'player', 'hiddenHints', 'stack']) ||
!state.getIn(['site', 'list']).some((s) => s.stackIntegrations),
}),
{
hideHint,
}
)
export default class StackEvents extends React.PureComponent {
// onFilterChange = (e, { value }) => this.setState({ filter: value })
state = {
currentEvent: null,
};
onDetailsClick(userEvent) {
this.setState({ currentEvent: userEvent });
}
closeModal() {
this.setState({ currentEvent: undefined });
}
renderPopupContent(userEvent) {
console.log('event', userEvent);
const { source, payload, name } = userEvent;
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}`} />;
}
}
render() {
const { stackEvents, activeTab, setActiveTab, hintIsHidden } = this.props;
//const filterRE = new RegExp(filter, 'i');
const { currentEvent } = this.state;
const tabs = TABS.filter(
({ key }) => key === ALL || stackEvents.some(({ source }) => key === source)
);
const filteredStackEvents = stackEvents
// .filter(({ data }) => data.includes(filter))
.filter(({ source }) => activeTab === ALL || activeTab === source);
let lastIndex = -1;
// TODO: Need to do filtering in store, or preferably in a selector
filteredStackEvents.forEach((item, index) => {
if (
this.props.stackEventsNow.length > 0 &&
item.time <= this.props.stackEventsNow[this.props.stackEventsNow.length - 1].time
) {
lastIndex = index;
}
});
return (
<>
<SlideModal
title={
currentEvent && (
<div className="mb-4">
<div className="text-xl mb-2">
<Link to={errorRoute(currentEvent.errorId)}>
<span className="font-bold">{currentEvent.name}</span>
</Link>
<span className="ml-2 text-sm color-gray-medium">{currentEvent.function}</span>
</div>
<div>{currentEvent.message}</div>
</div>
)
}
isDisplayed={currentEvent != null}
content={
currentEvent && <div className="px-4">{this.renderPopupContent(currentEvent)}</div>
}
onClose={this.closeModal.bind(this)}
/>
<BottomBlock>
<BottomBlock.Header>
<div className="flex items-center">
<span className="font-semibold color-gray-medium mr-4">Events</span>
<Tabs
className="uppercase"
tabs={tabs}
active={activeTab}
onClick={setActiveTab}
border={false}
/>
</div>
</BottomBlock.Header>
<BottomBlock.Content>
<NoContent
title="Nothing to display yet."
subtext={
!hintIsHidden ? (
<>
<a
className="underline color-teal"
href="https://docs.openreplay.com/integrations"
target="_blank"
>
Integrations
</a>
{' and '}
<a
className="underline color-teal"
href="https://docs.openreplay.com/api#event"
target="_blank"
>
Events
</a>
{
' make debugging easier. Sync your backend logs and custom events with session replay.'
}
<br />
<br />
<button className="color-teal" onClick={() => this.props.hideHint('stack')}>
Got It!
</button>
</>
) : null
}
size="small"
show={filteredStackEvents.length === 0}
>
<Autoscroll autoScrollTo={Math.max(lastIndex, 0)}>
{filteredStackEvents.map((userEvent, index) => (
<UserEvent
key={userEvent.key}
onDetailsClick={this.onDetailsClick.bind(this)}
inactive={index > lastIndex}
selected={lastIndex === index}
userEvent={userEvent}
onJump={() => jump(userEvent.time)}
/>
))}
</Autoscroll>
</NoContent>
</BottomBlock.Content>
</BottomBlock>
</>
);
}
}