feat(metrics): implement server-side pagination and sorting
Refactors metrics list view to use server-side pagination and sorting instead of client-side implementation. This improves performance for large datasets by reducing client workload and network payload size. Key changes: - Add pagination API endpoint in MetricService - Update MetricStore to handle server pagination - Refactor ListView component to use server-side sorting - Remove client-side sorting and pagination logic
This commit is contained in:
parent
4b09213448
commit
687ab05f22
5 changed files with 193 additions and 256 deletions
|
|
@ -41,9 +41,9 @@ function MetricViewHeader() {
|
||||||
// Show header if there are cards or if a filter is active
|
// Show header if there are cards or if a filter is active
|
||||||
const showHeader = cardsLength > 0 || isFilterActive;
|
const showHeader = cardsLength > 0 || isFilterActive;
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
metricStore.updateKey('sort', { by: 'desc' });
|
// metricStore.updateKey('sort', { by: 'desc' });
|
||||||
}, [metricStore]);
|
// }, [metricStore]);
|
||||||
|
|
||||||
const handleMenuClick = ({ key }: { key: string }) => {
|
const handleMenuClick = ({ key }: { key: string }) => {
|
||||||
metricStore.updateKey('filter', { ...filter, type: key });
|
metricStore.updateKey('filter', { ...filter, type: key });
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,13 @@ import {
|
||||||
Button,
|
Button,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
Modal as AntdModal,
|
Modal as AntdModal,
|
||||||
Avatar,
|
Avatar, TableColumnType
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import {
|
import {
|
||||||
TeamOutlined,
|
TeamOutlined,
|
||||||
LockOutlined,
|
LockOutlined,
|
||||||
EditOutlined,
|
EditOutlined,
|
||||||
DeleteOutlined,
|
DeleteOutlined
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { EllipsisVertical } from 'lucide-react';
|
import { EllipsisVertical } from 'lucide-react';
|
||||||
import { TablePaginationConfig, SorterResult } from 'antd/lib/table/interface';
|
import { TablePaginationConfig, SorterResult } from 'antd/lib/table/interface';
|
||||||
|
|
@ -37,90 +37,41 @@ interface Props {
|
||||||
toggleSelection?: (metricId: number | number[]) => void;
|
toggleSelection?: (metricId: number | number[]) => void;
|
||||||
disableSelection?: boolean;
|
disableSelection?: boolean;
|
||||||
inLibrary?: boolean;
|
inLibrary?: boolean;
|
||||||
|
loading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ListView: React.FC<Props> = ({
|
const ListView: React.FC<Props> = ({
|
||||||
list,
|
list,
|
||||||
siteId,
|
siteId,
|
||||||
selectedList,
|
selectedList,
|
||||||
toggleSelection,
|
toggleSelection,
|
||||||
disableSelection = false,
|
disableSelection = false,
|
||||||
inLibrary = false
|
inLibrary = false,
|
||||||
}) => {
|
loading = false
|
||||||
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [sorter, setSorter] = useState<{ field: string; order: 'ascend' | 'descend' }>({
|
|
||||||
field: 'lastModified',
|
|
||||||
order: 'descend',
|
|
||||||
});
|
|
||||||
const [pagination, setPagination] = useState<TablePaginationConfig>({
|
|
||||||
current: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
});
|
|
||||||
const [editingMetricId, setEditingMetricId] = useState<number | null>(null);
|
const [editingMetricId, setEditingMetricId] = useState<number | null>(null);
|
||||||
const [newName, setNewName] = useState('');
|
const [newName, setNewName] = useState('');
|
||||||
const { metricStore } = useStore();
|
const { metricStore } = useStore();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
const sortedData = useMemo(
|
|
||||||
() =>
|
|
||||||
[...list].sort((a, b) => {
|
|
||||||
if (sorter.field === 'lastModified') {
|
|
||||||
return sorter.order === 'ascend'
|
|
||||||
? new Date(a.lastModified).getTime() -
|
|
||||||
new Date(b.lastModified).getTime()
|
|
||||||
: new Date(b.lastModified).getTime() -
|
|
||||||
new Date(a.lastModified).getTime();
|
|
||||||
}
|
|
||||||
if (sorter.field === 'name') {
|
|
||||||
return sorter.order === 'ascend'
|
|
||||||
? a.name?.localeCompare(b.name) || 0
|
|
||||||
: b.name?.localeCompare(a.name) || 0;
|
|
||||||
}
|
|
||||||
if (sorter.field === 'owner') {
|
|
||||||
return sorter.order === 'ascend'
|
|
||||||
? a.owner?.localeCompare(b.owner) || 0
|
|
||||||
: b.owner?.localeCompare(a.owner) || 0;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}),
|
|
||||||
[list, sorter],
|
|
||||||
);
|
|
||||||
|
|
||||||
const paginatedData = useMemo(() => {
|
|
||||||
const start = ((pagination.current || 1) - 1) * (pagination.pageSize || 10);
|
|
||||||
return sortedData.slice(start, start + (pagination.pageSize || 10));
|
|
||||||
}, [sortedData, pagination]);
|
|
||||||
|
|
||||||
const totalMessage = (
|
const totalMessage = (
|
||||||
<>
|
<>
|
||||||
{t('Showing')}{' '}
|
{t('Showing')}{' '}
|
||||||
<Text strong>
|
<Text strong>
|
||||||
{(pagination.pageSize || 10) * ((pagination.current || 1) - 1) + 1}
|
{(metricStore.pageSize || 10) * ((metricStore.page || 1) - 1) + 1}
|
||||||
</Text>{' '}
|
</Text>{' '}
|
||||||
{t('to')}{' '}
|
{t('to')}{' '}
|
||||||
<Text strong>
|
<Text strong>
|
||||||
{Math.min(
|
{Math.min(
|
||||||
(pagination.pageSize || 10) * (pagination.current || 1),
|
(metricStore.pageSize || 10) * (metricStore.page || 1),
|
||||||
list.length,
|
list.length
|
||||||
)}
|
)}
|
||||||
</Text>{' '}
|
</Text>{' '}
|
||||||
{t('of')} <Text strong>{list.length}</Text> {t('cards')}
|
{t('of')} <Text strong>{list.length}</Text> {t('cards')}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleTableChange = (
|
|
||||||
pag: TablePaginationConfig,
|
|
||||||
_filters: Record<string, (string | number | boolean)[] | null>,
|
|
||||||
sorterParam: SorterResult<Widget> | SorterResult<Widget>[],
|
|
||||||
) => {
|
|
||||||
const sortRes = sorterParam as SorterResult<Widget>;
|
|
||||||
setSorter({
|
|
||||||
field: sortRes.field as string,
|
|
||||||
order: sortRes.order as 'ascend' | 'descend',
|
|
||||||
});
|
|
||||||
setPagination(pag);
|
|
||||||
};
|
|
||||||
|
|
||||||
const parseDate = (dateString: string) => {
|
const parseDate = (dateString: string) => {
|
||||||
let date = new Date(dateString);
|
let date = new Date(dateString);
|
||||||
if (isNaN(date.getTime())) {
|
if (isNaN(date.getTime())) {
|
||||||
|
|
@ -182,7 +133,7 @@ const ListView: React.FC<Props> = ({
|
||||||
cancelText: t('No'),
|
cancelText: t('No'),
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
await metricStore.delete(metric);
|
await metricStore.delete(metric);
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (key === 'rename') {
|
if (key === 'rename') {
|
||||||
|
|
@ -206,7 +157,7 @@ const ListView: React.FC<Props> = ({
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{ key: 'rename', icon: <EditOutlined />, label: t('Rename') },
|
{ key: 'rename', icon: <EditOutlined />, label: t('Rename') },
|
||||||
{ key: 'delete', icon: <DeleteOutlined />, label: t('Delete') },
|
{ key: 'delete', icon: <DeleteOutlined />, label: t('Delete') }
|
||||||
];
|
];
|
||||||
|
|
||||||
const renderTitle = (_text: string, metric: Widget) => (
|
const renderTitle = (_text: string, metric: Widget) => (
|
||||||
|
|
@ -245,80 +196,109 @@ const ListView: React.FC<Props> = ({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const columns = [
|
const columns: TableColumnType<any>[] = [
|
||||||
{
|
{
|
||||||
title: t('Title'),
|
title: t('Title'),
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
key: 'title',
|
key: 'title',
|
||||||
className: 'cap-first pl-4',
|
className: 'cap-first pl-4',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
|
sortOrder: metricStore.sort.field === 'name' ? metricStore.sort.order : undefined,
|
||||||
width: inLibrary ? '31%' : '25%',
|
width: inLibrary ? '31%' : '25%',
|
||||||
render: renderTitle,
|
render: renderTitle
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Owner'),
|
title: t('Owner'),
|
||||||
dataIndex: 'owner',
|
dataIndex: 'owner_email',
|
||||||
key: 'owner',
|
key: 'owner',
|
||||||
className: 'capitalize',
|
className: 'capitalize',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
|
sortOrder: metricStore.sort.field === 'owner_email' ? metricStore.sort.order : undefined,
|
||||||
width: inLibrary ? '31%' : '25%',
|
width: inLibrary ? '31%' : '25%',
|
||||||
render: renderOwner,
|
render: renderOwner
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Last Modified'),
|
title: t('Last Modified'),
|
||||||
dataIndex: 'lastModified',
|
dataIndex: 'edited_at',
|
||||||
key: 'lastModified',
|
key: 'lastModified',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
|
sortOrder: metricStore.sort.field === 'edited_at' ? metricStore.sort.order : undefined,
|
||||||
width: inLibrary ? '31%' : '25%',
|
width: inLibrary ? '31%' : '25%',
|
||||||
render: renderLastModified,
|
render: renderLastModified
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!inLibrary) {
|
if (!inLibrary) {
|
||||||
columns.push({
|
columns.push({
|
||||||
title: '',
|
title: '',
|
||||||
key: 'options',
|
key: 'options',
|
||||||
className: 'text-right',
|
className: 'text-right',
|
||||||
width: '5%',
|
width: '5%',
|
||||||
render: renderOptions,
|
render: renderOptions
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if (metricStore.sort.field) {
|
||||||
|
// columns.forEach((col) => {
|
||||||
|
// col.sortOrder = col.key === metricStore.sort.field ? metricStore.sort.order : false;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
console.log('store', metricStore.sort);
|
||||||
|
|
||||||
|
const handleTableChange = (
|
||||||
|
pag: TablePaginationConfig,
|
||||||
|
_filters: Record<string, (string | number | boolean)[] | null>,
|
||||||
|
sorterParam: SorterResult<Widget> | SorterResult<Widget>[]
|
||||||
|
) => {
|
||||||
|
const sorter = Array.isArray(sorterParam) ? sorterParam[0] : sorterParam;
|
||||||
|
let order = sorter.order;
|
||||||
|
if (metricStore.sort.field === sorter.field) {
|
||||||
|
order = metricStore.sort.order === 'ascend' ? 'descend' : 'ascend';
|
||||||
|
}
|
||||||
|
console.log('sorter', { field: sorter.field, order });
|
||||||
|
metricStore.updateKey('sort', { field: sorter.field, order });
|
||||||
|
metricStore.updateKey('page', pag.current || 1);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Table
|
<Table
|
||||||
|
loading={loading}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={paginatedData}
|
dataSource={list}
|
||||||
rowKey="metricId"
|
rowKey="metricId"
|
||||||
showSorterTooltip={false}
|
showSorterTooltip={false}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
sortDirections={['ascend', 'descend']}
|
||||||
onRow={
|
onRow={
|
||||||
inLibrary
|
inLibrary
|
||||||
? (record) => ({
|
? (record) => ({
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (!disableSelection) toggleSelection?.(record?.metricId);
|
if (!disableSelection) toggleSelection?.(record?.metricId);
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
rowSelection={
|
rowSelection={
|
||||||
!disableSelection
|
!disableSelection
|
||||||
? {
|
? {
|
||||||
selectedRowKeys: selectedList,
|
selectedRowKeys: selectedList,
|
||||||
onChange: (keys) => toggleSelection && toggleSelection(keys),
|
onChange: (keys) => toggleSelection && toggleSelection(keys),
|
||||||
columnWidth: 16,
|
columnWidth: 16
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
pagination={{
|
pagination={{
|
||||||
current: pagination.current,
|
current: metricStore.page,
|
||||||
pageSize: pagination.pageSize,
|
pageSize: metricStore.pageSize,
|
||||||
total: sortedData.length,
|
total: metricStore.total,
|
||||||
showSizeChanger: false,
|
showSizeChanger: false,
|
||||||
className: 'px-4',
|
className: 'px-4',
|
||||||
showLessItems: true,
|
showLessItems: true,
|
||||||
showTotal: () => totalMessage,
|
showTotal: () => totalMessage,
|
||||||
size: 'small',
|
size: 'small',
|
||||||
simple: true,
|
simple: true
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<AntdModal
|
<AntdModal
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,15 @@ import { sliceListPerPage } from 'App/utils';
|
||||||
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
|
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
|
||||||
import { Popover, Button } from 'antd';
|
import { Popover, Button } from 'antd';
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import GridView from './GridView';
|
|
||||||
import ListView from './ListView';
|
import ListView from './ListView';
|
||||||
import AddCardSection from '../AddCardSection/AddCardSection';
|
import AddCardSection from '../AddCardSection/AddCardSection';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
function MetricsList({
|
function MetricsList({
|
||||||
siteId,
|
siteId,
|
||||||
onSelectionChange,
|
onSelectionChange,
|
||||||
inLibrary,
|
inLibrary
|
||||||
}: {
|
}: {
|
||||||
siteId: string;
|
siteId: string;
|
||||||
onSelectionChange?: (selected: any[]) => void;
|
onSelectionChange?: (selected: any[]) => void;
|
||||||
inLibrary?: boolean;
|
inLibrary?: boolean;
|
||||||
|
|
@ -23,28 +22,27 @@ function MetricsList({
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { metricStore, dashboardStore } = useStore();
|
const { metricStore, dashboardStore } = useStore();
|
||||||
const metricsSearch = metricStore.filter.query;
|
const metricsSearch = metricStore.filter.query;
|
||||||
const listView = inLibrary ? true : metricStore.listView;
|
|
||||||
const [selectedMetrics, setSelectedMetrics] = useState<any>([]);
|
const [selectedMetrics, setSelectedMetrics] = useState<any>([]);
|
||||||
|
|
||||||
const dashboard = dashboardStore.selectedDashboard;
|
const dashboard = dashboardStore.selectedDashboard;
|
||||||
const existingCardIds = useMemo(
|
const existingCardIds = useMemo(
|
||||||
() => dashboard?.widgets?.map((i) => parseInt(i.metricId)),
|
() => dashboard?.widgets?.map((i) => parseInt(i.metricId)),
|
||||||
[dashboard],
|
[dashboard]
|
||||||
);
|
);
|
||||||
const cards = useMemo(
|
const cards = useMemo(
|
||||||
() =>
|
() =>
|
||||||
onSelectionChange
|
onSelectionChange
|
||||||
? metricStore.filteredCards.filter(
|
? metricStore.filteredCards.filter(
|
||||||
(i) => !existingCardIds?.includes(parseInt(i.metricId)),
|
(i) => !existingCardIds?.includes(parseInt(i.metricId))
|
||||||
)
|
)
|
||||||
: metricStore.filteredCards,
|
: metricStore.filteredCards,
|
||||||
[metricStore.filteredCards, existingCardIds, onSelectionChange],
|
[metricStore.filteredCards, existingCardIds, onSelectionChange]
|
||||||
);
|
);
|
||||||
const loading = metricStore.isLoading;
|
const loading = metricStore.isLoading;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void metricStore.fetchList();
|
void metricStore.fetchList();
|
||||||
}, [metricStore]);
|
}, [metricStore.page, metricStore.filter, metricStore.sort]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!onSelectionChange) return;
|
if (!onSelectionChange) return;
|
||||||
|
|
@ -69,14 +67,8 @@ function MetricsList({
|
||||||
metricStore.updateKey('sessionsPage', 1);
|
metricStore.updateKey('sessionsPage', 1);
|
||||||
}, [metricStore]);
|
}, [metricStore]);
|
||||||
|
|
||||||
const showOwn = metricStore.filter.showMine;
|
|
||||||
const toggleOwn = () => {
|
|
||||||
metricStore.updateKey('showMine', !showOwn);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isFiltered =
|
const isFiltered = metricStore.filter.query !== '' || metricStore.filter.type !== 'all';
|
||||||
metricsSearch !== '' ||
|
|
||||||
(metricStore.filter.type && metricStore.filter.type !== 'all');
|
|
||||||
|
|
||||||
const searchImageDimensions = { width: 60, height: 'auto' };
|
const searchImageDimensions = { width: 60, height: 'auto' };
|
||||||
const defaultImageDimensions = { width: 600, height: 'auto' };
|
const defaultImageDimensions = { width: 600, height: 'auto' };
|
||||||
|
|
@ -86,101 +78,65 @@ function MetricsList({
|
||||||
: defaultImageDimensions;
|
: defaultImageDimensions;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Loader loading={loading}>
|
<NoContent
|
||||||
<NoContent
|
show={!loading && length === 0}
|
||||||
show={length === 0}
|
title={
|
||||||
title={
|
<div className="flex flex-col items-center justify-center">
|
||||||
<div className="flex flex-col items-center justify-center">
|
<AnimatedSVG name={emptyImage} size={imageDimensions.width} />
|
||||||
<AnimatedSVG name={emptyImage} size={imageDimensions.width} />
|
<div className="text-center mt-3 text-lg font-medium">
|
||||||
<div className="text-center mt-3 text-lg font-medium">
|
{isFiltered
|
||||||
{isFiltered
|
? t('No matching results')
|
||||||
? t('No matching results')
|
: t('Unlock insights with data cards')}
|
||||||
: t('Unlock insights with data cards')}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
</div>
|
||||||
subtext={
|
}
|
||||||
isFiltered ? (
|
subtext={
|
||||||
''
|
isFiltered ? (
|
||||||
) : (
|
''
|
||||||
<div className="flex flex-col items-center">
|
|
||||||
<div>
|
|
||||||
{t('Create and customize cards to analyze trends and user behavior effectively.')}
|
|
||||||
</div>
|
|
||||||
<Popover
|
|
||||||
arrow={false}
|
|
||||||
overlayInnerStyle={{ padding: 0, borderRadius: '0.75rem' }}
|
|
||||||
content={<AddCardSection fit inCards />}
|
|
||||||
trigger="click"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
className="btn-create-card mt-3"
|
|
||||||
>
|
|
||||||
{t('Create Card')}
|
|
||||||
</Button>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{listView ? (
|
|
||||||
<ListView
|
|
||||||
disableSelection={!onSelectionChange}
|
|
||||||
siteId={siteId}
|
|
||||||
list={cards}
|
|
||||||
inLibrary={inLibrary}
|
|
||||||
selectedList={selectedMetrics}
|
|
||||||
existingCardIds={existingCardIds}
|
|
||||||
toggleSelection={toggleMetricSelection}
|
|
||||||
allSelected={cards.length === selectedMetrics.length}
|
|
||||||
showOwn={showOwn}
|
|
||||||
toggleOwn={toggleOwn}
|
|
||||||
toggleAll={({ target: { checked } }) =>
|
|
||||||
setSelectedMetrics(
|
|
||||||
checked
|
|
||||||
? cards
|
|
||||||
.map((i: any) => i.metricId)
|
|
||||||
.slice(0, 30 - (existingCardIds?.length || 0))
|
|
||||||
: [],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<>
|
<div className="flex flex-col items-center">
|
||||||
<GridView
|
<div>
|
||||||
siteId={siteId}
|
{t('Create and customize cards to analyze trends and user behavior effectively.')}
|
||||||
list={sliceListPerPage(
|
|
||||||
cards,
|
|
||||||
metricStore.page - 1,
|
|
||||||
metricStore.pageSize,
|
|
||||||
)}
|
|
||||||
selectedList={selectedMetrics}
|
|
||||||
toggleSelection={toggleMetricSelection}
|
|
||||||
/>
|
|
||||||
<div className="w-full flex items-center justify-between py-4 px-6 border-t">
|
|
||||||
<div>
|
|
||||||
{t('Showing')}{' '}
|
|
||||||
<span className="font-medium">
|
|
||||||
{Math.min(cards.length, metricStore.pageSize)}
|
|
||||||
</span>{' '}
|
|
||||||
{t('out of')}
|
|
||||||
<span className="font-medium">{cards.length}</span>
|
|
||||||
{t('cards')}
|
|
||||||
</div>
|
|
||||||
<Pagination
|
|
||||||
page={metricStore.page}
|
|
||||||
total={length}
|
|
||||||
onPageChange={(page) => metricStore.updateKey('page', page)}
|
|
||||||
limit={metricStore.pageSize}
|
|
||||||
debounceRequest={100}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
<Popover
|
||||||
)}
|
arrow={false}
|
||||||
</NoContent>
|
overlayInnerStyle={{ padding: 0, borderRadius: '0.75rem' }}
|
||||||
</Loader>
|
content={<AddCardSection fit inCards />}
|
||||||
|
trigger="click"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
className="btn-create-card mt-3"
|
||||||
|
>
|
||||||
|
{t('Create Card')}
|
||||||
|
</Button>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ListView
|
||||||
|
loading={loading}
|
||||||
|
disableSelection={!onSelectionChange}
|
||||||
|
siteId={siteId}
|
||||||
|
list={cards}
|
||||||
|
inLibrary={inLibrary}
|
||||||
|
selectedList={selectedMetrics}
|
||||||
|
// existingCardIds={existingCardIds}
|
||||||
|
toggleSelection={toggleMetricSelection}
|
||||||
|
// allSelected={cards.length === selectedMetrics.length}
|
||||||
|
// toggleAll={({ target: { checked } }) =>
|
||||||
|
// setSelectedMetrics(
|
||||||
|
// checked
|
||||||
|
// ? cards
|
||||||
|
// .map((i: any) => i.metricId)
|
||||||
|
// .slice(0, 30 - (existingCardIds?.length || 0))
|
||||||
|
// : []
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
/>
|
||||||
|
</NoContent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import {
|
||||||
HEATMAP,
|
HEATMAP,
|
||||||
USER_PATH,
|
USER_PATH,
|
||||||
RETENTION,
|
RETENTION,
|
||||||
CATEGORIES,
|
CATEGORIES
|
||||||
} from 'App/constants/card';
|
} from 'App/constants/card';
|
||||||
import { clickmapFilter } from 'App/types/filter/newFilter';
|
import { clickmapFilter } from 'App/types/filter/newFilter';
|
||||||
import { getRE } from 'App/utils';
|
import { getRE } from 'App/utils';
|
||||||
|
|
@ -31,7 +31,7 @@ const handleFilter = (card: Widget, filterType?: string) => {
|
||||||
FilterKey.ERRORS,
|
FilterKey.ERRORS,
|
||||||
FilterKey.FETCH,
|
FilterKey.FETCH,
|
||||||
`${TIMESERIES}_4xx_requests`,
|
`${TIMESERIES}_4xx_requests`,
|
||||||
`${TIMESERIES}_slow_network_requests`,
|
`${TIMESERIES}_slow_network_requests`
|
||||||
].includes(metricOf);
|
].includes(metricOf);
|
||||||
}
|
}
|
||||||
if (filterType === CATEGORIES.web_analytics) {
|
if (filterType === CATEGORIES.web_analytics) {
|
||||||
|
|
@ -41,7 +41,7 @@ const handleFilter = (card: Widget, filterType?: string) => {
|
||||||
FilterKey.REFERRER,
|
FilterKey.REFERRER,
|
||||||
FilterKey.USERID,
|
FilterKey.USERID,
|
||||||
FilterKey.LOCATION,
|
FilterKey.LOCATION,
|
||||||
FilterKey.USER_DEVICE,
|
FilterKey.USER_DEVICE
|
||||||
].includes(metricOf);
|
].includes(metricOf);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -75,58 +75,42 @@ interface MetricFilter {
|
||||||
query?: string;
|
query?: string;
|
||||||
showMine?: boolean;
|
showMine?: boolean;
|
||||||
type?: string;
|
type?: string;
|
||||||
dashboard?: [];
|
// dashboard?: [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class MetricStore {
|
export default class MetricStore {
|
||||||
isLoading: boolean = false;
|
isLoading: boolean = false;
|
||||||
|
|
||||||
isSaving: boolean = false;
|
isSaving: boolean = false;
|
||||||
|
|
||||||
metrics: Widget[] = [];
|
metrics: Widget[] = [];
|
||||||
|
|
||||||
instance = new Widget();
|
instance = new Widget();
|
||||||
|
|
||||||
page: number = 1;
|
page: number = 1;
|
||||||
|
total: number = 0;
|
||||||
pageSize: number = 10;
|
pageSize: number = 10;
|
||||||
|
|
||||||
metricsSearch: string = '';
|
metricsSearch: string = '';
|
||||||
|
sort: any = { columnKey: '', field: '', order: false };
|
||||||
sort: any = { by: 'desc' };
|
filter: any = { type: '', query: '' };
|
||||||
|
|
||||||
filter: MetricFilter = { type: 'all', dashboard: [], query: '' };
|
|
||||||
|
|
||||||
sessionsPage: number = 1;
|
sessionsPage: number = 1;
|
||||||
|
|
||||||
sessionsPageSize: number = 10;
|
sessionsPageSize: number = 10;
|
||||||
|
|
||||||
listView?: boolean = true;
|
listView?: boolean = true;
|
||||||
|
|
||||||
clickMapFilter: boolean = false;
|
clickMapFilter: boolean = false;
|
||||||
|
|
||||||
clickMapSearch = '';
|
clickMapSearch = '';
|
||||||
|
|
||||||
clickMapLabel = '';
|
clickMapLabel = '';
|
||||||
|
|
||||||
cardCategory: string | null = CATEGORIES.product_analytics;
|
cardCategory: string | null = CATEGORIES.product_analytics;
|
||||||
|
|
||||||
focusedSeriesName: string | null = null;
|
focusedSeriesName: string | null = null;
|
||||||
|
|
||||||
disabledSeries: string[] = [];
|
disabledSeries: string[] = [];
|
||||||
|
|
||||||
drillDown = false;
|
drillDown = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
makeAutoObservable(this);
|
makeAutoObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
get sortedWidgets() {
|
// get sortedWidgets() {
|
||||||
return [...this.metrics].sort((a, b) =>
|
// return [...this.metrics].sort((a, b) =>
|
||||||
this.sort.by === 'desc'
|
// this.sort.by === 'desc'
|
||||||
? b.lastModified - a.lastModified
|
// ? b.lastModified - a.lastModified
|
||||||
: a.lastModified - b.lastModified,
|
// : a.lastModified - b.lastModified
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
get filteredCards() {
|
get filteredCards() {
|
||||||
const filterRE = this.filter.query ? getRE(this.filter.query, 'i') : null;
|
const filterRE = this.filter.query ? getRE(this.filter.query, 'i') : null;
|
||||||
|
|
@ -138,7 +122,7 @@ export default class MetricStore {
|
||||||
(card) =>
|
(card) =>
|
||||||
(this.filter.showMine
|
(this.filter.showMine
|
||||||
? card.owner ===
|
? card.owner ===
|
||||||
JSON.parse(localStorage.getItem('user')!).account.email
|
JSON.parse(localStorage.getItem('user')!).account.email
|
||||||
: true) &&
|
: true) &&
|
||||||
handleFilter(card, this.filter.type) &&
|
handleFilter(card, this.filter.type) &&
|
||||||
(!dbIds.length ||
|
(!dbIds.length ||
|
||||||
|
|
@ -147,13 +131,13 @@ export default class MetricStore {
|
||||||
.some((id) => dbIds.includes(id))) &&
|
.some((id) => dbIds.includes(id))) &&
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
(!filterRE ||
|
(!filterRE ||
|
||||||
['name', 'owner'].some((key) => filterRE.test(card[key]))),
|
['name', 'owner'].some((key) => filterRE.test(card[key])))
|
||||||
)
|
|
||||||
.sort((a, b) =>
|
|
||||||
this.sort.by === 'desc'
|
|
||||||
? b.lastModified - a.lastModified
|
|
||||||
: a.lastModified - b.lastModified,
|
|
||||||
);
|
);
|
||||||
|
// .sort((a, b) =>
|
||||||
|
// this.sort.by === 'desc'
|
||||||
|
// ? b.lastModified - a.lastModified
|
||||||
|
// : a.lastModified - b.lastModified
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
// State Actions
|
// State Actions
|
||||||
|
|
@ -182,6 +166,7 @@ export default class MetricStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateKey(key: string, value: any) {
|
updateKey(key: string, value: any) {
|
||||||
|
console.log('key', key, value);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this[key] = value;
|
this[key] = value;
|
||||||
|
|
||||||
|
|
@ -207,7 +192,7 @@ export default class MetricStore {
|
||||||
this.instance.series[i].filter.eventsOrderSupport = [
|
this.instance.series[i].filter.eventsOrderSupport = [
|
||||||
'then',
|
'then',
|
||||||
'or',
|
'or',
|
||||||
'and',
|
'and'
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
if (type === HEATMAP && 'series' in obj) {
|
if (type === HEATMAP && 'series' in obj) {
|
||||||
|
|
@ -254,7 +239,7 @@ export default class MetricStore {
|
||||||
namesMap: {},
|
namesMap: {},
|
||||||
avg: 0,
|
avg: 0,
|
||||||
percentiles: [],
|
percentiles: [],
|
||||||
values: [],
|
values: []
|
||||||
};
|
};
|
||||||
const obj: any = { metricType: value, data: defaultData };
|
const obj: any = { metricType: value, data: defaultData };
|
||||||
obj.series = this.instance.series;
|
obj.series = this.instance.series;
|
||||||
|
|
@ -311,7 +296,7 @@ export default class MetricStore {
|
||||||
if (obj.series[0] && obj.series[0].filter.filters.length < 1) {
|
if (obj.series[0] && obj.series[0].filter.filters.length < 1) {
|
||||||
obj.series[0].filter.addFilter({
|
obj.series[0].filter.addFilter({
|
||||||
...clickmapFilter,
|
...clickmapFilter,
|
||||||
value: [''],
|
value: ['']
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -341,7 +326,7 @@ export default class MetricStore {
|
||||||
updateInList(metric: Widget) {
|
updateInList(metric: Widget) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const index = this.metrics.findIndex(
|
const index = this.metrics.findIndex(
|
||||||
(m: Widget) => m[Widget.ID_KEY] === metric[Widget.ID_KEY],
|
(m: Widget) => m[Widget.ID_KEY] === metric[Widget.ID_KEY]
|
||||||
);
|
);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
this.metrics[index] = metric;
|
this.metrics[index] = metric;
|
||||||
|
|
@ -358,12 +343,6 @@ export default class MetricStore {
|
||||||
this.metrics = this.metrics.filter((m) => m[Widget.ID_KEY] !== id);
|
this.metrics = this.metrics.filter((m) => m[Widget.ID_KEY] !== id);
|
||||||
}
|
}
|
||||||
|
|
||||||
get paginatedList(): Widget[] {
|
|
||||||
const start = (this.page - 1) * this.pageSize;
|
|
||||||
const end = start + this.pageSize;
|
|
||||||
return this.metrics.slice(start, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
// API Communication
|
// API Communication
|
||||||
async save(metric: Widget): Promise<Widget> {
|
async save(metric: Widget): Promise<Widget> {
|
||||||
this.isSaving = true;
|
this.isSaving = true;
|
||||||
|
|
@ -396,16 +375,27 @@ export default class MetricStore {
|
||||||
this.metrics = metrics;
|
this.metrics = metrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchList() {
|
async fetchList() {
|
||||||
this.setLoading(true);
|
this.setLoading(true);
|
||||||
return metricService
|
try {
|
||||||
.getMetrics()
|
const resp = await metricService
|
||||||
.then((metrics: any[]) => {
|
.getMetricsPaginated({
|
||||||
this.setMetrics(metrics.map((m) => new Widget().fromJson(m)));
|
page: this.page,
|
||||||
})
|
limit: this.pageSize,
|
||||||
.finally(() => {
|
sort: {
|
||||||
this.setLoading(false);
|
field: this.sort.field,
|
||||||
});
|
order: this.sort.order === 'ascend' ? 'asc' : 'desc'
|
||||||
|
},
|
||||||
|
filter: {
|
||||||
|
query: this.filter.query,
|
||||||
|
type: this.filter.type === 'all' ? '' : this.filter.type,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.total = resp.total;
|
||||||
|
this.setMetrics(resp.list.map((m) => new Widget().fromJson(m)));
|
||||||
|
} finally {
|
||||||
|
this.setLoading(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(id: string, period?: any) {
|
fetch(id: string, period?: any) {
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,17 @@ export default class MetricService {
|
||||||
.then((response: { data: any }) => response.data || []);
|
.then((response: { data: any }) => response.data || []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all metrics paginated.
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
getMetricsPaginated(params: any): Promise<any> {
|
||||||
|
return this.client
|
||||||
|
.post('/cards/search', params)
|
||||||
|
.then((response: { json: () => any }) => response.json())
|
||||||
|
.then((response: { data: any }) => response.data || []);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a metric by metricId.
|
* Get a metric by metricId.
|
||||||
* @param metricId
|
* @param metricId
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue