feat(ui): improve session search and count functionality

- Replace latestList with latestSessionCount to better track new sessions
- Move debouncing logic to PrivateRoutes for improved consistency
- Fix session count checking with proper API integration
- Clean up code formatting and remove unnecessary function calls

Signed-off-by: Shekar Siri <sshekarsiri@gmail.com>
This commit is contained in:
Shekar Siri 2025-03-04 19:37:20 +01:00
parent 78ddbb9233
commit ee4c5cf45d
5 changed files with 102 additions and 99 deletions

View file

@ -1,14 +1,15 @@
import withSiteIdUpdater from 'HOCs/withSiteIdUpdater'; import withSiteIdUpdater from 'HOCs/withSiteIdUpdater';
import React, { Suspense, lazy } from 'react'; import React, { Suspense, lazy } from 'react';
import { Redirect, Route, Switch } from 'react-router-dom'; import { Redirect, Route, Switch } from 'react-router-dom';
import { observer } from 'mobx-react-lite' import { observer } from 'mobx-react-lite';
import { useStore } from "./mstore"; import { useStore } from './mstore';
import { GLOBAL_HAS_NO_RECORDINGS } from 'App/constants/storageKeys'; import { GLOBAL_HAS_NO_RECORDINGS } from 'App/constants/storageKeys';
import { OB_DEFAULT_TAB } from 'App/routes'; import { OB_DEFAULT_TAB } from 'App/routes';
import { Loader } from 'UI'; import { Loader } from 'UI';
import APIClient from './api_client'; import APIClient from './api_client';
import * as routes from './routes'; import * as routes from './routes';
import { debounce } from '@/utils';
const components: any = { const components: any = {
SessionPure: lazy(() => import('Components/Session/Session')), SessionPure: lazy(() => import('Components/Session/Session')),
@ -31,7 +32,7 @@ const components: any = {
SpotsListPure: lazy(() => import('Components/Spots/SpotsList')), SpotsListPure: lazy(() => import('Components/Spots/SpotsList')),
SpotPure: lazy(() => import('Components/Spots/SpotPlayer')), SpotPure: lazy(() => import('Components/Spots/SpotPlayer')),
ScopeSetup: lazy(() => import('Components/ScopeForm')), ScopeSetup: lazy(() => import('Components/ScopeForm')),
HighlightsPure: lazy(() => import('Components/Highlights/HighlightsList')), HighlightsPure: lazy(() => import('Components/Highlights/HighlightsList'))
}; };
const enhancedComponents: any = { const enhancedComponents: any = {
@ -51,7 +52,7 @@ const enhancedComponents: any = {
SpotsList: withSiteIdUpdater(components.SpotsListPure), SpotsList: withSiteIdUpdater(components.SpotsListPure),
Spot: components.SpotPure, Spot: components.SpotPure,
ScopeSetup: components.ScopeSetup, ScopeSetup: components.ScopeSetup,
Highlights: withSiteIdUpdater(components.HighlightsPure), Highlights: withSiteIdUpdater(components.HighlightsPure)
}; };
const withSiteId = routes.withSiteId; const withSiteId = routes.withSiteId;
@ -97,9 +98,10 @@ const SPOT_PATH = routes.spot();
const SCOPE_SETUP = routes.scopeSetup(); const SCOPE_SETUP = routes.scopeSetup();
const HIGHLIGHTS_PATH = routes.highlights(); const HIGHLIGHTS_PATH = routes.highlights();
let debounceSearch: any = () => {};
function PrivateRoutes() { function PrivateRoutes() {
const { projectsStore, userStore, integrationsStore } = useStore(); const { projectsStore, userStore, integrationsStore, searchStore } = useStore();
const onboarding = userStore.onboarding; const onboarding = userStore.onboarding;
const scope = userStore.scopeState; const scope = userStore.scopeState;
const tenantId = userStore.account.tenantId; const tenantId = userStore.account.tenantId;
@ -113,10 +115,20 @@ function PrivateRoutes() {
React.useEffect(() => { React.useEffect(() => {
if (siteId && integrationsStore.integrations.siteId !== siteId) { if (siteId && integrationsStore.integrations.siteId !== siteId) {
integrationsStore.integrations.setSiteId(siteId) integrationsStore.integrations.setSiteId(siteId);
void integrationsStore.integrations.fetchIntegrations(siteId); void integrationsStore.integrations.fetchIntegrations(siteId);
} }
}, [siteId]) }, [siteId]);
React.useEffect(() => {
debounceSearch = debounce(() => searchStore.fetchSessions(), 500);
}, []);
React.useEffect(() => {
if (!searchStore.urlParsed) return;
debounceSearch();
}, [searchStore.instance.filters, searchStore.instance.eventsOrder]);
return ( return (
<Suspense fallback={<Loader loading={true} className="flex-1" />}> <Suspense fallback={<Loader loading={true} className="flex-1" />}>
<Switch key="content"> <Switch key="content">
@ -153,13 +165,13 @@ function PrivateRoutes() {
case '/integrations/slack': case '/integrations/slack':
client.post('integrations/slack/add', { client.post('integrations/slack/add', {
code: location.search.split('=')[1], code: location.search.split('=')[1],
state: tenantId, state: tenantId
}); });
break; break;
case '/integrations/msteams': case '/integrations/msteams':
client.post('integrations/msteams/add', { client.post('integrations/msteams/add', {
code: location.search.split('=')[1], code: location.search.split('=')[1],
state: tenantId, state: tenantId
}); });
break; break;
} }
@ -184,7 +196,7 @@ function PrivateRoutes() {
withSiteId(DASHBOARD_PATH, siteIdList), withSiteId(DASHBOARD_PATH, siteIdList),
withSiteId(DASHBOARD_SELECT_PATH, siteIdList), withSiteId(DASHBOARD_SELECT_PATH, siteIdList),
withSiteId(DASHBOARD_METRIC_CREATE_PATH, siteIdList), withSiteId(DASHBOARD_METRIC_CREATE_PATH, siteIdList),
withSiteId(DASHBOARD_METRIC_DETAILS_PATH, siteIdList), withSiteId(DASHBOARD_METRIC_DETAILS_PATH, siteIdList)
]} ]}
component={enhancedComponents.Dashboard} component={enhancedComponents.Dashboard}
/> />
@ -245,7 +257,7 @@ function PrivateRoutes() {
withSiteId(FFLAG_READ_PATH, siteIdList), withSiteId(FFLAG_READ_PATH, siteIdList),
withSiteId(FFLAG_CREATE_PATH, siteIdList), withSiteId(FFLAG_CREATE_PATH, siteIdList),
withSiteId(NOTES_PATH, siteIdList), withSiteId(NOTES_PATH, siteIdList),
withSiteId(BOOKMARKS_PATH, siteIdList), withSiteId(BOOKMARKS_PATH, siteIdList)
]} ]}
component={enhancedComponents.SessionsOverview} component={enhancedComponents.SessionsOverview}
/> />
@ -264,7 +276,7 @@ function PrivateRoutes() {
{Object.entries(routes.redirects).map(([fr, to]) => ( {Object.entries(routes.redirects).map(([fr, to]) => (
<Redirect key={fr} exact strict from={fr} to={to} /> <Redirect key={fr} exact strict from={fr} to={to} />
))} ))}
<Route path={"*"}> <Route path={'*'}>
<Redirect to={withSiteId(routes.sessions(), siteId)} /> <Redirect to={withSiteId(routes.sessions(), siteId)} />
</Route> </Route>
</Switch> </Switch>

View file

@ -57,10 +57,6 @@ function SessionFilters() {
} }
}); });
useEffect(() => {
debounceFetch();
}, [appliedFilter.filters]);
const onAddFilter = (filter: any) => { const onAddFilter = (filter: any) => {
filter.autoOpen = true; filter.autoOpen = true;
searchStore.addFilter(filter); searchStore.addFilter(filter);
@ -72,13 +68,13 @@ function SessionFilters() {
const onFilterMove = (newFilters: any) => { const onFilterMove = (newFilters: any) => {
searchStore.updateSearch({ ...appliedFilter, filters: newFilters}); searchStore.updateSearch({ ...appliedFilter, filters: newFilters});
debounceFetch(); // debounceFetch();
}; };
const onRemoveFilter = (filterIndex: any) => { const onRemoveFilter = (filterIndex: any) => {
searchStore.removeFilter(filterIndex); searchStore.removeFilter(filterIndex);
debounceFetch(); // debounceFetch();
}; };
const onChangeEventsOrder = (e: any, { value }: any) => { const onChangeEventsOrder = (e: any, { value }: any) => {
@ -86,7 +82,7 @@ function SessionFilters() {
eventsOrder: value, eventsOrder: value,
}); });
debounceFetch(); // debounceFetch();
}; };
return ( return (

View file

@ -6,9 +6,10 @@ import { observer } from 'mobx-react-lite';
function LatestSessionsMessage() { function LatestSessionsMessage() {
const { searchStore } = useStore(); const { searchStore } = useStore();
const count = searchStore.latestList.size; const count = searchStore.latestSessionCount;
const onShowNewSessions = () => { const onShowNewSessions = () => {
searchStore.updateLatestSessionCount(0);
void searchStore.updateCurrentPage(1, true); void searchStore.updateCurrentPage(1, true);
}; };

View file

@ -47,10 +47,10 @@ function SessionList() {
const hasNoRecordings = !activeSite || !activeSite.recorded; const hasNoRecordings = !activeSite || !activeSite.recorded;
const metaList = customFieldStore.list; const metaList = customFieldStore.list;
// useEffect(() => { useEffect(() => {
// if (!searchStore.urlParsed) return; if (!searchStore.urlParsed) return;
// void searchStore.fetchSessions(true, isBookmark); void searchStore.checkForLatestSessionCount();
// }, [location.pathname]); }, [location.pathname]);
const NO_CONTENT = React.useMemo(() => { const NO_CONTENT = React.useMemo(() => {
if (isBookmark && !isEnterprise) { if (isBookmark && !isEnterprise) {
@ -110,7 +110,7 @@ function SessionList() {
useEffect(() => { useEffect(() => {
const id = setInterval(() => { const id = setInterval(() => {
if (!document.hidden) { if (!document.hidden) {
searchStore.checkForLatestSessions(); void searchStore.checkForLatestSessionCount();
} }
}, AUTO_REFRESH_INTERVAL); }, AUTO_REFRESH_INTERVAL);
return () => clearInterval(id); return () => clearInterval(id);
@ -134,7 +134,7 @@ function SessionList() {
sessionTimeOut = setTimeout(function () { sessionTimeOut = setTimeout(function () {
if (!document.hidden) { if (!document.hidden) {
searchStore.checkForLatestSessions(); void searchStore.checkForLatestSessionCount();
} }
}, 5000); }, 5000);
}; };

View file

@ -5,11 +5,11 @@ import {
filtersMap, filtersMap,
generateFilterOptions, generateFilterOptions,
liveFiltersMap, liveFiltersMap,
mobileConditionalFiltersMap, mobileConditionalFiltersMap
} from 'Types/filter/newFilter'; } from 'Types/filter/newFilter';
import { List } from 'immutable'; import { List } from 'immutable';
import { makeAutoObservable, runInAction } from 'mobx'; import { makeAutoObservable, runInAction } from 'mobx';
import { searchService } from 'App/services'; import { searchService, sessionService } from 'App/services';
import Search from 'App/mstore/types/search'; import Search from 'App/mstore/types/search';
import { checkFilterValue } from 'App/mstore/types/filter'; import { checkFilterValue } from 'App/mstore/types/filter';
import FilterItem from 'App/mstore/types/filterItem'; import FilterItem from 'App/mstore/types/filterItem';
@ -28,18 +28,18 @@ export const checkValues = (key: any, value: any) => {
}; };
export const filterMap = ({ export const filterMap = ({
category, category,
value, value,
key, key,
operator, operator,
sourceOperator, sourceOperator,
source, source,
custom, custom,
isEvent, isEvent,
filters, filters,
sort, sort,
order, order
}: any) => ({ }: any) => ({
value: checkValues(key, value), value: checkValues(key, value),
custom, custom,
type: category === FilterCategory.METADATA ? FilterKey.METADATA : key, type: category === FilterCategory.METADATA ? FilterKey.METADATA : key,
@ -47,7 +47,7 @@ export const filterMap = ({
source: category === FilterCategory.METADATA ? key.replace(/^_/, '') : source, source: category === FilterCategory.METADATA ? key.replace(/^_/, '') : source,
sourceOperator, sourceOperator,
isEvent, isEvent,
filters: filters ? filters.map(filterMap) : [], filters: filters ? filters.map(filterMap) : []
}); });
export const TAB_MAP: any = { export const TAB_MAP: any = {
@ -55,7 +55,7 @@ export const TAB_MAP: any = {
sessions: { name: 'Sessions', type: 'sessions' }, sessions: { name: 'Sessions', type: 'sessions' },
bookmarks: { name: 'Bookmarks', type: 'bookmarks' }, bookmarks: { name: 'Bookmarks', type: 'bookmarks' },
notes: { name: 'Notes', type: 'notes' }, notes: { name: 'Notes', type: 'notes' },
recommendations: { name: 'Recommendations', type: 'recommendations' }, recommendations: { name: 'Recommendations', type: 'recommendations' }
}; };
class SearchStore { class SearchStore {
@ -72,6 +72,7 @@ class SearchStore {
scrollY = 0; scrollY = 0;
sessions = List(); sessions = List();
total: number = 0; total: number = 0;
latestSessionCount: number = 0;
loadingFilterSearch = false; loadingFilterSearch = false;
isSaving: boolean = false; isSaving: boolean = false;
activeTags: any[] = []; activeTags: any[] = [];
@ -111,9 +112,9 @@ class SearchStore {
this.edit({ this.edit({
filters: savedSearch.filter filters: savedSearch.filter
? savedSearch.filter.filters.map((i: FilterItem) => ? savedSearch.filter.filters.map((i: FilterItem) =>
new FilterItem().fromJson(i) new FilterItem().fromJson(i)
) )
: [], : []
}); });
this.currentPage = 1; this.currentPage = 1;
} }
@ -179,6 +180,10 @@ class SearchStore {
void this.fetchSessions(force); void this.fetchSessions(force);
} }
updateLatestSessionCount(count: number = 0) {
this.latestSessionCount = count;
}
setActiveTab(tab: string) { setActiveTab(tab: string) {
runInAction(() => { runInAction(() => {
this.activeTab = TAB_MAP[tab]; this.activeTab = TAB_MAP[tab];
@ -228,7 +233,7 @@ class SearchStore {
rangeValue: instance.rangeValue, rangeValue: instance.rangeValue,
startDate: instance.startDate, startDate: instance.startDate,
endDate: instance.endDate, endDate: instance.endDate,
filters: [], filters: []
}) })
); );
@ -237,78 +242,67 @@ class SearchStore {
void this.fetchSessions(true); void this.fetchSessions(true);
} }
checkForLatestSessionCount() { async checkForLatestSessionCount(): Promise<void> {
const filter = this.instance.toSearch(); try {
if (this.latestRequestTime) { const filter = this.instance.toSearch();
const period = Period({
rangeName: CUSTOM_RANGE, // Set time filter if we have the latest request time
start: this.latestRequestTime, if (this.latestRequestTime) {
end: Date.now(), const period = Period({
}); rangeName: CUSTOM_RANGE,
const newTimestamps: any = period.toJSON(); start: this.latestRequestTime,
filter.startDate = newTimestamps.startDate; end: Date.now()
filter.endDate = newTimestamps.endDate; });
} const timeRange: any = period.toJSON();
// TODO - dedicated API endpoint to get the count of latest sessions, or show X+ sessions filter.startDate = timeRange.startDate;
delete filter.limit; filter.endDate = timeRange.endDate;
delete filter.page; }
searchService.checkLatestSessions(filter).then((response: any) => {
console.log('response', response); // Only need the total count, not actual records
// runInAction(() => { filter.limit = 1;
// this.latestList = List(response); filter.page = 1;
// });
}); const response = await sessionService.getSessions(filter);
}
checkForLatestSessions() {
const filter = this.instance.toSearch();
if (this.latestRequestTime) {
const period = Period({
rangeName: CUSTOM_RANGE,
start: this.latestRequestTime,
end: Date.now(),
});
const newTimestamps: any = period.toJSON();
filter.startDate = newTimestamps.startDate;
filter.endDate = newTimestamps.endDate;
}
// TODO - dedicated API endpoint to get the count of latest sessions, or show X+ sessions
delete filter.limit;
delete filter.page;
searchService.checkLatestSessions(filter).then((response: any) => {
runInAction(() => { runInAction(() => {
this.latestList = List(response); if (response?.total && response.total > sessionStore.total) {
this.latestSessionCount = response.total - sessionStore.total;
} else {
this.latestSessionCount = 0;
}
}); });
}); } catch (error) {
console.error('Failed to check for latest session count:', error);
}
} }
addFilter(filter: any) { addFilter(filter: any) {
const index = filter.isEvent const index = filter.isEvent
? -1 ? -1
: this.instance.filters.findIndex( : this.instance.filters.findIndex(
(i: FilterItem) => i.key === filter.key (i: FilterItem) => i.key === filter.key
); );
filter.value = checkFilterValue(filter.value); filter.value = checkFilterValue(filter.value);
filter.filters = filter.filters filter.filters = filter.filters
? filter.filters.map((subFilter: any) => ({ ? filter.filters.map((subFilter: any) => ({
...subFilter, ...subFilter,
value: checkFilterValue(subFilter.value), value: checkFilterValue(subFilter.value)
})) }))
: null; : null;
if (index > -1) { if (index > -1) {
const oldFilter = new FilterItem(this.instance.filters[index]); const oldFilter = new FilterItem(this.instance.filters[index]);
const updatedFilter = { const updatedFilter = {
...oldFilter, ...oldFilter,
value: oldFilter.value.concat(filter.value), value: oldFilter.value.concat(filter.value)
}; };
oldFilter.merge(updatedFilter); oldFilter.merge(updatedFilter);
this.updateFilter(index, updatedFilter); this.updateFilter(index, updatedFilter);
} else { } else {
this.instance.filters.push(filter); this.instance.filters.push(filter);
this.instance = new Search({ this.instance = new Search({
...this.instance.toData(), ...this.instance.toData()
}); });
} }
@ -346,7 +340,7 @@ class SearchStore {
updateSearch = (search: Partial<Search>) => { updateSearch = (search: Partial<Search>) => {
this.instance = Object.assign(this.instance, search); this.instance = Object.assign(this.instance, search);
} };
updateFilter = (index: number, search: Partial<FilterItem>) => { updateFilter = (index: number, search: Partial<FilterItem>) => {
const newFilters = this.instance.filters.map((_filter: any, i: any) => { const newFilters = this.instance.filters.map((_filter: any, i: any) => {
@ -359,7 +353,7 @@ class SearchStore {
this.instance = new Search({ this.instance = new Search({
...this.instance.toData(), ...this.instance.toData(),
filters: newFilters, filters: newFilters
}); });
}; };
@ -370,7 +364,7 @@ class SearchStore {
this.instance = new Search({ this.instance = new Search({
...this.instance.toData(), ...this.instance.toData(),
filters: newFilters, filters: newFilters
}); });
}; };
@ -393,7 +387,7 @@ class SearchStore {
const tagFilter = filtersMap[FilterKey.ISSUE]; const tagFilter = filtersMap[FilterKey.ISSUE];
tagFilter.type = tagFilter.type.toLowerCase(); tagFilter.type = tagFilter.type.toLowerCase();
tagFilter.value = [ tagFilter.value = [
issues_types.find((i: any) => i.type === this.activeTags[0])?.type, issues_types.find((i: any) => i.type === this.activeTags[0])?.type
]; ];
delete tagFilter.operatorOptions; delete tagFilter.operatorOptions;
delete tagFilter.options; delete tagFilter.options;
@ -413,7 +407,7 @@ class SearchStore {
filter.filters.push({ filter.filters.push({
type: FilterKey.DURATION, type: FilterKey.DURATION,
value, value,
operator: 'is', operator: 'is'
}); });
} }
} }
@ -427,7 +421,7 @@ class SearchStore {
page: this.currentPage, page: this.currentPage,
perPage: this.pageSize, perPage: this.pageSize,
tab: this.activeTab.type, tab: this.activeTab.type,
bookmarked: bookmarked ? true : undefined, bookmarked: bookmarked ? true : undefined
}, },
force force
).finally(() => { ).finally(() => {