Files
supabase/apps/studio/components/interfaces/Integrations/CronJobs/PreviousRunsTab.tsx
Ivan VasilovandJordi Enric 56de26fe22 chore: Migrate the monorepo to use Tailwind v4 (#45318)
This PR migrates the whole monorepo to use Tailwind v4:
- Removed `@tailwindcss/container-queries` plugin since it's included by
default in v4,
- Bump all instances of Tailwind to v4. Made minimal changes to the
shared config to remove non-supported features (`alpha` mentions),
- Migrate all apps to be compatible with v4 configs,
- Fix the `typography.css` import in 3 apps,
- Add missing rules which were included by default in v3,
- Run `pnpm dlx @tailwindcss/upgrade` on all apps, which renames a lot
of classes
- Rename all misnamed classes according to
https://tailwindcss.com/docs/upgrade-guide#renamed-utilities in all
apps.

---------

Co-authored-by: Jordi Enric <jordi.err@gmail.com>
2026-04-30 10:53:24 +00:00

249 lines
6.6 KiB
TypeScript

import { useParams } from 'common'
import dayjs from 'dayjs'
import { CircleCheck, CircleX, Loader } from 'lucide-react'
import { useMemo } from 'react'
import DataGrid, { Column, Row } from 'react-data-grid'
import { cn, LoadingLine, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
import { TimestampInfo } from 'ui-patterns'
import { CodeBlock } from 'ui-patterns/CodeBlock'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import { calculateDuration, formatDate } from './CronJobs.utils'
import CronJobsEmptyState from './CronJobsEmptyState'
import {
CronJobRun,
useCronJobRunsInfiniteQuery,
} from '@/data/database-cron-jobs/database-cron-jobs-runs-infinite-query'
import { useInfiniteScroll } from '@/hooks/misc/useInfiniteScroll'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
const cronJobColumns = [
{
id: 'runid',
name: 'RunID',
minWidth: 30,
width: 30,
value: (row: CronJobRun) => (
<div className="flex items-center gap-1.5">
<h3 className="text-xs">{row.runid}</h3>
</div>
),
},
{
id: 'message',
name: 'Message',
minWidth: 200,
value: (row: CronJobRun) => (
<div className="flex items-center gap-1.5">
{row.return_message ? (
<Tooltip>
<TooltipTrigger asChild>
<span className="text-xs cursor-pointer truncate max-w-[300px]">
{row.return_message}
</span>
</TooltipTrigger>
<TooltipContent
side="bottom"
align="start"
className="min-w-[200px] max-w-[300px] text-wrap p-0"
>
<p className="text-xs font-mono px-2 py-1 border-b bg-surface-100">Message</p>
<CodeBlock
hideLineNumbers
language="sql"
value={row.return_message.trim()}
className={cn(
'py-0 px-3.5 max-w-full prose dark:prose-dark border-0 rounded-t-none',
'[&>code]:m-0 [&>code>span]:flex [&>code>span]:flex-wrap min-h-11',
'[&>code]:text-xs'
)}
/>
</TooltipContent>
</Tooltip>
) : (
<span>-</span>
)}
</div>
),
},
{
id: 'status',
name: 'Status',
minWidth: 75,
value: (row: CronJobRun) => <StatusBadge status={row.status} />,
},
{
id: 'start_time',
name: 'Start Time',
minWidth: 120,
value: (row: CronJobRun) => <div className="text-xs">{formatDate(row.start_time)}</div>,
},
{
id: 'end_time',
name: 'End Time',
minWidth: 120,
value: (row: CronJobRun) => (
<div className="flex items-center text-xs">
{row.end_time ? formatDate(row.end_time) : '-'}
</div>
),
},
{
id: 'duration',
name: 'Duration',
minWidth: 100,
value: (row: CronJobRun) => (
<div className="flex items-center">
<span className="text-xs">
{row.start_time && row.end_time ? calculateDuration(row.start_time, row.end_time) : ''}
</span>
</div>
),
},
]
const columns = cronJobColumns.map((col) => {
const result: Column<CronJobRun> = {
key: col.id,
name: col.name,
resizable: true,
minWidth: col.minWidth ?? 120,
headerCellClass: undefined,
renderHeaderCell: () => {
return (
<div
className={cn(
'flex items-center justify-between font-normal text-xs w-full',
col.id === 'runid' && 'ml-8'
)}
>
<p className="text-foreground!">{col.name}</p>
</div>
)
},
renderCell: (props) => {
const value = col.value(props.row)
if (['start_time', 'end_time'].includes(col.id)) {
const rawValue = (props.row as any)[(col as any).id]
if (rawValue) {
const formattedValue = dayjs(rawValue).valueOf()
return (
<div className="flex items-center">
<TimestampInfo
utcTimestamp={formattedValue}
labelFormat="DD MMM YYYY HH:mm:ss (ZZ)"
className="text-xs"
/>
</div>
)
}
}
return value
},
}
return result
})
export const PreviousRunsTab = () => {
const { childId } = useParams()
const { data: project } = useSelectedProjectQuery()
const jobId = Number(childId)
const {
data,
isPending: isLoadingCronJobRuns,
isFetching,
isFetchingNextPage,
hasNextPage,
fetchNextPage,
} = useCronJobRunsInfiniteQuery(
{
projectRef: project?.ref,
connectionString: project?.connectionString,
jobId: jobId,
},
{ enabled: !!jobId, staleTime: 30000 }
)
const cronJobRuns = useMemo(() => data?.pages.flatMap((p) => p) || [], [data?.pages])
const handleScroll = useInfiniteScroll({
isLoading: isLoadingCronJobRuns,
isFetchingNextPage,
hasNextPage,
fetchNextPage,
})
return (
<div className="h-full flex flex-col">
<LoadingLine loading={isFetching} />
<DataGrid
className="grow border-t-0"
rowHeight={44}
headerRowHeight={36}
onScroll={handleScroll}
columns={columns}
rows={cronJobRuns ?? []}
rowClass={() => {
return cn(
'cursor-pointer',
'[&>.rdg-cell]:border-box [&>.rdg-cell]:outline-hidden [&>.rdg-cell]:shadow-none',
'[&>.rdg-cell:first-child>div]:ml-8'
)
}}
renderers={{
renderRow(_idx, props) {
return <Row key={props.row.job_pid} {...props} />
},
noRowsFallback: isLoadingCronJobRuns ? (
<div className="absolute top-14 px-6 w-full">
<GenericSkeletonLoader />
</div>
) : (
<div className="flex items-center justify-center w-full col-span-6">
<CronJobsEmptyState page="runs" />
</div>
),
}}
/>
</div>
)
}
interface StatusBadgeProps {
status: string
}
function StatusBadge({ status }: StatusBadgeProps) {
if (status === 'succeeded') {
return (
<span className="text-brand-600 flex items-center gap-1 text-xs">
<CircleCheck size={14} /> Succeeded
</span>
)
}
if (status === 'failed') {
return (
<span className="text-destructive flex items-center gap-1 text-xs">
<CircleX size={14} /> Failed
</span>
)
}
if (['running', 'starting', 'sending', 'connecting'].includes(status)) {
return (
<span className="text-_secondary flex items-center gap-1 text-xs">
<Loader size={14} className="animate-spin" /> Running
</span>
)
}
return null
}