diff --git a/frontend/app/components/Assist/RequestingWindow/RequestingWindow.tsx b/frontend/app/components/Assist/RequestingWindow/RequestingWindow.tsx index 25283bd3b..212fea91c 100644 --- a/frontend/app/components/Assist/RequestingWindow/RequestingWindow.tsx +++ b/frontend/app/components/Assist/RequestingWindow/RequestingWindow.tsx @@ -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); diff --git a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx index a474c9cc8..f324ce433 100644 --- a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx +++ b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx @@ -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 } diff --git a/frontend/app/components/Session/PlayerContent.js b/frontend/app/components/Session/PlayerContent.js index 64d896455..c7ccfc62b 100644 --- a/frontend/app/components/Session/PlayerContent.js +++ b/frontend/app/components/Session/PlayerContent.js @@ -21,6 +21,7 @@ function PlayerContent({ session, live, fullscreen, activeTab, setActiveTab }) { const hasError = !!error + console.log(session, 'hi') const sessionDays = countDaysFrom(session.startedAt); return (
diff --git a/frontend/app/components/Session/WebPlayer.tsx b/frontend/app/components/Session/WebPlayer.tsx index 6e169777e..de9e84036 100644 --- a/frontend/app/components/Session/WebPlayer.tsx +++ b/frontend/app/components/Session/WebPlayer.tsx @@ -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 ( <> diff --git a/frontend/app/components/Session_/EventsBlock/Metadata/Metadata.js b/frontend/app/components/Session_/EventsBlock/Metadata/Metadata.js index f6724385f..ca0a953a2 100644 --- a/frontend/app/components/Session_/EventsBlock/Metadata/Metadata.js +++ b/frontend/app/components/Session_/EventsBlock/Metadata/Metadata.js @@ -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; diff --git a/frontend/app/components/Session_/EventsBlock/Metadata/SessionList.js b/frontend/app/components/Session_/EventsBlock/Metadata/SessionList.js index fe414a30d..92ac93432 100644 --- a/frontend/app/components/Session_/EventsBlock/Metadata/SessionList.js +++ b/frontend/app/components/Session_/EventsBlock/Metadata/SessionList.js @@ -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); diff --git a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx index ef8598ddc..c7e7912b1 100644 --- a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx +++ b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx @@ -132,7 +132,7 @@ function OverviewPanel({ issuesList }: { issuesList: Record[] }) { export default connect( (state: any) => ({ - issuesList: state.getIn(['sessions', 'current', 'issues']), + issuesList: state.getIn(['sessions', 'current']).issues, }), { toggleBottomBlock, diff --git a/frontend/app/components/Session_/Performance/Performance.tsx b/frontend/app/components/Session_/Performance/Performance.tsx index eca011fcb..fa4828af1 100644 --- a/frontend/app/components/Session_/Performance/Performance.tsx +++ b/frontend/app/components/Session_/Performance/Performance.tsx @@ -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)); diff --git a/frontend/app/components/Session_/Player/Controls/Controls.tsx b/frontend/app/components/Session_/Player/Controls/Controls.tsx index eb283c47a..29ede7fd0 100644 --- a/frontend/app/components/Session_/Player/Controls/Controls.tsx +++ b/frontend/app/components/Session_/Player/Controls/Controls.tsx @@ -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']), }; }, diff --git a/frontend/app/components/Session_/Player/Controls/Timeline.tsx b/frontend/app/components/Session_/Player/Controls/Timeline.tsx index 7bd8b7464..4a32d8efd 100644 --- a/frontend/app/components/Session_/Player/Controls/Timeline.tsx +++ b/frontend/app/components/Session_/Player/Controls/Timeline.tsx @@ -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 } diff --git a/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx b/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx index 68f510de6..e2e50d2e4 100644 --- a/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx @@ -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 } diff --git a/frontend/app/components/Session_/Player/Player.js b/frontend/app/components/Session_/Player/Player.js index da6fb0eac..e0a467ca2 100644 --- a/frontend/app/components/Session_/Player/Player.js +++ b/frontend/app/components/Session_/Player/Player.js @@ -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), }; }, { diff --git a/frontend/app/components/Session_/PlayerBlock.js b/frontend/app/components/Session_/PlayerBlock.js index 5da29a587..5f805dac2 100644 --- a/frontend/app/components/Session_/PlayerBlock.js +++ b/frontend/app/components/Session_/PlayerBlock.js @@ -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(), })) diff --git a/frontend/app/components/Session_/ScreenRecorder/ScreenRecorder.tsx b/frontend/app/components/Session_/ScreenRecorder/ScreenRecorder.tsx index 5031d2cca..fc5c66eea 100644 --- a/frontend/app/components/Session_/ScreenRecorder/ScreenRecorder.tsx +++ b/frontend/app/components/Session_/ScreenRecorder/ScreenRecorder.tsx @@ -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)) diff --git a/frontend/app/components/shared/Bookmark/Bookmark.tsx b/frontend/app/components/shared/Bookmark/Bookmark.tsx index 04c88e589..d011af5f7 100644 --- a/frontend/app/components/shared/Bookmark/Bookmark.tsx +++ b/frontend/app/components/shared/Bookmark/Bookmark.tsx @@ -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); diff --git a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx index f34b3e04e..1f2212152 100644 --- a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx +++ b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx @@ -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)); diff --git a/frontend/app/components/shared/SessionItem/SessionItem.tsx b/frontend/app/components/shared/SessionItem/SessionItem.tsx index fbfd2202e..a59f8e5aa 100644 --- a/frontend/app/components/shared/SessionItem/SessionItem.tsx +++ b/frontend/app/components/shared/SessionItem/SessionItem.tsx @@ -38,7 +38,6 @@ interface Props { userNumericHash: number; live: boolean; metadata: Record; - userSessionsCount: number; issueTypes: []; active: boolean; isCallActive?: boolean; diff --git a/frontend/app/components/shared/SharePopup/SharePopup.js b/frontend/app/components/shared/SharePopup/SharePopup.js index 47f2b68b7..1df16cf6d 100644 --- a/frontend/app/components/shared/SharePopup/SharePopup.js +++ b/frontend/app/components/shared/SharePopup/SharePopup.js @@ -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']), diff --git a/frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx b/frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx index 4acd355c2..2aca274ea 100644 --- a/frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx +++ b/frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx @@ -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); diff --git a/frontend/app/duck/customMetrics.js b/frontend/app/duck/customMetrics.js index 82ce78277..c740f7a57 100644 --- a/frontend/app/duck/customMetrics.js +++ b/frontend/app/duck/customMetrics.js @@ -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()); } diff --git a/frontend/app/duck/errors.js b/frontend/app/duck/errors.js index 624c05633..8a23875cc 100644 --- a/frontend/app/duck/errors.js +++ b/frontend/app/duck/errors.js @@ -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); diff --git a/frontend/app/duck/funnels.js b/frontend/app/duck/funnels.js index 3abfcc450..d175b64d5 100644 --- a/frontend/app/duck/funnels.js +++ b/frontend/app/duck/funnels.js @@ -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()) diff --git a/frontend/app/duck/liveSearch.js b/frontend/app/duck/liveSearch.js index 591dd7084..8500158bd 100644 --- a/frontend/app/duck/liveSearch.js +++ b/frontend/app/duck/liveSearch.js @@ -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); diff --git a/frontend/app/duck/sessions.js b/frontend/app/duck/sessions.js index b78d8fad4..6f015f510 100644 --- a/frontend/app/duck/sessions.js +++ b/frontend/app/duck/sessions.js @@ -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: { diff --git a/frontend/app/mstore/assistMultiviewStore.ts b/frontend/app/mstore/assistMultiviewStore.ts index e0895918f..379f852cb 100644 --- a/frontend/app/mstore/assistMultiviewStore.ts +++ b/frontend/app/mstore/assistMultiviewStore.ts @@ -95,7 +95,7 @@ export default class AssistMultiviewStore { const matchingSessions = data.sessions.filter( (s: Record) => 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) => { this.addSession(session); this.fetchAgentTokenInfo(session.sessionId); diff --git a/frontend/app/player/web/WebPlayer.ts b/frontend/app/player/web/WebPlayer.ts index ff6d3d0da..0bb84da42 100644 --- a/frontend/app/player/web/WebPlayer.ts +++ b/frontend/app/player/web/WebPlayer.ts @@ -26,10 +26,10 @@ export default class WebPlayer extends Player { private targetMarker: TargetMarker constructor(protected wpState: Store, 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({ diff --git a/frontend/app/types/dashboard/index.js b/frontend/app/types/dashboard/index.js index c250d64d6..670f44a2c 100644 --- a/frontend/app/types/dashboard/index.js +++ b/frontend/app/types/dashboard/index.js @@ -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", diff --git a/frontend/app/types/errorInfo.js b/frontend/app/types/errorInfo.js index b3bb48e1d..7da9c3a9b 100644 --- a/frontend/app/types/errorInfo.js +++ b/frontend/app/types/errorInfo.js @@ -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 || []), }) }); diff --git a/frontend/app/types/session/author.js b/frontend/app/types/session/author.js deleted file mode 100644 index 12edc3c1e..000000000 --- a/frontend/app/types/session/author.js +++ /dev/null @@ -1,11 +0,0 @@ -import Record from 'Types/Record'; - -export default Record({ - id: undefined, - avatarUrls: undefined, - name: undefined, -}, { - fromJS: author => ({ - ...author, - }) -}) diff --git a/frontend/app/types/session/event.js b/frontend/app/types/session/event.js deleted file mode 100644 index 537de1724..000000000 --- a/frontend/app/types/session/event.js +++ /dev/null @@ -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); -} - diff --git a/frontend/app/types/session/event.ts b/frontend/app/types/session/event.ts new file mode 100644 index 000000000..fa62c33d9 --- /dev/null +++ b/frontend/app/types/session/event.ts @@ -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}`) +} + diff --git a/frontend/app/types/session/profile.js b/frontend/app/types/session/profile.js deleted file mode 100644 index 98b9bc345..000000000 --- a/frontend/app/types/session/profile.js +++ /dev/null @@ -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), - }), -}); - - diff --git a/frontend/app/types/session/session.ts b/frontend/app/types/session/session.ts index 35206a5a4..298ec4fa0 100644 --- a/frontend/app/types/session/session.ts +++ b/frontend/app/types/session/session.ts @@ -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), + }) } -); +} \ No newline at end of file diff --git a/frontend/app/types/session/stackEvent.js b/frontend/app/types/session/stackEvent.ts similarity index 61% rename from frontend/app/types/session/stackEvent.js rename to frontend/app/types/session/stackEvent.ts index 762407916..d1c4b7b09 100644 --- a/frontend/app/types/session/stackEvent.js +++ b/frontend/app/types/session/stackEvent.ts @@ -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); + } +}