change(ui) - offline vs live session
This commit is contained in:
parent
7c4b410a35
commit
9cc5542fc7
13 changed files with 99 additions and 48 deletions
|
|
@ -1,18 +1,29 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { SlideModal } from 'UI';
|
||||
import { SlideModal, Icon } from 'UI';
|
||||
import SessionList from '../SessionList';
|
||||
import stl from './assistTabs.css'
|
||||
|
||||
interface Props {
|
||||
userId: any,
|
||||
}
|
||||
|
||||
const AssistTabs = React.memo((props: Props) => {
|
||||
const AssistTabs = (props: Props) => {
|
||||
const [showMenu, setShowMenu] = useState(false)
|
||||
|
||||
return (
|
||||
<div className="relative mr-4">
|
||||
<div className="p-2 cursor-pointer" onClick={() => setShowMenu(!showMenu)}>
|
||||
Live Sessions
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
className={stl.btnLink}
|
||||
onClick={() => setShowMenu(!showMenu)}
|
||||
>
|
||||
More Live Sessions
|
||||
</div>
|
||||
<span className="mx-3 color-gray-medium">by</span>
|
||||
<div className="flex items-center">
|
||||
<Icon name="user-alt" color="gray-darkest" />
|
||||
<div className="ml-2">{props.userId}</div>
|
||||
</div>
|
||||
</div>
|
||||
<SlideModal
|
||||
title={ <div>Live Sessions by {props.userId}</div> }
|
||||
|
|
@ -22,6 +33,6 @@ const AssistTabs = React.memo((props: Props) => {
|
|||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export default AssistTabs;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
.btnLink {
|
||||
cursor: pointer;
|
||||
color: $green;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
|
@ -31,10 +31,10 @@ const InitLoader = connectPlayer(state => ({
|
|||
}))(Loader);
|
||||
|
||||
|
||||
const WebPlayer = React.memo(({ showAssist, session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, loadingCredentials, assistCredendials, request }) => {
|
||||
function WebPlayer({ showAssist, session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, loadingCredentials, assistCredendials, request, hasSessionsPath }) {
|
||||
useEffect(() => {
|
||||
if (!loadingCredentials) {
|
||||
initPlayer(session, jwt, assistCredendials);
|
||||
initPlayer(session, jwt, assistCredendials, !hasSessionsPath && session.live);
|
||||
}
|
||||
return () => cleanPlayer()
|
||||
}, [ session.sessionId, loadingCredentials, assistCredendials ]);
|
||||
|
|
@ -60,7 +60,7 @@ const WebPlayer = React.memo(({ showAssist, session, toggleFullscreen, closeBott
|
|||
</InitLoader>
|
||||
</PlayerProvider>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export default withRequest({
|
||||
initialData: null,
|
||||
|
|
@ -74,6 +74,7 @@ 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'),
|
||||
}),
|
||||
{ toggleFullscreen, closeBottomBlock },
|
||||
)(WebPlayer)));
|
||||
|
|
@ -6,7 +6,6 @@ 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';
|
||||
|
|
@ -21,8 +20,7 @@ function Session({
|
|||
session,
|
||||
fetchSession,
|
||||
fetchSlackList,
|
||||
fetchLiveList,
|
||||
filters
|
||||
hasSessionsPath
|
||||
}) {
|
||||
usePageTitle("OpenReplay Session Player");
|
||||
useEffect(() => {
|
||||
|
|
@ -37,13 +35,7 @@ function Session({
|
|||
return () => {
|
||||
if (!session.exists()) return;
|
||||
}
|
||||
},[ sessionId ]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (session && session.live) {
|
||||
// fetchLiveList(filters.toJS())
|
||||
// }
|
||||
// }, [session])
|
||||
},[ sessionId, hasSessionsPath ]);
|
||||
|
||||
return (
|
||||
<NoContent
|
||||
|
|
@ -59,7 +51,7 @@ function Session({
|
|||
<Loader className="flex-1" loading={ loading || sessionId !== session.sessionId }>
|
||||
{ session.isIOS
|
||||
? <IOSPlayer session={session} />
|
||||
: (session.live ? <LivePlayer /> : <WebPlayer />)
|
||||
: (session.live && !hasSessionsPath ? <LivePlayer /> : <WebPlayer />)
|
||||
}
|
||||
</Loader>
|
||||
</NoContent>
|
||||
|
|
@ -73,10 +65,9 @@ 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' ]),
|
||||
hasSessionsPath: state.getIn([ 'sessions', 'sessionPath' ]).includes('/sessions'),
|
||||
};
|
||||
}, {
|
||||
fetchSession,
|
||||
fetchSlackList,
|
||||
fetchLiveList,
|
||||
})(Session));
|
||||
|
|
@ -4,14 +4,14 @@ import { browserIcon, osIcon, deviceTypeIcon } from 'App/iconNames';
|
|||
import { formatTimeOrDate } from 'App/date';
|
||||
import { sessions as sessionsRoute, funnel as funnelRoute, funnelIssue as funnelIssueRoute, withSiteId } from 'App/routes';
|
||||
import { Icon, CountryFlag, IconButton, BackLink } from 'UI';
|
||||
import { toggleFavorite } from 'Duck/sessions';
|
||||
import { toggleFavorite, setSessionPath } from 'Duck/sessions';
|
||||
import cn from 'classnames';
|
||||
import { connectPlayer } from 'Player';
|
||||
import HeaderInfo from './HeaderInfo';
|
||||
import SharePopup from '../shared/SharePopup/SharePopup';
|
||||
import { fetchList as fetchListIntegration } from 'Duck/integrations/actions';
|
||||
|
||||
import cls from './playerBlockHeader.css';
|
||||
import stl from './playerBlockHeader.css';
|
||||
import Issues from './Issues/Issues';
|
||||
import Autoplay from './Autoplay';
|
||||
import AssistActions from '../Assist/components/AssistActions';
|
||||
|
|
@ -38,8 +38,9 @@ function capitalise(str) {
|
|||
funnelRef: state.getIn(['funnels', 'navRef']),
|
||||
siteId: state.getIn([ 'user', 'siteId' ]),
|
||||
funnelPage: state.getIn(['sessions', 'funnelPage']),
|
||||
hasSessionsPath: state.getIn([ 'sessions', 'sessionPath' ]).includes('/sessions'),
|
||||
}), {
|
||||
toggleFavorite, fetchListIntegration
|
||||
toggleFavorite, fetchListIntegration, setSessionPath
|
||||
})
|
||||
@withRouter
|
||||
export default class PlayerBlockHeader extends React.PureComponent {
|
||||
|
|
@ -87,21 +88,24 @@ export default class PlayerBlockHeader extends React.PureComponent {
|
|||
userDevice,
|
||||
userBrowserVersion,
|
||||
userDeviceType,
|
||||
live,
|
||||
},
|
||||
loading,
|
||||
live,
|
||||
// live,
|
||||
disabled,
|
||||
jiraConfig,
|
||||
fullscreen,
|
||||
hasSessionsPath
|
||||
} = this.props;
|
||||
const { history, siteId } = this.props;
|
||||
// const { history, siteId } = this.props;
|
||||
const _live = live && !hasSessionsPath;
|
||||
|
||||
return (
|
||||
<div className={ cn(cls.header, "flex justify-between", { "hidden" : fullscreen}) }>
|
||||
<div className={ cn(stl.header, "flex justify-between", { "hidden" : fullscreen}) }>
|
||||
<div className="flex w-full">
|
||||
<BackLink onClick={this.backHandler} label="Back" />
|
||||
|
||||
<div className={ cls.divider } />
|
||||
<div className={ stl.divider } />
|
||||
|
||||
<div className="mx-4 flex items-center">
|
||||
<CountryFlag country={ userCountry } />
|
||||
|
|
@ -116,12 +120,17 @@ export default class PlayerBlockHeader extends React.PureComponent {
|
|||
<HeaderInfo icon={ osIcon(userOs) } label={ userOs } />
|
||||
|
||||
<div className='ml-auto flex items-center'>
|
||||
{ live && <AssistTabs userId={userId} />}
|
||||
{ live && <AssistActions isLive userId={userId} /> }
|
||||
{ !live && (
|
||||
{ live && hasSessionsPath && (
|
||||
<div className={stl.liveSwitchButton} onClick={() => this.props.setSessionPath('')}>
|
||||
This Session is Now Continuing Live
|
||||
</div>
|
||||
)}
|
||||
{ _live && <AssistTabs userId={userId} />}
|
||||
{ _live && <AssistActions isLive userId={userId} /> }
|
||||
{ !_live && (
|
||||
<>
|
||||
<Autoplay />
|
||||
<div className={ cls.divider } />
|
||||
<div className={ stl.divider } />
|
||||
<IconButton
|
||||
className="mr-2"
|
||||
tooltip="Bookmark"
|
||||
|
|
@ -145,7 +154,7 @@ export default class PlayerBlockHeader extends React.PureComponent {
|
|||
/>
|
||||
</>
|
||||
)}
|
||||
{ !live && jiraConfig && jiraConfig.token && <Issues sessionId={ sessionId } /> }
|
||||
{ !_live && jiraConfig && jiraConfig.token && <Issues sessionId={ sessionId } /> }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,3 +12,15 @@
|
|||
background-color: $gray-light;
|
||||
}
|
||||
|
||||
.liveSwitchButton {
|
||||
cursor: pointer;
|
||||
padding: 3px 8px;
|
||||
border: solid thin $green;
|
||||
color: $green;
|
||||
border-radius: 3px;
|
||||
margin-right: 10px;
|
||||
&:hover {
|
||||
background-color: $green;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
|
@ -10,21 +10,33 @@ import {
|
|||
TextEllipsis
|
||||
} from 'UI';
|
||||
import { deviceTypeIcon } from 'App/iconNames';
|
||||
import { toggleFavorite } from 'Duck/sessions';
|
||||
import { session as sessionRoute } from 'App/routes';
|
||||
import { toggleFavorite, setSessionPath } from 'Duck/sessions';
|
||||
import { session as sessionRoute, withSiteId } from 'App/routes';
|
||||
import { durationFormatted, formatTimeOrDate } from 'App/date';
|
||||
import stl from './sessionItem.css';
|
||||
import LiveTag from 'Shared/LiveTag';
|
||||
import Bookmark from 'Shared/Bookmark';
|
||||
import Counter from './Counter'
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
const Label = ({ label = '', color = 'color-gray-medium'}) => (
|
||||
<div className={ cn('font-light text-sm', color)}>{label}</div>
|
||||
)
|
||||
@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 {
|
|||
</div>
|
||||
|
||||
<div className={ stl.playLink } id="play-button" data-viewed={ viewed }>
|
||||
<Link to={ sessionRoute(sessionId) }>
|
||||
<div onClick={this.replaySession}>
|
||||
<Icon name={ viewed ? 'play-fill' : 'play-circle-light' } size="30" color="teal" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
transition: all 0.2s;
|
||||
/* opacity: 0; */
|
||||
cursor: pointer;
|
||||
&[data-viewed=true] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
@ -26,6 +25,7 @@ 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';
|
||||
|
||||
|
|
@ -59,6 +59,7 @@ const initialState = Map({
|
|||
host: '',
|
||||
funnelPage: Map(),
|
||||
timelinePointer: null,
|
||||
sessionPath: '',
|
||||
});
|
||||
|
||||
const reducer = (state = initialState, action = {}) => {
|
||||
|
|
@ -246,6 +247,8 @@ const reducer = (state = initialState, action = {}) => {
|
|||
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;
|
||||
}
|
||||
|
|
@ -386,4 +389,11 @@ export function setTimelinePointer(pointer) {
|
|||
type: SET_TIMELINE_POINTER,
|
||||
pointer
|
||||
}
|
||||
}
|
||||
|
||||
export function setSessionPath(path) {
|
||||
return {
|
||||
type: SET_SESSION_PATH,
|
||||
path
|
||||
}
|
||||
}
|
||||
|
|
@ -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({})
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue