From a32155efc3154f79c99433cb5f33299fe446d9c3 Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Tue, 30 Apr 2024 16:44:20 +0200 Subject: [PATCH] fix ui: reformat old files (saas consistency) --- frontend/app/components/Session/WebPlayer.tsx | 88 +- frontend/app/duck/search.js | 634 +++++----- frontend/app/duck/sessions.ts | 1047 +++++++++-------- frontend/app/mstore/sessionStore.ts | 313 ++--- 4 files changed, 1150 insertions(+), 932 deletions(-) diff --git a/frontend/app/components/Session/WebPlayer.tsx b/frontend/app/components/Session/WebPlayer.tsx index 08ef53589..eb8e28fa9 100644 --- a/frontend/app/components/Session/WebPlayer.tsx +++ b/frontend/app/components/Session/WebPlayer.tsx @@ -7,43 +7,50 @@ import { connect } from 'react-redux'; import { useParams } from 'react-router-dom'; import { toast } from 'react-toastify'; - - import { useStore } from 'App/mstore'; import { Note } from 'App/services/NotesService'; import { closeBottomBlock, toggleFullscreen } from 'Duck/components/player'; import { fetchList } from 'Duck/integrations'; import { Loader, Modal } from 'UI'; - - import ReadNote from '../Session_/Player/Controls/components/ReadNote'; import PlayerBlockHeader from './Player/ReplayPlayer/PlayerBlockHeader'; import PlayerContent from './Player/ReplayPlayer/PlayerContent'; -import { IPlayerContext, PlayerContext, defaultContextValue } from './playerContext'; - +import { + IPlayerContext, + PlayerContext, + defaultContextValue, +} from './playerContext'; const TABS = { EVENTS: 'Activity', CLICKMAP: 'Click map', - INSPECTOR: 'Tag' + INSPECTOR: 'Tag', }; const UXTTABS = { - EVENTS: TABS.EVENTS + EVENTS: TABS.EVENTS, }; let playerInst: IPlayerContext['player'] | undefined; function WebPlayer(props: any) { - const { session, toggleFullscreen, closeBottomBlock, fullscreen, fetchList, startedAt } = props; + const { + session, + toggleFullscreen, + closeBottomBlock, + fullscreen, + fetchList, + startedAt, + } = props; const { notesStore, sessionStore, uxtestingStore } = useStore(); const [activeTab, setActiveTab] = useState(''); const [noteItem, setNoteItem] = useState(undefined); const [visuallyAdjusted, setAdjusted] = useState(false); const [windowActive, setWindowActive] = useState(!document.hidden); // @ts-ignore - const [contextValue, setContextValue] = useState(defaultContextValue); + const [contextValue, setContextValue] = + useState(defaultContextValue); const params: { sessionId: string } = useParams(); const [fullView, setFullView] = useState(false); @@ -54,15 +61,17 @@ function WebPlayer(props: any) { setWindowActive(true); document.removeEventListener('visibilitychange', handleActivation); } - } + }; document.addEventListener('visibilitychange', handleActivation); } - }, []) + }, []); useEffect(() => { playerInst = undefined; if (!session.sessionId || contextValue.player !== undefined) return; - const mobData = sessionStore.prefetchedMobUrls[session.sessionId] as Record | undefined; + const mobData = sessionStore.prefetchedMobUrls[session.sessionId] as + | Record + | undefined; const usePrefetched = props.prefetched && mobData?.data; fetchList('issues'); sessionStore.setUserTimezone(session.timezone); @@ -70,11 +79,11 @@ function WebPlayer(props: any) { session, (state) => makeAutoObservable(state), toast, - props.prefetched, + props.prefetched ); if (usePrefetched) { if (mobData?.data) { - WebPlayerInst.preloadFirstFile(mobData?.data) + WebPlayerInst.preloadFirstFile(mobData?.data); } } setContextValue({ player: WebPlayerInst, store: PlayerStore }); @@ -96,17 +105,26 @@ function WebPlayer(props: any) { useEffect(() => { if (!props.prefetched && session.domURL.length > 0) { - playerInst?.reinit(session) + playerInst?.reinit(session); } - }, [session.domURL.length, props.prefetched]) + }, [session.domURL.length, props.prefetched]); - const { firstVisualEvent: visualOffset, messagesProcessed, tabStates, ready } = contextValue.store?.get() || {}; - const cssLoading = ready && tabStates ? Object.values(tabStates).some( - ({ cssLoading }) => cssLoading - ) : true + const { + firstVisualEvent: visualOffset, + messagesProcessed, + tabStates, + ready, + } = contextValue.store?.get() || {}; + const cssLoading = + ready && tabStates + ? Object.values(tabStates).some(({ cssLoading }) => cssLoading) + : true; React.useEffect(() => { - if (messagesProcessed && (session.events.length > 0 || session.errors.length > 0)) { + if ( + messagesProcessed && + (session.events.length > 0 || session.errors.length > 0) + ) { contextValue.player?.updateLists?.(session); } }, [session.events, session.errors, contextValue.player, messagesProcessed]); @@ -119,7 +137,8 @@ function WebPlayer(props: any) { if (jumpToTime || shouldAdjustOffset) { if (jumpToTime > visualOffset) { - const diff = startedAt < jumpToTime ? jumpToTime - startedAt : jumpToTime + const diff = + startedAt < jumpToTime ? jumpToTime - startedAt : jumpToTime; contextValue.player.jump(Math.max(diff, 0)); } else { contextValue.player.jump(visualOffset); @@ -136,7 +155,7 @@ function WebPlayer(props: any) { } else if (ready) { contextValue.player?.play(); } - }, [cssLoading, ready, noteItem]) + }, [cssLoading, ready, noteItem]); React.useEffect(() => { if (activeTab === 'Click map') { @@ -168,7 +187,7 @@ function WebPlayer(props: any) { }; useEffect(() => { - const isFullView = new URLSearchParams(location.search).get('fullview') + const isFullView = new URLSearchParams(location.search).get('fullview'); setFullView(isFullView === 'true'); }, [session.sessionId]); @@ -181,7 +200,7 @@ function WebPlayer(props: any) { top: '50%', left: '50%', transform: 'translateX(-50%)', - height: 75 + height: 75, }} /> ); @@ -207,12 +226,21 @@ function WebPlayer(props: any) { /> ) : ( )} {noteItem !== undefined ? ( - + ) : null} @@ -229,11 +257,11 @@ export default connect( fullscreen: state.getIn(['components', 'player', 'fullscreen']), showEvents: state.get('showEvents'), members: state.getIn(['members', 'list']), - startedAt: state.getIn(['sessions', 'current']).startedAt || 0 + startedAt: state.getIn(['sessions', 'current']).startedAt || 0, }), { toggleFullscreen, closeBottomBlock, - fetchList + fetchList, } )(withLocationHandlers()(observer(WebPlayer))); diff --git a/frontend/app/duck/search.js b/frontend/app/duck/search.js index b968cf67e..9a2c9bf07 100644 --- a/frontend/app/duck/search.js +++ b/frontend/app/duck/search.js @@ -1,22 +1,30 @@ -import { List, Map } from 'immutable'; -import { fetchListType, fetchType, saveType, removeType, editType } from './funcTools/crud'; -import { createRequestReducer, ROOT_KEY } from './funcTools/request'; -import { array, success, mergeReducers } from './funcTools/tools'; +import Period, { CUSTOM_RANGE } from 'Types/app/period'; import Filter from 'Types/filter'; -import SavedFilter from 'Types/filter/savedFilter'; -import { errors as errorsRoute, isRoute } from 'App/routes'; -import { fetchList as fetchSessionList, fetchAutoplayList } from './sessions'; -import { fetchList as fetchErrorsList } from './errors'; import { FilterCategory, FilterKey } from 'Types/filter/filterType'; import { - filtersMap, - liveFiltersMap, - conditionalFiltersMap, - generateFilterOptions, - mobileConditionalFiltersMap, -} from "Types/filter/newFilter"; + conditionalFiltersMap, + filtersMap, + generateFilterOptions, + liveFiltersMap, + mobileConditionalFiltersMap, +} from 'Types/filter/newFilter'; +import SavedFilter from 'Types/filter/savedFilter'; +import { List, Map } from 'immutable'; + import { DURATION_FILTER } from 'App/constants/storageKeys'; -import Period, { CUSTOM_RANGE } from 'Types/app/period'; +import { errors as errorsRoute, isRoute } from 'App/routes'; + +import { fetchList as fetchErrorsList } from './errors'; +import { + editType, + fetchListType, + fetchType, + removeType, + saveType, +} from './funcTools/crud'; +import { ROOT_KEY, createRequestReducer } from './funcTools/request'; +import { array, mergeReducers, success } from './funcTools/tools'; +import { fetchAutoplayList, fetchList as fetchSessionList } from './sessions'; const ERRORS_ROUTE = errorsRoute(); @@ -41,182 +49,216 @@ const SET_SCROLL_POSITION = `${name}/SET_SCROLL_POSITION`; const REFRESH_FILTER_OPTIONS = 'filters/REFRESH_FILTER_OPTIONS'; const CHECK_LATEST = fetchListType(`${name}/CHECK_LATEST`); -const UPDATE_LATEST_REQUEST_TIME = 'filters/UPDATE_LATEST_REQUEST_TIME' +const UPDATE_LATEST_REQUEST_TIME = 'filters/UPDATE_LATEST_REQUEST_TIME'; const initialState = Map({ - filterList: generateFilterOptions(filtersMap), - filterListLive: generateFilterOptions(liveFiltersMap), - filterListConditional: generateFilterOptions(conditionalFiltersMap), - filterListMobileConditional: generateFilterOptions(mobileConditionalFiltersMap), - list: List(), - latestRequestTime: null, - latestList: List(), - alertMetricId: null, - instance: new Filter({ filters: [] }), - savedSearch: new SavedFilter({}), - filterSearchList: {}, - currentPage: 1, - pageSize: PER_PAGE, - activeTab: { name: 'All', type: 'all' }, - scrollY: 0, + filterList: generateFilterOptions(filtersMap), + filterListLive: generateFilterOptions(liveFiltersMap), + filterListConditional: generateFilterOptions(conditionalFiltersMap), + filterListMobileConditional: generateFilterOptions( + mobileConditionalFiltersMap + ), + list: List(), + latestRequestTime: null, + latestList: List(), + alertMetricId: null, + instance: new Filter({ filters: [] }), + savedSearch: new SavedFilter({}), + filterSearchList: {}, + currentPage: 1, + pageSize: PER_PAGE, + activeTab: { name: 'All', type: 'all' }, + scrollY: 0, }); // Metric - Series - [] - filters function reducer(state = initialState, action = {}) { - switch (action.type) { - case REFRESH_FILTER_OPTIONS: - return state - .set('filterList', generateFilterOptions(filtersMap)) - .set('filterListLive', generateFilterOptions(liveFiltersMap)) - .set('filterListConditional', generateFilterOptions(conditionalFiltersMap)) - .set('filterListMobileConditional', generateFilterOptions(mobileConditionalFiltersMap)) - case EDIT: - return state.mergeIn(['instance'], action.instance).set('currentPage', 1); - case APPLY: - if (action.fromUrl) { - return state.set('instance', Filter(action.filter)).set('currentPage', 1) - } else { - return action.filter ? state.mergeIn(['instance'], action.filter).set('currentPage', 1) : state.mergeIn(['instance'], action.filter) - } - case success(FETCH): - return state.set('instance', action.data); - case success(FETCH_LIST): - const { data } = action; - return state.set( - 'list', - List(data.map(SavedFilter)).sortBy((i) => i.searchId) - ); - case UPDATE_LATEST_REQUEST_TIME: - return state.set('latestRequestTime', Date.now()).set('latestList', []) - case success(CHECK_LATEST): - return state.set('latestList', action.data) - case success(FETCH_FILTER_SEARCH): - const groupedList = action.data.reduce((acc, item) => { - const { projectId, type, value } = item; - const key = type; - if (!acc[key]) { - acc[key] = []; - } - acc[key].push({ projectId, value }); - return acc; - }, {}); - return state.set('filterSearchList', groupedList); - case APPLY_SAVED_SEARCH: - return state.set('savedSearch', action.filter); - case CLEAR_SEARCH: - return state.set('savedSearch', new SavedFilter({})); - case EDIT_SAVED_SEARCH: - return state.mergeIn(['savedSearch'], action.instance); - case UPDATE_CURRENT_PAGE: - return state.set('currentPage', action.page); - case SET_ACTIVE_TAB: - return state.set('activeTab', action.tab).set('currentPage', 1); - case SET_SCROLL_POSITION: - return state.set('scrollY', action.scrollPosition); - } - return state; + switch (action.type) { + case REFRESH_FILTER_OPTIONS: + return state + .set('filterList', generateFilterOptions(filtersMap)) + .set('filterListLive', generateFilterOptions(liveFiltersMap)) + .set( + 'filterListConditional', + generateFilterOptions(conditionalFiltersMap) + ) + .set( + 'filterListMobileConditional', + generateFilterOptions(mobileConditionalFiltersMap) + ); + case EDIT: + return state.mergeIn(['instance'], action.instance).set('currentPage', 1); + case APPLY: + if (action.fromUrl) { + return state + .set('instance', Filter(action.filter)) + .set('currentPage', 1); + } else { + return action.filter + ? state.mergeIn(['instance'], action.filter).set('currentPage', 1) + : state.mergeIn(['instance'], action.filter); + } + case success(FETCH): + return state.set('instance', action.data); + case success(FETCH_LIST): + const { data } = action; + return state.set( + 'list', + List(data.map(SavedFilter)).sortBy((i) => i.searchId) + ); + case UPDATE_LATEST_REQUEST_TIME: + return state.set('latestRequestTime', Date.now()).set('latestList', []); + case success(CHECK_LATEST): + return state.set('latestList', action.data); + case success(FETCH_FILTER_SEARCH): + const groupedList = action.data.reduce((acc, item) => { + const { projectId, type, value } = item; + const key = type; + if (!acc[key]) { + acc[key] = []; + } + acc[key].push({ projectId, value }); + return acc; + }, {}); + return state.set('filterSearchList', groupedList); + case APPLY_SAVED_SEARCH: + return state.set('savedSearch', action.filter); + case CLEAR_SEARCH: + return state.set('savedSearch', new SavedFilter({})); + case EDIT_SAVED_SEARCH: + return state.mergeIn(['savedSearch'], action.instance); + case UPDATE_CURRENT_PAGE: + return state.set('currentPage', action.page); + case SET_ACTIVE_TAB: + return state.set('activeTab', action.tab).set('currentPage', 1); + case SET_SCROLL_POSITION: + return state.set('scrollY', action.scrollPosition); + } + return state; } export default mergeReducers( - reducer, - createRequestReducer({ - [ROOT_KEY]: FETCH_LIST, - fetch: FETCH, - fetchFilterSearch: FETCH_FILTER_SEARCH, - }) + reducer, + createRequestReducer({ + [ROOT_KEY]: FETCH_LIST, + fetch: FETCH, + fetchFilterSearch: FETCH_FILTER_SEARCH, + }) ); const checkValues = (key, value) => { - if (key === FilterKey.DURATION) { - return value[0] === '' || value[0] === null ? [0, value[1]] : value; - } - return value.filter((i) => i !== '' && i !== null); + if (key === FilterKey.DURATION) { + return value[0] === '' || value[0] === null ? [0, value[1]] : value; + } + return value.filter((i) => i !== '' && i !== null); }; export const checkFilterValue = (value) => { - return Array.isArray(value) ? (value.length === 0 ? [''] : value) : [value]; + return Array.isArray(value) ? (value.length === 0 ? [''] : value) : [value]; }; -export const filterMap = ({ category, value, key, operator, sourceOperator, source, custom, isEvent, filters, sort, order }) => ({ - value: checkValues(key, value), - custom, - type: category === FilterCategory.METADATA ? FilterKey.METADATA : key, - operator, - source: category === FilterCategory.METADATA ? key.replace(/^_/, '') : source, - sourceOperator, - isEvent, - filters: filters ? filters.map(filterMap) : [], +export const filterMap = ({ + category, + value, + key, + operator, + sourceOperator, + source, + custom, + isEvent, + filters, + sort, + order, +}) => ({ + value: checkValues(key, value), + custom, + type: category === FilterCategory.METADATA ? FilterKey.METADATA : key, + operator, + source: category === FilterCategory.METADATA ? key.replace(/^_/, '') : source, + sourceOperator, + isEvent, + filters: filters ? filters.map(filterMap) : [], }); - const getFilters = (state) => { - const filter = state.getIn(['search', 'instance']).toData(); - const activeTab = state.getIn(['search', 'activeTab']); - if (activeTab.type !== 'all' && activeTab.type !== 'bookmark' && activeTab.type !== 'vault') { - const tmpFilter = filtersMap[FilterKey.ISSUE]; - tmpFilter.value = [activeTab.type]; - filter.filters = filter.filters.concat(tmpFilter); + const filter = state.getIn(['search', 'instance']).toData(); + const activeTab = state.getIn(['search', 'activeTab']); + if ( + activeTab.type !== 'all' && + activeTab.type !== 'bookmark' && + activeTab.type !== 'vault' + ) { + const tmpFilter = filtersMap[FilterKey.ISSUE]; + tmpFilter.value = [activeTab.type]; + filter.filters = filter.filters.concat(tmpFilter); + } + + if (activeTab.type === 'bookmark' || activeTab.type === 'vault') { + filter.bookmarked = true; + } + + filter.filters = filter.filters.map(filterMap); + + // duration filter from local storage + if (!filter.filters.find((f) => f.type === FilterKey.DURATION)) { + const durationFilter = JSON.parse( + localStorage.getItem(DURATION_FILTER) || '{"count": 0}' + ); + let durationValue = parseInt(durationFilter.count); + if (durationValue > 0) { + const value = [0]; + durationValue = + durationFilter.countType === 'min' + ? durationValue * 60 * 1000 + : durationValue * 1000; + if (durationFilter.operator === '<') { + value[0] = durationValue; + } else if (durationFilter.operator === '>') { + value[1] = durationValue; + } + + filter.filters = filter.filters.concat({ + type: FilterKey.DURATION, + operator: 'is', + value, + }); } + } - if (activeTab.type === 'bookmark' || activeTab.type === 'vault') { - filter.bookmarked = true; - } - - filter.filters = filter.filters.map(filterMap); - - // duration filter from local storage - if (!filter.filters.find((f) => f.type === FilterKey.DURATION)) { - const durationFilter = JSON.parse(localStorage.getItem(DURATION_FILTER) || '{"count": 0}'); - let durationValue = parseInt(durationFilter.count); - if (durationValue > 0) { - const value = [0]; - durationValue = durationFilter.countType === 'min' ? durationValue * 60 * 1000 : durationValue * 1000; - if (durationFilter.operator === '<') { - value[0] = durationValue; - } else if (durationFilter.operator === '>') { - value[1] = durationValue; - } - - filter.filters = filter.filters.concat({ - type: FilterKey.DURATION, - operator: 'is', - value, - }); - } - } - - return filter; -} + return filter; +}; export const reduceThenFetchResource = - (actionCreator) => - (...args) => - (dispatch, getState) => { - dispatch(actionCreator(...args)); - const activeTab = getState().getIn(['search', 'activeTab']); - if (['notes', 'flags'].includes(activeTab.type)) return; - - const filter = getFilters(getState()); - filter.limit = PER_PAGE; - filter.page = getState().getIn(['search', 'currentPage']); + (actionCreator) => + (...args) => + (dispatch, getState) => { + dispatch(actionCreator(...args)); + const activeTab = getState().getIn(['search', 'activeTab']); + if (['notes', 'flags'].includes(activeTab.type)) return; - const forceFetch = filter.filters.length === 0 || args[1] === true; + const filter = getFilters(getState()); + filter.limit = PER_PAGE; + filter.page = getState().getIn(['search', 'currentPage']); - // reset the timestamps to latest - if (filter.rangeValue !== CUSTOM_RANGE) { - const period = new Period({ rangeName: filter.rangeValue }) - const newTimestamps = period.toJSON(); - filter.startDate = newTimestamps.startDate - filter.endDate = newTimestamps.endDate - } + const forceFetch = filter.filters.length === 0 || args[1] === true; - dispatch(updateLatestRequestTime()) - return isRoute(ERRORS_ROUTE, window.location.pathname) ? dispatch(fetchErrorsList(filter)) : dispatch(fetchSessionList(filter, forceFetch)); - }; + // reset the timestamps to latest + if (filter.rangeValue !== CUSTOM_RANGE) { + const period = new Period({ rangeName: filter.rangeValue }); + const newTimestamps = period.toJSON(); + filter.startDate = newTimestamps.startDate; + filter.endDate = newTimestamps.endDate; + } + + dispatch(updateLatestRequestTime()); + return isRoute(ERRORS_ROUTE, window.location.pathname) + ? dispatch(fetchErrorsList(filter)) + : dispatch(fetchSessionList(filter, forceFetch)); + }; export const edit = reduceThenFetchResource((instance) => ({ - type: EDIT, - instance, + type: EDIT, + instance, })); export const editDefault = (instance) => ({ @@ -225,69 +267,73 @@ export const editDefault = (instance) => ({ }); export const setActiveTab = reduceThenFetchResource((tab) => ({ - type: SET_ACTIVE_TAB, - tab, + type: SET_ACTIVE_TAB, + tab, })); export const remove = (id) => (dispatch, getState) => { - return dispatch({ - types: REMOVE.array, - call: (client) => client.delete(`/saved_search/${id}`), - id, - }).then(() => { - dispatch(applySavedSearch(new SavedFilter({}))); - dispatch(fetchList()); - }); + return dispatch({ + types: REMOVE.array, + call: (client) => client.delete(`/saved_search/${id}`), + id, + }).then(() => { + dispatch(applySavedSearch(new SavedFilter({}))); + dispatch(fetchList()); + }); }; // export const remove = createRemove(name, (id) => `/saved_search/${id}`); export const applyFilter = reduceThenFetchResource((filter, force = false) => ({ - type: APPLY, - filter, - force, + type: APPLY, + filter, + force, })); export const updateFilter = (filter, force = false, resetPage = true) => ({ - type: APPLY, - filter, - force, - resetPage, + type: APPLY, + filter, + force, + resetPage, }); export const updateCurrentPage = reduceThenFetchResource((page) => ({ - type: UPDATE_CURRENT_PAGE, - page, + type: UPDATE_CURRENT_PAGE, + page, })); export const applySavedSearch = (filter) => (dispatch, getState) => { - dispatch(edit({ filters: filter ? filter.filter.filters : [] })); - return dispatch({ - type: APPLY_SAVED_SEARCH, - filter, - }); + dispatch(edit({ filters: filter ? filter.filter.filters : [] })); + return dispatch({ + type: APPLY_SAVED_SEARCH, + filter, + }); }; -export const fetchSessions = (filter, force = false) => (dispatch, getState) => { +export const fetchSessions = + (filter, force = false) => + (dispatch, getState) => { const _filter = filter ? filter : getState().getIn(['search', 'instance']); return dispatch(applyFilter(_filter, force)); -}; + }; export const updateSeries = (index, series) => ({ - type: UPDATE, - index, - series, + type: UPDATE, + index, + series, }); export function fetch(id) { - return { - id, - types: array(FETCH), - call: (c) => c.get(`/errors/${id}`), - }; + return { + id, + types: array(FETCH), + call: (c) => c.get(`/errors/${id}`), + }; } -export const save = (id, rename = false) => (dispatch, getState) => { +export const save = + (id, rename = false) => + (dispatch, getState) => { const filter = getState().getIn(['search', 'instance']).toData(); // filter.filters = filter.filters.map(filterMap); const isNew = !id; @@ -296,68 +342,72 @@ export const save = (id, rename = false) => (dispatch, getState) => { const newInstance = rename ? instance : { ...instance, filter }; newInstance.filter.filters = newInstance.filter.filters.map(filterMap); return dispatch({ - types: SAVE.array, - call: (client) => client.post(isNew ? '/saved_search' : `/saved_search/${id}`, newInstance), + types: SAVE.array, + call: (client) => + client.post( + isNew ? '/saved_search' : `/saved_search/${id}`, + newInstance + ), }).then(() => { - dispatch(fetchList()).then(() => { - if (isNew) { - const lastSavedSearch = getState().getIn(['search', 'list']).last(); - dispatch(applySavedSearch(lastSavedSearch)); - } - }); + dispatch(fetchList()).then(() => { + if (isNew) { + const lastSavedSearch = getState().getIn(['search', 'list']).last(); + dispatch(applySavedSearch(lastSavedSearch)); + } + }); }); -}; + }; export function fetchList() { - return { - types: array(FETCH_LIST), - call: (client) => client.get(`/saved_search`), - }; + return { + types: array(FETCH_LIST), + call: (client) => client.get(`/saved_search`), + }; } export function setAlertMetricId(id) { - return { - type: SET_ALERT_METRIC_ID, - id, - }; + return { + type: SET_ALERT_METRIC_ID, + id, + }; } export function fetchFilterSearch(params) { - return { - types: FETCH_FILTER_SEARCH.array, - call: (client) => client.get('/events/search', params), - params, - }; + return { + types: FETCH_FILTER_SEARCH.array, + call: (client) => client.get('/events/search', params), + params, + }; } export const clearSearch = () => (dispatch, getState) => { - const instance = getState().getIn(['search', 'instance']); - dispatch( - edit( - new Filter({ - rangeValue: instance.rangeValue, - startDate: instance.startDate, - endDate: instance.endDate, - filters: [], - }) - ) - ); - return dispatch({ - type: CLEAR_SEARCH, - }); + const instance = getState().getIn(['search', 'instance']); + dispatch( + edit( + new Filter({ + rangeValue: instance.rangeValue, + startDate: instance.startDate, + endDate: instance.endDate, + filters: [], + }) + ) + ); + return dispatch({ + type: CLEAR_SEARCH, + }); }; export const hasFilterApplied = (filters, filter) => { - return !filter.isEvent && filters.some((f) => f.key === filter.key); + return !filter.isEvent && filters.some((f) => f.key === filter.key); }; export const getAppliedFilterIndex = (filters, filterToFind) => { - if (!filterToFind.isEvent) { - return filters.findIndex((filter) => filter.key === filterToFind.key); - } - return -1; - }; - + if (!filterToFind.isEvent) { + return filters.findIndex((filter) => filter.key === filterToFind.key); + } + return -1; +}; + export const addFilter = (filter) => (dispatch, getState) => { const instance = getState().getIn(['search', 'instance']); const filters = instance.get('filters'); @@ -388,67 +438,77 @@ export const addFilter = (filter) => (dispatch, getState) => { }; export const addFilterByKeyAndValue = - (key, value, operator = undefined, sourceOperator = undefined, source = undefined) => - (dispatch, getState) => { - let defaultFilter = {...filtersMap[key]}; - defaultFilter.value = value; - if (operator) { - defaultFilter.operator = operator; - } - if (defaultFilter.hasSource && source && sourceOperator) { - defaultFilter.sourceOperator = sourceOperator; - defaultFilter.source = source; - } + ( + key, + value, + operator = undefined, + sourceOperator = undefined, + source = undefined + ) => + (dispatch, getState) => { + let defaultFilter = { ...filtersMap[key] }; + defaultFilter.value = value; + if (operator) { + defaultFilter.operator = operator; + } + if (defaultFilter.hasSource && source && sourceOperator) { + defaultFilter.sourceOperator = sourceOperator; + defaultFilter.source = source; + } - dispatch(addFilter(defaultFilter)); - }; + dispatch(addFilter(defaultFilter)); + }; export const editSavedSearch = (instance) => { - return { - type: EDIT_SAVED_SEARCH, - instance, - }; + return { + type: EDIT_SAVED_SEARCH, + instance, + }; }; export const refreshFilterOptions = () => { - return { - type: REFRESH_FILTER_OPTIONS, - }; + return { + type: REFRESH_FILTER_OPTIONS, + }; }; export const setScrollPosition = (scrollPosition) => { - return { - type: SET_SCROLL_POSITION, - scrollPosition, - }; + return { + type: SET_SCROLL_POSITION, + scrollPosition, + }; }; export const updateLatestRequestTime = () => { - return { - type: UPDATE_LATEST_REQUEST_TIME - } -} + return { + type: UPDATE_LATEST_REQUEST_TIME, + }; +}; export const checkForLatestSessions = () => (dispatch, getState) => { - const state = getState(); - const filter = getFilters(state); - const latestRequestTime = state.getIn(['search', 'latestRequestTime']) - if (!!latestRequestTime) { - const period = new Period({ rangeName: CUSTOM_RANGE, start: latestRequestTime, end: Date.now() }) - const newTimestamps = period.toJSON(); - filter.startDate = newTimestamps.startDate - filter.endDate = newTimestamps.endDate - } - - return dispatch({ - types: array(CHECK_LATEST), - call: (client) => client.post(`/sessions/search/ids`, filter), + const state = getState(); + const filter = getFilters(state); + const latestRequestTime = state.getIn(['search', 'latestRequestTime']); + if (!!latestRequestTime) { + const period = new Period({ + rangeName: CUSTOM_RANGE, + start: latestRequestTime, + end: Date.now(), }); -} + const newTimestamps = period.toJSON(); + filter.startDate = newTimestamps.startDate; + filter.endDate = newTimestamps.endDate; + } + + return dispatch({ + types: array(CHECK_LATEST), + call: (client) => client.post(`/sessions/search/ids`, filter), + }); +}; export const fetchAutoplaySessions = (page) => (dispatch, getState) => { - const filter = getFilters(getState()); - filter.page = page; - filter.limit = PER_PAGE; - return dispatch(fetchAutoplayList(filter)); -} \ No newline at end of file + const filter = getFilters(getState()); + filter.page = page; + filter.limit = PER_PAGE; + return dispatch(fetchAutoplayList(filter)); +}; diff --git a/frontend/app/duck/sessions.ts b/frontend/app/duck/sessions.ts index 9c434a54c..f236d4d5f 100644 --- a/frontend/app/duck/sessions.ts +++ b/frontend/app/duck/sessions.ts @@ -1,23 +1,31 @@ -import { List, Map } from 'immutable'; +import { LAST_7_DAYS } from 'Types/app/period'; import Session from 'Types/session'; import ErrorStack from 'Types/session/errorStack'; -import { EventData, Location } from "Types/session/event"; +import { EventData, Location } from 'Types/session/event'; import Watchdog from 'Types/watchdog'; +import { List, Map } from 'immutable'; + import { clean as cleanParams } from 'App/api_client'; -import withRequestState, { RequestTypes } from './requestStateCreator'; -import { getRE, setSessionFilter, getSessionFilter, compareJsonObjects, cleanSessionFilters } from 'App/utils'; -import { LAST_7_DAYS } from 'Types/app/period'; -import { getDateRangeFromValue } from 'App/dateRange'; import APIClient from 'App/api_client'; -import { FETCH_ACCOUNT, UPDATE_JWT } from "Duck/user"; -import logger from "App/logger"; -import { parseError } from 'App/api_middleware' +import { parseError } from 'App/api_middleware'; +import { getDateRangeFromValue } from 'App/dateRange'; +import logger from 'App/logger'; +import { + cleanSessionFilters, + compareJsonObjects, + getRE, + getSessionFilter, + setSessionFilter, +} from 'App/utils'; +import { FETCH_ACCOUNT, UPDATE_JWT } from 'Duck/user'; + +import withRequestState, { RequestTypes } from './requestStateCreator'; const name = 'sessions'; const FETCH_LIST = new RequestTypes('sessions/FETCH_LIST'); const FETCH_AUTOPLAY_LIST = new RequestTypes('sessions/FETCH_AUTOPLAY_LIST'); -const FETCH = new RequestTypes('sessions/FETCH') -const FETCHV2 = new RequestTypes('sessions/FETCHV2') +const FETCH = new RequestTypes('sessions/FETCH'); +const FETCHV2 = new RequestTypes('sessions/FETCHV2'); const FETCH_EVENTS = new RequestTypes('sessions/FETCH_EVENTS'); const FETCH_NOTES = new RequestTypes('sessions/FETCH_NOTES'); @@ -36,570 +44,649 @@ 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'; -const SET_CREATE_NOTE_TOOLTIP = 'sessions/SET_CREATE_NOTE_TOOLTIP' -const SET_EDIT_NOTE_TOOLTIP = 'sessions/SET_EDIT_NOTE_TOOLTIP' -const FILTER_OUT_NOTE = 'sessions/FILTER_OUT_NOTE' -const ADD_NOTE = 'sessions/ADD_NOTE' -const UPDATE_NOTE = 'sessions/UPDATE_NOTE' +const SET_CREATE_NOTE_TOOLTIP = 'sessions/SET_CREATE_NOTE_TOOLTIP'; +const SET_EDIT_NOTE_TOOLTIP = 'sessions/SET_EDIT_NOTE_TOOLTIP'; +const FILTER_OUT_NOTE = 'sessions/FILTER_OUT_NOTE'; +const ADD_NOTE = 'sessions/ADD_NOTE'; +const UPDATE_NOTE = 'sessions/UPDATE_NOTE'; const SET_SESSION_PATH = 'sessions/SET_SESSION_PATH'; const LAST_PLAYED_SESSION_ID = `${name}/LAST_PLAYED_SESSION_ID`; const SET_ACTIVE_TAB = 'sessions/SET_ACTIVE_TAB'; -const CLEAR_CURRENT_SESSION = 'sessions/CLEAR_CURRENT_SESSION' +const CLEAR_CURRENT_SESSION = 'sessions/CLEAR_CURRENT_SESSION'; -const PREFETCH_SESSION = 'sessions/PREFETCH_SESSION' +const PREFETCH_SESSION = 'sessions/PREFETCH_SESSION'; const range = getDateRangeFromValue(LAST_7_DAYS); const defaultDateFilters = { - url: '', - rangeValue: LAST_7_DAYS, - startDate: range.start.unix() * 1000, - endDate: range.end.unix() * 1000, + url: '', + rangeValue: LAST_7_DAYS, + startDate: range.start.unix() * 1000, + endDate: range.end.unix() * 1000, }; const initObj = { - list: [], - sessionIds: [], - current: new Session(), - prefetched: false, - eventsAsked: false, - total: 0, - keyMap: Map(), - wdTypeCount: Map(), - favoriteList: List(), - activeTab: Watchdog({ name: 'All', type: 'all' }), - timezone: 'local', - errorStack: List(), - eventsIndex: [], - sourcemapUploaded: true, - filteredEvents: null, - eventsQuery: '', - showChatWindow: false, - liveSessions: [], - visitedEvents: List(), - insights: List(), - insightFilters: defaultDateFilters, - host: '', - funnelPage: Map(), - timelinePointer: null, - sessionPath: {}, - lastPlayedSessionId: null, - timeLineTooltip: { time: 0, offset: 0, isVisible: false, localTime: '', userTime: '' }, - createNoteTooltip: { time: 0, isVisible: false, isEdit: false, note: null }, - fetchFailed: false, -} + list: [], + sessionIds: [], + current: new Session(), + prefetched: false, + eventsAsked: false, + total: 0, + keyMap: Map(), + wdTypeCount: Map(), + favoriteList: List(), + activeTab: Watchdog({ name: 'All', type: 'all' }), + timezone: 'local', + errorStack: List(), + eventsIndex: [], + sourcemapUploaded: true, + filteredEvents: null, + eventsQuery: '', + showChatWindow: false, + liveSessions: [], + visitedEvents: List(), + insights: List(), + insightFilters: defaultDateFilters, + host: '', + funnelPage: Map(), + timelinePointer: null, + sessionPath: {}, + lastPlayedSessionId: null, + timeLineTooltip: { + time: 0, + offset: 0, + isVisible: false, + localTime: '', + userTime: '', + }, + createNoteTooltip: { time: 0, isVisible: false, isEdit: false, note: null }, + fetchFailed: false, +}; const initialState = Map(initObj); -interface IAction extends Record{ - type: string; - data: any; +interface IAction extends Record { + type: string; + data: any; } const reducer = (state = initialState, action: IAction) => { - switch (action.type) { - case FETCH_ERROR_STACK.SUCCESS: - return state.set('errorStack', List(action.data.trace).map(es => new ErrorStack(es))).set('sourcemapUploaded', action.data.sourcemapUploaded); - case FETCH_LIVE_LIST.SUCCESS: - const liveList = action.data.sessions.map((s) => new Session({ ...s, live: true })); - return state.set('liveSessions', liveList); - case FETCH_LIST.SUCCESS: - const { sessions, total } = action.data; - const list = sessions.map(s => new Session(s)); + switch (action.type) { + case FETCH_ERROR_STACK.SUCCESS: + return state + .set( + 'errorStack', + List(action.data.trace).map((es) => new ErrorStack(es)) + ) + .set('sourcemapUploaded', action.data.sourcemapUploaded); + case FETCH_LIVE_LIST.SUCCESS: + const liveList = action.data.sessions.map( + (s) => new Session({ ...s, live: true }) + ); + return state.set('liveSessions', liveList); + case FETCH_LIST.SUCCESS: + const { sessions, total } = action.data; + const list = sessions.map((s) => new Session(s)); - return state - .set('list', list) - .set('sessionIds', list.map(({ sessionId }) => sessionId)) - .set('favoriteList', list.filter(({ favorite }) => favorite)) - .set('total', total); - case FETCH_AUTOPLAY_LIST.SUCCESS: - let sessionIds = state.get('sessionIds') as []; - sessionIds = sessionIds.concat(action.data.map(i => i.sessionId + '')) - return state.set('sessionIds', sessionIds.filter((i, index) => sessionIds.indexOf(i) === index )) - case SET_AUTOPLAY_VALUES: { - const sessionIds = state.get('sessionIds'); - const currentSessionId = state.get('current').sessionId; - const currentIndex = sessionIds.indexOf(currentSessionId); - return state.set('previousId', sessionIds[currentIndex - 1]).set('nextId', sessionIds[currentIndex + 1]); - } - case SET_EVENT_QUERY: { - const events = state.get('current').events; - const query = action.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; - - return state.set('filteredEvents', filteredEvents).set('eventsQuery', query); - } - case CLEAR_CURRENT_SESSION: { - const session = new Session(); - - return state.set('current', session) - .set('eventsIndex', []) - .set('visitedEvents', List()) - .set('host', ''); - } - case FETCH.FAILURE: - return state.set('fetchFailed', true); - case FETCH.SUCCESS: { - // TODO: more common.. or TEMP filters', 'appliedFilter - const events = action.filter.events; - const session = new Session(action.data); - - const matching: number[] = []; - - const visitedEvents: Location[] = []; - const tmpMap = new Set(); - session.events.forEach((event) => { - if (event.type === 'LOCATION' && !tmpMap.has(event.url)) { - tmpMap.add(event.url); - visitedEvents.push(event); - } - }); - - events.forEach(({ key, operator, value }) => { - session.events.forEach((e, index) => { - if (key === e.type) { - const val = e.type === 'LOCATION' ? e.url : e.value; - if (operator === 'is' && value === val) { - matching.push(index); - } - if (operator === 'contains' && val.includes(value)) { - matching.push(index); - } - } - }); - }); - return state - .set('current', session) - .set('eventsIndex', matching) - .set('visitedEvents', visitedEvents) - .set('host', visitedEvents[0] && visitedEvents[0].host); - } - case FETCHV2.SUCCESS: { - const session = new Session(action.data); - - return state - .set('current', session) - .set('prefetched', false) - } - case PREFETCH_SESSION: { - const { data } = action; - return state - .set('current', data) - .set('prefetched', true); - } - case FETCH_EVENTS.SUCCESS: { - const { - errors, - events, - issues, - crashes, - resources, - stackEvents, - userEvents, - userTesting - } = action.data as { errors: any[], crashes: any[], events: any[], issues: any[], resources: any[], stackEvents: any[], userEvents: EventData[], userTesting: any[] }; - const filterEvents = action.filter.events as Record[]; - const session = state.get('current') as Session; - const matching: number[] = []; - - const visitedEvents: Location[] = []; - const tmpMap = new Set(); - events.forEach((event) => { - // @ts-ignore assume that event is LocationEvent - if (event.type === 'LOCATION' && !tmpMap.has(event.url)) { - // @ts-ignore assume that event is LocationEvent - tmpMap.add(event.url); - // @ts-ignore assume that event is LocationEvent - visitedEvents.push(event); - } - }); - - filterEvents.forEach(({ key, operator, value }) => { - events.forEach((e, index) => { - if (key === e.type) { - // @ts-ignore assume that event is LocationEvent - const val = e.type === 'LOCATION' ? e.url : e.value; - if (operator === 'is' && value === val) { - matching.push(index); - } - if (operator === 'contains' && val.includes(value)) { - matching.push(index); - } - } - }); - }); - - const newSession = session.addEvents( - events, - crashes, - errors, - issues, - resources, - userEvents, - stackEvents, - userTesting - ); - - const forceUpdate = state.set('current', {}) - return forceUpdate - .set('current', newSession) - .set('eventsIndex', matching) - .set('visitedEvents', visitedEvents) - .set('host', visitedEvents[0] && visitedEvents[0].host); - } - case FETCH_NOTES.SUCCESS: { - const notes = action.data; - if (notes.length > 0) { - const session = state.get('current') as Session; - const newSession = session.addNotes(notes); - const forceUpdate = state.set('current', {}) - return forceUpdate.set('current', newSession); - } - return state - } - case FETCH_FAVORITE_LIST.SUCCESS: - return state.set('favoriteList', action.data.map(s => new Session(s))); - case TOGGLE_FAVORITE.SUCCESS: { - const id = action.sessionId; - let mutableState = state - const list = state.get('list') as unknown as Session[] - const sessionIdx = list.findIndex(({ sessionId }) => sessionId === id); - const session = list[sessionIdx] - const current = state.get('current') as unknown as Session; - const wasInFavorite = state.get('favoriteList').findIndex(({ sessionId }) => sessionId === id) > -1; - - if (session && !wasInFavorite) { - session.favorite = true - mutableState = mutableState.updateIn(['list', sessionIdx], () => session) - } - if (current.sessionId === id) { - mutableState = mutableState.update('current', - (s: Session) => ({ ...s, favorite: !wasInFavorite}) - ) - } - return mutableState - .update('favoriteList', (list: Session[]) => session ? - wasInFavorite ? list.filter(({ sessionId }) => sessionId !== id) : list.push(session) : list - ); - } - case SORT: { - const comparator = (s1, s2) => { - let diff = s1[action.sortKey] - s2[action.sortKey]; - diff = diff === 0 ? s1.startedAt - s2.startedAt : diff; - return action.sign * diff; - }; - return state.update('list', (list: Session[]) => list.sort(comparator)).update('favoriteList', (list: Session[]) => list.sort(comparator)); - } - case SET_ACTIVE_TAB: - const allList = action.tab.type === 'all' ? state.get('list') : state.get('list').filter((s) => s.issueTypes.includes(action.tab.type)); - - return state.set('activeTab', action.tab).set('sessionIds', allList.map(({ sessionId }) => sessionId)); - case SET_TIMEZONE: - return state.set('timezone', action.timezone); - case TOGGLE_CHAT_WINDOW: - return state.set('showChatWindow', action.state); - 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: - return state.set('timeLineTooltip', action.timeLineTooltip); - case SET_CREATE_NOTE_TOOLTIP: - return state.set('createNoteTooltip', action.noteTooltip); - case SET_EDIT_NOTE_TOOLTIP: - return state.set('createNoteTooltip', action.noteTooltip); - case FILTER_OUT_NOTE: - return state.updateIn(['current'], - (session: Session) => ({ - ...session, - notesWithEvents: session.notesWithEvents.filter(item => { - if ('noteId' in item) { - return item.noteId !== action.noteId - } - return true - }) - }) - ) - case ADD_NOTE: - const session = state.get('current') as Session; - session.notesWithEvents = [...session.notesWithEvents, action.note].sort((a, b) => { - const aTs = a.time || a.timestamp - const bTs = b.time || b.timestamp - - return aTs - bTs - }); - return state.set('current', session); - case UPDATE_NOTE: - const currSession = state.get('current') as Session; - const noteIndex = currSession.notesWithEvents.findIndex(item => item.noteId === action.note.noteId) - currSession.notesWithEvents[noteIndex] = action.note; - return state.set('current', currSession); - case SET_SESSION_PATH: - return state.set('sessionPath', action.path); - case LAST_PLAYED_SESSION_ID: - const sessionList = state.get('list') as unknown as Session[]; - const sIndex = sessionList.findIndex(({ sessionId }) => sessionId === action.sessionId); - if (sIndex === -1) return state; - - return state.updateIn(['list', sIndex], (session: Session) => ({ ...session, viewed: true })); - default: - return state; + return state + .set('list', list) + .set( + 'sessionIds', + list.map(({ sessionId }) => sessionId) + ) + .set( + 'favoriteList', + list.filter(({ favorite }) => favorite) + ) + .set('total', total); + case FETCH_AUTOPLAY_LIST.SUCCESS: + let sessionIds = state.get('sessionIds') as []; + sessionIds = sessionIds.concat(action.data.map((i) => i.sessionId + '')); + return state.set( + 'sessionIds', + sessionIds.filter((i, index) => sessionIds.indexOf(i) === index) + ); + case SET_AUTOPLAY_VALUES: { + const sessionIds = state.get('sessionIds'); + const currentSessionId = state.get('current').sessionId; + const currentIndex = sessionIds.indexOf(currentSessionId); + return state + .set('previousId', sessionIds[currentIndex - 1]) + .set('nextId', sessionIds[currentIndex + 1]); } + case SET_EVENT_QUERY: { + const events = state.get('current').events; + const query = action.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; + + return state + .set('filteredEvents', filteredEvents) + .set('eventsQuery', query); + } + case CLEAR_CURRENT_SESSION: { + const session = new Session(); + + return state + .set('current', session) + .set('eventsIndex', []) + .set('visitedEvents', List()) + .set('host', ''); + } + case FETCH.FAILURE: + return state.set('fetchFailed', true); + case FETCH.SUCCESS: { + // TODO: more common.. or TEMP filters', 'appliedFilter + const events = action.filter.events; + const session = new Session(action.data); + + const matching: number[] = []; + + const visitedEvents: Location[] = []; + const tmpMap = new Set(); + session.events.forEach((event) => { + if (event.type === 'LOCATION' && !tmpMap.has(event.url)) { + tmpMap.add(event.url); + visitedEvents.push(event); + } + }); + + events.forEach(({ key, operator, value }) => { + session.events.forEach((e, index) => { + if (key === e.type) { + const val = e.type === 'LOCATION' ? e.url : e.value; + if (operator === 'is' && value === val) { + matching.push(index); + } + if (operator === 'contains' && val.includes(value)) { + matching.push(index); + } + } + }); + }); + return state + .set('current', session) + .set('eventsIndex', matching) + .set('visitedEvents', visitedEvents) + .set('host', visitedEvents[0] && visitedEvents[0].host); + } + case FETCHV2.SUCCESS: { + const session = new Session(action.data); + + return state.set('current', session).set('prefetched', false); + } + case PREFETCH_SESSION: { + const { data } = action; + return state.set('current', data).set('prefetched', true); + } + case FETCH_EVENTS.SUCCESS: { + const { + errors, + events, + issues, + crashes, + resources, + stackEvents, + userEvents, + userTesting, + } = action.data as { + errors: any[]; + crashes: any[]; + events: any[]; + issues: any[]; + resources: any[]; + stackEvents: any[]; + userEvents: EventData[]; + userTesting: any[]; + }; + const filterEvents = action.filter.events as Record[]; + const session = state.get('current') as Session; + const matching: number[] = []; + + const visitedEvents: Location[] = []; + const tmpMap = new Set(); + events.forEach((event) => { + // @ts-ignore assume that event is LocationEvent + if (event.type === 'LOCATION' && !tmpMap.has(event.url)) { + // @ts-ignore assume that event is LocationEvent + tmpMap.add(event.url); + // @ts-ignore assume that event is LocationEvent + visitedEvents.push(event); + } + }); + + filterEvents.forEach(({ key, operator, value }) => { + events.forEach((e, index) => { + if (key === e.type) { + // @ts-ignore assume that event is LocationEvent + const val = e.type === 'LOCATION' ? e.url : e.value; + if (operator === 'is' && value === val) { + matching.push(index); + } + if (operator === 'contains' && val.includes(value)) { + matching.push(index); + } + } + }); + }); + + const newSession = session.addEvents( + events, + crashes, + errors, + issues, + resources, + userEvents, + stackEvents, + userTesting + ); + + const forceUpdate = state.set('current', {}); + return forceUpdate + .set('current', newSession) + .set('eventsIndex', matching) + .set('visitedEvents', visitedEvents) + .set('host', visitedEvents[0] && visitedEvents[0].host); + } + case FETCH_NOTES.SUCCESS: { + const notes = action.data; + if (notes.length > 0) { + const session = state.get('current') as Session; + const newSession = session.addNotes(notes); + const forceUpdate = state.set('current', {}); + return forceUpdate.set('current', newSession); + } + return state; + } + case FETCH_FAVORITE_LIST.SUCCESS: + return state.set( + 'favoriteList', + action.data.map((s) => new Session(s)) + ); + case TOGGLE_FAVORITE.SUCCESS: { + const id = action.sessionId; + let mutableState = state; + const list = state.get('list') as unknown as Session[]; + const sessionIdx = list.findIndex(({ sessionId }) => sessionId === id); + const session = list[sessionIdx]; + const current = state.get('current') as unknown as Session; + const wasInFavorite = + state + .get('favoriteList') + .findIndex(({ sessionId }) => sessionId === id) > -1; + + if (session && !wasInFavorite) { + session.favorite = true; + mutableState = mutableState.updateIn( + ['list', sessionIdx], + () => session + ); + } + if (current.sessionId === id) { + mutableState = mutableState.update('current', (s: Session) => ({ + ...s, + favorite: !wasInFavorite, + })); + } + return mutableState.update('favoriteList', (list: Session[]) => + session + ? wasInFavorite + ? list.filter(({ sessionId }) => sessionId !== id) + : list.push(session) + : list + ); + } + case SORT: { + const comparator = (s1, s2) => { + let diff = s1[action.sortKey] - s2[action.sortKey]; + diff = diff === 0 ? s1.startedAt - s2.startedAt : diff; + return action.sign * diff; + }; + return state + .update('list', (list: Session[]) => list.sort(comparator)) + .update('favoriteList', (list: Session[]) => list.sort(comparator)); + } + case SET_ACTIVE_TAB: + const allList = + action.tab.type === 'all' + ? state.get('list') + : state + .get('list') + .filter((s) => s.issueTypes.includes(action.tab.type)); + + return state.set('activeTab', action.tab).set( + 'sessionIds', + allList.map(({ sessionId }) => sessionId) + ); + case SET_TIMEZONE: + return state.set('timezone', action.timezone); + case TOGGLE_CHAT_WINDOW: + return state.set('showChatWindow', action.state); + 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: + return state.set('timeLineTooltip', action.timeLineTooltip); + case SET_CREATE_NOTE_TOOLTIP: + return state.set('createNoteTooltip', action.noteTooltip); + case SET_EDIT_NOTE_TOOLTIP: + return state.set('createNoteTooltip', action.noteTooltip); + case FILTER_OUT_NOTE: + return state.updateIn(['current'], (session: Session) => ({ + ...session, + notesWithEvents: session.notesWithEvents.filter((item) => { + if ('noteId' in item) { + return item.noteId !== action.noteId; + } + return true; + }), + })); + case ADD_NOTE: + const session = state.get('current') as Session; + session.notesWithEvents = [...session.notesWithEvents, action.note].sort( + (a, b) => { + const aTs = a.time || a.timestamp; + const bTs = b.time || b.timestamp; + + return aTs - bTs; + } + ); + return state.set('current', session); + case UPDATE_NOTE: + const currSession = state.get('current') as Session; + const noteIndex = currSession.notesWithEvents.findIndex( + (item) => item.noteId === action.note.noteId + ); + currSession.notesWithEvents[noteIndex] = action.note; + return state.set('current', currSession); + case SET_SESSION_PATH: + return state.set('sessionPath', action.path); + case LAST_PLAYED_SESSION_ID: + const sessionList = state.get('list') as unknown as Session[]; + const sIndex = sessionList.findIndex( + ({ sessionId }) => sessionId === action.sessionId + ); + if (sIndex === -1) return state; + + return state.updateIn(['list', sIndex], (session: Session) => ({ + ...session, + viewed: true, + })); + default: + return state; + } }; export default withRequestState( - { - _: [FETCH, FETCH_LIST], - fetchLiveListRequest: FETCH_LIVE_LIST, - fetchFavoriteListRequest: FETCH_FAVORITE_LIST, - toggleFavoriteRequest: TOGGLE_FAVORITE, - fetchErrorStackList: FETCH_ERROR_STACK, - fetchInsightsRequest: FETCH_INSIGHTS, - }, - reducer + { + _: [FETCH, FETCH_LIST], + fetchLiveListRequest: FETCH_LIVE_LIST, + fetchFavoriteListRequest: FETCH_FAVORITE_LIST, + toggleFavoriteRequest: TOGGLE_FAVORITE, + fetchErrorStackList: FETCH_ERROR_STACK, + fetchInsightsRequest: FETCH_INSIGHTS, + }, + reducer ); export const fetchList = - (params = {}, force = false) => - (dispatch) => { - if (!force) { // compare with the last fetched filter - const oldFilters = getSessionFilter(); - if (compareJsonObjects(oldFilters, cleanSessionFilters(params))) { - return; - } - } + (params = {}, force = false) => + (dispatch) => { + if (!force) { + // compare with the last fetched filter + const oldFilters = getSessionFilter(); + if (compareJsonObjects(oldFilters, cleanSessionFilters(params))) { + return; + } + } - setSessionFilter(cleanSessionFilters(params)); - return dispatch({ - types: FETCH_LIST.toArray(), - call: (client) => client.post('/sessions/search', params), - params: cleanParams(params), - }); - }; + setSessionFilter(cleanSessionFilters(params)); + return dispatch({ + types: FETCH_LIST.toArray(), + call: (client) => client.post('/sessions/search', params), + params: cleanParams(params), + }); + }; export const fetchAutoplayList = - (params = {}) => - (dispatch) => { - setSessionFilter(cleanSessionFilters(params)); - return dispatch({ - types: FETCH_AUTOPLAY_LIST.toArray(), - call: (client) => client.post('/sessions/search/ids', params), - params: cleanParams(params), - }); - }; - - + (params = {}) => + (dispatch) => { + setSessionFilter(cleanSessionFilters(params)); + return dispatch({ + types: FETCH_AUTOPLAY_LIST.toArray(), + call: (client) => client.post('/sessions/search/ids', params), + params: cleanParams(params), + }); + }; export function fetchErrorStackList(sessionId, errorId) { - return { - types: FETCH_ERROR_STACK.toArray(), - call: (client) => client.get(`/sessions/${sessionId}/errors/${errorId}/sourcemaps`), - }; + return { + types: FETCH_ERROR_STACK.toArray(), + call: (client) => + client.get(`/sessions/${sessionId}/errors/${errorId}/sourcemaps`), + }; } export const fetch = - (sessionId, isLive = false) => - (dispatch, getState) => { - dispatch({ - types: FETCH.toArray(), - call: (client) => client.get(isLive ? `/assist/sessions/${sessionId}` : `/sessions/${sessionId}`), - filter: getState().getIn(['filters', 'appliedFilter']), - }); - }; + (sessionId, isLive = false) => + (dispatch, getState) => { + dispatch({ + types: FETCH.toArray(), + call: (client) => + client.get( + isLive ? `/assist/sessions/${sessionId}` : `/sessions/${sessionId}` + ), + filter: getState().getIn(['filters', 'appliedFilter']), + }); + }; // implementing custom middleware-like request to keep the behavior // TODO: move all to mobx -export const fetchV2 = (sessionId: string) => - (dispatch, getState) => { - const apiClient = new APIClient() - const apiGet = (url: string, dispatch: any, FAILURE?: string) => apiClient.get(url) - .then(async (response) => { - if (response.status === 403) { - dispatch({ type: FETCH_ACCOUNT.FAILURE }); - } - if (!response.ok) { - const text = await response.text(); - return Promise.reject(text); - } - return response.json(); - }) - .then((json) => json || {}) - .catch(async (e) => { - const data = await e.response?.json(); - logger.error('Error during API request. ', e); - return FAILURE && dispatch({ type: FAILURE, errors: data ? parseError(data.errors) : [] }); - }); - - const filter = getState().getIn(['filters', 'appliedFilter']) - apiGet(`/sessions/${sessionId}/replay`, dispatch, FETCH.FAILURE) - .then(async ({ jwt, errors, data }) => { - if (errors) { - dispatch({ type: FETCH.FAILURE, errors, data }); - } else { - dispatch({ type: FETCHV2.SUCCESS, data, ...filter }); - - const events = await apiGet(`/sessions/${sessionId}/events`, dispatch); - if (events) { - dispatch({ type: FETCH_EVENTS.SUCCESS, data: events.data, filter }); - } - } - if (jwt) { - dispatch({ type: UPDATE_JWT, data: jwt }); - } +export const fetchV2 = (sessionId: string) => (dispatch, getState) => { + const apiClient = new APIClient(); + const apiGet = (url: string, dispatch: any, FAILURE?: string) => + apiClient + .get(url) + .then(async (response) => { + if (response.status === 403) { + dispatch({ type: FETCH_ACCOUNT.FAILURE }); + } + if (!response.ok) { + const text = await response.text(); + return Promise.reject(text); + } + return response.json(); + }) + .then((json) => json || {}) + .catch(async (e) => { + const data = await e.response?.json(); + logger.error('Error during API request. ', e); + return ( + FAILURE && + dispatch({ + type: FAILURE, + errors: data ? parseError(data.errors) : [], + }) + ); }); - } + const filter = getState().getIn(['filters', 'appliedFilter']); + apiGet(`/sessions/${sessionId}/replay`, dispatch, FETCH.FAILURE).then( + async ({ jwt, errors, data }) => { + if (errors) { + dispatch({ type: FETCH.FAILURE, errors, data }); + } else { + dispatch({ type: FETCHV2.SUCCESS, data, ...filter }); - export function presetSession(sessionData) { - return { - type: PREFETCH_SESSION, - data: sessionData + const events = await apiGet(`/sessions/${sessionId}/events`, dispatch); + if (events) { + dispatch({ type: FETCH_EVENTS.SUCCESS, data: events.data, filter }); + } + } + if (jwt) { + dispatch({ type: UPDATE_JWT, data: jwt }); + } } - } + ); +}; -export function clearCurrentSession() { - return { - type: CLEAR_CURRENT_SESSION - } +export function presetSession(sessionData) { + return { + type: PREFETCH_SESSION, + data: sessionData, + }; } -export const setCustomSession = (session, filter) => - (dispatch, getState) => { dispatch({ +export function clearCurrentSession() { + return { + type: CLEAR_CURRENT_SESSION, + }; +} + +export const setCustomSession = (session, filter) => (dispatch, getState) => { + dispatch({ type: FETCH.SUCCESS, filter: getState().getIn(['filters', 'appliedFilter']), data: session, -})} + }); +}; export function toggleFavorite(sessionId) { - return { - types: TOGGLE_FAVORITE.toArray(), - call: (client) => client.get(`/sessions/${sessionId}/favorite`), - sessionId, - }; + return { + types: TOGGLE_FAVORITE.toArray(), + call: (client) => client.get(`/sessions/${sessionId}/favorite`), + sessionId, + }; } export function fetchInsights(params) { - return { - types: FETCH_INSIGHTS.toArray(), - call: (client) => client.post('/heatmaps/url', params), - }; + return { + types: FETCH_INSIGHTS.toArray(), + call: (client) => client.post('/heatmaps/url', params), + }; } export function fetchLiveList(params = {}) { - return { - types: FETCH_LIVE_LIST.toArray(), - call: (client) => client.get('/assist/sessions', params), - }; + return { + types: FETCH_LIVE_LIST.toArray(), + call: (client) => client.get('/assist/sessions', params), + }; } export function toggleChatWindow(state) { - return { - type: TOGGLE_CHAT_WINDOW, - state, - }; + return { + type: TOGGLE_CHAT_WINDOW, + state, + }; } export function sort(sortKey, sign = 1, listName = 'list') { - return { - type: SORT, - sortKey, - sign, - listName, - }; + return { + type: SORT, + sortKey, + sign, + listName, + }; } export const setAutoplayValues = (sessionId) => { - return { - type: SET_AUTOPLAY_VALUES, - sessionId, - }; + return { + type: SET_AUTOPLAY_VALUES, + sessionId, + }; }; export const setActiveTab = (tab) => ({ - type: SET_ACTIVE_TAB, - tab, + type: SET_ACTIVE_TAB, + tab, }); export function setTimezone(timezone) { - return { - type: SET_TIMEZONE, - timezone, - }; + return { + type: SET_TIMEZONE, + timezone, + }; } export function setEventFilter(filter) { - return { - type: SET_EVENT_QUERY, - filter, - }; + return { + type: SET_EVENT_QUERY, + filter, + }; } export function setFunnelPage(funnelPage) { - return { - type: SET_FUNNEL_PAGE_FLAG, - funnelPage, - }; + return { + type: SET_FUNNEL_PAGE_FLAG, + funnelPage, + }; } export function setTimelinePointer(pointer) { - return { - type: SET_TIMELINE_POINTER, - pointer, - }; + return { + type: SET_TIMELINE_POINTER, + pointer, + }; } export function setTimelineHoverTime(timeLineTooltip) { - return { - type: SET_TIMELINE_HOVER_POINTER, - timeLineTooltip - }; + return { + type: SET_TIMELINE_HOVER_POINTER, + timeLineTooltip, + }; } export function filterOutNote(noteId) { - return { - type: FILTER_OUT_NOTE, - noteId - } + return { + type: FILTER_OUT_NOTE, + noteId, + }; } export function addNote(note) { - return { - type: ADD_NOTE, - note - } + return { + type: ADD_NOTE, + note, + }; } export function updateNote(note) { - return { - type: UPDATE_NOTE, - note - } + return { + type: UPDATE_NOTE, + note, + }; } export function setSessionPath(path: any) { - return { - type: SET_SESSION_PATH, - path, - }; + return { + type: SET_SESSION_PATH, + path, + }; } export function updateLastPlayedSession(sessionId) { - return { - type: LAST_PLAYED_SESSION_ID, - sessionId, - }; + return { + type: LAST_PLAYED_SESSION_ID, + sessionId, + }; } diff --git a/frontend/app/mstore/sessionStore.ts b/frontend/app/mstore/sessionStore.ts index 2161f45ef..c9ff7c064 100644 --- a/frontend/app/mstore/sessionStore.ts +++ b/frontend/app/mstore/sessionStore.ts @@ -1,16 +1,24 @@ -import { makeAutoObservable, observable, action } from 'mobx'; -import { sessionService } from 'App/services'; -import { filterMap } from 'Duck/search'; -import Session from 'Types/session'; import Record, { LAST_7_DAYS } from 'Types/app/period'; -import Watchdog from "Types/watchdog"; +import Session from 'Types/session'; import ErrorStack from 'Types/session/errorStack'; -import { Location, InjectedEvent } from 'Types/session/event' -import { getDateRangeFromValue } from "App/dateRange"; -import { getRE, setSessionFilter, getSessionFilter, compareJsonObjects, cleanSessionFilters } from 'App/utils'; -import store from 'App/store' -import { Note } from "App/services/NotesService"; -import { loadFile } from "../player/web/network/loadFiles"; +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, + getRE, + getSessionFilter, + setSessionFilter, +} from 'App/utils'; +import { filterMap } from 'Duck/search'; + +import { loadFile } from '../player/web/network/loadFiles'; class UserFilter { endDate: number = new Date().getTime(); @@ -71,7 +79,12 @@ class DevTools { constructor() { this.network = { index: 0, filter: '', activeTab: 'ALL', isError: false }; - this.stackEvent = { 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, @@ -96,37 +109,44 @@ 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' }) - timezone = 'local' - errorStack: ErrorStack[] = [] - eventsIndex = [] - sourcemapUploaded = true - filteredEvents: InjectedEvent[] | null = null - eventsQuery = '' - showChatWindow = false - liveSessions: Session[] = [] - visitedEvents = [] - insights: any[] = [] - insightFilters = defaultDateFilters - host = '' - funnelPage = {} + list: Session[] = []; + sessionIds: string[] = []; + current = new Session(); + total = 0; + keyMap = {}; + wdTypeCount = {}; + favoriteList: Session[] = []; + activeTab = Watchdog({ name: 'All', type: 'all' }); + timezone = 'local'; + errorStack: ErrorStack[] = []; + eventsIndex = []; + sourcemapUploaded = true; + filteredEvents: InjectedEvent[] | null = null; + eventsQuery = ''; + showChatWindow = false; + liveSessions: Session[] = []; + visitedEvents = []; + insights: any[] = []; + insightFilters = defaultDateFilters; + host = ''; + funnelPage = {}; /** @Deprecated */ - timelinePointer = {} - sessionPath = {} - lastPlayedSessionId: string - timeLineTooltip = { time: 0, offset: 0, isVisible: false, localTime: '', userTime: '' } - createNoteTooltip = { time: 0, isVisible: false, isEdit: false, note: null } - previousId = '' - nextId = '' - userTimezone = '' - prefetchedMobUrls: Record = {} + timelinePointer = {}; + sessionPath = {}; + lastPlayedSessionId: string; + timeLineTooltip = { + time: 0, + offset: 0, + isVisible: false, + localTime: '', + userTime: '', + }; + createNoteTooltip = { time: 0, isVisible: false, isEdit: false, note: null }; + previousId = ''; + nextId = ''; + userTimezone = ''; + prefetchedMobUrls: Record = + {}; constructor() { makeAutoObservable(this, { @@ -144,25 +164,32 @@ export default class SessionStore { } async getFirstMob(sessionId: string) { - const { domURL } = await sessionService.getFirstMobUrl(sessionId) - await loadFile( - domURL[0], - (data) => this.setPrefetchedMobUrl(sessionId, data) - ) + const { domURL } = await sessionService.getFirstMobUrl(sessionId); + await loadFile(domURL[0], (data) => + this.setPrefetchedMobUrl(sessionId, data) + ); } setPrefetchedMobUrl(sessionId: string, fileData: Uint8Array) { - const keys = Object.keys(this.prefetchedMobUrls) - const toLimit = 10 - keys.length + 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 - )[0] - delete this.prefetchedMobUrls[oldest] + (a, b) => + 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].entryNum)) + 1 : 0 - this.prefetchedMobUrls[sessionId] = { data: fileData, entryNum: nextEntryNum } + const nextEntryNum = + keys.length > 0 + ? Math.max(...keys.map((key) => this.prefetchedMobUrls[key].entryNum)) + + 1 + : 0; + this.prefetchedMobUrls[sessionId] = { + data: fileData, + entryNum: nextEntryNum, + }; } getSessions(filter: any): Promise { @@ -171,7 +198,9 @@ export default class SessionStore { .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, }); }) @@ -184,9 +213,11 @@ export default class SessionStore { async fetchLiveSessions(params = {}) { 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) + console.error(e); } } @@ -200,22 +231,22 @@ export default class SessionStore { } setSessionFilter(cleanSessionFilters(params)); const data = await sessionService.getSessions(params); - const list = data.sessions.map(s => new Session(s)) + const list = data.sessions.map((s) => new Session(s)); this.list = list; this.total = data.total; - this.sessionIds = data.sessions.map(s => s.sessionId); - this.favoriteList = list.filter(s => s.favorite); + this.sessionIds = data.sessions.map((s) => s.sessionId); + this.favoriteList = list.filter((s) => s.favorite); } catch (e) { - console.error(e) + console.error(e); } } async fetchSessionInfo(sessionId: string, isLive = false) { try { const { events } = store.getState().getIn(['filters', 'appliedFilter']); - const data = await sessionService.getSessionInfo(sessionId, isLive) - const session = new Session(data) + const data = await sessionService.getSessionInfo(sessionId, isLive); + const session = new Session(data); const matching: number[] = []; const visitedEvents: Location[] = []; @@ -228,7 +259,6 @@ export default class SessionStore { } }); - (events as {}[]).forEach(({ key, operator, value }: any) => { session.events.forEach((e, index) => { if (key === e.type) { @@ -243,16 +273,16 @@ export default class SessionStore { }); }); } catch (e) { - console.error(e) + console.error(e); } } async fetchErrorStack(sessionId: string, errorId: string) { try { const data = await sessionService.getErrorStack(sessionId, errorId); - this.errorStack = data.trace.map(es => new ErrorStack(es)) + this.errorStack = data.trace.map((es) => new ErrorStack(es)); } catch (e) { - console.error(e) + console.error(e); } } @@ -260,68 +290,74 @@ export default class SessionStore { try { setSessionFilter(cleanSessionFilters(params)); const data = await sessionService.getAutoplayList(params); - const list = [...this.sessionIds, ...data.map(s => s.sessionId)] + const list = [...this.sessionIds, ...data.map((s) => s.sessionId)]; this.sessionIds = list.filter((id, ind) => list.indexOf(id) === ind); } catch (e) { - console.error(e) + console.error(e); } } setAutoplayValues() { - const currentId = this.current.sessionId - const currentIndex = this.sessionIds.indexOf(currentId) + 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]; } setEventQuery(filter: { query: string }) { - const events = this.current.events + const events = this.current.events; const query = filter.query; - const searchRe = getRE(query, 'i') + 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; + 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; - this.filteredEvents = filteredEvents - this.eventsQuery = query + this.filteredEvents = filteredEvents; + this.eventsQuery = query; } async toggleFavorite(id: string) { try { - const r = await sessionService.toggleFavorite(id) + const r = await sessionService.toggleFavorite(id); if (r.success) { const list = this.list; const current = this.current; const sessionIdx = list.findIndex(({ sessionId }) => sessionId === id); - const session = list[sessionIdx] - const wasInFavorite = this.favoriteList.findIndex(({ sessionId }) => sessionId === id) > -1; + const session = list[sessionIdx]; + const wasInFavorite = + this.favoriteList.findIndex(({ sessionId }) => sessionId === id) > -1; if (session && !wasInFavorite) { - session.favorite = true - this.list[sessionIdx] = session + session.favorite = true; + this.list[sessionIdx] = session; } if (current.sessionId === id) { - this.current.favorite = !wasInFavorite + this.current.favorite = !wasInFavorite; } if (session) { if (wasInFavorite) { - this.favoriteList = this.favoriteList.filter(({ sessionId }) => sessionId !== id) + this.favoriteList = this.favoriteList.filter( + ({ sessionId }) => sessionId !== id + ); } else { - this.favoriteList.push(session) + this.favoriteList.push(session); } } } else { - console.error(r) + console.error(r); } } catch (e) { - console.error(e) + console.error(e); } } @@ -333,18 +369,20 @@ export default class SessionStore { return sign * diff; }; - this.list = this.list.sort(comparator) - this.favoriteList = this.favoriteList.sort(comparator) + this.list = this.list.sort(comparator); + this.favoriteList = this.favoriteList.sort(comparator); return; } setActiveTab(tab: { type: string }) { - const list = tab.type === 'all' - ? this.list : this.list.filter(s => s.issueTypes.includes(tab.type)) + const list = + tab.type === 'all' + ? this.list + : this.list.filter((s) => s.issueTypes.includes(tab.type)); // @ts-ignore - this.activeTab = tab - this.sessionIds = list.map(s => s.sessionId) + this.activeTab = tab; + this.sessionIds = list.map((s) => s.sessionId); } setTimezone(tz: string) { @@ -352,80 +390,85 @@ export default class SessionStore { } toggleChatWindow(isActive: boolean) { - this.showChatWindow = isActive + this.showChatWindow = isActive; } async fetchInsights(filters = {}) { try { - const data = await sessionService.getClickMap(filters) + const data = await sessionService.getClickMap(filters); - this.insights = data + this.insights = data; } catch (e) { - console.error(e) + console.error(e); } } setFunnelPage(page = {}) { - this.funnelPage = page || false + this.funnelPage = page || false; } /* @deprecated */ setTimelinePointer(pointer: {}) { - this.timelinePointer = pointer + this.timelinePointer = pointer; } - setTimelineTooltip(tp: { time: number, offset: number, isVisible: boolean, localTime: string, userTime?: string }) { - this.timeLineTooltip = tp + setTimelineTooltip(tp: { + time: number; + offset: number; + isVisible: boolean; + localTime: string; + userTime?: string; + }) { + this.timeLineTooltip = tp; } filterOutNote(noteId: string) { - const current = this.current + const current = this.current; - current.notesWithEvents = current.notesWithEvents.filter(n => { + current.notesWithEvents = current.notesWithEvents.filter((n) => { if ('noteId' in item) { - return item.noteId !== noteId + return item.noteId !== noteId; } - return true - }) + return true; + }); - this.current = current + this.current = current; } addNote(note: Note) { - const current = this.current + const current = this.current; - current.notesWithEvents.push(note) - current.notesWithEvents.sort((a,b) => { - const aTs = a.time || a.timestamp - const bTs = b.time || b.timestamp + current.notesWithEvents.push(note); + current.notesWithEvents.sort((a, b) => { + const aTs = a.time || a.timestamp; + const bTs = b.time || b.timestamp; - return aTs - bTs - }) + return aTs - bTs; + }); - this.current = current + this.current = current; } updateNote(note: Note) { - const noteIndex = this.current.notesWithEvents.findIndex(item => { - if ('noteId' in item) { - return item.noteId === note.noteId - } - return false - }) + const noteIndex = this.current.notesWithEvents.findIndex((item) => { + if ('noteId' in item) { + return item.noteId === note.noteId; + } + return false; + }); - this.current.notesWithEvents[noteIndex] = note + this.current.notesWithEvents[noteIndex] = note; } setSessionPath(path = {}) { - this.sessionPath = path + this.sessionPath = path; } setLastPlayed(sessionId: string) { - const list = this.list - const sIndex = list.findIndex((s) => s.sessionId === sessionId) + const list = this.list; + const sIndex = list.findIndex((s) => s.sessionId === sessionId); if (sIndex !== -1) { - this.list[sIndex].viewed = true + this.list[sIndex].viewed = true; } } - }