fix(ui): sessions list sidemenu fix, reload properly, sessions tags filter issue

This commit is contained in:
Shekar Siri 2024-10-11 16:06:10 +02:00
parent 44d0bb1369
commit beb3eeac0c
11 changed files with 113 additions and 202 deletions

View file

@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect } from 'react';
import withPageTitle from 'HOCs/withPageTitle';
import NoSessionsMessage from 'Shared/NoSessionsMessage';
import MainSearchBar from 'Shared/MainSearchBar';
@ -8,9 +8,10 @@ import FFlagsList from 'Components/FFlags';
import NewFFlag from 'Components/FFlags/NewFFlag';
import { Switch, Route } from 'react-router';
import { sessions, fflags, withSiteId, newFFlag, fflag, notes, fflagRead, bookmarks } from 'App/routes';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import { withRouter, RouteComponentProps, useLocation } from 'react-router-dom';
import FlagView from 'Components/FFlags/FlagView/FlagView';
import { observer } from 'mobx-react-lite';
import { useStore } from '@/mstore';
// @ts-ignore
interface IProps extends RouteComponentProps {
@ -23,7 +24,16 @@ interface IProps extends RouteComponentProps {
}
function Overview({ match: { params } }: IProps) {
const { searchStore } = useStore();
const { siteId, fflagId } = params;
const location = useLocation();
const tab = location.pathname.split('/')[2];
searchStore.setActiveTab(tab);
// useEffect(() => {
// // const tab = location.pathname.split('/')[2];
// searchStore.setActiveTab(tab);
// }, [tab]);
return (
<Switch>

View file

@ -1,87 +0,0 @@
import React from 'react';
import { SideMenuitem } from 'UI';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import { sessions, fflags, withSiteId, notes, bookmarks } from 'App/routes';
import { useStore } from 'App/mstore';
interface Props {
activeTab: string;
}
const TabToUrlMap = {
all: sessions() as '/sessions',
bookmark: bookmarks() as '/bookmarks',
notes: notes() as '/notes',
flags: fflags() as '/feature-flags'
};
function OverviewMenu(props: Props & RouteComponentProps) {
// @ts-ignore
const { history, match: { params: { siteId } }, location } = props;
const { searchStore, userStore } = useStore();
const isEnterprise = userStore.isEnterprise;
const activeTab = searchStore.activeTab.type;
React.useEffect(() => {
const currentLocation = location.pathname;
const tab = Object.keys(TabToUrlMap).find((tab: keyof typeof TabToUrlMap) => currentLocation.includes(TabToUrlMap[tab]));
if (tab && tab !== activeTab) {
searchStore.setActiveTab({ type: tab });
}
}, [location.pathname]);
return (
<div className={'flex flex-col gap-2 w-full'}>
<div className="w-full">
<SideMenuitem
active={activeTab === 'all'}
id="menu-sessions"
title="Sessions"
iconName="play-circle-bold"
onClick={() => {
searchStore.setActiveTab({ type: 'all' });
!location.pathname.includes(sessions()) && history.push(withSiteId(sessions(), siteId));
}}
/>
</div>
<div className="w-full">
<SideMenuitem
active={activeTab === 'bookmark'}
id="menu-bookmarks"
title={`${isEnterprise ? 'Vault' : 'Bookmarks'}`}
iconName={isEnterprise ? 'safe' : 'star'}
onClick={() => {
// props.setActiveTab({ type: 'bookmark' });
!location.pathname.includes(bookmarks()) && history.push(withSiteId(bookmarks(), siteId));
}}
/>
</div>
<div className="w-full">
<SideMenuitem
active={activeTab === 'notes'}
id="menu-notes"
title="Notes"
iconName="stickies"
onClick={() => {
searchStore.setActiveTab({ type: 'notes' });
!location.pathname.includes(notes()) && history.push(withSiteId(notes(), siteId));
}}
/>
</div>
<div className="w-full">
<SideMenuitem
active={activeTab === 'flags'}
id="menu-flags"
title="Feature Flags"
iconName="toggles"
onClick={() => {
searchStore.setActiveTab({ type: 'flags' });
!location.pathname.includes(fflags()) && history.push(withSiteId(fflags(), siteId));
}}
/>
</div>
</div>
);
}
export default withRouter(OverviewMenu);

View file

@ -1 +0,0 @@
export { default } from './OverviewMenu';

View file

@ -47,7 +47,7 @@ function SessionSearch(props: Props) {
useEffect(() => {
debounceFetch = debounce(() => searchStore.fetchSessions(), 500);
void searchStore.fetchSessions(true)
// void searchStore.fetchSessions(true)
}, []);
const onAddFilter = (filter: any) => {

View file

@ -12,7 +12,7 @@ function SessionsTabOverview() {
const [query, setQuery] = React.useState('');
const { aiFiltersStore, searchStore } = useStore();
const appliedFilter = searchStore.instance;
const activeTab = searchStore.activeTab.type;
const isNotesRoute = searchStore.activeTab.type === 'notes';
const handleKeyDown = (event: any) => {
if (event.key === 'Enter') {
@ -38,7 +38,7 @@ function SessionsTabOverview() {
<SessionHeader />
<div className="border-b" />
<LatestSessionsMessage />
{activeTab !== 'notes' ? (
{!isNotesRoute ? (
<SessionList />
) : (
<NotesList />

View file

@ -20,16 +20,16 @@ function SessionHeader() {
if (activeTab.type === 'notes') {
return 'Notes';
}
if (activeTab.type === 'bookmark') {
if (activeTab.type === 'bookmarks') {
return isEnterprise ? 'Vault' : 'Bookmarks';
}
return 'Sessions';
}, [activeTab]);
}, [activeTab.type, isEnterprise]);
const onDateChange = (e: any) => {
const dateValues = e.toJSON();
searchStore.edit(dateValues);
searchStore.fetchSessions();
void searchStore.fetchSessions(true);
};
return (
@ -37,7 +37,7 @@ function SessionHeader() {
<h2 className="text-2xl capitalize mr-4">{title}</h2>
{activeTab.type !== 'notes' ? (
<div className="flex items-center w-full justify-end">
{activeTab.type !== 'bookmark' && (
{activeTab.type !== 'bookmarks' && (
<>
<SessionTags />
<div className="mr-auto" />

View file

@ -2,7 +2,7 @@ import React, { useEffect } from 'react';
import { FilterKey } from 'Types/filter/filterType';
import SessionItem from 'Shared/SessionItem';
import { NoContent, Loader, Pagination, Button } from 'UI';
import { withRouter } from 'react-router-dom';
import { useLocation, withRouter } from 'react-router-dom';
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
import { numberWithCommas } from 'App/utils';
import SessionDateRange from './SessionDateRange';
@ -23,6 +23,10 @@ let sessionStatusTimeOut: any = null;
const STATUS_FREQUENCY = 5000;
function SessionList() {
const location = useLocation(); // Get the current URL location
const isSessionsRoute = location.pathname.includes('/sessions');
const isBookmark = location.pathname.includes('/bookmarks');
const { projectsStore, sessionStore, customFieldStore, userStore } = useStore();
const isEnterprise = userStore.isEnterprise;
const isLoggedIn = userStore.isLoggedIn;
@ -39,12 +43,17 @@ function SessionList() {
const _filterKeys = filters.map((i: any) => i.key);
const hasUserFilter =
_filterKeys.includes(FilterKey.USERID) || _filterKeys.includes(FilterKey.USERANONYMOUSID);
const isBookmark = activeTab.type === 'bookmark';
// const isBookmark = activeTab.type === 'bookmark';
const isVault = isBookmark && isEnterprise;
const activeSite = projectsStore.active;
const hasNoRecordings = !activeSite || !activeSite.recorded;
const metaList = customFieldStore.list;
useEffect(() => {
void searchStore.fetchSessions(true, isBookmark);
}, [location.pathname]);
const NO_CONTENT = React.useMemo(() => {
if (isBookmark && !isEnterprise) {
return {
@ -61,7 +70,7 @@ function SessionList() {
icon: ICONS.NO_SESSIONS,
message: <SessionDateRange />
};
}, [isBookmark, isVault, activeTab]);
}, [isBookmark, isVault, activeTab, location.pathname]);
const [statusData, setStatusData] = React.useState<SessionStatus>({ status: 0, count: 0 });
@ -101,11 +110,11 @@ function SessionList() {
}
}, [statusData, siteId]);
useEffect(() => {
if (siteId) {
void searchStore.fetchSessions();
}
}, [siteId])
// useEffect(() => {
// if (siteId) {
// void searchStore.fetchSessions();
// }
// }, [siteId]);
useEffect(() => {
const id = setInterval(() => {
@ -121,11 +130,11 @@ function SessionList() {
const { scrollY } = searchStore;
window.scrollTo(0, scrollY);
if (total === 0 && !loading && !hasNoRecordings) {
setTimeout(() => {
void searchStore.fetchSessions();
}, 100);
}
// if (total === 0 && !loading && !hasNoRecordings) {
// setTimeout(() => {
// void searchStore.fetchSessions();
// }, 100);
// }
return () => {
searchStore.setScrollPosition(window.scrollY);

View file

@ -1,12 +1,9 @@
import { issues_types, types } from 'Types/session/issue';
import { Segmented } from 'antd';
import cn from 'classnames';
import { Angry, CircleAlert, Skull, WifiOff } from 'lucide-react';
import { observer } from 'mobx-react-lite';
import React, { memo } from 'react';
import React from 'react';
import { useStore } from 'App/mstore';
import { Icon } from 'UI';
interface Tag {
name: string;
@ -15,7 +12,6 @@ interface Tag {
}
interface StateProps {
}
type Props = StateProps;
@ -25,94 +21,37 @@ const tagIcons = {
[types.JS_EXCEPTION]: <CircleAlert size={14} />,
[types.BAD_REQUEST]: <WifiOff size={14} />,
[types.CLICK_RAGE]: <Angry size={14} />,
[types.CRASH]: <Skull size={14} />,
[types.CRASH]: <Skull size={14} />
} as Record<string, any>;
const SessionTags: React.FC<Props> = () => {
const { projectsStore, sessionStore, searchStore } = useStore();
const total = sessionStore.total;
const platform = projectsStore.active?.platform || '';
const disable = searchStore.activeTab.type === 'all' && total === 0;
const activeTab = searchStore.activeTab;
const tags = issues_types.filter(
(tag) =>
tag.type !== 'mouse_thrashing' &&
(platform === 'web'
? tag.type !== types.TAP_RAGE
: tag.type !== types.CLICK_RAGE)
);
const activeTab = searchStore.activeTags;
const options = tags.map((tag, i) => ({
label: (
<div className={'flex items-center gap-2'}>
{tag.icon ? (
tagIcons[tag.type] ? (
tagIcons[tag.type]
) : (
<Icon
name={tag.icon}
color={activeTab.type === tag.type ? 'main' : undefined}
size="14"
className={cn('group-hover:fill-teal')}
/>
)
) : null}
<div className={activeTab.type === tag.type ? 'text-main' : ''}>
{tag.name}
</div>
</div>
),
value: tag.type,
disabled: disable && tag.type !== 'all',
}));
const onPick = (tabValue: string) => {
const tab = tags.find((t) => t.type === tabValue);
if (tab) {
searchStore.setActiveTab(tab);
}
};
return (
return total === 0 && (activeTab.length === 0 || activeTab[0] === 'all') ? null : (
<div className="flex items-center">
<Segmented
options={options}
value={activeTab.type}
onChange={onPick}
options={issues_types
.filter(
(tag) =>
tag.type !== 'mouse_thrashing' &&
(platform === 'web'
? tag.type !== types.TAP_RAGE
: tag.type !== types.CLICK_RAGE)
)
.map((tag: any) => ({
value: tag.type,
icon: tagIcons[tag.type],
label: tag.name
}))}
value={activeTab[0]}
onChange={(value: any) => searchStore.toggleTag(value)}
size={'small'}
/>
</div>
);
};
// Separate the TagItem into its own memoized component.
export const TagItem: React.FC<{
isActive: boolean;
onClick: () => void;
label: string;
icon?: string;
disabled: boolean;
}> = memo(({ isActive, onClick, label, icon, disabled }) => (
<button
onClick={onClick}
className={cn(
'transition group rounded ml-2 px-2 py-1 flex items-center uppercase text-sm hover:bg-active-blue hover:text-teal',
{
'bg-active-blue text-teal': isActive,
disabled: disabled
}
)}
style={{ height: '36px' }}
>
{icon && (
<Icon
name={icon}
color={isActive ? 'teal' : 'gray-medium'}
size="14"
className={cn('group-hover:fill-teal mr-2')}
/>
)}
<span className="leading-none font-medium">{label}</span>
</button>
));
export default observer(SessionTags);

View file

@ -139,12 +139,12 @@ function SideMenu(props: Props) {
React.useEffect(() => {
const currentLocation = location.pathname;
const tab = Object.keys(TabToUrlMap).find((tab: keyof typeof TabToUrlMap) =>
currentLocation.includes(TabToUrlMap[tab])
);
if (tab && tab !== searchStore.activeTab && siteId) {
searchStore.setActiveTab({ type: tab });
}
// const tab = Object.keys(TabToUrlMap).find((tab: keyof typeof TabToUrlMap) =>
// currentLocation.includes(TabToUrlMap[tab])
// );
// if (tab && tab !== searchStore.activeTab && siteId) {
// searchStore.setActiveTab({ type: tab });
// }
}, [location.pathname]);
const menuRoutes: any = {

View file

@ -15,6 +15,8 @@ import Filter, { checkFilterValue, IFilter } from 'App/mstore/types/filter';
import FilterItem from 'App/mstore/types/filterItem';
import { sessionStore } from 'App/mstore';
import SavedSearch, { ISavedSearch } from 'App/mstore/types/savedSearch';
import { iTag } from '@/services/NotesService';
import { issues_types } from 'Types/session/issue';
const PER_PAGE = 10;
@ -48,6 +50,14 @@ export const filterMap = ({
filters: filters ? filters.map(filterMap) : []
});
export const TAB_MAP: any = {
all: { name: 'All', type: 'all' },
sessions: { name: 'Sessions', type: 'sessions' },
bookmarks: { name: 'Bookmarks', type: 'bookmarks' },
notes: { name: 'Notes', type: 'notes' },
recommendations: { name: 'Recommendations', type: 'recommendations' }
};
class SearchStore {
filterList = generateFilterOptions(filtersMap);
filterListLive = generateFilterOptions(liveFiltersMap);
@ -68,6 +78,7 @@ class SearchStore {
total: number = 0;
loadingFilterSearch = false;
isSaving: boolean = false;
activeTags: any[] = [];
constructor() {
makeAutoObservable(this);
@ -79,7 +90,7 @@ class SearchStore {
// filters: savedSearch.filter.filters
// });
console.log('savedSearch.filter.filters', savedSearch.filter.filters);
this.edit({ filters: savedSearch.filter ? savedSearch.filter.filters.map((i: FilterItem) => new FilterItem().fromJson(i)) : []});
this.edit({ filters: savedSearch.filter ? savedSearch.filter.filters.map((i: FilterItem) => new FilterItem().fromJson(i)) : [] });
// this.edit({ filters: savedSearch.filter ? savedSearch.filter.filters : [] });
this.currentPage = 1;
}
@ -131,12 +142,23 @@ class SearchStore {
void this.fetchSessions();
}
setActiveTab(tab: any) {
this.activeTab = tab;
setActiveTab(tab: string) {
this.activeTab = TAB_MAP[tab];
// this.activeTab = tab;
this.currentPage = 1;
// this.fetchSessions();
}
toggleTag(tag?: iTag) {
if (!tag) {
this.activeTags = [];
void this.fetchSessions(true);
} else {
this.activeTags = [tag];
void this.fetchSessions(true);
}
}
async removeSavedSearch(id: string): Promise<void> {
await searchService.deleteSavedSearch(id);
this.savedSearch = new SavedSearch({});
@ -174,6 +196,7 @@ class SearchStore {
this.savedSearch = new SavedSearch({});
sessionStore.clearList();
void this.fetchSessions(true);
}
checkForLatestSessions() {
@ -274,13 +297,26 @@ class SearchStore {
// TODO
}
async fetchSessions(force: boolean = false): Promise<void> {
async fetchSessions(force: boolean = false, bookmarked: boolean = false): Promise<void> {
const filter = this.instance.toSearch();
if (this.activeTags[0] && this.activeTags[0] !== 'all') {
const tagFilter = filtersMap[FilterKey.ISSUE];
tagFilter.value = [issues_types.find((i: any) => i.type === this.activeTags[0])?.type];
delete tagFilter.operatorOptions;
delete tagFilter.options;
delete tagFilter.placeholder;
delete tagFilter.label;
delete tagFilter.icon;
filter.filters = filter.filters.concat(tagFilter);
}
await sessionStore.fetchSessions({
...this.instance.toSearch(),
...filter,
page: this.currentPage,
perPage: this.pageSize,
tab: this.activeTab.type,
bookmarked: this.activeTab.type === 'bookmark' ? true : undefined
bookmarked: bookmarked ? true : undefined
}, force);
};
}

View file

@ -431,3 +431,8 @@ p {
.ant-segmented-group{
gap:0.25rem;
}
.ant-segmented-item-label {
display: flex;
align-items: center;
}