diff --git a/frontend/app/Router.tsx b/frontend/app/Router.tsx index b3986e35b..5020b1cf8 100644 --- a/frontend/app/Router.tsx +++ b/frontend/app/Router.tsx @@ -17,7 +17,6 @@ import { useStore } from 'App/mstore'; import { checkParam, handleSpotJWT, isTokenExpired } from 'App/utils'; import { ModalProvider } from 'Components/Modal'; import { ModalProvider as NewModalProvider } from 'Components/ModalContext'; -import { setSessionPath } from 'Duck/sessions'; import { fetchUserInfo, getScope, logout, setJwt } from 'Duck/user'; import { Loader } from 'UI'; import * as routes from './routes'; @@ -30,7 +29,6 @@ interface RouterProps changePassword: boolean; isEnterprise: boolean; fetchUserInfo: () => any; - setSessionPath: (path: any) => any; match: { params: { siteId: string; @@ -47,15 +45,15 @@ const Router: React.FC = (props) => { location, fetchUserInfo, history, - setSessionPath, localSpotJwt, logout, scopeSetup, setJwt, } = props; const mstore = useStore(); - const { customFieldStore, projectsStore } = mstore; + const { customFieldStore, projectsStore, sessionStore } = mstore; + const setSessionPath = sessionStore.setSessionPath; const siteId = projectsStore.siteId; const sitesLoading = projectsStore.sitesLoading; const sites = projectsStore.list; @@ -256,7 +254,6 @@ const mapStateToProps = (state: Map) => { const mapDispatchToProps = { fetchUserInfo, - setSessionPath, setJwt, logout }; diff --git a/frontend/app/components/Assist/components/SessionList/SessionList.tsx b/frontend/app/components/Assist/components/SessionList/SessionList.tsx index 6fd78da60..2bf5d2ecc 100644 --- a/frontend/app/components/Assist/components/SessionList/SessionList.tsx +++ b/frontend/app/components/Assist/components/SessionList/SessionList.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from 'react'; -import { connect } from 'react-redux'; -import { fetchLiveList } from 'Duck/sessions'; +import { observer } from 'mobx-react-lite' +import { useStore } from 'App/mstore'; import { Loader, NoContent, Label } from 'UI'; import SessionItem from 'Shared/SessionItem'; import { useModal } from 'App/components/Modal'; @@ -11,16 +11,20 @@ interface Props { list: any; session: any; userId: any; - fetchLiveList: (params: any) => void; } function SessionList(props: Props) { const { hideModal } = useModal(); + const { sessionStore } = useStore(); + const fetchLiveList = sessionStore.fetchLiveSessions; + const session = sessionStore.current; + const list = sessionStore.liveSessions.filter((i: any) => i.userId === session.userId && i.sessionId !== session.sessionId); + const loading = sessionStore.loadingLiveSessions; useEffect(() => { const params: any = {}; if (props.session.userId) { params.userId = props.session.userId; } - props.fetchLiveList(params); + void fetchLiveList(params); }, []); return ( @@ -33,9 +37,9 @@ function SessionList(props: Props) { {props.userId}'s Live Sessions{' '} - + @@ -45,7 +49,7 @@ function SessionList(props: Props) { } >
- {props.list.map((session: any) => ( + {list.map((session: any) => (
{session.pageTitle && session.pageTitle !== '' && (
@@ -65,14 +69,4 @@ function SessionList(props: Props) { ); } -export default connect( - (state: any) => { - const session = state.getIn(['sessions', 'current']); - return { - session, - list: state.getIn(['sessions', 'liveSessions']).filter((i: any) => i.userId === session.userId && i.sessionId !== session.sessionId), - loading: state.getIn(['sessions', 'fetchLiveListRequest', 'loading']), - }; - }, - { fetchLiveList } -)(SessionList); +export default observer(SessionList); diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx index 3a1b4ebfc..81a1d00cf 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx @@ -81,7 +81,6 @@ export default connect( insightsFilters: state.getIn(['sessions', 'insightFilters']), visitedEvents: state.getIn(['sessions', 'visitedEvents']), insights: state.getIn(['sessions', 'insights']), - host: state.getIn(['sessions', 'host']), }), { fetchInsights, } ) diff --git a/frontend/app/components/Session/LivePlayer.tsx b/frontend/app/components/Session/LivePlayer.tsx index 2e1eda943..7e9f0963e 100644 --- a/frontend/app/components/Session/LivePlayer.tsx +++ b/frontend/app/components/Session/LivePlayer.tsx @@ -142,7 +142,6 @@ export default withPermissions(['ASSIST_LIVE', 'SERVICE_ASSIST_LIVE'], '', true, connect((state: any) => { return { session: state.getIn(['sessions', 'current']), - showAssist: state.getIn(['sessions', 'showChatWindow']), isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee', userEmail: state.getIn(['user', 'account', 'email']), userName: state.getIn(['user', 'account', 'name']), diff --git a/frontend/app/components/Session/Player/MobilePlayer/MobilePlayerHeader.tsx b/frontend/app/components/Session/Player/MobilePlayer/MobilePlayerHeader.tsx index 5a4a97773..a6a243e77 100644 --- a/frontend/app/components/Session/Player/MobilePlayer/MobilePlayerHeader.tsx +++ b/frontend/app/components/Session/Player/MobilePlayer/MobilePlayerHeader.tsx @@ -105,7 +105,6 @@ const PlayerHeaderCont = connect( return { session, sessionPath: state.getIn(['sessions', 'sessionPath']), - local: state.getIn(['sessions', 'timezone']), funnelRef: state.getIn(['funnels', 'navRef']), metaList: state.getIn(['customFields', 'list']).map((i: any) => i.key), }; diff --git a/frontend/app/components/Session/Player/ReplayPlayer/PlayerBlockHeader.tsx b/frontend/app/components/Session/Player/ReplayPlayer/PlayerBlockHeader.tsx index 7a8b05629..6b7c899d3 100644 --- a/frontend/app/components/Session/Player/ReplayPlayer/PlayerBlockHeader.tsx +++ b/frontend/app/components/Session/Player/ReplayPlayer/PlayerBlockHeader.tsx @@ -135,7 +135,6 @@ const PlayerHeaderCont = connect( return { session, sessionPath: state.getIn(['sessions', 'sessionPath']), - local: state.getIn(['sessions', 'timezone']), funnelRef: state.getIn(['funnels', 'navRef']), metaList: state.getIn(['customFields', 'list']).map((i: any) => i.key), }; diff --git a/frontend/app/components/shared/Filters/FilterSelection/FilterSelection.tsx b/frontend/app/components/shared/Filters/FilterSelection/FilterSelection.tsx index b8de307bd..fdac13eae 100644 --- a/frontend/app/components/shared/Filters/FilterSelection/FilterSelection.tsx +++ b/frontend/app/components/shared/Filters/FilterSelection/FilterSelection.tsx @@ -14,7 +14,6 @@ interface Props { filterListLive: any; onFilterClick: (filter: any) => void; children?: any; - isLive?: boolean; excludeFilterKeys?: Array; allowedFilterKeys?: Array; disabled?: boolean; @@ -81,7 +80,6 @@ export default connect( (state: any) => ({ filterList: state.getIn(['search', 'filterList']), filterListLive: state.getIn(['search', 'filterListLive']), - isLive: state.getIn(['sessions', 'activeTab']).type === 'live' }), {} )(FilterSelection); diff --git a/frontend/app/components/shared/SessionItem/SessionItem.tsx b/frontend/app/components/shared/SessionItem/SessionItem.tsx index 9449bc5b1..acceae4ed 100644 --- a/frontend/app/components/shared/SessionItem/SessionItem.tsx +++ b/frontend/app/components/shared/SessionItem/SessionItem.tsx @@ -2,11 +2,9 @@ import cn from 'classnames'; import { Duration } from 'luxon'; import { observer } from 'mobx-react-lite'; import React from 'react'; -import { connect } from 'react-redux'; import { RouteComponentProps, withRouter } from 'react-router-dom'; import { durationFormatted, formatTimeOrDate } from 'App/date'; -import { presetSession } from 'App/duck/sessions'; import { useStore } from 'App/mstore'; import { assist as assistRoute, @@ -79,7 +77,6 @@ interface Props { bookmarked?: boolean; toggleFavorite?: (sessionId: string) => void; query?: string; - presetSession?: typeof presetSession; } const PREFETCH_STATE = { @@ -105,7 +102,6 @@ function SessionItem(props: RouteComponentProps & Props) { ignoreAssist = false, bookmarked = false, query, - presetSession, } = props; const { @@ -176,8 +172,11 @@ function SessionItem(props: RouteComponentProps & Props) { || isAssist || prefetchState === PREFETCH_STATE.none || isMobile - ) return - presetSession?.(session); + ) { + return; + } + + sessionStore.prefetchSession(session); }; return ( void; @@ -54,24 +49,24 @@ interface Props extends RouteComponentProps { activeTab: any; isEnterprise?: boolean; checkForLatestSessions: () => void; - toggleFavorite: (sessionId: string) => Promise; isLoggedIn: boolean; } function SessionList(props: Props) { - const { projectsStore } = useStore(); + const { projectsStore, sessionStore } = useStore(); + const list = sessionStore.list; + const lastPlayedSessionId = sessionStore.lastPlayedSessionId; + const loading = sessionStore.loadingSessions; + const total = sessionStore.total; + const onToggleFavorite = sessionStore.toggleFavorite; const sites = projectsStore.list; const siteId = projectsStore.siteId; const updateProjectRecordingStatus = projectsStore.updateProjectRecordingStatus; const [noContentType, setNoContentType] = React.useState(NoContentType.ToDate); const { - loading, - list, currentPage, pageSize, - total, filters, - lastPlayedSessionId, metaList, activeTab, isEnterprise = false, @@ -199,7 +194,7 @@ function SessionList(props: Props) { }; const toggleFavorite = (sessionId: string) => { - props.toggleFavorite(sessionId).then(() => { + onToggleFavorite(sessionId).then(() => { props.fetchSessions(null, true); }); }; @@ -282,13 +277,9 @@ function SessionList(props: Props) { export default connect( (state: any) => ({ - list: state.getIn(['sessions', 'list']), filters: state.getIn(['search', 'instance', 'filters']), - lastPlayedSessionId: state.getIn(['sessions', 'lastPlayedSessionId']), metaList: state.getIn(['customFields', 'list']).map((i: any) => i.key), - loading: state.getIn(['sessions', 'loading']), currentPage: state.getIn(['search', 'currentPage']) || 1, - total: state.getIn(['sessions', 'total']) || 0, scrollY: state.getIn(['search', 'scrollY']), activeTab: state.getIn(['search', 'activeTab']), pageSize: state.getIn(['search', 'pageSize']), @@ -301,6 +292,5 @@ export default connect( setScrollPosition, fetchSessions, checkForLatestSessions, - toggleFavorite, } )(withRouter(observer(SessionList))); diff --git a/frontend/app/components/shared/SessionsTabOverview/components/SessionSort/SessionSort.tsx b/frontend/app/components/shared/SessionsTabOverview/components/SessionSort/SessionSort.tsx index 57a258660..f84d14316 100644 --- a/frontend/app/components/shared/SessionsTabOverview/components/SessionSort/SessionSort.tsx +++ b/frontend/app/components/shared/SessionsTabOverview/components/SessionSort/SessionSort.tsx @@ -4,7 +4,8 @@ import React from 'react'; import { connect } from 'react-redux'; import { applyFilter } from 'Duck/search'; -import { sort } from 'Duck/sessions'; +import { observer } from 'mobx-react-lite'; +import { useStore } from 'App/mstore'; const sortOptionsMap = { 'startTs-desc': 'Newest', @@ -32,7 +33,6 @@ export function SortDropdown({ defaultOption, onSort, sortOptions, current }: sortOptions: any, current: string }) { - return ( ({ defaultOption, onSort, sortOptions, current }: } function SessionSort(props: Props) { + const { sessionStore } = useStore(); + const onSessionSort = sessionStore.sortSessions; const { sort, order } = props.filter; const onSort = ({ key }: { key: string }) => { const [sort, order] = key.split('-'); const sign = order === 'desc' ? -1 : 1; props.applyFilter({ order, sort }); - props.sort(sort, sign); + onSessionSort(sort, sign); }; const defaultOption = `${sort}-${order}`; @@ -79,5 +81,5 @@ export default connect( (state: any) => ({ filter: state.getIn(['search', 'instance']), }), - { sort, applyFilter } -)(SessionSort); + { applyFilter } +)(observer(SessionSort)); diff --git a/frontend/app/components/ui/TimezoneDropdown/TimezoneDropdown.js b/frontend/app/components/ui/TimezoneDropdown/TimezoneDropdown.js index ab8113692..c67b60f53 100644 --- a/frontend/app/components/ui/TimezoneDropdown/TimezoneDropdown.js +++ b/frontend/app/components/ui/TimezoneDropdown/TimezoneDropdown.js @@ -1,7 +1,7 @@ import React from 'react' import Select from 'Shared/Select'; -import { connect } from 'react-redux'; -import { setTimezone } from 'Duck/sessions'; +import { observer } from 'mobx-react-lite'; +import { useStore } from "App/mstore"; const localMachineFormat = new Date().toString().match(/([A-Z]+[\+-][0-9]+)/)[1] const middlePoint = localMachineFormat.length - 2 @@ -13,7 +13,10 @@ const timezoneOptions = { 'UTC': 'UTC' }; -function TimezoneDropdown({ local, setTimezone }) { +function TimezoneDropdown() { + const { sessionStore } = useStore(); + const local = sessionStore.timezone; + const setTimezone = sessionStore.setTimezone; const sortOptions = Object.entries(timezoneOptions) .map(([ value, label ]) => ({ value, label })); @@ -33,6 +36,4 @@ function TimezoneDropdown({ local, setTimezone }) { ) } -export default connect(state => ({ - local: state.getIn(['sessions', 'timezone']), -}), { setTimezone })(TimezoneDropdown) +export default observer(TimezoneDropdown) diff --git a/frontend/app/duck/sessions.ts b/frontend/app/duck/sessions.ts index 4f9335569..be151c7ea 100644 --- a/frontend/app/duck/sessions.ts +++ b/frontend/app/duck/sessions.ts @@ -36,12 +36,9 @@ const FETCH_ERROR_STACK = new RequestTypes('sessions/FETCH_ERROR_STACK'); const FETCH_INSIGHTS = new RequestTypes('sessions/FETCH_INSIGHTS'); const FETCH_SESSION_CLICKMAP = new RequestTypes('sessions/FETCH_SESSION_CLICKMAP'); const SORT = 'sessions/SORT'; -const REDEFINE_TARGET = 'sessions/REDEFINE_TARGET'; const SET_TIMEZONE = 'sessions/SET_TIMEZONE'; const SET_EVENT_QUERY = 'sessions/SET_EVENT_QUERY'; const SET_AUTOPLAY_VALUES = 'sessions/SET_AUTOPLAY_VALUES'; -const TOGGLE_CHAT_WINDOW = 'sessions/TOGGLE_CHAT_WINDOW'; -const SET_FUNNEL_PAGE_FLAG = 'sessions/SET_FUNNEL_PAGE_FLAG'; const SET_TIMELINE_POINTER = 'sessions/SET_TIMELINE_POINTER'; const SET_TIMELINE_HOVER_POINTER = 'sessions/SET_TIMELINE_HOVER_POINTER'; @@ -75,8 +72,6 @@ const initObj = { prefetched: false, eventsAsked: false, total: 0, - keyMap: Map(), - wdTypeCount: Map(), favoriteList: List(), activeTab: Watchdog({ name: 'All', type: 'all' }), timezone: 'local', @@ -85,13 +80,11 @@ const initObj = { sourcemapUploaded: true, filteredEvents: null, eventsQuery: '', - showChatWindow: false, liveSessions: [], visitedEvents: List(), insights: List(), insightFilters: defaultDateFilters, host: '', - funnelPage: Map(), timelinePointer: null, sessionPath: {}, lastPlayedSessionId: null, @@ -374,19 +367,12 @@ const reducer = (state = initialState, action: IAction) => { ); case SET_TIMEZONE: return state.set('timezone', action.timezone); - case TOGGLE_CHAT_WINDOW: - return state.set('showChatWindow', action.state); case FETCH_SESSION_CLICKMAP.SUCCESS: case FETCH_INSIGHTS.SUCCESS: return state.set( 'insights', List(action.data).sort((a, b) => b.count - a.count) ); - case SET_FUNNEL_PAGE_FLAG: - return state.set( - 'funnelPage', - action.funnelPage ? Map(action.funnelPage) : false - ); case SET_TIMELINE_POINTER: return state.set('timelinePointer', action.pointer); case SET_TIMELINE_HOVER_POINTER: @@ -605,13 +591,6 @@ export function fetchLiveList(params = {}) { }; } -export function toggleChatWindow(state) { - return { - type: TOGGLE_CHAT_WINDOW, - state, - }; -} - export function sort(sortKey, sign = 1, listName = 'list') { return { type: SORT, @@ -647,13 +626,6 @@ export function setEventFilter(filter) { }; } -export function setFunnelPage(funnelPage) { - return { - type: SET_FUNNEL_PAGE_FLAG, - funnelPage, - }; -} - export function setTimelinePointer(pointer) { return { type: SET_TIMELINE_POINTER, diff --git a/frontend/app/mstore/sessionStore.ts b/frontend/app/mstore/sessionStore.ts index 6d2ac4e1b..165143477 100644 --- a/frontend/app/mstore/sessionStore.ts +++ b/frontend/app/mstore/sessionStore.ts @@ -1,14 +1,9 @@ -import Record, { LAST_7_DAYS } from 'Types/app/period'; +import { makeAutoObservable, runInAction } from 'mobx'; +import { sessionService } from 'App/services'; +import { Note } from 'App/services/NotesService'; import Session from 'Types/session'; import ErrorStack from 'Types/session/errorStack'; import { InjectedEvent, Location } from 'Types/session/event'; -import Watchdog from 'Types/watchdog'; -import { action, makeAutoObservable, observable } from 'mobx'; - -import { getDateRangeFromValue } from 'App/dateRange'; -import { sessionService } from 'App/services'; -import { Note } from 'App/services/NotesService'; -import store from 'App/store'; import { cleanSessionFilters, compareJsonObjects, @@ -16,124 +11,27 @@ import { getSessionFilter, setSessionFilter, } from 'App/utils'; -import { filterMap } from 'Duck/search'; - -import { loadFile } from '../player/web/network/loadFiles'; - -class UserFilter { - endDate: number = new Date().getTime(); - startDate: number = new Date().getTime() - 24 * 60 * 60 * 1000; - rangeName: string = LAST_7_DAYS; - filters: any = []; - page: number = 1; - limit: number = 10; - period: any = Record({ rangeName: LAST_7_DAYS }); - - constructor() { - makeAutoObservable(this, { - page: observable, - update: action, - }); - } - - update(key: string, value: any) { - // @ts-ignore - this[key] = value; - - if (key === 'period') { - this.startDate = this.period.start; - this.endDate = this.period.end; - } - } - - setFilters(filters: any[]) { - this.filters = filters; - } - - setPage(page: number) { - this.page = page; - } - - toJson() { - return { - endDate: this.period.end, - startDate: this.period.start, - filters: this.filters.map(filterMap), - page: this.page, - limit: this.limit, - }; - } -} - -interface BaseDevState { - index: number; - filter: string; - activeTab: string; - isError: boolean; -} - -class DevTools { - network: BaseDevState; - stackEvent: BaseDevState; - console: BaseDevState; - - constructor() { - this.network = { index: 0, filter: '', activeTab: 'ALL', isError: false }; - this.stackEvent = { - index: 0, - filter: '', - activeTab: 'ALL', - isError: false, - }; - this.console = { index: 0, filter: '', activeTab: 'ALL', isError: false }; - makeAutoObservable(this, { - update: action, - }); - } - - update(key: string, value: any) { - // @ts-ignore - this[key] = Object.assign(this[key], value); - } -} - -const range = getDateRangeFromValue(LAST_7_DAYS); -const defaultDateFilters = { - url: '', - rangeValue: LAST_7_DAYS, - startDate: range.start.ts, - endDate: range.end.ts, -}; +import { loadFile } from 'App/player/web/network/loadFiles'; export default class SessionStore { - userFilter: UserFilter = new UserFilter(); - devTools: DevTools = new DevTools(); - list: Session[] = []; sessionIds: string[] = []; current = new Session(); total = 0; - keyMap = {}; - wdTypeCount = {}; favoriteList: Session[] = []; - activeTab = Watchdog({ name: 'All', type: 'all' }); + activeTab = { name: 'All', type: 'all' } timezone = 'local'; errorStack: ErrorStack[] = []; - eventsIndex = []; + eventsIndex: number[] = []; sourcemapUploaded = true; filteredEvents: InjectedEvent[] | null = null; eventsQuery = ''; - showChatWindow = false; liveSessions: Session[] = []; - visitedEvents = []; + visitedEvents: Location[] = []; insights: any[] = []; - insightFilters = defaultDateFilters; host = ''; - funnelPage = {}; - /** @Deprecated */ - timelinePointer = {}; sessionPath = {}; - lastPlayedSessionId: string; + lastPlayedSessionId: string = ''; timeLineTooltip = { time: 0, offset: 0, @@ -145,16 +43,17 @@ export default class SessionStore { previousId = ''; nextId = ''; userTimezone = ''; - prefetchedMobUrls: Record = - {}; + prefetchedMobUrls: Record = {}; + prefetched: boolean = false; + fetchFailed: boolean = false; + loadingLiveSessions: boolean = false; + loadingSessions: boolean = false; constructor() { - makeAutoObservable(this, { - userFilter: observable, - devTools: observable, - }); + makeAutoObservable(this); } + // Set User Timezone setUserTimezone(timezone: string) { this.userTimezone = timezone; } @@ -163,49 +62,41 @@ export default class SessionStore { this.userFilter = new UserFilter(); } + // Get First Mob (Mobile) File async getFirstMob(sessionId: string) { const { domURL } = await sessionService.getFirstMobUrl(sessionId); - await loadFile(domURL[0], (data) => - this.setPrefetchedMobUrl(sessionId, data) - ); + await loadFile(domURL[0], (data) => this.setPrefetchedMobUrl(sessionId, data)); } + // Set Prefetched Mobile URL setPrefetchedMobUrl(sessionId: string, fileData: Uint8Array) { const keys = Object.keys(this.prefetchedMobUrls); const toLimit = 10 - keys.length; if (toLimit < 0) { const oldest = keys.sort( (a, b) => - this.prefetchedMobUrls[a].entryNum - - this.prefetchedMobUrls[b].entryNum + this.prefetchedMobUrls[a].entryNum - this.prefetchedMobUrls[b].entryNum )[0]; delete this.prefetchedMobUrls[oldest]; } const nextEntryNum = keys.length > 0 - ? Math.max( - ...keys.map((key) => - this.prefetchedMobUrls[key] - ? this.prefetchedMobUrls[key].entryNum - : 0 - ) - ) + 1 - : 0; + ? Math.max(...keys.map((key) => this.prefetchedMobUrls[key].entryNum || 0)) + 1 + : 0; this.prefetchedMobUrls[sessionId] = { data: fileData, entryNum: nextEntryNum, }; } + // Get Sessions (Helper Function) getSessions(filter: any): Promise { return new Promise((resolve, reject) => { sessionService .getSessions(filter.toJson?.() || filter) .then((response: any) => { resolve({ - sessions: response.sessions.map( - (session: any) => new Session(session) - ), + sessions: response.sessions.map((session: any) => new Session(session)), total: response.total, }); }) @@ -216,17 +107,25 @@ export default class SessionStore { } async fetchLiveSessions(params = {}) { + runInAction(() => { + this.loadingLiveSessions = true; + }) try { const data = await sessionService.getLiveSessions(params); - this.liveSessions = data.map( - (session) => new Session({ ...session, live: true }) - ); + this.liveSessions = data.map((session) => new Session({ ...session, live: true })); } catch (e) { console.error(e); + } finally { + runInAction(() => { + this.loadingLiveSessions = false; + }); } } async fetchSessions(params = {}, force = false) { + runInAction(() => { + this.loadingSessions = true; + }) try { if (!force) { const oldFilters = getSessionFilter(); @@ -244,28 +143,48 @@ export default class SessionStore { this.favoriteList = list.filter((s) => s.favorite); } catch (e) { console.error(e); + } finally { + runInAction(() => { + this.loadingSessions = false; + }); } } - async fetchSessionInfo(sessionId: string, isLive = false) { + // Fetch Session Data (Info and Events) + async fetchSessionData(sessionId: string, isLive = false) { try { - const { events } = store.getState().getIn(['filters', 'appliedFilter']); + const filter = useReducerData('filters.appliedFilter'); const data = await sessionService.getSessionInfo(sessionId, isLive); - const session = new Session(data); + this.current = new Session(data); + const eventsData = await sessionService.getSessionEvents(sessionId); + + const { + errors, + events, + issues, + crashes, + resources, + stackEvents, + userEvents, + userTesting, + } = eventsData; + + const filterEvents = filter.events as Record[]; const matching: number[] = []; - const visitedEvents: Location[] = []; - const tmpMap: Set = new Set(); - session.events.forEach((event) => { + const visitedEvents: Location[] = []; + const tmpMap = new Set(); + + events.forEach((event) => { if (event.type === 'LOCATION' && !tmpMap.has(event.url)) { tmpMap.add(event.url); - visitedEvents.push(event); + visitedEvents.push(event as Location); } }); - (events as {}[]).forEach(({ key, operator, value }: any) => { - session.events.forEach((e, index) => { + filterEvents.forEach(({ key, operator, value }) => { + events.forEach((e, index) => { if (key === e.type) { const val = e.type === 'LOCATION' ? e.url : e.value; if (operator === 'is' && value === val) { @@ -277,59 +196,89 @@ export default class SessionStore { } }); }); + + this.current = this.current.addEvents( + events, + crashes, + errors, + issues, + resources, + userEvents, + stackEvents, + userTesting + ); + this.eventsIndex = matching; + this.visitedEvents = visitedEvents; + this.host = visitedEvents[0]?.host || ''; } catch (e) { console.error(e); + this.fetchFailed = true; } } - async fetchErrorStack(sessionId: string, errorId: string) { + // Fetch Notes + async fetchNotes(sessionId: string) { try { - const data = await sessionService.getErrorStack(sessionId, errorId); - this.errorStack = data.trace.map((es) => new ErrorStack(es)); + const notes = await sessionService.getSessionNotes(sessionId); + if (notes.length > 0) { + this.current = this.current.addNotes(notes); + } } catch (e) { console.error(e); } } - async fetchAutoplayList(params = {}) { + // Fetch Favorite List + async fetchFavoriteList() { try { - setSessionFilter(cleanSessionFilters(params)); - const data = await sessionService.getAutoplayList(params); - const list = [...this.sessionIds, ...data.map((s) => s.sessionId)]; - this.sessionIds = list.filter((id, ind) => list.indexOf(id) === ind); + const data = await sessionService.getFavoriteSessions(); + this.favoriteList = data.map((s: any) => new Session(s)); } catch (e) { console.error(e); } } + // Fetch Session Clickmap + async fetchSessionClickmap(sessionId: string, params: any) { + try { + const data = await sessionService.getSessionClickmap(sessionId, params); + this.insights = data; + } catch (e) { + console.error(e); + } + } + + // Set Autoplay Values setAutoplayValues() { const currentId = this.current.sessionId; const currentIndex = this.sessionIds.indexOf(currentId); - this.previousId = this.sessionIds[currentIndex - 1]; - this.nextId = this.sessionIds[currentIndex + 1]; + this.previousId = this.sessionIds[currentIndex - 1] || ''; + this.nextId = this.sessionIds[currentIndex + 1] || ''; } + // Set Event Query setEventQuery(filter: { query: string }) { const events = this.current.events; const query = filter.query; const searchRe = getRE(query, 'i'); const filteredEvents = query - ? events.filter( - (e) => - searchRe.test(e.url) || - searchRe.test(e.value) || - searchRe.test(e.label) || - searchRe.test(e.type) || - (e.type === 'LOCATION' && searchRe.test('visited')) - ) - : null; + ? events.filter( + (e) => + searchRe.test(e.url) || + searchRe.test(e.value) || + searchRe.test(e.label) || + searchRe.test(e.type) || + (e.type === 'LOCATION' && searchRe.test('visited')) + ) + : null; this.filteredEvents = filteredEvents; this.eventsQuery = query; } + // Toggle Favorite async toggleFavorite(id: string) { try { const r = await sessionService.toggleFavorite(id); @@ -341,22 +290,20 @@ export default class SessionStore { const wasInFavorite = this.favoriteList.findIndex(({ sessionId }) => sessionId === id) > -1; - if (session && !wasInFavorite) { - session.favorite = true; + if (session) { + session.favorite = !wasInFavorite; this.list[sessionIdx] = session; } if (current.sessionId === id) { this.current.favorite = !wasInFavorite; } - if (session) { - if (wasInFavorite) { - this.favoriteList = this.favoriteList.filter( - ({ sessionId }) => sessionId !== id - ); - } else { - this.favoriteList.push(session); - } + if (wasInFavorite) { + this.favoriteList = this.favoriteList.filter( + ({ sessionId }) => sessionId !== id + ); + } else { + this.favoriteList.push(session); } } else { console.error(r); @@ -366,7 +313,7 @@ export default class SessionStore { } } - sortSessions(sortKey: string, sign: number) { + sortSessions(sortKey: string, sign: number = 1) { const comparator = (s1: Session, s2: Session) => { // @ts-ignore let diff = s1[sortKey] - s2[sortKey]; @@ -374,49 +321,36 @@ export default class SessionStore { return sign * diff; }; - this.list = this.list.sort(comparator); - this.favoriteList = this.favoriteList.sort(comparator); - return; + this.list = this.list.slice().sort(comparator); + this.favoriteList = this.favoriteList.slice().sort(comparator); } - setActiveTab(tab: { type: string }) { + // Set Active Tab + setActiveTab(tab: { type: string, name: string }) { const list = tab.type === 'all' - ? this.list - : this.list.filter((s) => s.issueTypes.includes(tab.type)); + ? this.list + : this.list.filter((s) => s.issueTypes.includes(tab.type)); - // @ts-ignore this.activeTab = tab; this.sessionIds = list.map((s) => s.sessionId); } + // Set Timezone setTimezone(tz: string) { this.timezone = tz; } - toggleChatWindow(isActive: boolean) { - this.showChatWindow = isActive; - } - - async fetchInsights(filters = {}) { + // Fetch Insights + async fetchInsights(params = {}) { try { - const data = await sessionService.getClickMap(filters); - - this.insights = data; + const data = await sessionService.getClickMap(params); + this.insights = data.sort((a: any, b: any) => b.count - a.count); } catch (e) { console.error(e); } } - setFunnelPage(page = {}) { - this.funnelPage = page || false; - } - - /* @deprecated */ - setTimelinePointer(pointer: {}) { - this.timelinePointer = pointer; - } - setTimelineTooltip(tp: { time: number; offset: number; @@ -427,33 +361,37 @@ export default class SessionStore { this.timeLineTooltip = tp; } - filterOutNote(noteId: string) { - const current = this.current; + // Set Create Note Tooltip + setCreateNoteTooltip(noteTooltip: any) { + this.createNoteTooltip = noteTooltip; + } - current.notesWithEvents = current.notesWithEvents.filter((n) => { + // Set Edit Note Tooltip + setEditNoteTooltip(noteTooltip: any) { + this.createNoteTooltip = noteTooltip; + } + + // Filter Out Note + filterOutNote(noteId: string) { + this.current.notesWithEvents = this.current.notesWithEvents.filter((item) => { if ('noteId' in item) { return item.noteId !== noteId; } return true; }); - - this.current = current; } + // Add Note addNote(note: Note) { - const current = this.current; - - current.notesWithEvents.push(note); - current.notesWithEvents.sort((a, b) => { + this.current.notesWithEvents.push(note); + this.current.notesWithEvents.sort((a, b) => { const aTs = a.time || a.timestamp; const bTs = b.time || b.timestamp; - return aTs - bTs; }); - - this.current = current; } + // Update Note updateNote(note: Note) { const noteIndex = this.current.notesWithEvents.findIndex((item) => { if ('noteId' in item) { @@ -462,18 +400,46 @@ export default class SessionStore { return false; }); - this.current.notesWithEvents[noteIndex] = note; + if (noteIndex !== -1) { + this.current.notesWithEvents[noteIndex] = note; + } } + // Set Session Path setSessionPath(path = {}) { this.sessionPath = path; } - setLastPlayed(sessionId: string) { - const list = this.list; - const sIndex = list.findIndex((s) => s.sessionId === sessionId); + // Update Last Played Session + updateLastPlayedSession(sessionId: string) { + const sIndex = this.list.findIndex((s) => s.sessionId === sessionId); if (sIndex !== -1) { this.list[sIndex].viewed = true; } } + + // Clear Current Session + clearCurrentSession() { + this.current = new Session(); + this.eventsIndex = []; + this.visitedEvents = []; + this.host = ''; + } + + prefetchSession(sessionData: Session) { + this.current = sessionData; + this.prefetched = true; + } + + setCustomSession(session: Session) { + this.current = session; + // If additional filter logic is needed, implement here + } +} + +// Helper function to simulate useReducerData +function useReducerData(path: string): any { + // Implement this function based on your application's context + // For now, we'll return an empty object + return {}; }