change(ui): rewrite session type

This commit is contained in:
sylenien 2022-12-30 17:59:56 +01:00
parent 3a30f0cd1d
commit b2b1caf98e
34 changed files with 465 additions and 326 deletions

View file

@ -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);

View file

@ -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 }

View file

@ -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">

View file

@ -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}>
<>

View file

@ -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;

View file

@ -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);

View file

@ -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,

View file

@ -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));

View file

@ -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']),
};
},

View file

@ -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 }

View file

@ -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 }

View file

@ -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),
};
},
{

View file

@ -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(),
}))

View file

@ -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))

View file

@ -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);

View file

@ -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));

View file

@ -38,7 +38,6 @@ interface Props {
userNumericHash: number;
live: boolean;
metadata: Record<string, any>;
userSessionsCount: number;
issueTypes: [];
active: boolean;
isCallActive?: boolean;

View file

@ -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']),

View file

@ -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);

View file

@ -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());
}

View file

@ -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);

View file

@ -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())

View file

@ -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);

View file

@ -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: {

View file

@ -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);

View file

@ -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({

View file

@ -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",

View file

@ -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 || []),
})
});

View file

@ -1,11 +0,0 @@
import Record from 'Types/Record';
export default Record({
id: undefined,
avatarUrls: undefined,
name: undefined,
}, {
fromJS: author => ({
...author,
})
})

View file

@ -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);
}

View 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}`)
}

View file

@ -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),
}),
});

View file

@ -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),
})
}
);
}

View file

@ -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);
}
}