columns.tsx•8.74 kB
import { ColumnDef } from '@tanstack/react-table';
import { t } from 'i18next';
import { Archive, ChevronDown, Hourglass } from 'lucide-react';
import { Dispatch, SetStateAction } from 'react';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { RowDataWithActions } from '@/components/ui/data-table';
import { DataTableColumnHeader } from '@/components/ui/data-table/data-table-column-header';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { StatusIconWithText } from '@/components/ui/status-icon-with-text';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { flowRunUtils } from '@/features/flow-runs/lib/flow-run-utils';
import { formatUtils } from '@/lib/utils';
import { FlowRun, FlowRunStatus, isNil, SeekPage } from '@activepieces/shared';
type SelectedRow = {
id: string;
status: FlowRunStatus;
};
type RunsTableColumnsProps = {
data: SeekPage<FlowRun> | undefined;
selectedRows: SelectedRow[];
setSelectedRows: Dispatch<SetStateAction<SelectedRow[]>>;
selectedAll: boolean;
setSelectedAll: Dispatch<SetStateAction<boolean>>;
excludedRows: Set<string>;
setExcludedRows: Dispatch<SetStateAction<Set<string>>>;
};
export const runsTableColumns = ({
setSelectedRows,
selectedRows,
selectedAll,
setSelectedAll,
excludedRows,
setExcludedRows,
data,
}: RunsTableColumnsProps): ColumnDef<RowDataWithActions<FlowRun>>[] => [
{
id: 'select',
header: ({ table }) => (
<div className="flex items-center w-8">
<Checkbox
checked={selectedAll || table.getIsAllPageRowsSelected()}
variant="secondary"
onCheckedChange={(value) => {
const isChecked = !!value;
table.toggleAllPageRowsSelected(isChecked);
if (isChecked) {
const currentPageRows = table.getRowModel().rows.map((row) => ({
id: row.original.id,
status: row.original.status,
}));
setSelectedRows((prev) => {
const uniqueRows = new Map<string, SelectedRow>([
...prev.map((row) => [row.id, row] as [string, SelectedRow]),
...currentPageRows.map(
(row) => [row.id, row] as [string, SelectedRow],
),
]);
return Array.from(uniqueRows.values());
});
} else {
setSelectedAll(false);
setSelectedRows([]);
setExcludedRows(new Set());
}
}}
/>
{selectedRows.length > 0 && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm">
<ChevronDown className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="z-50">
<DropdownMenuItem
className="cursor-pointer"
onClick={() => {
const currentPageRows = table
.getRowModel()
.rows.map((row) => ({
id: row.original.id,
status: row.original.status,
}));
setSelectedRows(currentPageRows);
setSelectedAll(false);
setExcludedRows(new Set());
table.toggleAllPageRowsSelected(true);
}}
>
{t('Select shown')}
</DropdownMenuItem>
<DropdownMenuItem
className="cursor-pointer"
onClick={() => {
if (data?.data) {
const allRows = data.data.map((row) => ({
id: row.id,
status: row.status,
}));
setSelectedRows(allRows);
setSelectedAll(true);
setExcludedRows(new Set());
table.toggleAllPageRowsSelected(true);
}
}}
>
{t('Select all')}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
),
cell: ({ row }) => {
const isExcluded = excludedRows.has(row.original.id);
const isSelected = selectedAll
? !isExcluded
: selectedRows.some(
(selectedRow) => selectedRow.id === row.original.id,
);
return (
<Checkbox
variant="secondary"
checked={isSelected}
onCheckedChange={(value) => {
const isChecked = !!value;
if (selectedAll) {
if (isChecked) {
const newExcluded = new Set(excludedRows);
newExcluded.delete(row.original.id);
setExcludedRows(newExcluded);
} else {
setExcludedRows(new Set([...excludedRows, row.original.id]));
}
} else {
if (isChecked) {
setSelectedRows((prev) => [
...prev,
{
id: row.original.id,
status: row.original.status,
},
]);
} else {
setSelectedRows((prev) =>
prev.filter(
(selectedRow) => selectedRow.id !== row.original.id,
),
);
}
}
row.toggleSelected(isChecked);
}}
/>
);
},
},
{
accessorKey: 'flowId',
header: ({ column }) => (
<DataTableColumnHeader column={column} title={t('Flow')} />
),
cell: ({ row }) => {
const { archivedAt, flowVersion } = row.original;
const displayName = flowVersion?.displayName ?? '—';
return (
<div className="flex items-center gap-2 text-left">
{!isNil(archivedAt) && (
<Archive className="size-4 text-muted-foreground" />
)}
<span>{displayName}</span>
</div>
);
},
},
{
accessorKey: 'status',
header: ({ column }) => (
<DataTableColumnHeader column={column} title={t('Status')} />
),
cell: ({ row }) => {
const status = row.original.status;
const { variant, Icon } = flowRunUtils.getStatusIcon(status);
return (
<div className="text-left">
<StatusIconWithText
icon={Icon}
text={formatUtils.convertEnumToHumanReadable(status)}
variant={variant}
/>
</div>
);
},
},
{
accessorKey: 'created',
header: ({ column }) => (
<DataTableColumnHeader column={column} title={t('Started At')} />
),
cell: ({ row }) => {
return (
<div className="text-left">
{formatUtils.formatDate(new Date(row.original.created ?? new Date()))}
</div>
);
},
},
{
accessorKey: 'duration',
header: ({ column }) => (
<DataTableColumnHeader column={column} title={t('Duration')} />
),
cell: ({ row }) => {
const duration =
row.original.startTime && row.original.finishTime
? new Date(row.original.finishTime).getTime() -
new Date(row.original.startTime).getTime()
: undefined;
const waitDuration =
row.original.startTime && row.original.created
? new Date(row.original.startTime).getTime() -
new Date(row.original.created).getTime()
: undefined;
return (
<Tooltip>
<TooltipTrigger>
<div className="text-left flex items-center gap-2">
{row.original.finishTime && (
<>
<Hourglass className="h-4 w-4 text-muted-foreground" />
{formatUtils.formatDuration(duration)}
</>
)}
</div>
</TooltipTrigger>
<TooltipContent side="bottom">
{t(
`Time waited before first execution attempt: ${formatUtils.formatDuration(
waitDuration,
)}`,
)}
</TooltipContent>
</Tooltip>
);
},
},
{
accessorKey: 'failedStep',
header: ({ column }) => (
<DataTableColumnHeader column={column} title={t('Failed Step')} />
),
cell: ({ row }) => {
return (
<div className="text-left">
{row.original.failedStep?.displayName ?? '-'}
</div>
);
},
},
];