feat(ui) - fetch error nav, and highlight row, assist tabs
This commit is contained in:
parent
edd6cc737e
commit
bd957f97f8
19 changed files with 246 additions and 247 deletions
|
|
@ -0,0 +1,68 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { applyFilter, addAttribute } from 'Duck/filters';
|
||||
import { fetchList } from 'Duck/sessions';
|
||||
import { KEYS } from 'Types/filter/customFilter';
|
||||
import { Link } from 'UI';
|
||||
import Filter from 'Types/filter';
|
||||
import { List } from 'immutable';
|
||||
import Counter from 'App/components/shared/SessionItem/Counter';
|
||||
import { fetchLiveList } from 'Duck/sessions';
|
||||
import { session as sessionRoute } from 'App/routes';
|
||||
import { session } from 'App/components/Session_/session.css';
|
||||
|
||||
const RowItem = ({ startedAt, sessionId }) => {
|
||||
return (
|
||||
<Link to={ sessionRoute(sessionId) }>
|
||||
<div className="flex justify-between p-2 cursor-pointer">
|
||||
Tab1
|
||||
<Counter startTime={startedAt} />
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
list: List<any>,
|
||||
session: any,
|
||||
fetchLiveList: () => void,
|
||||
applyFilter: () => void,
|
||||
filters: Filter
|
||||
addAttribute: (obj) => void,
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
const AssistTabs = React.memo((props: Props) => {
|
||||
const [showMenu, setShowMenu] = useState(false)
|
||||
useEffect(() => {
|
||||
if (!props.loading && props.list.size === 0) {
|
||||
props.fetchLiveList();
|
||||
}
|
||||
}, [props.list])
|
||||
|
||||
return (
|
||||
<div className="relative mr-4">
|
||||
<div className="p-2 cursor-pointer" onClick={() => setShowMenu(!showMenu)}>Active Tabs</div>
|
||||
{showMenu && (
|
||||
<div
|
||||
className="border z-10 absolute bg-white rounded shadow right-0"
|
||||
style={{ minWidth: "180px"}}
|
||||
>
|
||||
{props.list.map((item, index) => (
|
||||
<RowItem key={index} startedAt={item.startedAt} sessionId={item.sessionId} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default connect(state => {
|
||||
const session = state.getIn([ 'sessions', 'current' ]);
|
||||
return {
|
||||
loading: state.getIn([ 'sessions', 'loading' ]),
|
||||
list: state.getIn(['sessions', 'liveSessions']).filter(i => i.userId === session.userId),
|
||||
session,
|
||||
filters: state.getIn([ 'filters', 'appliedFilter' ]),
|
||||
}
|
||||
}, { applyFilter, addAttribute, fetchLiveList })(AssistTabs);
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './AssistTabs';
|
||||
|
|
@ -119,7 +119,7 @@ export default connect(state => ({
|
|||
activeFlow: state.getIn([ 'filters', 'activeFlow' ]),
|
||||
captureRate: state.getIn(['watchdogs', 'captureRate']),
|
||||
filters: state.getIn([ 'filters', 'appliedFilter' ]),
|
||||
sessionsLoading: state.getIn([ 'sessions', 'loading' ]),
|
||||
sessionsLoading: state.getIn([ 'sessions', 'fetchLiveListRequest', 'loading' ]),
|
||||
}), {
|
||||
fetchWatchdogStatus, setActiveFlow, clearEvents, setActiveTab, fetchSessionList
|
||||
})(SessionsMenu);
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ const InitLoader = connectPlayer(state => ({
|
|||
}))(Loader);
|
||||
|
||||
|
||||
function WebPlayer ({ showAssist, session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, loadingCredentials, assistCredendials, request }) {
|
||||
const WebPlayer = React.memo(({ showAssist, session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, loadingCredentials, assistCredendials, request }) => {
|
||||
useEffect(() => {
|
||||
if (!loadingCredentials) {
|
||||
initPlayer(session, jwt, assistCredendials);
|
||||
|
|
@ -60,7 +60,7 @@ function WebPlayer ({ showAssist, session, toggleFullscreen, closeBottomBlock, l
|
|||
</InitLoader>
|
||||
</PlayerProvider>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default withRequest({
|
||||
initialData: null,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { fetchList as fetchSlackList } from 'Duck/integrations/slack';
|
|||
import { Link, NoContent, Loader } from 'UI';
|
||||
import { sessions as sessionsRoute } from 'App/routes';
|
||||
import withPermissions from 'HOCs/withPermissions'
|
||||
import { fetchLiveList } from 'Duck/sessions';
|
||||
|
||||
import LivePlayer from './LivePlayer';
|
||||
import WebPlayer from './WebPlayer';
|
||||
|
|
@ -20,6 +21,8 @@ function Session({
|
|||
session,
|
||||
fetchSession,
|
||||
fetchSlackList,
|
||||
fetchLiveList,
|
||||
filters
|
||||
}) {
|
||||
usePageTitle("OpenReplay Session Player");
|
||||
useEffect(() => {
|
||||
|
|
@ -36,6 +39,12 @@ function Session({
|
|||
}
|
||||
},[ sessionId ]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (session && session.live) {
|
||||
// fetchLiveList(filters.toJS())
|
||||
// }
|
||||
// }, [session])
|
||||
|
||||
return (
|
||||
<NoContent
|
||||
show={ hasErrors }
|
||||
|
|
@ -64,8 +73,10 @@ export default withPermissions(['SESSION_REPLAY'], '', true)(connect((state, pro
|
|||
loading: state.getIn([ 'sessions', 'loading' ]),
|
||||
hasErrors: !!state.getIn([ 'sessions', 'errors' ]),
|
||||
session: state.getIn([ 'sessions', 'current' ]),
|
||||
filters: state.getIn([ 'filters', 'appliedFilter' ]),
|
||||
};
|
||||
}, {
|
||||
fetchSession,
|
||||
fetchSlackList,
|
||||
fetchLiveList,
|
||||
})(Session));
|
||||
|
|
@ -1,30 +1,51 @@
|
|||
|
||||
//import cn from 'classnames';
|
||||
import { getRE } from 'App/utils';
|
||||
import { Label, NoContent, Input, SlideModal, CloseButton } from 'UI';
|
||||
import { connectPlayer, pause } from 'Player';
|
||||
import { connectPlayer, pause, jump } from 'Player';
|
||||
import Autoscroll from '../Autoscroll';
|
||||
import BottomBlock from '../BottomBlock';
|
||||
import TimeTable from '../TimeTable';
|
||||
import FetchDetails from './FetchDetails';
|
||||
import { renderName, renderDuration } from '../Network';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
@connectPlayer(state => ({
|
||||
list: state.fetchList,
|
||||
}))
|
||||
@connect(state => ({
|
||||
timelinePointer: state.getIn(['sessions', 'timelinePointer']),
|
||||
}))
|
||||
export default class Fetch extends React.PureComponent {
|
||||
state = {
|
||||
filter: "",
|
||||
filteredList: this.props.list,
|
||||
current: null,
|
||||
currentIndex: 0,
|
||||
showFetchDetails: false,
|
||||
hasNextError: false,
|
||||
hasPreviousError: false,
|
||||
}
|
||||
onFilterChange = (e, { value }) => this.setState({ filter: value })
|
||||
|
||||
onFilterChange = (e, { value }) => {
|
||||
const { list } = this.props;
|
||||
const filterRE = getRE(value, 'i');
|
||||
const filtered = list
|
||||
.filter((r) => filterRE.test(r.name) || filterRE.test(r.url) || filterRE.test(r.method) || filterRE.test(r.status));
|
||||
this.setState({ filter: value, filteredList: value ? filtered : list, currentIndex: 0 });
|
||||
}
|
||||
|
||||
setCurrent = (item, index) => {
|
||||
pause()
|
||||
jump(item.time)
|
||||
this.setState({ current: item, currentIndex: index });
|
||||
}
|
||||
|
||||
closeModal = () => this.setState({ current: null})
|
||||
onRowClick = (item, index) => {
|
||||
pause()
|
||||
this.setState({ current: item, currentIndex: index, showFetchDetails: true });
|
||||
}
|
||||
|
||||
closeModal = () => this.setState({ current: null, showFetchDetails: false });
|
||||
|
||||
nextClickHander = () => {
|
||||
const { list } = this.props;
|
||||
|
|
@ -33,6 +54,7 @@ export default class Fetch extends React.PureComponent {
|
|||
if (currentIndex === list.length - 1) return;
|
||||
const newIndex = currentIndex + 1;
|
||||
this.setCurrent(list[newIndex], newIndex);
|
||||
this.setState({ showFetchDetails: true });
|
||||
}
|
||||
|
||||
prevClickHander = () => {
|
||||
|
|
@ -42,15 +64,24 @@ export default class Fetch extends React.PureComponent {
|
|||
if (currentIndex === 0) return;
|
||||
const newIndex = currentIndex - 1;
|
||||
this.setCurrent(list[newIndex], newIndex);
|
||||
this.setState({ showFetchDetails: true });
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
const { filteredList } = prevState;
|
||||
if (nextProps.timelinePointer) {
|
||||
let activeItem = filteredList.find((r) => r.time >= nextProps.timelinePointer.time);
|
||||
activeItem = activeItem || filteredList[filteredList.length - 1];
|
||||
return {
|
||||
current: activeItem,
|
||||
currentIndex: filteredList.indexOf(activeItem),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { list } = this.props;
|
||||
const { filter, current, currentIndex } = this.state;
|
||||
const filterRE = getRE(filter, 'i');
|
||||
const filtered = list
|
||||
.filter((r) => filterRE.test(r.name) || filterRE.test(r.url) || filterRE.test(r.method) || filterRE.test(r.status));
|
||||
|
||||
// const { list } = this.props;
|
||||
const { current, currentIndex, showFetchDetails, filteredList } = this.state;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<SlideModal
|
||||
|
|
@ -73,14 +104,14 @@ export default class Fetch extends React.PureComponent {
|
|||
</div>
|
||||
</div>
|
||||
}
|
||||
isDisplayed={ current != null }
|
||||
content={ current &&
|
||||
isDisplayed={ current != null && showFetchDetails }
|
||||
content={ current && showFetchDetails &&
|
||||
<FetchDetails
|
||||
resource={ current }
|
||||
nextClick={this.nextClickHander}
|
||||
prevClick={this.prevClickHander}
|
||||
first={currentIndex === 0}
|
||||
last={currentIndex === filtered.length - 1}
|
||||
last={currentIndex === filteredList.length - 1}
|
||||
/>
|
||||
}
|
||||
onClose={ this.closeModal }
|
||||
|
|
@ -88,25 +119,31 @@ export default class Fetch extends React.PureComponent {
|
|||
<BottomBlock>
|
||||
<BottomBlock.Header>
|
||||
<h4 className="text-lg">Fetch</h4>
|
||||
<Input
|
||||
className="input-small"
|
||||
placeholder="Filter"
|
||||
icon="search"
|
||||
iconPosition="left"
|
||||
name="filter"
|
||||
onChange={ this.onFilterChange }
|
||||
/>
|
||||
<div className="flex items-center">
|
||||
{/* <div className="flex items-center mr-3 text-sm uppercase">
|
||||
<div className="p-2 cursor-pointer" onClick={this.goToPrevError}>Prev</div>
|
||||
<div className="p-2 cursor-pointer" onClick={this.goToNextError}>Next</div>
|
||||
</div> */}
|
||||
<Input
|
||||
className="input-small"
|
||||
placeholder="Filter"
|
||||
icon="search"
|
||||
iconPosition="left"
|
||||
name="filter"
|
||||
onChange={ this.onFilterChange }
|
||||
/>
|
||||
</div>
|
||||
</BottomBlock.Header>
|
||||
<BottomBlock.Content>
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ filtered.length === 0}
|
||||
show={ filteredList.length === 0}
|
||||
>
|
||||
<TimeTable
|
||||
rows={ filtered }
|
||||
onRowClick={ this.setCurrent }
|
||||
rows={ filteredList }
|
||||
onRowClick={ this.onRowClick }
|
||||
hoverable
|
||||
// navigation
|
||||
navigation
|
||||
activeIndex={currentIndex}
|
||||
>
|
||||
{[
|
||||
|
|
@ -120,7 +157,7 @@ export default class Fetch extends React.PureComponent {
|
|||
width: 60,
|
||||
}, {
|
||||
label: "Name",
|
||||
width: 130,
|
||||
width: 180,
|
||||
render: renderName,
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,14 +3,9 @@ import { connectPlayer, jump, pause } from 'Player';
|
|||
import { QuestionMarkHint, Popup, Tabs, Input } from 'UI';
|
||||
import { getRE } from 'App/utils';
|
||||
import { TYPES } from 'Types/session/resource';
|
||||
import { formatBytes } from 'App/utils';
|
||||
import { formatMs } from 'App/date';
|
||||
|
||||
import TimeTable from '../TimeTable';
|
||||
import BottomBlock from '../BottomBlock';
|
||||
import InfoLine from '../BottomBlock/InfoLine';
|
||||
import stl from './network.css';
|
||||
import NetworkContent from './NetworkContent';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
const ALL = 'ALL';
|
||||
const XHR = 'xhr';
|
||||
|
|
@ -28,73 +23,24 @@ const TAB_TO_TYPE_MAP = {
|
|||
[ MEDIA ]: TYPES.MEDIA,
|
||||
[ OTHER ]: TYPES.OTHER
|
||||
}
|
||||
const TABS = [ ALL, XHR, JS, CSS, IMG, MEDIA, OTHER ].map(tab => ({
|
||||
text: tab,
|
||||
key: tab,
|
||||
}));
|
||||
|
||||
const DOM_LOADED_TIME_COLOR = "teal";
|
||||
const LOAD_TIME_COLOR = "red";
|
||||
|
||||
export function renderName(r) {
|
||||
return (
|
||||
<Popup
|
||||
trigger={ <div className={ stl.popupNameTrigger }>{ r.name }</div> }
|
||||
content={ <div className={ stl.popupNameContent }>{ r.url }</div> }
|
||||
size="mini"
|
||||
position="right center"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const renderXHRText = () => (
|
||||
<span className="flex items-center">
|
||||
{XHR}
|
||||
<QuestionMarkHint
|
||||
onHover
|
||||
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) {
|
||||
let triggerText;
|
||||
let content;
|
||||
if (r.decodedBodySize == null) {
|
||||
triggerText = "x";
|
||||
content = "Not captured";
|
||||
} else {
|
||||
const headerSize = r.headerSize || 0;
|
||||
const encodedSize = r.encodedBodySize || 0;
|
||||
const transferred = headerSize + encodedSize;
|
||||
const showTransferred = r.headerSize != null;
|
||||
|
||||
triggerText = formatBytes(r.decodedBodySize);
|
||||
content = (
|
||||
<ul>
|
||||
{ showTransferred &&
|
||||
<li>{`${formatBytes( r.encodedBodySize + headerSize )} transfered over network`}</li>
|
||||
}
|
||||
<li>{`Resource size: ${formatBytes(r.decodedBodySize)} `}</li>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Popup
|
||||
trigger={ <div>{ triggerText }</div> }
|
||||
content={ content }
|
||||
size="mini"
|
||||
position="right center"
|
||||
/>
|
||||
<div className="flex w-full relative items-center">
|
||||
<Popup
|
||||
trigger={ <div className={ stl.popupNameTrigger }>{ r.name }</div> }
|
||||
content={ <div className={ stl.popupNameContent }>{ r.url }</div> }
|
||||
size="mini"
|
||||
position="right center"
|
||||
/>
|
||||
<div
|
||||
className="absolute right-0 text-xs uppercase p-2 color-gray-500 hover:color-black"
|
||||
onClick={ (e) => {
|
||||
e.stopPropagation();
|
||||
jump(r.time)
|
||||
}}
|
||||
>Jump</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -130,14 +76,18 @@ export function renderDuration(r) {
|
|||
resources: state.resourceList,
|
||||
domContentLoadedTime: state.domContentLoadedTime,
|
||||
loadTime: state.loadTime,
|
||||
time: state.time,
|
||||
// time: state.time,
|
||||
playing: state.playing,
|
||||
domBuildingTime: state.domBuildingTime,
|
||||
fetchPresented: state.fetchList.length > 0,
|
||||
}))
|
||||
@connect(state => ({
|
||||
timelinePointer: state.getIn(['sessions', 'timelinePointer']),
|
||||
}))
|
||||
export default class Network extends React.PureComponent {
|
||||
state = {
|
||||
filter: '',
|
||||
filteredList: this.props.resources,
|
||||
activeTab: ALL,
|
||||
currentIndex: 0
|
||||
}
|
||||
|
|
@ -149,7 +99,30 @@ export default class Network extends React.PureComponent {
|
|||
}
|
||||
|
||||
onTabClick = activeTab => this.setState({ activeTab })
|
||||
onFilterChange = (e, { value }) => this.setState({ filter: value })
|
||||
|
||||
onFilterChange = (e, { value }) => {
|
||||
const { resources } = this.props;
|
||||
const filterRE = getRE(value, 'i');
|
||||
const filtered = resources.filter(({ type, name }) =>
|
||||
filterRE.test(name) && (activeTab === ALL || type === TAB_TO_TYPE_MAP[ activeTab ]));
|
||||
|
||||
this.setState({ filter: value, filteredList: value ? filtered : resources, currentIndex: 0 });
|
||||
}
|
||||
// onFilterChange = (e, { value }) => this.setState({ filter: value })
|
||||
|
||||
componentDidUpdate() {
|
||||
console.log('test')
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
const { filteredList } = prevState;
|
||||
if (nextProps.timelinePointer) {
|
||||
const activeItem = filteredList.find((r) => r.time >= nextProps.timelinePointer.time);
|
||||
return {
|
||||
currentIndex: activeItem ? filteredList.indexOf(activeItem) : filteredList.length - 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
|
|
@ -159,50 +132,23 @@ export default class Network extends React.PureComponent {
|
|||
loadTime,
|
||||
domBuildingTime,
|
||||
fetchPresented,
|
||||
time,
|
||||
// time,
|
||||
playing
|
||||
} = this.props;
|
||||
const { filter, activeTab, currentIndex } = this.state;
|
||||
const filterRE = getRE(filter, 'i');
|
||||
let filtered = resources.filter(({ type, name }) =>
|
||||
filterRE.test(name) && (activeTab === ALL || type === TAB_TO_TYPE_MAP[ activeTab ]));
|
||||
|
||||
// const referenceLines = [];
|
||||
// if (domContentLoadedTime != null) {
|
||||
// referenceLines.push({
|
||||
// time: domContentLoadedTime,
|
||||
// color: DOM_LOADED_TIME_COLOR,
|
||||
// })
|
||||
// }
|
||||
// if (loadTime != null) {
|
||||
// referenceLines.push({
|
||||
// time: loadTime,
|
||||
// color: LOAD_TIME_COLOR,
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// let tabs = TABS;
|
||||
// if (!fetchPresented) {
|
||||
// tabs = TABS.map(tab => tab.key === XHR
|
||||
// ? {
|
||||
// text: renderXHRText(),
|
||||
// key: XHR,
|
||||
// }
|
||||
// : tab
|
||||
// );
|
||||
// }
|
||||
|
||||
const resourcesSize = filtered.reduce((sum, { decodedBodySize }) => sum + (decodedBodySize || 0), 0);
|
||||
const transferredSize = filtered
|
||||
const { filter, activeTab, currentIndex, filteredList } = this.state;
|
||||
// const filterRE = getRE(filter, 'i');
|
||||
// let filtered = resources.filter(({ type, name }) =>
|
||||
// filterRE.test(name) && (activeTab === ALL || type === TAB_TO_TYPE_MAP[ activeTab ]));
|
||||
const resourcesSize = filteredList.reduce((sum, { decodedBodySize }) => sum + (decodedBodySize || 0), 0);
|
||||
const transferredSize = filteredList
|
||||
.reduce((sum, { headerSize, encodedBodySize }) => sum + (headerSize || 0) + (encodedBodySize || 0), 0);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<NetworkContent
|
||||
// {...this.props }
|
||||
time = { time }
|
||||
// time = { time }
|
||||
location = { location }
|
||||
resources = { resources }
|
||||
resources = { filteredList }
|
||||
domContentLoadedTime = { domContentLoadedTime }
|
||||
loadTime = { loadTime }
|
||||
domBuildingTime = { domBuildingTime }
|
||||
|
|
@ -210,91 +156,8 @@ export default class Network extends React.PureComponent {
|
|||
resourcesSize={resourcesSize}
|
||||
transferredSize={transferredSize}
|
||||
onRowClick={ this.onRowClick }
|
||||
currentIndex={playing ? null : currentIndex}
|
||||
currentIndex={currentIndex}
|
||||
/>
|
||||
{/* <BottomBlock>
|
||||
<BottomBlock.Header>
|
||||
<Tabs
|
||||
className="uppercase"
|
||||
tabs={ tabs }
|
||||
active={ activeTab }
|
||||
onClick={ this.onTabClick }
|
||||
border={ false }
|
||||
/>
|
||||
<Input
|
||||
className="input-small"
|
||||
placeholder="Filter by Name"
|
||||
icon="search"
|
||||
iconPosition="left"
|
||||
name="filter"
|
||||
onChange={ this.onFilterChange }
|
||||
/>
|
||||
</BottomBlock.Header>
|
||||
<BottomBlock.Content>
|
||||
<InfoLine>
|
||||
<InfoLine.Point label={ filtered.length } value=" requests" />
|
||||
<InfoLine.Point
|
||||
label={ formatBytes(transferredSize) }
|
||||
value="transferred"
|
||||
display={ transferredSize > 0 }
|
||||
/>
|
||||
<InfoLine.Point
|
||||
label={ formatBytes(resourcesSize) }
|
||||
value="resources"
|
||||
display={ resourcesSize > 0 }
|
||||
/>
|
||||
<InfoLine.Point
|
||||
label="DOM Building Time"
|
||||
value={ formatMs(domBuildingTime)}
|
||||
display={ domBuildingTime != null }
|
||||
/>
|
||||
<InfoLine.Point
|
||||
label="DOMContentLoaded"
|
||||
value={ formatMs(domContentLoadedTime)}
|
||||
display={ domContentLoadedTime != null }
|
||||
dotColor={ DOM_LOADED_TIME_COLOR }
|
||||
/>
|
||||
<InfoLine.Point
|
||||
label="Load"
|
||||
value={ formatMs(loadTime)}
|
||||
display={ loadTime != null }
|
||||
dotColor={ LOAD_TIME_COLOR }
|
||||
/>
|
||||
</InfoLine>
|
||||
<TimeTable
|
||||
rows={ filtered }
|
||||
referenceLines={referenceLines}
|
||||
renderPopup
|
||||
navigation
|
||||
>
|
||||
{[
|
||||
{
|
||||
label: "Status",
|
||||
dataKey: 'status',
|
||||
width: 70,
|
||||
}, {
|
||||
label: "Type",
|
||||
dataKey: 'type',
|
||||
width: 60,
|
||||
}, {
|
||||
label: "Name",
|
||||
width: 130,
|
||||
render: renderName,
|
||||
},
|
||||
{
|
||||
label: "Size",
|
||||
width: 60,
|
||||
render: renderSize,
|
||||
},
|
||||
{
|
||||
label: "Time",
|
||||
width: 80,
|
||||
render: renderDuration,
|
||||
}
|
||||
]}
|
||||
</TimeTable>
|
||||
</BottomBlock.Content>
|
||||
</BottomBlock> */}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
max-width: 80%;
|
||||
width: fit-content;
|
||||
}
|
||||
.popupNameContent {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import TimeTracker from './TimeTracker';
|
|||
import { ReduxTime } from './Time';
|
||||
import stl from './timeline.css';
|
||||
import { TYPES } from 'Types/session/event';
|
||||
import { setTimelinePointer } from 'Duck/sessions';
|
||||
|
||||
const getPointerIcon = (type) => {
|
||||
// exception,
|
||||
|
|
@ -69,7 +70,7 @@ const getPointerIcon = (type) => {
|
|||
state.getIn([ 'sessions', 'current', 'clickRageTime' ]),
|
||||
returningLocationTime: state.getIn([ 'sessions', 'current', 'returningLocation' ]) &&
|
||||
state.getIn([ 'sessions', 'current', 'returningLocationTime' ]),
|
||||
}))
|
||||
}), { setTimelinePointer })
|
||||
export default class Timeline extends React.PureComponent {
|
||||
seekProgress = (e) => {
|
||||
const { endTime } = this.props;
|
||||
|
|
@ -78,9 +79,10 @@ export default class Timeline extends React.PureComponent {
|
|||
this.props.jump(time);
|
||||
}
|
||||
|
||||
createEventClickHandler = time => (e) => {
|
||||
createEventClickHandler = pointer => (e) => {
|
||||
e.stopPropagation();
|
||||
this.props.jump(time)
|
||||
this.props.jump(pointer.time);
|
||||
this.props.setTimelinePointer(pointer);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
|
@ -144,7 +146,7 @@ export default class Timeline extends React.PureComponent {
|
|||
//width: `${ 2000 * scale }%`
|
||||
} }
|
||||
className={ stl.clickRage }
|
||||
onClick={ this.createEventClickHandler(iss.time) }
|
||||
onClick={ this.createEventClickHandler(iss) }
|
||||
>
|
||||
<TimelinePointer
|
||||
icon={iss.icon}
|
||||
|
|
@ -165,7 +167,7 @@ export default class Timeline extends React.PureComponent {
|
|||
//width: `${ 2000 * scale }%`
|
||||
} }
|
||||
className={ stl.clickRage }
|
||||
onClick={ this.createEventClickHandler(e.time) }
|
||||
onClick={ this.createEventClickHandler(e) }
|
||||
>
|
||||
<TimelinePointer
|
||||
icon={getPointerIcon('click_rage')}
|
||||
|
|
@ -259,7 +261,7 @@ export default class Timeline extends React.PureComponent {
|
|||
key={ e.key }
|
||||
className={ cn(stl.markup, stl.error) }
|
||||
style={ { left: `${ e.time * scale }%`, top: '-30px' } }
|
||||
onClick={ this.createEventClickHandler(e.time) }
|
||||
onClick={ this.createEventClickHandler(e) }
|
||||
>
|
||||
<TimelinePointer
|
||||
icon={getPointerIcon('exception')}
|
||||
|
|
@ -305,7 +307,7 @@ export default class Timeline extends React.PureComponent {
|
|||
//[ stl.info ]: !l.isYellow() && !l.isRed(),
|
||||
}) }
|
||||
style={ { left: `${ l.time * scale }%`, top: '-30px' } }
|
||||
onClick={ this.createEventClickHandler(l.time) }
|
||||
onClick={ this.createEventClickHandler(l) }
|
||||
>
|
||||
<TimelinePointer
|
||||
icon={getPointerIcon('log')}
|
||||
|
|
@ -360,7 +362,7 @@ export default class Timeline extends React.PureComponent {
|
|||
[ stl.warning ]: r.isYellow(),
|
||||
}) }
|
||||
style={ { left: `${ r.time * scale }%`, top: '-30px' } }
|
||||
onClick={ this.createEventClickHandler(r.time) }
|
||||
onClick={ this.createEventClickHandler(r) }
|
||||
>
|
||||
<TimelinePointer
|
||||
icon={getPointerIcon('resource')}
|
||||
|
|
@ -407,7 +409,7 @@ export default class Timeline extends React.PureComponent {
|
|||
key={ e.key }
|
||||
className={ cn(stl.markup, stl.error) }
|
||||
style={ { left: `${ e.time * scale }%`, top: '-30px' } }
|
||||
onClick={ this.createEventClickHandler(e.time) }
|
||||
onClick={ this.createEventClickHandler(e) }
|
||||
>
|
||||
<TimelinePointer
|
||||
icon={getPointerIcon('fetch')}
|
||||
|
|
@ -448,7 +450,7 @@ export default class Timeline extends React.PureComponent {
|
|||
key={ e.key }
|
||||
className={ cn(stl.markup, stl.error) }
|
||||
style={ { left: `${ e.time * scale }%`, top: '-30px' } }
|
||||
onClick={ this.createEventClickHandler(e.time) }
|
||||
onClick={ this.createEventClickHandler(e) }
|
||||
>
|
||||
<TimelinePointer
|
||||
icon={getPointerIcon('stack')}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import cls from './playerBlockHeader.css';
|
|||
import Issues from './Issues/Issues';
|
||||
import Autoplay from './Autoplay';
|
||||
import AssistActions from '../Assist/components/AssistActions';
|
||||
import AssistTabs from '../Assist/components/AssistTabs';
|
||||
|
||||
const SESSIONS_ROUTE = sessionsRoute();
|
||||
|
||||
|
|
@ -115,6 +116,7 @@ export default class PlayerBlockHeader extends React.PureComponent {
|
|||
<HeaderInfo icon={ osIcon(userOs) } label={ userOs } />
|
||||
|
||||
<div className='ml-auto flex items-center'>
|
||||
{ live && <AssistTabs />}
|
||||
{ live && <AssistActions isLive userId={userId} /> }
|
||||
{ !live && (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ export default class TimeTable extends React.PureComponent<Props, State> {
|
|||
...computeTimeLine(this.props.rows, this.state.firstVisibleRowIndex, this.visibleCount),
|
||||
});
|
||||
}
|
||||
if (this.props.activeIndex && prevProps.activeIndex !== this.props.activeIndex && this.scroller.current != null) {
|
||||
if (this.props.activeIndex >= 0 && prevProps.activeIndex !== this.props.activeIndex && this.scroller.current != null) {
|
||||
this.scroller.current.scrollToRow(this.props.activeIndex);
|
||||
}
|
||||
}
|
||||
|
|
@ -168,7 +168,7 @@ export default class TimeTable extends React.PureComponent<Props, State> {
|
|||
<div
|
||||
style={ rowStyle }
|
||||
key={ key }
|
||||
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, 'color-white' : activeIndex === index }) }
|
||||
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 }) }
|
||||
onClick={ typeof onRowClick === "function" ? () => onRowClick(row, index) : null }
|
||||
id="table-row"
|
||||
>
|
||||
|
|
@ -223,7 +223,7 @@ export default class TimeTable extends React.PureComponent<Props, State> {
|
|||
navigation=false,
|
||||
referenceLines = [],
|
||||
additionalHeight = 0,
|
||||
activeIndex
|
||||
activeIndex,
|
||||
} = this.props;
|
||||
const {
|
||||
timewidth,
|
||||
|
|
@ -247,7 +247,7 @@ export default class TimeTable extends React.PureComponent<Props, State> {
|
|||
return (
|
||||
<div className={ cn(className, "relative") }>
|
||||
{ navigation &&
|
||||
<div className={ cn(autoscrollStl.navButtons, "flex items-center") } style={{ top: '-33px', right: '30px' }} >
|
||||
<div className={ cn(autoscrollStl.navButtons, "flex items-center") } >
|
||||
<IconButton
|
||||
size="small"
|
||||
icon="chevron-up"
|
||||
|
|
|
|||
|
|
@ -99,8 +99,6 @@ $offset: 10px;
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.activeRow {
|
||||
background-color: $teal;
|
||||
background-color: rgba(54, 108, 217, 0.1);
|
||||
}
|
||||
|
|
@ -12,8 +12,8 @@
|
|||
|
||||
.navButtons {
|
||||
position: absolute;
|
||||
right: 40px;
|
||||
top: 10px;
|
||||
right: 260px;
|
||||
top: -34px;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Duration } from 'luxon';
|
|||
|
||||
interface Props {
|
||||
startTime: any,
|
||||
className: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
function Counter({ startTime, className }: Props) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
border-radius: 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
height: 36px;
|
||||
font-size: 14px;
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ const SET_EVENT_QUERY = 'sessions/SET_EVENT_QUERY';
|
|||
const SET_AUTOPLAY_VALUES = 'sessions/SET_AUTOPLAY_VALUES';
|
||||
const TOGGLE_CHAT_WINDOW = 'sessions/TOGGLE_CHAT_WINDOW';
|
||||
const SET_FUNNEL_PAGE_FLAG = 'sessions/SET_FUNNEL_PAGE_FLAG';
|
||||
const SET_TIMELINE_POINTER = 'sessions/SET_TIMELINE_POINTER';
|
||||
|
||||
const SET_ACTIVE_TAB = 'sessions/SET_ACTIVE_TAB';
|
||||
|
||||
|
|
@ -57,6 +58,7 @@ const initialState = Map({
|
|||
insightFilters: defaultDateFilters,
|
||||
host: '',
|
||||
funnelPage: Map(),
|
||||
timelinePointer: null,
|
||||
});
|
||||
|
||||
const reducer = (state = initialState, action = {}) => {
|
||||
|
|
@ -242,13 +244,16 @@ const reducer = (state = initialState, action = {}) => {
|
|||
return state.set('insights', List(action.data).sort((a, b) => b.count - a.count));
|
||||
case SET_FUNNEL_PAGE_FLAG:
|
||||
return state.set('funnelPage', action.funnelPage ? Map(action.funnelPage) : false);
|
||||
case SET_TIMELINE_POINTER:
|
||||
return state.set('timelinePointer', action.pointer);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default withRequestState({
|
||||
_: [ FETCH, FETCH_LIST, FETCH_LIVE_LIST ],
|
||||
_: [ FETCH, FETCH_LIST ],
|
||||
fetchLiveListRequest: FETCH_LIVE_LIST,
|
||||
fetchFavoriteListRequest: FETCH_FAVORITE_LIST,
|
||||
toggleFavoriteRequest: TOGGLE_FAVORITE,
|
||||
fetchErrorStackList: FETCH_ERROR_STACK,
|
||||
|
|
@ -262,10 +267,10 @@ function init(session) {
|
|||
}
|
||||
}
|
||||
|
||||
export const fetchList = (params = {}, clear = false) => (dispatch, getState) => {
|
||||
export const fetchList = (params = {}, clear = false, live = false) => (dispatch, getState) => {
|
||||
const activeTab = getState().getIn([ 'sessions', 'activeTab' ]);
|
||||
|
||||
return dispatch(activeTab && activeTab.type === 'live' ? {
|
||||
return dispatch((activeTab && activeTab.type === 'live' || live )? {
|
||||
types: FETCH_LIVE_LIST.toArray(),
|
||||
call: client => client.post('/assist/sessions', params),
|
||||
} : {
|
||||
|
|
@ -376,3 +381,9 @@ export function setFunnelPage(funnelPage) {
|
|||
}
|
||||
}
|
||||
|
||||
export function setTimelinePointer(pointer) {
|
||||
return {
|
||||
type: SET_TIMELINE_POINTER,
|
||||
pointer
|
||||
}
|
||||
}
|
||||
5
frontend/app/svg/icons/os/fedora.svg
Normal file
5
frontend/app/svg/icons/os/fedora.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" viewBox="0 0 204.7 200.9" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>Fedora logo (2021)</title>
|
||||
<path d="m102.7 1.987c-55.41 0-100.3 44.21-100.4 98.79h-0.01773v76.47h0.01773c0.02659 12.38 10.22 22.4 22.8 22.4h77.58c55.42-0.0349 100.3-44.24 100.3-98.79 8e-5 -54.58-44.91-98.79-100.4-98.79zm20.39 40.68c16.85 0 32.76 12.7 32.76 30.23 0 1.625 0.01 3.252-0.26 5.095-0.4668 4.662-4.794 8.012-9.505 7.355-4.711-0.6649-7.909-5.07-7.037-9.679 0.0799-0.526 0.1083-1.352 0.1083-2.772 0-9.938-8.257-13.77-16.06-13.77-7.805 0-14.84 6.462-14.85 13.77 0.1349 8.455 0 16.84 0 25.29l14.49-0.1067c11.31-0.2306 11.44 16.54 0.1305 16.46l-14.61 0.1067c-0.0354 6.801 0.0532 5.571 0.0178 8.996 0 0 0.1225 8.318-0.1296 14.62-1.749 18.52-17.76 33.32-37 33.32-20.4 0-37.2-16.41-37.2-36.54 0.6124-20.7 17.38-36.99 38.5-36.8l11.78-0.08737v16.43l-11.78 0.1066h-0.06216c-11.6 0.3382-21.55 8.1-21.74 20.34 0 11.15 9.148 20.08 20.5 20.08 11.34 0 20.42-8.124 20.42-20.06l-0.01772-62.23c0.0058-1.155 0.04435-2.073 0.1731-3.347 1.914-15.22 15.74-26.82 31.39-26.82z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -34,8 +34,8 @@ export default Record({
|
|||
startDate,
|
||||
endDate,
|
||||
|
||||
sort: undefined,
|
||||
order: undefined,
|
||||
sort: 'startTs',
|
||||
order: 'desc',
|
||||
|
||||
viewed: undefined,
|
||||
consoleLogCount: undefined,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue