2025-02-02 13:17:37 +08:00
|
|
|
|
import { ShareAssetType, VerificationStatus, BusterMetricListItem } from '@/api/asset_interfaces';
|
2025-02-19 06:14:51 +08:00
|
|
|
|
import { makeHumanReadble, formatDate } from '@/utils';
|
2025-01-23 06:58:06 +08:00
|
|
|
|
import React, { memo, useMemo, useRef, useState } from 'react';
|
2025-01-07 02:29:29 +08:00
|
|
|
|
import { StatusBadgeIndicator, getShareStatus } from '../../_components/Lists';
|
2025-02-20 11:05:30 +08:00
|
|
|
|
import { BusterUserAvatar, Text } from '@/components/ui';
|
2025-01-07 02:29:29 +08:00
|
|
|
|
import { BusterRoutes, createBusterRoute } from '@/routes';
|
2025-01-23 06:58:06 +08:00
|
|
|
|
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-01-07 02:29:29 +08:00
|
|
|
|
import { FavoriteStar } from '../../_components/Lists';
|
2025-02-20 10:53:49 +08:00
|
|
|
|
import { useCreateListByDate } from '@/components/ui/list/useCreateListByDate';
|
2025-01-07 02:29:29 +08:00
|
|
|
|
|
2025-02-01 06:21:50 +08:00
|
|
|
|
export const MetricItemsContainer: React.FC<{
|
|
|
|
|
metrics: BusterMetricListItem[];
|
2025-01-07 02:29:29 +08:00
|
|
|
|
className?: string;
|
2025-02-02 13:17:37 +08:00
|
|
|
|
openNewMetricModal: () => void;
|
2025-02-01 06:21:50 +08:00
|
|
|
|
type: 'logs' | 'metrics';
|
2025-01-07 02:29:29 +08:00
|
|
|
|
loading: boolean;
|
2025-02-02 13:17:37 +08:00
|
|
|
|
}> = ({ type, metrics = [], className = '', loading, openNewMetricModal }) => {
|
2025-01-07 02:29:29 +08:00
|
|
|
|
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;
|
|
|
|
|
|
2025-02-19 06:14:51 +08:00
|
|
|
|
const logsRecord = useCreateListByDate({ data: metrics });
|
2025-01-07 02:29:29 +08:00
|
|
|
|
|
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,
|
2025-01-07 02:29:29 +08:00
|
|
|
|
link: createBusterRoute({
|
2025-02-01 06:21:50 +08:00
|
|
|
|
route: BusterRoutes.APP_METRIC_ID,
|
|
|
|
|
metricId: metric.id
|
2025-01-07 02:29:29 +08:00
|
|
|
|
})
|
|
|
|
|
}));
|
|
|
|
|
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} />
|
2025-01-07 02:29:29 +08:00
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
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}
|
2025-01-07 02:29:29 +08:00
|
|
|
|
columns={columns}
|
|
|
|
|
onSelectChange={onSelectChange}
|
|
|
|
|
selectedRowKeys={selectedRowKeys}
|
|
|
|
|
emptyState={
|
2025-02-02 13:17:37 +08:00
|
|
|
|
<EmptyState loading={loading} type={type} openNewMetricModal={openNewMetricModal} />
|
2025-01-07 02:29:29 +08:00
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
|
2025-02-02 13:17:37 +08:00
|
|
|
|
<MetricSelectedOptionPopup
|
2025-01-07 02:29:29 +08:00
|
|
|
|
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
|
|
|
|
|
2025-01-07 02:29:29 +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 }) => {
|
2025-01-07 02:29:29 +08:00
|
|
|
|
if (type === 'logs') {
|
|
|
|
|
return (
|
2025-01-23 06:58:06 +08:00
|
|
|
|
<ListEmptyStateWithButton
|
2025-01-07 02:29:29 +08:00
|
|
|
|
title="You don’t have any logs yet."
|
|
|
|
|
description="You don’t 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}
|
2025-01-07 02:29:29 +08:00
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
2025-01-23 06:58:06 +08:00
|
|
|
|
<ListEmptyStateWithButton
|
2025-01-07 02:29:29 +08:00
|
|
|
|
title="You don’t have any metrics yet."
|
|
|
|
|
description="You don’t 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-01-07 02:29:29 +08:00
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2025-02-01 06:21:50 +08:00
|
|
|
|
const TitleCell = React.memo<{ title: string; status: VerificationStatus; metricId: string }>(
|
|
|
|
|
({ title, status, metricId }) => {
|
2025-01-07 02:29:29 +08:00
|
|
|
|
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}>
|
2025-01-07 02:29:29 +08:00
|
|
|
|
<FavoriteStar
|
2025-02-01 06:21:50 +08:00
|
|
|
|
id={metricId}
|
2025-02-01 02:04:49 +08:00
|
|
|
|
type={ShareAssetType.METRIC}
|
2025-01-07 02:29:29 +08:00
|
|
|
|
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"
|
2025-01-07 02:29:29 +08:00
|
|
|
|
/>
|
|
|
|
|
</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';
|