diff --git a/frontend/app/components/Assist/components/AssistTabs/AssistTabs.tsx b/frontend/app/components/Assist/components/AssistTabs/AssistTabs.tsx
new file mode 100644
index 000000000..ff62a899a
--- /dev/null
+++ b/frontend/app/components/Assist/components/AssistTabs/AssistTabs.tsx
@@ -0,0 +1,38 @@
+import React, { useEffect, useState } from 'react';
+import { SlideModal, Icon } from 'UI';
+import SessionList from '../SessionList';
+import stl from './assistTabs.css'
+
+interface Props {
+ userId: any,
+}
+
+const AssistTabs = (props: Props) => {
+ const [showMenu, setShowMenu] = useState(false)
+
+ return (
+
+
+
setShowMenu(!showMenu)}
+ >
+ More Live Sessions
+
+
by
+
+
+
Live Sessions by {props.userId} }
+ isDisplayed={ showMenu }
+ content={ showMenu && }
+ onClose={ () => setShowMenu(false) }
+ />
+
+ );
+};
+
+export default AssistTabs;
\ No newline at end of file
diff --git a/frontend/app/components/Assist/components/AssistTabs/assistTabs.css b/frontend/app/components/Assist/components/AssistTabs/assistTabs.css
new file mode 100644
index 000000000..462879395
--- /dev/null
+++ b/frontend/app/components/Assist/components/AssistTabs/assistTabs.css
@@ -0,0 +1,5 @@
+.btnLink {
+ cursor: pointer;
+ color: $green;
+ text-decoration: underline;
+}
\ No newline at end of file
diff --git a/frontend/app/components/Assist/components/AssistTabs/index.ts b/frontend/app/components/Assist/components/AssistTabs/index.ts
new file mode 100644
index 000000000..b089b3734
--- /dev/null
+++ b/frontend/app/components/Assist/components/AssistTabs/index.ts
@@ -0,0 +1 @@
+export { default } from './AssistTabs';
\ No newline at end of file
diff --git a/frontend/app/components/Assist/components/SessionList/SessionList.tsx b/frontend/app/components/Assist/components/SessionList/SessionList.tsx
new file mode 100644
index 000000000..f556a8f1d
--- /dev/null
+++ b/frontend/app/components/Assist/components/SessionList/SessionList.tsx
@@ -0,0 +1,40 @@
+import React, { useEffect } from 'react';
+import { connect } from 'react-redux';
+import { fetchLiveList } from 'Duck/sessions';
+import { Loader, NoContent } from 'UI';
+import SessionItem from 'Shared/SessionItem';
+
+interface Props {
+ loading: boolean,
+ list: any,
+ session: any,
+ fetchLiveList: () => void,
+}
+function SessionList(props: Props) {
+ useEffect(() => {
+ props.fetchLiveList();
+ }, [])
+
+ return (
+
+
+
+ { props.list.map(session => ) }
+
+
+
+ );
+}
+
+export default connect(state => {
+ const session = state.getIn([ 'sessions', 'current' ]);
+ return {
+ session,
+ list: state.getIn(['sessions', 'liveSessions'])
+ .filter(i => i.userId === session.userId && i.sessionId !== session.sessionId),
+ loading: state.getIn([ 'sessions', 'fetchLiveListRequest', 'loading' ]),
+ }
+}, { fetchLiveList })(SessionList);
\ No newline at end of file
diff --git a/frontend/app/components/Assist/components/SessionList/index.ts b/frontend/app/components/Assist/components/SessionList/index.ts
new file mode 100644
index 000000000..779c9df2a
--- /dev/null
+++ b/frontend/app/components/Assist/components/SessionList/index.ts
@@ -0,0 +1 @@
+export { default } from './SessionList';
\ No newline at end of file
diff --git a/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js b/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js
index fb89f6967..7275d9ac0 100644
--- a/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js
+++ b/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js
@@ -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);
diff --git a/frontend/app/components/Session/LivePlayer.js b/frontend/app/components/Session/LivePlayer.js
index cbc3410cd..34c56721e 100644
--- a/frontend/app/components/Session/LivePlayer.js
+++ b/frontend/app/components/Session/LivePlayer.js
@@ -31,17 +31,19 @@ const InitLoader = connectPlayer(state => ({
}))(Loader);
-function WebPlayer ({ showAssist, session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, loadingCredentials, assistCredendials, request }) {
+function WebPlayer ({ showAssist, session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, loadingCredentials, assistCredendials, request, isEnterprise, hasSessionsPath }) {
useEffect(() => {
if (!loadingCredentials) {
- initPlayer(session, jwt, assistCredendials);
+ initPlayer(session, jwt, assistCredendials, !hasSessionsPath && session.live);
}
return () => cleanPlayer()
}, [ session.sessionId, loadingCredentials, assistCredendials ]);
// LAYOUT (TODO: local layout state - useContext or something..)
useEffect(() => {
- request();
+ if (isEnterprise) {
+ request();
+ }
return () => {
toggleFullscreen(false);
closeBottomBlock();
@@ -60,7 +62,7 @@ function WebPlayer ({ showAssist, session, toggleFullscreen, closeBottomBlock, l
);
-}
+};
export default withRequest({
initialData: null,
@@ -74,6 +76,8 @@ export default withRequest({
showAssist: state.getIn([ 'sessions', 'showChatWindow' ]),
jwt: state.get('jwt'),
fullscreen: state.getIn([ 'components', 'player', 'fullscreen' ]),
+ hasSessionsPath: state.getIn([ 'sessions', 'sessionPath' ]).includes('/sessions'),
+ isEnterprise: state.getIn([ 'user', 'client', 'edition' ]) === 'ee',
}),
{ toggleFullscreen, closeBottomBlock },
-)(WebPlayer)));
\ No newline at end of file
+)(WebPlayer)));
diff --git a/frontend/app/components/Session/Session.js b/frontend/app/components/Session/Session.js
index 6b90ff4c1..e7af00642 100644
--- a/frontend/app/components/Session/Session.js
+++ b/frontend/app/components/Session/Session.js
@@ -20,6 +20,7 @@ function Session({
session,
fetchSession,
fetchSlackList,
+ hasSessionsPath
}) {
usePageTitle("OpenReplay Session Player");
useEffect(() => {
@@ -34,7 +35,7 @@ function Session({
return () => {
if (!session.exists()) return;
}
- },[ sessionId ]);
+ },[ sessionId, hasSessionsPath ]);
return (
{ session.isIOS
?
- : (session.live ? : )
+ : (session.live && !hasSessionsPath ? : )
}
@@ -64,6 +65,7 @@ export default withPermissions(['SESSION_REPLAY'], '', true)(connect((state, pro
loading: state.getIn([ 'sessions', 'loading' ]),
hasErrors: !!state.getIn([ 'sessions', 'errors' ]),
session: state.getIn([ 'sessions', 'current' ]),
+ hasSessionsPath: state.getIn([ 'sessions', 'sessionPath' ]).includes('/sessions'),
};
}, {
fetchSession,
diff --git a/frontend/app/components/Session_/Fetch/Fetch.js b/frontend/app/components/Session_/Fetch/Fetch.js
index 71e7a2fb9..8bc4f2117 100644
--- a/frontend/app/components/Session_/Fetch/Fetch.js
+++ b/frontend/app/components/Session_/Fetch/Fetch.js
@@ -1,56 +1,89 @@
-//import cn from 'classnames';
import { getRE } from 'App/utils';
import { Label, NoContent, Input, SlideModal, CloseButton } from 'UI';
-import { connectPlayer, pause } from 'Player';
-import Autoscroll from '../Autoscroll';
+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';
+import { setTimelinePointer } from 'Duck/sessions';
@connectPlayer(state => ({
list: state.fetchList,
}))
+@connect(state => ({
+ timelinePointer: state.getIn(['sessions', 'timelinePointer']),
+}), { setTimelinePointer })
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 });
+ this.props.setTimelinePointer(null);
+ }
+
+ closeModal = () => this.setState({ current: null, showFetchDetails: false });
nextClickHander = () => {
- const { list } = this.props;
- const { currentIndex } = this.state;
+ // const { list } = this.props;
+ const { currentIndex, filteredList } = this.state;
- if (currentIndex === list.length - 1) return;
+ if (currentIndex === filteredList.length - 1) return;
const newIndex = currentIndex + 1;
- this.setCurrent(list[newIndex], newIndex);
+ this.setCurrent(filteredList[newIndex], newIndex);
+ this.setState({ showFetchDetails: true });
}
prevClickHander = () => {
- const { list } = this.props;
- const { currentIndex } = this.state;
+ // const { list } = this.props;
+ const { currentIndex, filteredList } = this.state;
if (currentIndex === 0) return;
const newIndex = currentIndex - 1;
- this.setCurrent(list[newIndex], newIndex);
+ this.setCurrent(filteredList[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 (
}
- isDisplayed={ current != null }
- content={ current &&
+ isDisplayed={ current != null && showFetchDetails }
+ content={ current && showFetchDetails &&
}
onClose={ this.closeModal }
@@ -88,25 +121,31 @@ export default class Fetch extends React.PureComponent {
Fetch
-
+
{[
@@ -120,7 +159,7 @@ export default class Fetch extends React.PureComponent {
width: 60,
}, {
label: "Name",
- width: 130,
+ width: 180,
render: renderName,
},
{
diff --git a/frontend/app/components/Session_/Network/Network.js b/frontend/app/components/Session_/Network/Network.js
index 878266bda..150cadc59 100644
--- a/frontend/app/components/Session_/Network/Network.js
+++ b/frontend/app/components/Session_/Network/Network.js
@@ -3,14 +3,10 @@ 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';
+import { setTimelinePointer } from 'Duck/sessions';
const ALL = 'ALL';
const XHR = 'xhr';
@@ -28,73 +24,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 (
- { r.name } }
- content={ { r.url }
}
- size="mini"
- position="right center"
- />
- );
-}
-
-const renderXHRText = () => (
-
- {XHR}
-
- Use our Fetch plugin
- {' to capture HTTP requests and responses, including status codes and bodies.'}
- We also provide support for GraphQL
- {' for easy debugging of your queries.'}
- >
- }
- className="ml-1"
- />
-
-);
-
-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 = (
-
- { showTransferred &&
- - {`${formatBytes( r.encodedBodySize + headerSize )} transfered over network`}
- }
- - {`Resource size: ${formatBytes(r.decodedBodySize)} `}
-
- );
- }
-
- return (
- { triggerText } }
- content={ content }
- size="mini"
- position="right center"
- />
+ }
+ content={ { r.url }
}
+ size="mini"
+ position="right center"
+ />
+ {
+ e.stopPropagation();
+ jump(r.time)
+ }}
+ >Jump
+
);
}
@@ -130,14 +77,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']),
+}), { setTimelinePointer })
export default class Network extends React.PureComponent {
state = {
filter: '',
+ filteredList: this.props.resources,
activeTab: ALL,
currentIndex: 0
}
@@ -146,10 +97,29 @@ export default class Network extends React.PureComponent {
pause();
jump(e.time);
this.setState({ currentIndex: index })
+ this.props.setTimelinePointer(null);
}
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 });
+ }
+
+ 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 +129,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 (
- {/*
-
-
-
-
-
-
-
- 0 }
- />
- 0 }
- />
-
-
-
-
-
- {[
- {
- 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,
- }
- ]}
-
-
- */}
);
}
diff --git a/frontend/app/components/Session_/Network/network.css b/frontend/app/components/Session_/Network/network.css
index eb37e49c9..e299211da 100644
--- a/frontend/app/components/Session_/Network/network.css
+++ b/frontend/app/components/Session_/Network/network.css
@@ -22,7 +22,7 @@
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
- max-width: 100%;
+ max-width: 80%;
width: fit-content;
}
.popupNameContent {
diff --git a/frontend/app/components/Session_/Player/Controls/Timeline.js b/frontend/app/components/Session_/Player/Controls/Timeline.js
index c25101d69..27c1de1ab 100644
--- a/frontend/app/components/Session_/Player/Controls/Timeline.js
+++ b/frontend/app/components/Session_/Player/Controls/Timeline.js
@@ -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) }
>
+
-
+
@@ -115,11 +120,17 @@ export default class PlayerBlockHeader extends React.PureComponent {
- { live &&
}
- { !live && (
+ { live && hasSessionsPath && (
+
this.props.setSessionPath('')}>
+ This Session is Now Continuing Live
+
+ )}
+ { _live &&
}
+ { _live &&
}
+ { !_live && (
<>
-
+
>
)}
- { !live && jiraConfig && jiraConfig.token &&
}
+ { !_live && jiraConfig && jiraConfig.token &&
}
diff --git a/frontend/app/components/Session_/TimeTable/TimeTable.js b/frontend/app/components/Session_/TimeTable/TimeTable.js
index 9753e3d44..e316dae56 100644
--- a/frontend/app/components/Session_/TimeTable/TimeTable.js
+++ b/frontend/app/components/Session_/TimeTable/TimeTable.js
@@ -135,7 +135,7 @@ export default class TimeTable extends React.PureComponent
{
...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 {
onRowClick(row, index) : null }
id="table-row"
>
@@ -223,7 +223,7 @@ export default class TimeTable extends React.PureComponent
{
navigation=false,
referenceLines = [],
additionalHeight = 0,
- activeIndex
+ activeIndex,
} = this.props;
const {
timewidth,
@@ -247,7 +247,7 @@ export default class TimeTable extends React.PureComponent {
return (
{ navigation &&
-
+
(
{label}
)
@connect(state => ({
- timezone: state.getIn(['sessions', 'timezone'])
-}), { toggleFavorite })
+ timezone: state.getIn(['sessions', 'timezone']),
+ isAssist: state.getIn(['sessions', 'activeTab']).type === 'live',
+ siteId: state.getIn([ 'user', 'siteId' ]),
+}), { toggleFavorite, setSessionPath })
+@withRouter
export default class SessionItem extends React.PureComponent {
+
+ replaySession = () => {
+ const { history, session: { sessionId }, siteId, isAssist } = this.props;
+ if (!isAssist) {
+ this.props.setSessionPath(history.location.pathname)
+ }
+ history.push(withSiteId(sessionRoute(sessionId), siteId))
+ }
// eslint-disable-next-line complexity
render() {
const {
@@ -110,9 +122,9 @@ export default class SessionItem extends React.PureComponent {
diff --git a/frontend/app/components/shared/SessionItem/sessionItem.css b/frontend/app/components/shared/SessionItem/sessionItem.css
index 8f9824bec..cbf7bb2d1 100644
--- a/frontend/app/components/shared/SessionItem/sessionItem.css
+++ b/frontend/app/components/shared/SessionItem/sessionItem.css
@@ -92,7 +92,7 @@
display: flex;
align-items: center;
transition: all 0.2s;
- /* opacity: 0; */
+ cursor: pointer;
&[data-viewed=true] {
opacity: 1;
}
diff --git a/frontend/app/components/ui/IconButton/iconButton.css b/frontend/app/components/ui/IconButton/iconButton.css
index 41ed89e45..3aaff8347 100644
--- a/frontend/app/components/ui/IconButton/iconButton.css
+++ b/frontend/app/components/ui/IconButton/iconButton.css
@@ -7,6 +7,7 @@
border-radius: 3px;
display: flex;
align-items: center;
+ justify-content: center;
cursor: pointer;
height: 36px;
font-size: 14px;
diff --git a/frontend/app/duck/funnels.js b/frontend/app/duck/funnels.js
index 4e591237c..7dad8550a 100644
--- a/frontend/app/duck/funnels.js
+++ b/frontend/app/duck/funnels.js
@@ -119,7 +119,6 @@ const reducer = (state = initialState, action = {}) => {
let stages = [];
if (action.isRefresh) {
const activeStages = state.get('activeStages');
- console.log('test', activeStages);
const oldInsights = state.get('insights');
const lastStage = action.data.stages[action.data.stages.length - 1]
const lastStageIndex = activeStages.toJS()[1];
diff --git a/frontend/app/duck/sessions.js b/frontend/app/duck/sessions.js
index e82602799..977af85bb 100644
--- a/frontend/app/duck/sessions.js
+++ b/frontend/app/duck/sessions.js
@@ -8,7 +8,6 @@ import { getRE } from 'App/utils';
import { LAST_7_DAYS } from 'Types/app/period';
import { getDateRangeFromValue } from 'App/dateRange';
-
const INIT = 'sessions/INIT';
const FETCH_LIST = new RequestTypes('sessions/FETCH_LIST');
@@ -25,6 +24,8 @@ 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_SESSION_PATH = 'sessions/SET_SESSION_PATH';
const SET_ACTIVE_TAB = 'sessions/SET_ACTIVE_TAB';
@@ -57,6 +58,8 @@ const initialState = Map({
insightFilters: defaultDateFilters,
host: '',
funnelPage: Map(),
+ timelinePointer: null,
+ sessionPath: '',
});
const reducer = (state = initialState, action = {}) => {
@@ -242,13 +245,18 @@ 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);
+ case SET_SESSION_PATH:
+ return state.set('sessionPath', action.path);
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 +270,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 +384,16 @@ export function setFunnelPage(funnelPage) {
}
}
+export function setTimelinePointer(pointer) {
+ return {
+ type: SET_TIMELINE_POINTER,
+ pointer
+ }
+}
+
+export function setSessionPath(path) {
+ return {
+ type: SET_SESSION_PATH,
+ path
+ }
+}
\ No newline at end of file
diff --git a/frontend/app/player/MessageDistributor/MessageDistributor.ts b/frontend/app/player/MessageDistributor/MessageDistributor.ts
index c742c10b5..35ada8ce3 100644
--- a/frontend/app/player/MessageDistributor/MessageDistributor.ts
+++ b/frontend/app/player/MessageDistributor/MessageDistributor.ts
@@ -118,7 +118,7 @@ export default class MessageDistributor extends StatedScreen {
private navigationStartOffset: number = 0;
private lastMessageTime: number = 0;
- constructor(private readonly session: any /*Session*/, jwt: string, config) {
+ constructor(private readonly session: any /*Session*/, jwt: string, config, live: boolean) {
super();
this.pagesManager = new PagesManager(this, this.session.isMobile)
this.mouseManager = new MouseManager(this);
@@ -126,7 +126,7 @@ export default class MessageDistributor extends StatedScreen {
this.sessionStart = this.session.startedAt;
- if (this.session.live) {
+ if (live) {
// const sockUrl = `wss://live.openreplay.com/1/${ this.session.siteId }/${ this.session.sessionId }/${ jwt }`;
// this.subscribeOnMessages(sockUrl);
initListsDepr({})
diff --git a/frontend/app/player/singletone.js b/frontend/app/player/singletone.js
index 619f9b02b..9d811023e 100644
--- a/frontend/app/player/singletone.js
+++ b/frontend/app/player/singletone.js
@@ -28,11 +28,11 @@ document.addEventListener("visibilitychange", function() {
}
});
-export function init(session, jwt, config) {
- const live = session.live;
+export function init(session, jwt, config, live = false) {
+ // const live = session.live;
const endTime = !live && session.duration.valueOf();
- instance = new Player(session, jwt, config);
+ instance = new Player(session, jwt, config, live);
update({
initialized: true,
live,
diff --git a/frontend/app/svg/icons/os/fedora.svg b/frontend/app/svg/icons/os/fedora.svg
new file mode 100644
index 000000000..86e9f115e
--- /dev/null
+++ b/frontend/app/svg/icons/os/fedora.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/frontend/app/types/filter/filter.js b/frontend/app/types/filter/filter.js
index 42637a2fa..8b3301aad 100644
--- a/frontend/app/types/filter/filter.js
+++ b/frontend/app/types/filter/filter.js
@@ -34,8 +34,8 @@ export default Record({
startDate,
endDate,
- sort: undefined,
- order: undefined,
+ sort: 'startTs',
+ order: 'desc',
viewed: undefined,
consoleLogCount: undefined,
diff --git a/frontend/app/types/session/session.js b/frontend/app/types/session/session.js
index 1fabc79a6..61ca9e489 100644
--- a/frontend/app/types/session/session.js
+++ b/frontend/app/types/session/session.js
@@ -88,6 +88,7 @@ export default Record({
...session
}) => {
const duration = Duration.fromMillis(session.duration < 1000 ? 1000 : session.duration);
+ const durationSeconds = duration.valueOf();
const startedAt = +startTs;
const userDevice = session.userDevice || session.userDeviceType || 'Other';
@@ -96,7 +97,7 @@ export default Record({
const events = List(session.events)
.map(e => SessionEvent({ ...e, time: e.timestamp - startedAt }))
- .filter(({ type }) => type !== TYPES.CONSOLE);
+ .filter(({ type, time }) => type !== TYPES.CONSOLE && time <= durationSeconds);
let resources = List(session.resources)
.map(Resource);