fix ui: reformat old files (saas consistency)

This commit is contained in:
nick-delirium 2024-04-30 16:44:20 +02:00
parent 2e65173e9a
commit a32155efc3
No known key found for this signature in database
GPG key ID: 93ABD695DF5FDBA0
4 changed files with 1150 additions and 932 deletions

View file

@ -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<Note | undefined>(undefined);
const [visuallyAdjusted, setAdjusted] = useState(false);
const [windowActive, setWindowActive] = useState(!document.hidden);
// @ts-ignore
const [contextValue, setContextValue] = useState<IPlayerContext>(defaultContextValue);
const [contextValue, setContextValue] =
useState<IPlayerContext>(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<string, any> | undefined;
const mobData = sessionStore.prefetchedMobUrls[session.sessionId] as
| Record<string, any>
| 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) {
/>
) : (
<Loader
style={{ position: 'fixed', top: '0%', left: '50%', transform: 'translateX(-50%)' }}
style={{
position: 'fixed',
top: '0%',
left: '50%',
transform: 'translateX(-50%)',
}}
/>
)}
<Modal open={noteItem !== undefined} onClose={onNoteClose}>
{noteItem !== undefined ? (
<ReadNote note={noteItem} onClose={onNoteClose} notFound={!noteItem} />
<ReadNote
note={noteItem}
onClose={onNoteClose}
notFound={!noteItem}
/>
) : null}
</Modal>
</PlayerContext.Provider>
@ -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)));

View file

@ -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));
}
const filter = getFilters(getState());
filter.page = page;
filter.limit = PER_PAGE;
return dispatch(fetchAutoplayList(filter));
};

File diff suppressed because it is too large Load diff

View file

@ -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<string, { data: Uint8Array, entryNum: number }> = {}
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<string, { data: Uint8Array; entryNum: number }> =
{};
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<any> {
@ -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;
}
}
}