change(ui): rewrite session type
This commit is contained in:
parent
3a30f0cd1d
commit
b2b1caf98e
34 changed files with 465 additions and 326 deletions
|
|
@ -82,5 +82,5 @@ function RequestingWindow({ userDisplayName, getWindowType }: Props) {
|
|||
}
|
||||
|
||||
export default connect((state: any) => ({
|
||||
userDisplayName: state.getIn(['sessions', 'current', 'userDisplayName']),
|
||||
userDisplayName: state.getIn(['sessions', 'current']).userDisplayName,
|
||||
}))(RequestingWindow);
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ const con = connect(
|
|||
return {
|
||||
hasPermission: permissions.includes('ASSIST_CALL'),
|
||||
isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee',
|
||||
userDisplayName: state.getIn(['sessions', 'current', 'userDisplayName']),
|
||||
userDisplayName: state.getIn(['sessions', 'current']).userDisplayName,
|
||||
};
|
||||
},
|
||||
{ toggleChatWindow }
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ function PlayerContent({ session, live, fullscreen, activeTab, setActiveTab }) {
|
|||
|
||||
const hasError = !!error
|
||||
|
||||
console.log(session, 'hi')
|
||||
const sessionDays = countDaysFrom(session.startedAt);
|
||||
return (
|
||||
<div className="relative">
|
||||
|
|
|
|||
|
|
@ -74,8 +74,9 @@ function WebPlayer(props: any) {
|
|||
contextValue.player.togglePlay();
|
||||
};
|
||||
|
||||
if (!contextValue.player) return null;
|
||||
if (!contextValue.player || !session) return null;
|
||||
|
||||
console.log(session)
|
||||
return (
|
||||
<PlayerContext.Provider value={contextValue}>
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { connect } from 'react-redux';
|
|||
import MetadataItem from './MetadataItem';
|
||||
|
||||
export default connect(state => ({
|
||||
metadata: state.getIn([ 'sessions', 'current', 'metadata' ]),
|
||||
metadata: state.getIn([ 'sessions', 'current' ]).metadata,
|
||||
}))(function Metadata ({ metadata }) {
|
||||
|
||||
const metaLenth = Object.keys(metadata).length;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import stl from './sessionList.module.css';
|
|||
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
|
||||
|
||||
@connect((state) => ({
|
||||
currentSessionId: state.getIn(['sessions', 'current', 'sessionId']),
|
||||
currentSessionId: state.getIn(['sessions', 'current']).sessionId,
|
||||
}))
|
||||
class SessionList extends React.PureComponent {
|
||||
render() {
|
||||
|
|
@ -17,7 +17,7 @@ class SessionList extends React.PureComponent {
|
|||
.map(({ sessions, ...rest }) => {
|
||||
return {
|
||||
...rest,
|
||||
sessions: sessions.map(Session).filter(({ sessionId }) => sessionId !== currentSessionId),
|
||||
sessions: sessions.map(s => new Session(s)).filter(({ sessionId }) => sessionId !== currentSessionId),
|
||||
};
|
||||
})
|
||||
.filter((site) => site.sessions.length > 0);
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ function OverviewPanel({ issuesList }: { issuesList: Record<string, any>[] }) {
|
|||
|
||||
export default connect(
|
||||
(state: any) => ({
|
||||
issuesList: state.getIn(['sessions', 'current', 'issues']),
|
||||
issuesList: state.getIn(['sessions', 'current']).issues,
|
||||
}),
|
||||
{
|
||||
toggleBottomBlock,
|
||||
|
|
|
|||
|
|
@ -492,5 +492,5 @@ function Performance({
|
|||
}
|
||||
|
||||
export const ConnectedPerformance = connect((state: any) => ({
|
||||
userDeviceHeapSize: state.getIn(['sessions', 'current', 'userDeviceHeapSize']),
|
||||
userDeviceHeapSize: state.getIn(['sessions', 'current']).userDeviceHeapSize,
|
||||
}))(observer(Performance));
|
||||
|
|
|
|||
|
|
@ -386,7 +386,7 @@ export default connect(
|
|||
session: state.getIn(['sessions', 'current']),
|
||||
totalAssistSessions: state.getIn(['liveSearch', 'total']),
|
||||
closedLive:
|
||||
!!state.getIn(['sessions', 'errors']) || !state.getIn(['sessions', 'current', 'live']),
|
||||
!!state.getIn(['sessions', 'errors']) || !state.getIn(['sessions', 'current']).live,
|
||||
skipInterval: state.getIn(['components', 'player', 'skipInterval']),
|
||||
};
|
||||
},
|
||||
|
|
|
|||
|
|
@ -241,8 +241,8 @@ function Timeline(props: IProps) {
|
|||
|
||||
export default connect(
|
||||
(state: any) => ({
|
||||
issues: state.getIn(['sessions', 'current', 'issues']),
|
||||
startedAt: state.getIn(['sessions', 'current', 'startedAt']),
|
||||
issues: state.getIn(['sessions', 'current']).issues || [],
|
||||
startedAt: state.getIn(['sessions', 'current']).startedAt || 0,
|
||||
tooltipVisible: state.getIn(['sessions', 'timeLineTooltip', 'isVisible']),
|
||||
}),
|
||||
{ setTimelinePointer, setTimelineHoverTime }
|
||||
|
|
|
|||
|
|
@ -298,7 +298,7 @@ export default connect(
|
|||
} = state.getIn(['sessions', 'createNoteTooltip']);
|
||||
const slackChannels = state.getIn(['slack', 'list']);
|
||||
const teamsChannels = state.getIn(['teams', 'list']);
|
||||
const sessionId = state.getIn(['sessions', 'current', 'sessionId']);
|
||||
const sessionId = state.getIn(['sessions', 'current']).sessionId;
|
||||
return { isVisible, time, sessionId, isEdit, editNote, slackChannels, teamsChannels };
|
||||
},
|
||||
{ setCreateNoteTooltip, addNote, updateNote, fetchSlack, fetchTeams }
|
||||
|
|
|
|||
|
|
@ -107,11 +107,11 @@ export default connect((state) => {
|
|||
return {
|
||||
fullscreen: state.getIn(['components', 'player', 'fullscreen']),
|
||||
nextId: state.getIn(['sessions', 'nextId']),
|
||||
sessionId: state.getIn(['sessions', 'current', 'sessionId']),
|
||||
sessionId: state.getIn(['sessions', 'current']).sessionId,
|
||||
bottomBlock: state.getIn(['components', 'player', 'bottomBlock']),
|
||||
closedLive:
|
||||
!!state.getIn(['sessions', 'errors']) ||
|
||||
(isAssist && !state.getIn(['sessions', 'current', 'live'])),
|
||||
(isAssist && !state.getIn(['sessions', 'current']).live),
|
||||
};
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import styles from './playerBlock.module.css';
|
|||
|
||||
@connect((state) => ({
|
||||
fullscreen: state.getIn(['components', 'player', 'fullscreen']),
|
||||
sessionId: state.getIn(['sessions', 'current', 'sessionId']),
|
||||
sessionId: state.getIn(['sessions', 'current']).sessionId,
|
||||
disabled: state.getIn(['components', 'targetDefiner', 'inspectorMode']),
|
||||
jiraConfig: state.getIn(['issues', 'list']).first(),
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -120,5 +120,5 @@ function ScreenRecorder({
|
|||
|
||||
export default connect((state: any) => ({
|
||||
siteId: state.getIn(['site', 'siteId']),
|
||||
sessionId: state.getIn(['sessions', 'current', 'sessionId']),
|
||||
sessionId: state.getIn(['sessions', 'current']).sessionId,
|
||||
}))(observer(ScreenRecorder))
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ function Bookmark(props: Props) {
|
|||
export default connect(
|
||||
(state: any) => ({
|
||||
isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee',
|
||||
favorite: state.getIn(['sessions', 'current', 'favorite']),
|
||||
favorite: state.getIn(['sessions', 'current']).favorite,
|
||||
}),
|
||||
{ toggleFavorite }
|
||||
)(Bookmark);
|
||||
|
|
|
|||
|
|
@ -384,5 +384,5 @@ function NetworkPanel({ startedAt }: { startedAt: number }) {
|
|||
}
|
||||
|
||||
export default connect((state: any) => ({
|
||||
startedAt: state.getIn(['sessions', 'current', 'startedAt']),
|
||||
startedAt: state.getIn(['sessions', 'current']).startedAt,
|
||||
}))(observer(NetworkPanel));
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ interface Props {
|
|||
userNumericHash: number;
|
||||
live: boolean;
|
||||
metadata: Record<string, any>;
|
||||
userSessionsCount: number;
|
||||
issueTypes: [];
|
||||
active: boolean;
|
||||
isCallActive?: boolean;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { fetchList as fetchTeams, sendMsTeamsMsg } from 'Duck/integrations/teams
|
|||
|
||||
@connect(
|
||||
(state) => ({
|
||||
sessionId: state.getIn(['sessions', 'current', 'sessionId']),
|
||||
sessionId: state.getIn(['sessions', 'current']).sessionId,
|
||||
channels: state.getIn(['slack', 'list']),
|
||||
msTeamsChannels: state.getIn(['teams', 'list']),
|
||||
tenantId: state.getIn(['user', 'account', 'tenantId']),
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ export default connect(
|
|||
(state: any) => ({
|
||||
// errorStack: state.getIn(['sessions', 'errorStack']),
|
||||
errorStack: state.getIn(['errors', 'instanceTrace']),
|
||||
sessionId: state.getIn(['sessions', 'current', 'sessionId']),
|
||||
sessionId: state.getIn(['sessions', 'current']).sessionId,
|
||||
}),
|
||||
{ fetchErrorStackList }
|
||||
)(ErrorDetails);
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ function reducer(state = initialState, action = {}) {
|
|||
const { data } = action;
|
||||
return state.set("list", List(data.map(CustomMetric)));
|
||||
case success(FETCH_SESSION_LIST):
|
||||
return state.set("sessionList", List(action.data.map(item => ({ ...item, sessions: item.sessions.map(Session) }))));
|
||||
return state.set("sessionList", List(action.data.map(item => ({ ...item, sessions: item.sessions.map(s => new Session(s)) }))));
|
||||
case SET_ACTIVE_WIDGET:
|
||||
return state.set("activeWidget", action.widget).set('sessionList', List());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ const initialState = Map({
|
|||
|
||||
function reducer(state = initialState, action = {}) {
|
||||
let updError;
|
||||
console.log(action)
|
||||
switch (action.type) {
|
||||
case EDIT_OPTIONS:
|
||||
return state.mergeIn(["options"], action.instance).set('currentPage', 1);
|
||||
|
|
|
|||
|
|
@ -99,12 +99,12 @@ const reducer = (state = initialState, action = {}) => {
|
|||
.set('criticalIssuesCount', action.data.issues.criticalIssuesCount)
|
||||
case FETCH_SESSIONS_SUCCESS:
|
||||
return state
|
||||
.set('sessions', List(action.data.sessions).map(Session))
|
||||
.set('sessions', List(action.data.sessions).map(s => new Session(s)))
|
||||
.set('total', action.data.total)
|
||||
case FETCH_ISSUE_SUCCESS:
|
||||
return state
|
||||
.set('issue', FunnelIssue(action.data.issue))
|
||||
.set('sessions', List(action.data.sessions.sessions).map(Session))
|
||||
.set('sessions', List(action.data.sessions.sessions).map(s => new Session(s)))
|
||||
.set('sessionsTotal', action.data.sessions.total)
|
||||
case RESET_ISSUE:
|
||||
return state.set('isses', FunnelIssue())
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ function reducer(state = initialState, action = {}) {
|
|||
return state.set('currentPage', action.page);
|
||||
case success(FETCH_SESSION_LIST):
|
||||
const { sessions, total } = action.data;
|
||||
const list = List(sessions).map(Session);
|
||||
const list = List(sessions).map(s => new Session(s));
|
||||
return state
|
||||
.set('list', list)
|
||||
.set('total', total);
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ const defaultDateFilters = {
|
|||
const initialState = Map({
|
||||
list: List(),
|
||||
sessionIds: [],
|
||||
current: Session(),
|
||||
current: new Session(),
|
||||
total: 0,
|
||||
keyMap: Map(),
|
||||
wdTypeCount: Map(),
|
||||
|
|
@ -127,8 +127,8 @@ const reducer = (state = initialState, action = {}) => {
|
|||
// TODO: more common.. or TEMP
|
||||
const events = action.filter.events;
|
||||
// const filters = action.filter.filters;
|
||||
const current = state.get('list').find(({ sessionId }) => sessionId === action.data.sessionId) || Session();
|
||||
const session = Session(action.data);
|
||||
const current = state.get('list').find(({ sessionId }) => sessionId === action.data.sessionId) || new Session();
|
||||
const session = new Session(action.data);
|
||||
|
||||
const matching = [];
|
||||
|
||||
|
|
@ -155,7 +155,7 @@ const reducer = (state = initialState, action = {}) => {
|
|||
});
|
||||
});
|
||||
return state
|
||||
.set('current', current.merge(session))
|
||||
.set('current', session)
|
||||
.set('eventsIndex', matching)
|
||||
.set('visitedEvents', visitedEvents)
|
||||
.set('host', visitedEvents[0] && visitedEvents[0].host);
|
||||
|
|
@ -167,11 +167,15 @@ const reducer = (state = initialState, action = {}) => {
|
|||
const session = state.get('list').find(({ sessionId }) => sessionId === id);
|
||||
const wasInFavorite = state.get('favoriteList').findIndex(({ sessionId }) => sessionId === id) > -1;
|
||||
|
||||
if (session && !wasInFavorite) {
|
||||
session.favorite = true
|
||||
}
|
||||
return state
|
||||
.update('current', (currentSession) => (currentSession.sessionId === id ? currentSession.set('favorite', !wasInFavorite) : currentSession))
|
||||
.update('list', (list) => list.map((listSession) => (listSession.sessionId === id ? listSession.set('favorite', !wasInFavorite) : listSession)))
|
||||
// TODO returns bool here???
|
||||
.update('current', (currentSession) => (currentSession.sessionId === id ? (currentSession.favorite = !wasInFavorite) : currentSession))
|
||||
.update('list', (list) => list.map((listSession) => (listSession.sessionId === id ? (listSession.favorite = !wasInFavorite) : listSession)))
|
||||
.update('favoriteList', (list) => session ?
|
||||
wasInFavorite ? list.filter(({ sessionId }) => sessionId !== id) : list.push(session.set('favorite', true)) : list
|
||||
wasInFavorite ? list.filter(({ sessionId }) => sessionId !== id) : list.push(session) : list
|
||||
);
|
||||
}
|
||||
case SORT: {
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ export default class AssistMultiviewStore {
|
|||
const matchingSessions = data.sessions.filter(
|
||||
(s: Record<string, any>) => ids.includes(s.sessionID) || ids.includes(s.sessionId)
|
||||
);
|
||||
const immutMatchingSessions = List(matchingSessions).map(Session);
|
||||
const immutMatchingSessions = List(matchingSessions).map(s => new Session(s));
|
||||
immutMatchingSessions.forEach((session: Record<string, any>) => {
|
||||
this.addSession(session);
|
||||
this.fetchAgentTokenInfo(session.sessionId);
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@ export default class WebPlayer extends Player {
|
|||
private targetMarker: TargetMarker
|
||||
|
||||
constructor(protected wpState: Store<typeof WebPlayer.INITIAL_STATE>, session: any, live: boolean) {
|
||||
console.log(session.events, session.resources, session.errors)
|
||||
console.log(session.events, session.stackEvents, session.resources, session.errors)
|
||||
let initialLists = live ? {} : {
|
||||
event: session.events.toJSON(),
|
||||
stack: session.stackEvents.toJSON(),
|
||||
event: session.events,
|
||||
stack: session.stackEvents,
|
||||
resource: session.resources.toJSON(), // MBTODO: put ResourceTiming in file
|
||||
exceptions: session.errors.map(({ time, errorId, name }: any) =>
|
||||
Log({
|
||||
|
|
|
|||
|
|
@ -97,14 +97,14 @@ export const WIDGET_LIST = [{
|
|||
name: "Recent Frustrations",
|
||||
description: "List of recent sessions where users experienced some kind of frustrations, such as click rage.",
|
||||
thumb: 'recent_frustrations.png',
|
||||
dataWrapper: list => List(list).map(Session),
|
||||
dataWrapper: list => List(list).map(s => new Session(s)),
|
||||
},
|
||||
{
|
||||
key: "sessionsFeedback",
|
||||
name: "Recent Negative Feedback",
|
||||
description: "List of recent sessions where users reported an issue or a bad experience.",
|
||||
thumb: 'negative_feedback.png',
|
||||
dataWrapper: list => List(list).map(Session),
|
||||
dataWrapper: list => List(list).map(s => new Session(s)),
|
||||
},
|
||||
{
|
||||
key: "missingResources",
|
||||
|
|
|
|||
|
|
@ -37,12 +37,12 @@ const ErrorInfo = Record({
|
|||
chart30: [],
|
||||
tags: [],
|
||||
customTags: [],
|
||||
lastHydratedSession: Session(),
|
||||
lastHydratedSession: new Session(),
|
||||
disabled: false,
|
||||
}, {
|
||||
fromJS: ({ stack, lastHydratedSession, ...other }) => ({
|
||||
...other,
|
||||
lastHydratedSession: Session(lastHydratedSession),
|
||||
lastHydratedSession: new Session(lastHydratedSession),
|
||||
stack0InfoString: getStck0InfoString(stack || []),
|
||||
})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
import Record from 'Types/Record';
|
||||
|
||||
export default Record({
|
||||
id: undefined,
|
||||
avatarUrls: undefined,
|
||||
name: undefined,
|
||||
}, {
|
||||
fromJS: author => ({
|
||||
...author,
|
||||
})
|
||||
})
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
import Record from 'Types/Record';
|
||||
import Target from 'Types/target';
|
||||
|
||||
const CONSOLE = 'CONSOLE';
|
||||
const CLICK = 'CLICK';
|
||||
const INPUT = 'INPUT';
|
||||
const LOCATION = 'LOCATION';
|
||||
const CUSTOM = 'CUSTOM';
|
||||
const CLICKRAGE = 'CLICKRAGE';
|
||||
const IOS_VIEW = 'VIEW';
|
||||
export const TYPES = { CONSOLE, CLICK, INPUT, LOCATION, CUSTOM, CLICKRAGE, IOS_VIEW};
|
||||
|
||||
|
||||
const Event = Record({
|
||||
time: 0,
|
||||
label: ''
|
||||
}, {
|
||||
fromJS: event => ({
|
||||
...event,
|
||||
target: Target(event.target || { path: event.targetPath }),
|
||||
})
|
||||
})
|
||||
|
||||
const Console = Event.extend({
|
||||
type: CONSOLE,
|
||||
subtype: '', // level ???
|
||||
value: '',
|
||||
},{
|
||||
name: 'Console'
|
||||
})
|
||||
|
||||
const Click = Event.extend({
|
||||
type: CLICK,
|
||||
targetContent: '',
|
||||
target: Target(),
|
||||
count: undefined
|
||||
}, {
|
||||
name: 'Click'
|
||||
});
|
||||
|
||||
const Input = Event.extend({
|
||||
type: INPUT,
|
||||
target: Target(),
|
||||
value: '',
|
||||
},{
|
||||
name: 'Input'
|
||||
});
|
||||
|
||||
const View = Event.extend({
|
||||
type: IOS_VIEW,
|
||||
name: '',
|
||||
},{
|
||||
name: 'View'
|
||||
})
|
||||
|
||||
const Location = Event.extend({
|
||||
type: LOCATION,
|
||||
url: '',
|
||||
host: '',
|
||||
pageLoad: false,
|
||||
fcpTime: undefined,
|
||||
//fpTime: undefined,
|
||||
loadTime: undefined,
|
||||
domContentLoadedTime: undefined,
|
||||
domBuildingTime: undefined,
|
||||
speedIndex: undefined,
|
||||
visuallyComplete: undefined,
|
||||
timeToInteractive: undefined,
|
||||
referrer: '',
|
||||
}, {
|
||||
fromJS: event => ({
|
||||
...event,
|
||||
//fpTime: event.firstPaintTime,
|
||||
fcpTime: event.firstContentfulPaintTime || event.firstPaintTime,
|
||||
}),
|
||||
name: 'Location'
|
||||
});
|
||||
|
||||
const TYPE_CONSTRUCTOR_MAP = {
|
||||
[CONSOLE]: Console,
|
||||
[CLICK]: Click,
|
||||
[INPUT]: Input,
|
||||
[LOCATION]: Location,
|
||||
[CLICKRAGE]: Click,
|
||||
[IOS_VIEW]: View,
|
||||
}
|
||||
|
||||
export default function(event = {}) {
|
||||
return (TYPE_CONSTRUCTOR_MAP[event.type] || Event)(event);
|
||||
}
|
||||
|
||||
162
frontend/app/types/session/event.ts
Normal file
162
frontend/app/types/session/event.ts
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
import Record from 'Types/Record';
|
||||
import Target from 'Types/target';
|
||||
|
||||
const CONSOLE = 'CONSOLE';
|
||||
const CLICK = 'CLICK';
|
||||
const INPUT = 'INPUT';
|
||||
const LOCATION = 'LOCATION';
|
||||
const CUSTOM = 'CUSTOM';
|
||||
const CLICKRAGE = 'CLICKRAGE';
|
||||
const IOS_VIEW = 'VIEW';
|
||||
export const TYPES = { CONSOLE, CLICK, INPUT, LOCATION, CUSTOM, CLICKRAGE, IOS_VIEW};
|
||||
|
||||
interface IEvent {
|
||||
time: number;
|
||||
timestamp: number;
|
||||
type: typeof CONSOLE | typeof CLICK | typeof INPUT | typeof LOCATION | typeof CUSTOM | typeof CLICKRAGE;
|
||||
name: string;
|
||||
key: number;
|
||||
label: string;
|
||||
targetPath: string;
|
||||
target: {
|
||||
path: string;
|
||||
label: string;
|
||||
}
|
||||
}
|
||||
interface ConsoleEvent extends IEvent {
|
||||
subtype: string
|
||||
value: string
|
||||
}
|
||||
interface ClickEvent extends IEvent {
|
||||
targetContent: string;
|
||||
count: number;
|
||||
}
|
||||
interface InputEvent extends IEvent {
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface LocationEvent extends IEvent {
|
||||
url: string;
|
||||
host: string;
|
||||
pageLoad: boolean;
|
||||
fcpTime: number;
|
||||
loadTime: number;
|
||||
domContentLoadedTime: number;
|
||||
domBuildingTime: number;
|
||||
speedIndex: number;
|
||||
visuallyComplete: number;
|
||||
timeToInteractive: number;
|
||||
referrer: string;
|
||||
firstContentfulPaintTime: number;
|
||||
firstPaintTime: number;
|
||||
}
|
||||
|
||||
export type EventData = ConsoleEvent | ClickEvent | InputEvent | LocationEvent | IEvent;
|
||||
|
||||
class Event {
|
||||
key: IEvent["key"]
|
||||
time: IEvent["time"];
|
||||
label: IEvent["label"];
|
||||
target: IEvent["target"];
|
||||
|
||||
|
||||
constructor(event: IEvent) {
|
||||
Object.assign(this, {
|
||||
time: event.time,
|
||||
label: event.label,
|
||||
key: event.key,
|
||||
target: {
|
||||
path: event.target?.path || event.targetPath,
|
||||
label: event.target?.label
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class Console extends Event {
|
||||
readonly type = CONSOLE;
|
||||
readonly name = 'Console'
|
||||
subtype: string;
|
||||
value: string;
|
||||
|
||||
constructor(evt: ConsoleEvent) {
|
||||
super(evt);
|
||||
this.subtype = evt.subtype
|
||||
this.value = evt.value
|
||||
}
|
||||
}
|
||||
|
||||
class Click extends Event {
|
||||
readonly type = CLICK;
|
||||
readonly name = 'Click'
|
||||
targetContent = '';
|
||||
count: number
|
||||
|
||||
constructor(evt: ClickEvent) {
|
||||
super(evt);
|
||||
this.targetContent = evt.targetContent
|
||||
this.count = evt.count
|
||||
}
|
||||
}
|
||||
|
||||
class Input extends Event {
|
||||
readonly type = INPUT;
|
||||
readonly name = 'Input'
|
||||
value = ''
|
||||
|
||||
constructor(evt: InputEvent) {
|
||||
super(evt);
|
||||
this.value = evt.value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Location extends Event {
|
||||
readonly name = 'Location';
|
||||
readonly type = LOCATION;
|
||||
url: LocationEvent["url"]
|
||||
host: LocationEvent["host"];
|
||||
pageLoad: LocationEvent["pageLoad"];
|
||||
fcpTime: LocationEvent["fcpTime"];
|
||||
loadTime: LocationEvent["loadTime"];
|
||||
domContentLoadedTime: LocationEvent["domContentLoadedTime"];
|
||||
domBuildingTime: LocationEvent["domBuildingTime"];
|
||||
speedIndex: LocationEvent["speedIndex"];
|
||||
visuallyComplete: LocationEvent["visuallyComplete"];
|
||||
timeToInteractive: LocationEvent["timeToInteractive"];
|
||||
referrer: LocationEvent["referrer"];
|
||||
|
||||
constructor(evt: LocationEvent) {
|
||||
super(evt);
|
||||
Object.assign(this, {
|
||||
...evt,
|
||||
fcpTime: evt.firstContentfulPaintTime || evt.firstPaintTime
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export type InjectedEvent = Console | Click | Input | Location;
|
||||
|
||||
export default function(event: EventData) {
|
||||
if (event.type && event.type === CONSOLE) {
|
||||
return new Console(event as ConsoleEvent)
|
||||
}
|
||||
if (event.type && event.type === CLICK) {
|
||||
return new Click(event as ClickEvent)
|
||||
}
|
||||
if (event.type && event.type === INPUT) {
|
||||
return new Input(event as InputEvent)
|
||||
}
|
||||
if (event.type && event.type === LOCATION) {
|
||||
return new Location(event as LocationEvent)
|
||||
}
|
||||
if (event.type && event.type === CLICKRAGE) {
|
||||
return new Click(event as ClickEvent)
|
||||
}
|
||||
// not used right now?
|
||||
// if (event.type === CUSTOM || !event.type) {
|
||||
// return new Event(event)
|
||||
// }
|
||||
console.error(`Unknown event type: ${event.type}`)
|
||||
}
|
||||
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
import { List } from 'immutable';
|
||||
import Record from 'Types/Record';
|
||||
|
||||
export default Record({
|
||||
name: '',
|
||||
args: List(),
|
||||
result: undefined,
|
||||
time: undefined,
|
||||
index: undefined,
|
||||
duration: undefined,
|
||||
}, {
|
||||
fromJS: ({ start_time, end_time, args, ...profile }) => ({
|
||||
...profile,
|
||||
args: List(args),
|
||||
time: Math.round(start_time),
|
||||
duration: Math.round(end_time - start_time || 0),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import Record from 'Types/Record';
|
||||
import { List, Map } from 'immutable';
|
||||
import { List } from 'immutable';
|
||||
import { Duration } from 'luxon';
|
||||
import SessionEvent, { TYPES } from './event';
|
||||
import SessionEvent, { TYPES, EventData, InjectedEvent } from './event';
|
||||
import StackEvent from './stackEvent';
|
||||
import Resource from './resource';
|
||||
import SessionError, { IError } from './error';
|
||||
import Issue, { IIssue } from './issue';
|
||||
import { Note } from 'App/services/NotesService'
|
||||
|
||||
const HASH_MOD = 1610612741;
|
||||
const HASH_P = 53;
|
||||
|
|
@ -19,163 +19,247 @@ function hashString(s: string): number {
|
|||
return hash;
|
||||
}
|
||||
|
||||
export default Record(
|
||||
{
|
||||
sessionId: '',
|
||||
pageTitle: '',
|
||||
active: false,
|
||||
siteId: '',
|
||||
projectKey: '',
|
||||
peerId: '',
|
||||
live: false,
|
||||
startedAt: 0,
|
||||
duration: 0,
|
||||
events: List(),
|
||||
stackEvents: List(),
|
||||
resources: [],
|
||||
missedResources: [],
|
||||
metadata: Map(),
|
||||
favorite: false,
|
||||
filterId: '',
|
||||
messagesUrl: '',
|
||||
domURL: [],
|
||||
devtoolsURL: [],
|
||||
mobsUrl: [], // @depricated
|
||||
userBrowser: '',
|
||||
userBrowserVersion: '?',
|
||||
userCountry: '',
|
||||
userDevice: '',
|
||||
userDeviceType: '',
|
||||
isMobile: false,
|
||||
userOs: '',
|
||||
userOsVersion: '',
|
||||
userId: '',
|
||||
userAnonymousId: '',
|
||||
userUuid: undefined,
|
||||
userDisplayName: '',
|
||||
userNumericHash: 0,
|
||||
viewed: false,
|
||||
consoleLogCount: '?',
|
||||
eventsCount: '?',
|
||||
pagesCount: '?',
|
||||
clickRage: undefined,
|
||||
clickRageTime: undefined,
|
||||
resourcesScore: 0,
|
||||
consoleError: undefined,
|
||||
resourceError: undefined,
|
||||
returningLocation: undefined,
|
||||
returningLocationTime: undefined,
|
||||
errorsCount: 0,
|
||||
issueTypes: [],
|
||||
issues: [],
|
||||
userDeviceHeapSize: 0,
|
||||
userDeviceMemorySize: 0,
|
||||
errors: List(),
|
||||
crashes: [],
|
||||
socket: null,
|
||||
isIOS: false,
|
||||
revId: '',
|
||||
userSessionsCount: 0,
|
||||
agentIds: [],
|
||||
isCallActive: false,
|
||||
agentToken: '',
|
||||
notes: [],
|
||||
notesWithEvents: [],
|
||||
fileKey: '',
|
||||
},
|
||||
{
|
||||
fromJS: ({
|
||||
export interface ISession {
|
||||
sessionId: string,
|
||||
pageTitle: string,
|
||||
active: boolean,
|
||||
siteId: string,
|
||||
projectKey: string,
|
||||
peerId: string,
|
||||
live: boolean,
|
||||
startedAt: number,
|
||||
duration: number,
|
||||
events: InjectedEvent[],
|
||||
stackEvents: StackEvent[],
|
||||
resources: Resource[],
|
||||
missedResources: Resource[],
|
||||
metadata: [],
|
||||
favorite: boolean,
|
||||
filterId?: string,
|
||||
domURL: string[],
|
||||
devtoolsURL: string[],
|
||||
mobsUrl: string[], // @depricated
|
||||
userBrowser: string,
|
||||
userBrowserVersion: string,
|
||||
userCountry: string,
|
||||
userDevice: string,
|
||||
userDeviceType: string,
|
||||
isMobile: boolean,
|
||||
userOs: string,
|
||||
userOsVersion: string,
|
||||
userId: string,
|
||||
userAnonymousId: string,
|
||||
userUuid: string,
|
||||
userDisplayName: string,
|
||||
userNumericHash: number,
|
||||
viewed: boolean,
|
||||
consoleLogCount: number,
|
||||
eventsCount: number,
|
||||
pagesCount: number,
|
||||
errorsCount: number,
|
||||
issueTypes: [],
|
||||
issues: [],
|
||||
referrer: string | null,
|
||||
userDeviceHeapSize: number,
|
||||
userDeviceMemorySize: number,
|
||||
errors: SessionError[],
|
||||
crashes?: [],
|
||||
socket: string,
|
||||
isIOS: boolean,
|
||||
revId: string | null,
|
||||
agentIds?: string[],
|
||||
isCallActive?: boolean,
|
||||
agentToken: string,
|
||||
notes: Note[],
|
||||
notesWithEvents: Note[] | InjectedEvent[],
|
||||
fileKey: string,
|
||||
platform: string,
|
||||
projectId: string,
|
||||
startTs: number,
|
||||
timestamp: number,
|
||||
backendErrors: number,
|
||||
consoleErrors: number,
|
||||
sessionID?: string,
|
||||
userID: string,
|
||||
userUUID: string,
|
||||
userEvents: any[],
|
||||
}
|
||||
|
||||
const emptyValues = {
|
||||
startTs: 0,
|
||||
timestamp: 0,
|
||||
backendErrors: 0,
|
||||
consoleErrors: 0,
|
||||
sessionID: '',
|
||||
projectId: '',
|
||||
errors: [],
|
||||
stackEvents: [],
|
||||
issues: [],
|
||||
sessionId: '',
|
||||
domURL: [],
|
||||
devtoolsURL: [],
|
||||
mobsUrl: [],
|
||||
notes: [],
|
||||
metadata: {},
|
||||
startedAt: 0,
|
||||
}
|
||||
export default class Session {
|
||||
sessionId: ISession["sessionId"]
|
||||
pageTitle: ISession["pageTitle"]
|
||||
active: ISession["active"]
|
||||
siteId: ISession["siteId"]
|
||||
projectKey: ISession["projectKey"]
|
||||
peerId: ISession["peerId"]
|
||||
live: ISession["live"]
|
||||
startedAt: ISession["startedAt"]
|
||||
duration: ISession["duration"]
|
||||
events: ISession["events"]
|
||||
stackEvents: ISession["stackEvents"]
|
||||
resources: ISession["resources"]
|
||||
missedResources: ISession["missedResources"]
|
||||
metadata: ISession["metadata"]
|
||||
favorite: ISession["favorite"]
|
||||
filterId?: ISession["filterId"]
|
||||
domURL: ISession["domURL"]
|
||||
devtoolsURL: ISession["devtoolsURL"]
|
||||
mobsUrl: ISession["mobsUrl"] // @depricated
|
||||
userBrowser: ISession["userBrowser"]
|
||||
userBrowserVersion: ISession["userBrowserVersion"]
|
||||
userCountry: ISession["userCountry"]
|
||||
userDevice: ISession["userDevice"]
|
||||
userDeviceType: ISession["userDeviceType"]
|
||||
isMobile: ISession["isMobile"]
|
||||
userOs: ISession["userOs"]
|
||||
userOsVersion: ISession["userOsVersion"]
|
||||
userId: ISession["userId"]
|
||||
userAnonymousId: ISession["userAnonymousId"]
|
||||
userUuid: ISession["userUuid"]
|
||||
userDisplayName: ISession["userDisplayName"]
|
||||
userNumericHash: ISession["userNumericHash"]
|
||||
viewed: ISession["viewed"]
|
||||
consoleLogCount: ISession["consoleLogCount"]
|
||||
eventsCount: ISession["eventsCount"]
|
||||
pagesCount: ISession["pagesCount"]
|
||||
errorsCount: ISession["errorsCount"]
|
||||
issueTypes: ISession["issueTypes"]
|
||||
issues: ISession["issues"]
|
||||
referrer: ISession["referrer"]
|
||||
userDeviceHeapSize: ISession["userDeviceHeapSize"]
|
||||
userDeviceMemorySize: ISession["userDeviceMemorySize"]
|
||||
errors: ISession["errors"]
|
||||
crashes?: ISession["crashes"]
|
||||
socket: ISession["socket"]
|
||||
isIOS: ISession["isIOS"]
|
||||
revId: ISession["revId"]
|
||||
agentIds?: ISession["agentIds"]
|
||||
isCallActive?: ISession["isCallActive"]
|
||||
agentToken: ISession["agentToken"]
|
||||
notes: ISession["notes"]
|
||||
notesWithEvents: ISession["notesWithEvents"]
|
||||
fileKey: ISession["fileKey"]
|
||||
|
||||
constructor(plainSession?: ISession) {
|
||||
const sessionData = plainSession || (emptyValues as unknown as ISession)
|
||||
const {
|
||||
startTs = 0,
|
||||
timestamp = 0,
|
||||
backendErrors = 0,
|
||||
consoleErrors = 0,
|
||||
projectId,
|
||||
sessionID = '',
|
||||
projectId = '',
|
||||
errors = [],
|
||||
stackEvents = [],
|
||||
issues = [],
|
||||
sessionId,
|
||||
sessionID,
|
||||
sessionId = '',
|
||||
domURL = [],
|
||||
devtoolsURL = [],
|
||||
mobsUrl = [],
|
||||
notes = [],
|
||||
...session
|
||||
}) => {
|
||||
const duration = Duration.fromMillis(session.duration < 1000 ? 1000 : session.duration);
|
||||
const durationSeconds = duration.valueOf();
|
||||
const startedAt = +startTs || +timestamp;
|
||||
} = sessionData
|
||||
const duration = Duration.fromMillis(session.duration < 1000 ? 1000 : session.duration);
|
||||
const durationSeconds = duration.valueOf();
|
||||
const startedAt = +startTs || +timestamp;
|
||||
|
||||
const userDevice = session.userDevice || session.userDeviceType || 'Other';
|
||||
const userDeviceType = session.userDeviceType || 'other';
|
||||
const isMobile = ['console', 'mobile', 'tablet'].includes(userDeviceType);
|
||||
const userDevice = session.userDevice || session.userDeviceType || 'Other';
|
||||
const userDeviceType = session.userDeviceType || 'other';
|
||||
const isMobile = ['console', 'mobile', 'tablet'].includes(userDeviceType);
|
||||
|
||||
const events = List(session.events)
|
||||
.map((e) => SessionEvent({ ...e, time: e.timestamp - startedAt }))
|
||||
.filter(({ type, time }) => type !== TYPES.CONSOLE && time <= durationSeconds);
|
||||
const events: InjectedEvent[] = []
|
||||
const rawEvents: (EventData & { key: number})[] = []
|
||||
|
||||
let resources = List(session.resources).map((r) => new Resource(r as any));
|
||||
resources.forEach((r: Resource) => {
|
||||
r.time = Math.max(0, r.time - startedAt)
|
||||
})
|
||||
resources = resources.sort((r1, r2) => r1.time - r2.time);
|
||||
const missedResources = resources.filter(({ success }) => !success);
|
||||
if (session.events?.length) {
|
||||
(session.events as EventData[]).forEach((event: EventData, k) => {
|
||||
const time = event.timestamp - startedAt
|
||||
if (event.type !== TYPES.CONSOLE && time <= durationSeconds) {
|
||||
const EventClass = SessionEvent({ ...event, time, key: k })
|
||||
if (EventClass) {
|
||||
events.push(EventClass);
|
||||
}
|
||||
rawEvents.push({ ...event, time, key: k });
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const stackEventsList = List(stackEvents)
|
||||
.concat(List(session.userEvents))
|
||||
.sortBy((se) => se.timestamp)
|
||||
.map((se) => StackEvent({ ...se, time: se.timestamp - startedAt }));
|
||||
const exceptions = (errors as IError[]).map(e => new SessionError(e));
|
||||
let resources = List(session.resources).map((r) => new Resource(r as any));
|
||||
resources.forEach((r: Resource) => {
|
||||
r.time = Math.max(0, r.time - startedAt)
|
||||
})
|
||||
resources = resources.sort((r1, r2) => r1.time - r2.time);
|
||||
const missedResources = resources.filter(({ success }) => !success);
|
||||
|
||||
const issuesList = (issues as IIssue[]).map((i, k) => new Issue({ ...i, time: i.timestamp - startedAt, key: k }));
|
||||
const stackEventsList: StackEvent[] = []
|
||||
if (stackEvents?.length || session.userEvents?.length) {
|
||||
const mergedArrays = [...stackEvents, ...session.userEvents]
|
||||
.sort((a, b) => a.timestamp - b.timestamp)
|
||||
.map((se) => new StackEvent({ ...se, time: se.timestamp - startedAt }))
|
||||
stackEventsList.push(...mergedArrays);
|
||||
}
|
||||
|
||||
const rawEvents = !session.events
|
||||
? []
|
||||
: // @ts-ignore
|
||||
session.events
|
||||
.map((evt) => ({ ...evt, time: evt.timestamp - startedAt }))
|
||||
.filter(({ type, time }) => type !== TYPES.CONSOLE && time <= durationSeconds) || [];
|
||||
const rawNotes = notes;
|
||||
const notesWithEvents = [...rawEvents, ...rawNotes].sort((a, b) => {
|
||||
const aTs = a.time || a.timestamp;
|
||||
const bTs = b.time || b.timestamp;
|
||||
const exceptions = (errors as IError[]).map(e => new SessionError(e)) || [];
|
||||
|
||||
return aTs - bTs;
|
||||
});
|
||||
const issuesList = (issues as IIssue[]).map((i, k) => new Issue({ ...i, time: i.timestamp - startedAt, key: k })) || [];
|
||||
|
||||
return {
|
||||
...session,
|
||||
isIOS: session.platform === 'ios',
|
||||
errors: exceptions,
|
||||
siteId: projectId,
|
||||
events,
|
||||
stackEvents: stackEventsList,
|
||||
resources,
|
||||
missedResources,
|
||||
userDevice,
|
||||
userDeviceType,
|
||||
isMobile,
|
||||
startedAt,
|
||||
duration,
|
||||
userNumericHash: hashString(
|
||||
session.userId ||
|
||||
session.userAnonymousId ||
|
||||
session.userUuid ||
|
||||
session.userID ||
|
||||
session.userUUID ||
|
||||
''
|
||||
),
|
||||
userDisplayName:
|
||||
session.userId || session.userAnonymousId || session.userID || 'Anonymous User',
|
||||
issues: issuesList,
|
||||
sessionId: sessionId || sessionID,
|
||||
userId: session.userId || session.userID,
|
||||
mobsUrl: Array.isArray(mobsUrl) ? mobsUrl : [mobsUrl],
|
||||
domURL,
|
||||
devtoolsURL,
|
||||
notes,
|
||||
notesWithEvents: List(notesWithEvents),
|
||||
};
|
||||
},
|
||||
idKey: 'sessionId',
|
||||
const rawNotes = notes;
|
||||
const notesWithEvents = [...rawEvents, ...rawNotes].sort((a, b) => {
|
||||
const aTs = a.time || a.timestamp;
|
||||
const bTs = b.time || b.timestamp;
|
||||
|
||||
return aTs - bTs;
|
||||
}) || [];
|
||||
|
||||
Object.assign(this, {
|
||||
...session,
|
||||
isIOS: session.platform === 'ios',
|
||||
errors: exceptions,
|
||||
siteId: projectId,
|
||||
events,
|
||||
stackEvents: stackEventsList,
|
||||
resources,
|
||||
missedResources,
|
||||
userDevice,
|
||||
userDeviceType,
|
||||
isMobile,
|
||||
startedAt,
|
||||
duration,
|
||||
userNumericHash: hashString(
|
||||
session.userId ||
|
||||
session.userAnonymousId ||
|
||||
session.userUuid ||
|
||||
session.userID ||
|
||||
session.userUUID ||
|
||||
''
|
||||
),
|
||||
userDisplayName:
|
||||
session.userId || session.userAnonymousId || session.userID || 'Anonymous User',
|
||||
issues: issuesList,
|
||||
sessionId: sessionId || sessionID,
|
||||
userId: session.userId || session.userID,
|
||||
mobsUrl: Array.isArray(mobsUrl) ? mobsUrl : [mobsUrl],
|
||||
domURL,
|
||||
devtoolsURL,
|
||||
notes,
|
||||
notesWithEvents: List(notesWithEvents),
|
||||
})
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
import Record from 'Types/Record';
|
||||
|
||||
export const OPENREPLAY = 'openreplay';
|
||||
export const SENTRY = 'sentry';
|
||||
export const DATADOG = 'datadog';
|
||||
|
|
@ -37,23 +35,36 @@ export function isRed(event) {
|
|||
}
|
||||
}
|
||||
|
||||
export default Record({
|
||||
time: undefined,
|
||||
index: undefined,
|
||||
name: '',
|
||||
message: "",
|
||||
payload: null,
|
||||
source: null,
|
||||
level: "",
|
||||
}, {
|
||||
fromJS: ue => ({
|
||||
...ue,
|
||||
source: ue.source || OPENREPLAY,
|
||||
}),
|
||||
methods: {
|
||||
isRed() {
|
||||
return isRed(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
export interface IStackEvent {
|
||||
time: number;
|
||||
timestamp: number;
|
||||
index: number;
|
||||
name: string;
|
||||
message: string;
|
||||
payload: any;
|
||||
source: any;
|
||||
level: string;
|
||||
|
||||
isRed: () => boolean;
|
||||
}
|
||||
|
||||
export default class StackEvent {
|
||||
time: IStackEvent["time"]
|
||||
index: IStackEvent["index"];
|
||||
name: IStackEvent["name"];
|
||||
message: IStackEvent["message"];
|
||||
payload: IStackEvent["payload"];
|
||||
source: IStackEvent["source"];
|
||||
level: IStackEvent["level"];
|
||||
|
||||
constructor(evt: IStackEvent) {
|
||||
Object.assign(this, {
|
||||
...evt,
|
||||
source: evt.source || OPENREPLAY
|
||||
});
|
||||
}
|
||||
|
||||
isRed() {
|
||||
return isRed(this);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue