feat(widget-sessions): improve session filtering logic

- Refactored session filtering logic to handle nested filters properly.
- Enhanced `fetchSessions` to ensure null checks and avoid errors.
- Updated `loadData` to handle `USER_PATH` and `HEATMAP` metric types.
- Improved UI consistency by adjusting spacing and formatting.
- Replaced redundant code with cleaner, more maintainable patterns.

This change improves the reliability and readability of the session
filtering and loading logic in the WidgetSessions component.
This commit is contained in:
Shekar Siri 2025-04-15 18:14:10 +02:00
parent dcd19e3c83
commit 3f1f6c03f2

View file

@ -1,33 +1,33 @@
import React, { useEffect, useState } from 'react';
import { NoContent, Loader, Pagination } from 'UI';
import { Button, Tag, Tooltip, Dropdown, message } from 'antd';
import { UndoOutlined, DownOutlined } from '@ant-design/icons';
import React, {useEffect, useState} from 'react';
import {NoContent, Loader, Pagination} from 'UI';
import {Button, Tag, Tooltip, Dropdown, message} from 'antd';
import {UndoOutlined, DownOutlined} from '@ant-design/icons';
import cn from 'classnames';
import { useStore } from 'App/mstore';
import {useStore} from 'App/mstore';
import SessionItem from 'Shared/SessionItem';
import { observer } from 'mobx-react-lite';
import { DateTime } from 'luxon';
import { debounce, numberWithCommas } from 'App/utils';
import {observer} from 'mobx-react-lite';
import {DateTime} from 'luxon';
import {debounce, numberWithCommas} from 'App/utils';
import useIsMounted from 'App/hooks/useIsMounted';
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
import { HEATMAP, USER_PATH, FUNNEL } from 'App/constants/card';
import { useTranslation } from 'react-i18next';
import AnimatedSVG, {ICONS} from 'Shared/AnimatedSVG/AnimatedSVG';
import {HEATMAP, USER_PATH, FUNNEL} from 'App/constants/card';
import {useTranslation} from 'react-i18next';
interface Props {
className?: string;
}
function WidgetSessions(props: Props) {
const { t } = useTranslation();
const {t} = useTranslation();
const listRef = React.useRef<HTMLDivElement>(null);
const { className = '' } = props;
const {className = ''} = props;
const [activeSeries, setActiveSeries] = useState('all');
const [data, setData] = useState<any>([]);
const isMounted = useIsMounted();
const [loading, setLoading] = useState(false);
// all filtering done through series now
const filteredSessions = getListSessionsBySeries(data, 'all');
const { dashboardStore, metricStore, sessionStore, customFieldStore } =
const {dashboardStore, metricStore, sessionStore, customFieldStore} =
useStore();
const focusedSeries = metricStore.focusedSeriesName;
const filter = dashboardStore.drillDownFilter;
@ -39,7 +39,7 @@ function WidgetSessions(props: Props) {
'LLL dd, yyyy HH:mm',
);
const [seriesOptions, setSeriesOptions] = useState([
{ label: t('All'), value: 'all' },
{label: t('All'), value: 'all'},
]);
const hasFilters =
filter.filters.length > 0 ||
@ -61,13 +61,12 @@ function WidgetSessions(props: Props) {
label: item.name,
value: item.seriesId ?? item.name,
}));
setSeriesOptions([{ label: t('All'), value: 'all' }, ...seriesOptions]);
setSeriesOptions([{label: t('All'), value: 'all'}, ...seriesOptions]);
}, [widget.series.length]);
const fetchSessions = (metricId: any, filter: any) => {
if (!isMounted()) return;
setLoading(true);
delete filter.eventsOrderSupport;
if (widget.metricType === FUNNEL) {
if (filter.series[0].filter.filters.length === 0) {
setLoading(false);
@ -75,14 +74,34 @@ function WidgetSessions(props: Props) {
}
}
setLoading(true);
const filterCopy = {...filter};
delete filterCopy.eventsOrderSupport;
try {
// Handle filters properly with null checks
if (filterCopy.filters && filterCopy.filters.length > 0) {
// Ensure the nested path exists before pushing
if (filterCopy.series?.[0]?.filter) {
if (!filterCopy.series[0].filter.filters) {
filterCopy.series[0].filter.filters = [];
}
filterCopy.series[0].filter.filters.push(...filterCopy.filters);
}
filterCopy.filters = [];
}
} catch (e) {
// do nothing
}
widget
.fetchSessions(metricId, filter)
.fetchSessions(metricId, filterCopy)
.then((res: any) => {
setData(res);
if (metricStore.drillDown) {
setTimeout(() => {
message.info(t('Sessions Refreshed!'));
listRef.current?.scrollIntoView({ behavior: 'smooth' });
listRef.current?.scrollIntoView({behavior: 'smooth'});
metricStore.setDrillDown(false);
}, 0);
}
@ -93,7 +112,7 @@ function WidgetSessions(props: Props) {
};
const fetchClickmapSessions = (customFilters: Record<string, any>) => {
sessionStore.getSessions(customFilters).then((data) => {
setData([{ ...data, seriesId: 1, seriesName: 'Clicks' }]);
setData([{...data, seriesId: 1, seriesName: 'Clicks'}]);
});
};
const debounceRequest: any = React.useCallback(
@ -235,7 +254,7 @@ function WidgetSessions(props: Props) {
{hasFilters && (
<Tooltip title={t('Clear Drilldown')} placement="top">
<Button type="text" size="small" onClick={clearFilters}>
<UndoOutlined />
<UndoOutlined/>
</Button>
</Tooltip>
)}
@ -271,7 +290,7 @@ function WidgetSessions(props: Props) {
<Button type="text" size="small">
{seriesOptions.find((option) => option.value === activeSeries)
?.label || t('Select Series')}
<DownOutlined />
<DownOutlined/>
</Button>
</Dropdown>
</div>
@ -284,8 +303,8 @@ function WidgetSessions(props: Props) {
<NoContent
title={
<div className="flex items-center justify-center flex-col">
<AnimatedSVG name={ICONS.NO_SESSIONS} size={60} />
<div className="mt-4" />
<AnimatedSVG name={ICONS.NO_SESSIONS} size={60}/>
<div className="mt-4"/>
<div className="text-center">
{t('No relevant sessions found for the selected time period')}
</div>
@ -300,7 +319,7 @@ function WidgetSessions(props: Props) {
session={session}
metaList={metaList}
/>
<div className="border-b" />
<div className="border-b"/>
</React.Fragment>
))}
@ -364,7 +383,7 @@ const getListSessionsBySeries = (data: any, seriesId: any) => {
}
return arr;
},
{ sessions: [] },
{sessions: []},
);
arr.total =
seriesId === 'all'