* ui: start redesign for live search/list
* ui: remove search field, show filters picker by default for assist
* ui: filter modal wip
* ui: filter modal wip
* ui: finish with omnisearch thing
* ui: start new dashboard redesign
* refining new card section
* ui: some "new dashboard" view improvs, fix icons fill inheritance, add ai button colors
* ui: split up search component (1.22+ tbd?), restrict filter type to own modals
* ui: mimic ant card
* ui: some changes for card creation flow, add series table to CustomMetricLineChart.tsx
* ui: more chart types, add table with filtering out series, start "compare to" thing
* ui: comparison designs
* ui: better granularity support, comparison view for bar chart
* ui: add comparison to more charts, add "metric" chart (BigNumChart.tsx)
* ui: cleanup logs
* ui: fix defualt import, fix sessheader crash, fix condition set ui
* ui: some refactoring and type coverage...
* ui: more refactoring; silence warnings for list renderers
* ui: moveing and renaming filters
* ui: add metricOf selector
* ui: check for metric type
* ui: fix crashes, add widget library table
* ui: change new series btn
* ui: restrict filterselection
* ui: fix timeseries table format
* ui: autoclose autocomplete modal
* ui: some fixes to issue filters default value, display and placeholder consistency
* ui: some dashboard issues with card selection modal and empty states
* ui: comparing for funnels, alternate column view, some refactoring to prepare for customizations...
* Style improvements in omnisearch headers
* Revert "Style improvements in omnisearch headers"
This reverts commit 89e51b0531.
* ui: show health status fetch error
* ui: table, bignum and comp for funnel, add csv export
* Omni-search improvements. (#2823)
Co-authored-by: Sudheer Salavadi <connect.uxmaster@gmail.com>
* ui: fix bad merge (git hallo?)
* ui: fix filter mapper
* rm husky
* ui: add card floater
* ui: add card floater
* ui: refactor local autocomplete input
* ui: filterout empty options
* UI improvements in New Cards (#2864)
* ui: some minor dashb improvements
* ui: metric type selector for head
* ui: change card type selector, add automapping
* ui: check chart/widget components for crashes
* ui: fix crash with table metrics
* ui: fix crashes related to metric type changes
* ui: filter category for clickmap filt
* ui: fix dash options menu, fix cr/up button
* ui: fix dash list menu propagation
* ui: hide addevent in heatmaps
* ui: fix time mapping for charts
* ui: fix exclusion component for path
* ui: fix series amount for path analysis, rm grid/list selector
* ui: fix icons in list view
* ui: fix for dlt button in widgets
* Various improvements Cards, OmniSearch and Cards Listing (#2881)
* ui: some improvements for cards list view, funnels and general filter display
* ui: longer node width for journey
* Product Analytics UI Improvements. (#2896)
* Various improvements Cards, OmniSearch and Cards Listing
* Improved cards listing page
* Various improvements in product analytics
* Charts UI improvements
---------
Co-authored-by: nick-delirium <nikita@openreplay.com>
* Live se red s2 (#2902)
* Various improvements Cards, OmniSearch and Cards Listing
* Improved cards listing page
* Various improvements in product analytics
* Charts UI improvements
* ui crash
---------
Co-authored-by: Sudheer Salavadi <connect.uxmaster@gmail.com>
* ui: fix lucide version
* ui: fix custom comparison period
* ui: fix custom comparison period
* ui: handle minor paths on frontend for path/sankey
* ui: assign icon for event types in sankey nodes
* ui: some strings changed
* ui: hide btn control for table view
* Various improvements in graphs, and analytics pages. (#2908)
* Various improvements Cards, OmniSearch and Cards Listing
* Improved cards listing page
* Various improvements in product analytics
* Charts UI improvements
* ui crash
* Chart improvements and layout toggling
* Various improvements
* Tooltips
---------
Co-authored-by: nick-delirium <nikita@openreplay.com>
* ui: fix weekday mapper for x axis on >7d range
* ui: lower default density to 35, fix table card display
* ui: filterMinorPaths -> return input data if nodes arr. is empty
* ui: use default filter for sessions, move around saved search actions, remove tags modal
* ui: fix card creator visibility in grid, fix table exporter visiblility in grid
* ui: fix some proptype warnings
* ui: change new series default expand state
* ui: save comp range in widget details
* ui: move timeseries to apache echarts
* ui: use unique id for window values
* ui: add timestamp for comp tooltip row
* ui: rename var for readability
* ui: fix comparison for 24hr
* Streamlined icons and improved echarts trends (#2920)
* Various improvements Cards, OmniSearch and Cards Listing
* Improved cards listing page
* Various improvements in product analytics
* Charts UI improvements
* ui crash
* Chart improvements and layout toggling
* Various improvements
* Tooltips
* Improved icons in cards listing page
* Update WidgetFormNew.tsx
* Sankey improvements
* Icon and text updates
Text alignment and color changes in x-ray
Icon Mapping with appropriate names and shapes
* Colors and Trend Chart Interaction updates
* ui
---------
Co-authored-by: nick-delirium <nikita@openreplay.com>
* ui: series update observe
* ui: resize chart on window
* ui: move barchart to echarts
* ui: fixing bars under comparison
* ui: fixing horizontal bar tooltip
* ui: rm unused
* ui: keep state in storage
* ui: small fixes for granularity and comparisons
* ui: fix savesearch button, fix comparison period tracking
* ui: fix funnel type selection
* ui: fixing saved search button
* ui: enable error logging, remove immutable reference
* ui: update savedsearch drop
* ui: disable button if no saved
* ui: small ui fixes
* ui: add drill to summary charts, add more options to card category picker
* ui: filter compSeries with table
* ui: swap tag_el operator and value
* ui: fix top countries
* ui: further changes for search/cards
* ui: move focus to session list on line click
* ui: fix issue filter mapper
* ui: fix alert pre-init function, fix metric list options, fix legend placement
* ui: fixes for card library
* ui: work on new sankey chart
* ui: fix metadata prefetch
* ui: moving snakey to echarts
* ui: fix funnel comparison focus
* ui: stale loader
---------
Co-authored-by: Sudheer Salavadi <connect.uxmaster@gmail.com>
270 lines
10 KiB
TypeScript
270 lines
10 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { NoContent, Loader, Pagination } from 'UI';
|
|
import {Button, Tag, Tooltip, Dropdown, notification} from 'antd';
|
|
import {UndoOutlined, DownOutlined} from '@ant-design/icons'
|
|
import cn from 'classnames';
|
|
import { useStore } from 'App/mstore';
|
|
import SessionItem from 'Shared/SessionItem';
|
|
import { observer } from 'mobx-react-lite';
|
|
import { DateTime } from 'luxon';
|
|
import { debounce } from 'App/utils';
|
|
import useIsMounted from 'App/hooks/useIsMounted';
|
|
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
|
|
import { numberWithCommas } from 'App/utils';
|
|
import { HEATMAP } from 'App/constants/card';
|
|
|
|
interface Props {
|
|
className?: string;
|
|
}
|
|
|
|
function WidgetSessions(props: Props) {
|
|
const listRef = React.useRef<HTMLDivElement>(null);
|
|
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 } = useStore();
|
|
const focusedSeries = metricStore.focusedSeriesName;
|
|
const filter = dashboardStore.drillDownFilter;
|
|
const widget = metricStore.instance;
|
|
const startTime = DateTime.fromMillis(filter.startTimestamp).toFormat('LLL dd, yyyy HH:mm');
|
|
const endTime = DateTime.fromMillis(filter.endTimestamp).toFormat('LLL dd, yyyy HH:mm');
|
|
const [seriesOptions, setSeriesOptions] = useState([{ label: 'All', value: 'all' }]);
|
|
const hasFilters = filter.filters.length > 0 || (filter.startTimestamp !== dashboardStore.drillDownPeriod.start || filter.endTimestamp !== dashboardStore.drillDownPeriod.end);
|
|
const filterText = filter.filters.length > 0 ? filter.filters[0].value : '';
|
|
const metaList = customFieldStore.list.map((i: any) => i.key);
|
|
|
|
const seriesDropdownItems = seriesOptions.map((option) => ({
|
|
key: option.value,
|
|
label: (
|
|
<div onClick={() => setActiveSeries(option.value)}>
|
|
{option.label}
|
|
</div>
|
|
)
|
|
}));
|
|
|
|
useEffect(() => {
|
|
if (!widget.series) return;
|
|
const seriesOptions = widget.series.map((item: any) => ({
|
|
label: item.name,
|
|
value: item.seriesId
|
|
}));
|
|
setSeriesOptions([{ label: 'All', value: 'all' }, ...seriesOptions]);
|
|
}, [widget.series]);
|
|
|
|
const fetchSessions = (metricId: any, filter: any) => {
|
|
if (!isMounted()) return;
|
|
setLoading(true);
|
|
delete filter.eventsOrderSupport;
|
|
widget
|
|
.fetchSessions(metricId, filter)
|
|
.then((res: any) => {
|
|
setData(res);
|
|
if (metricStore.drillDown) {
|
|
setTimeout(() => {
|
|
notification.open({
|
|
placement: 'top',
|
|
role: 'status',
|
|
message: 'Sessions Refreshed!'
|
|
})
|
|
listRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
metricStore.setDrillDown(false);
|
|
}, 0)
|
|
}
|
|
})
|
|
.finally(() => {
|
|
setLoading(false);
|
|
});
|
|
};
|
|
const fetchClickmapSessions = (customFilters: Record<string, any>) => {
|
|
sessionStore.getSessions(customFilters).then((data) => {
|
|
setData([{ ...data, seriesId: 1, seriesName: 'Clicks' }]);
|
|
});
|
|
};
|
|
const debounceRequest: any = React.useCallback(debounce(fetchSessions, 1000), []);
|
|
const debounceClickMapSearch = React.useCallback(debounce(fetchClickmapSessions, 1000), []);
|
|
|
|
const depsString = JSON.stringify(widget.series);
|
|
|
|
const loadData = () => {
|
|
if (widget.metricType === HEATMAP && metricStore.clickMapSearch) {
|
|
const clickFilter = {
|
|
value: [metricStore.clickMapSearch],
|
|
type: 'CLICK',
|
|
operator: 'onSelector',
|
|
isEvent: true,
|
|
// @ts-ignore
|
|
filters: []
|
|
};
|
|
const timeRange = {
|
|
rangeValue: dashboardStore.drillDownPeriod.rangeValue,
|
|
startDate: dashboardStore.drillDownPeriod.start,
|
|
endDate: dashboardStore.drillDownPeriod.end
|
|
};
|
|
const customFilter = {
|
|
...filter,
|
|
...timeRange,
|
|
filters: [...sessionStore.userFilter.filters, clickFilter]
|
|
};
|
|
debounceClickMapSearch(customFilter);
|
|
} else {
|
|
const usedSeries = focusedSeries ? widget.series.filter((s) => s.name === focusedSeries) : widget.series;
|
|
debounceRequest(widget.metricId, {
|
|
...filter,
|
|
series: usedSeries.map((s) => s.toJson()),
|
|
page: metricStore.sessionsPage,
|
|
limit: metricStore.sessionsPageSize
|
|
});
|
|
}
|
|
};
|
|
useEffect(() => {
|
|
metricStore.updateKey('sessionsPage', 1);
|
|
loadData();
|
|
}, [
|
|
filter.startTimestamp,
|
|
filter.endTimestamp,
|
|
filter.filters,
|
|
depsString,
|
|
metricStore.clickMapSearch,
|
|
focusedSeries
|
|
]);
|
|
useEffect(loadData, [metricStore.sessionsPage]);
|
|
useEffect(() => {
|
|
if (activeSeries === 'all') {
|
|
metricStore.setFocusedSeriesName(null);
|
|
} else {
|
|
metricStore.setFocusedSeriesName(seriesOptions.find((option) => option.value === activeSeries)?.label, false);
|
|
}
|
|
}, [activeSeries])
|
|
useEffect(() => {
|
|
if (focusedSeries) {
|
|
setActiveSeries(seriesOptions.find((option) => option.label === focusedSeries)?.value || 'all');
|
|
} else {
|
|
setActiveSeries('all');
|
|
}
|
|
}, [focusedSeries])
|
|
|
|
const clearFilters = () => {
|
|
metricStore.updateKey('sessionsPage', 1);
|
|
dashboardStore.resetDrillDownFilter();
|
|
};
|
|
|
|
return (
|
|
<div className={cn(className, 'bg-white p-3 pb-0 rounded-xl shadow-sm border mt-3')}>
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<div className="flex items-baseline gap-2">
|
|
<h2 className="text-xl">{metricStore.clickMapSearch ? 'Clicks' : 'Sessions'}</h2>
|
|
<div className="ml-2 color-gray-medium">
|
|
{metricStore.clickMapLabel ? `on "${metricStore.clickMapLabel}" ` : null}
|
|
between <span className="font-medium color-gray-darkest">{startTime}</span> and{' '}
|
|
<span className="font-medium color-gray-darkest">{endTime}</span>{' '}
|
|
</div>
|
|
{hasFilters && <Tooltip title='Clear Drilldown' placement='top'><Button type='text' size='small' onClick={clearFilters}><UndoOutlined /></Button></Tooltip>}
|
|
</div>
|
|
|
|
{hasFilters && widget.metricType === 'table' && <div className="py-2"><Tag closable onClose={clearFilters}>{filterText}</Tag></div>}
|
|
|
|
</div>
|
|
|
|
<div className="flex items-center gap-4">
|
|
{widget.metricType !== 'table' && widget.metricType !== HEATMAP && (
|
|
<div className="flex items-center ml-6">
|
|
<span className="mr-2 color-gray-medium">Filter by Series</span>
|
|
<Dropdown
|
|
menu={{
|
|
items: seriesDropdownItems,
|
|
selectable: true,
|
|
selectedKeys: [activeSeries]
|
|
}}
|
|
trigger={['click']}
|
|
>
|
|
<Button type="text" size='small'>
|
|
{seriesOptions.find(option => option.value === activeSeries)?.label || 'Select Series'}
|
|
<DownOutlined />
|
|
</Button>
|
|
</Dropdown>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-3" >
|
|
<Loader loading={loading}>
|
|
<NoContent
|
|
title={
|
|
<div className="flex items-center justify-center flex-col">
|
|
<AnimatedSVG name={ICONS.NO_SESSIONS} size={60} />
|
|
<div className="mt-4" />
|
|
<div className="text-center">
|
|
No relevant sessions found for the selected time period
|
|
</div>
|
|
</div>
|
|
}
|
|
show={filteredSessions.sessions.length === 0}
|
|
>
|
|
{filteredSessions.sessions.map((session: any) => (
|
|
<React.Fragment key={session.sessionId}>
|
|
<SessionItem session={session} metaList={metaList} />
|
|
<div className="border-b" />
|
|
</React.Fragment>
|
|
))}
|
|
|
|
<div className="flex items-center justify-between p-5" ref={listRef}>
|
|
<div>
|
|
Showing{' '}
|
|
<span className="font-medium">
|
|
{(metricStore.sessionsPage - 1) * metricStore.sessionsPageSize + 1}
|
|
</span>{' '}
|
|
to{' '}
|
|
<span className="font-medium">
|
|
{(metricStore.sessionsPage - 1) * metricStore.sessionsPageSize +
|
|
filteredSessions.sessions.length}
|
|
</span>{' '}
|
|
of <span className="font-medium">{numberWithCommas(filteredSessions.total)}</span>{' '}
|
|
sessions.
|
|
</div>
|
|
<Pagination
|
|
page={metricStore.sessionsPage}
|
|
total={filteredSessions.total}
|
|
onPageChange={(page: any) => metricStore.updateKey('sessionsPage', page)}
|
|
limit={metricStore.sessionsPageSize}
|
|
debounceRequest={500}
|
|
/>
|
|
</div>
|
|
</NoContent>
|
|
</Loader>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const getListSessionsBySeries = (data: any, seriesId: any) => {
|
|
const arr = data.reduce(
|
|
(arr: any, element: any) => {
|
|
if (seriesId === 'all') {
|
|
const sessionIds = arr.sessions.map((i: any) => i.sessionId);
|
|
const sessions = element.sessions.filter((i: any) => !sessionIds.includes(i.sessionId));
|
|
arr.sessions.push(...sessions);
|
|
} else if (element.seriesId === seriesId) {
|
|
const sessionIds = arr.sessions.map((i: any) => i.sessionId);
|
|
const sessions = element.sessions.filter((i: any) => !sessionIds.includes(i.sessionId));
|
|
const duplicates = element.sessions.length - sessions.length;
|
|
arr.sessions.push(...sessions);
|
|
arr.total = element.total - duplicates;
|
|
}
|
|
return arr;
|
|
},
|
|
{ sessions: [] }
|
|
);
|
|
arr.total =
|
|
seriesId === 'all'
|
|
? Math.max(...data.map((i: any) => i.total))
|
|
: data.find((i: any) => i.seriesId === seriesId).total;
|
|
return arr;
|
|
};
|
|
|
|
export default observer(WidgetSessions);
|