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

203 lines
6.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { ShareAssetType, VerificationStatus, BusterMetricListItem } from '@/api/asset_interfaces';
import { makeHumanReadble, formatDate } from '@/lib';
import React, { memo, useMemo, useRef, useState } from 'react';
import { StatusBadgeIndicator, getShareStatus } from '@/components/features/Lists';
import { BusterUserAvatar, Text } from '@/components/ui';
import { BusterRoutes, createBusterRoute } from '@/routes';
import { useMemoizedFn } from 'ahooks';
import { BusterListColumn, BusterListRow } from '@/components/ui/list';
import { MetricSelectedOptionPopup } from './MetricItemsSelectedPopup';
import { BusterList, ListEmptyStateWithButton } from '@/components/ui/list';
import { FavoriteStar } from '@/components/features/Lists';
import { useCreateListByDate } from '@/components/ui/list/useCreateListByDate';
export const MetricItemsContainer: React.FC<{
metrics: BusterMetricListItem[];
className?: string;
openNewMetricModal: () => void;
type: 'logs' | 'metrics';
loading: boolean;
}> = ({ 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 });
const metricsByDate: BusterListRow[] = useMemo(() => {
return Object.entries(logsRecord).flatMap(([key, metrics]) => {
const records = metrics.map((metric) => ({
id: metric.id,
data: metric,
link: createBusterRoute({
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) => (
<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
rows={metricsByDate}
columns={columns}
onSelectChange={onSelectChange}
selectedRowKeys={selectedRowKeys}
emptyState={
<EmptyState loading={loading} type={type} openNewMetricModal={openNewMetricModal} />
}
/>
<MetricSelectedOptionPopup
selectedRowKeys={selectedRowKeys}
onSelectChange={onSelectChange}
hasSelected={hasSelected}
/>
</div>
);
};
const EmptyState: React.FC<{
loading: boolean;
type: 'logs' | 'metrics';
openNewMetricModal: () => void;
}> = React.memo(({ loading, type, openNewMetricModal }) => {
if (loading) {
return <></>;
}
return <MetricsEmptyState openNewMetricModal={openNewMetricModal} type={type} />;
});
EmptyState.displayName = 'EmptyState';
const MetricsEmptyState: React.FC<{
openNewMetricModal: () => void;
type: 'logs' | 'metrics';
}> = ({ 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."
buttonText="New chat"
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."
buttonText="New chat"
onClick={openNewMetricModal}
/>
);
};
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>
<div className="flex items-center" onClick={onFavoriteDivClick}>
<FavoriteStar
id={metricId}
type={ShareAssetType.METRIC}
iconStyle="tertiary"
title={title}
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';