buster/web/src/controllers/MetricListContainer/MetricItemsContainer.tsx

203 lines
6.2 KiB
TypeScript
Raw Normal View History

2025-02-02 13:17:37 +08:00
import { ShareAssetType, VerificationStatus, BusterMetricListItem } from '@/api/asset_interfaces';
2025-02-22 03:07:30 +08:00
import { makeHumanReadble, formatDate } from '@/lib';
import React, { memo, useMemo, useRef, useState } from 'react';
2025-02-20 13:16:28 +08:00
import { StatusBadgeIndicator, getShareStatus } from '@/components/features/Lists';
2025-02-20 11:05:30 +08:00
import { BusterUserAvatar, Text } from '@/components/ui';
import { BusterRoutes, createBusterRoute } from '@/routes';
import { useMemoizedFn } from 'ahooks';
2025-02-20 10:53:49 +08:00
import { BusterListColumn, BusterListRow } from '@/components/ui/list';
2025-02-08 03:45:47 +08:00
import { MetricSelectedOptionPopup } from './MetricItemsSelectedPopup';
2025-02-20 10:53:49 +08:00
import { BusterList, ListEmptyStateWithButton } from '@/components/ui/list';
2025-02-20 13:16:28 +08:00
import { FavoriteStar } from '@/components/features/Lists';
2025-02-20 10:53:49 +08:00
import { useCreateListByDate } from '@/components/ui/list/useCreateListByDate';
2025-02-01 06:21:50 +08:00
export const MetricItemsContainer: React.FC<{
metrics: BusterMetricListItem[];
className?: string;
2025-02-02 13:17:37 +08:00
openNewMetricModal: () => void;
2025-02-01 06:21:50 +08:00
type: 'logs' | 'metrics';
loading: boolean;
2025-02-02 13:17:37 +08:00
}> = ({ type, metrics = [], className = '', loading, openNewMetricModal }) => {
const [selectedRowKeys, setSelectedRowKeys] = useState<string[]>([]);
const renderedDates = useRef<Record<string, string>>({});
const renderedOwners = useRef<Record<string, React.ReactNode>>({});
const tableContainerRef = useRef<HTMLDivElement>(null);
const onSelectChange = useMemoizedFn((selectedRowKeys: string[]) => {
setSelectedRowKeys(selectedRowKeys);
});
const hasSelected = selectedRowKeys.length > 0;
const logsRecord = useCreateListByDate({ data: metrics });
2025-02-01 06:21:50 +08:00
const metricsByDate: BusterListRow[] = useMemo(() => {
return Object.entries(logsRecord).flatMap(([key, metrics]) => {
const records = metrics.map((metric) => ({
id: metric.id,
data: metric,
link: createBusterRoute({
2025-02-01 06:21:50 +08:00
route: BusterRoutes.APP_METRIC_ID,
metricId: metric.id
})
}));
const hasRecords = records.length > 0;
if (!hasRecords) {
return [];
}
return [
{
id: key,
data: {},
rowSection: {
title: makeHumanReadble(key),
secondaryTitle: String(records.length)
}
},
...records
];
});
}, [logsRecord]);
const columns: BusterListColumn[] = useMemo(
() => [
{
dataIndex: 'title',
title: 'Title',
render: (title, record) => (
2025-02-01 06:21:50 +08:00
<TitleCell title={title} status={record?.status} metricId={record?.id} />
)
},
{
dataIndex: 'last_edited',
title: 'Last updated',
width: 132,
render: (v) => {
if (renderedDates.current[v]) {
return renderedDates.current[v];
}
const date = formatDate({ date: v, format: 'lll' });
renderedDates.current[v] = date;
return date;
}
},
{ dataIndex: 'dataset_name', title: 'Dataset', width: 115 },
{
dataIndex: 'is_shared',
title: 'Sharing',
width: 65,
render: (v) => getShareStatus({ is_shared: v })
},
{
dataIndex: 'created_by_name',
title: 'Owner',
width: 45,
render: (name, record) => {
if (renderedOwners.current[name]) {
return renderedOwners.current[name];
}
const avatarCell = (
<OwnerCell name={name} image={record?.created_by_avatar || undefined} />
);
renderedOwners.current[name] = avatarCell;
return avatarCell;
}
}
],
[]
);
return (
<div
ref={tableContainerRef}
className={`${className} relative flex h-full flex-col items-center`}>
<BusterList
2025-02-01 06:21:50 +08:00
rows={metricsByDate}
columns={columns}
onSelectChange={onSelectChange}
selectedRowKeys={selectedRowKeys}
emptyState={
2025-02-02 13:17:37 +08:00
<EmptyState loading={loading} type={type} openNewMetricModal={openNewMetricModal} />
}
/>
2025-02-02 13:17:37 +08:00
<MetricSelectedOptionPopup
selectedRowKeys={selectedRowKeys}
onSelectChange={onSelectChange}
hasSelected={hasSelected}
/>
</div>
);
};
2025-01-21 06:32:59 +08:00
const EmptyState: React.FC<{
loading: boolean;
2025-02-01 06:21:50 +08:00
type: 'logs' | 'metrics';
2025-02-02 13:17:37 +08:00
openNewMetricModal: () => void;
}> = React.memo(({ loading, type, openNewMetricModal }) => {
2025-01-21 06:32:59 +08:00
if (loading) {
return <></>;
}
2025-02-02 13:17:37 +08:00
return <MetricsEmptyState openNewMetricModal={openNewMetricModal} type={type} />;
2025-01-21 06:32:59 +08:00
});
2025-01-21 08:01:10 +08:00
EmptyState.displayName = 'EmptyState';
2025-01-21 06:32:59 +08:00
const MetricsEmptyState: React.FC<{
2025-02-02 13:17:37 +08:00
openNewMetricModal: () => void;
2025-02-01 06:21:50 +08:00
type: 'logs' | 'metrics';
2025-02-02 13:17:37 +08:00
}> = ({ openNewMetricModal, type }) => {
if (type === 'logs') {
return (
<ListEmptyStateWithButton
title="You dont have any logs yet."
description="You dont have any logs. As soon as you do, they will start to appear here."
2025-02-04 01:31:15 +08:00
buttonText="New chat"
2025-02-02 13:17:37 +08:00
onClick={openNewMetricModal}
/>
);
}
return (
<ListEmptyStateWithButton
title="You dont have any metrics yet."
description="You dont have any metrics. As soon as you do, they will start to appear here."
2025-02-04 01:31:15 +08:00
buttonText="New chat"
2025-02-02 13:17:37 +08:00
onClick={openNewMetricModal}
/>
);
};
2025-02-01 06:21:50 +08:00
const TitleCell = React.memo<{ title: string; status: VerificationStatus; metricId: string }>(
({ title, status, metricId }) => {
const onFavoriteDivClick = useMemoizedFn((e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
});
return (
<div className="flex w-full items-center space-x-2">
<div className="flex items-center justify-center">
<StatusBadgeIndicator status={status} />
</div>
<Text ellipsis={true}>{title}</Text>
2025-01-24 07:33:33 +08:00
<div className="flex items-center" onClick={onFavoriteDivClick}>
<FavoriteStar
2025-02-01 06:21:50 +08:00
id={metricId}
2025-02-01 02:04:49 +08:00
type={ShareAssetType.METRIC}
iconStyle="tertiary"
2025-02-13 07:02:08 +08:00
title={title}
2025-01-24 07:33:33 +08:00
className="opacity-0 group-hover:opacity-100"
/>
</div>
</div>
);
}
);
TitleCell.displayName = 'TitleCell';
const OwnerCell = memo<{ name: string; image: string | null | undefined }>(({ name, image }) => (
<div className="flex pl-0">
<BusterUserAvatar image={image || undefined} name={name} size={18} />
</div>
));
OwnerCell.displayName = 'OwnerCell';