mirror of
https://github.com/supabase/supabase.git
synced 2026-09-22 13:37:53 +08:00
e21e0c73bb
## What kind of change does this PR introduce? Studio UI refactor. ## What is the current behavior? Failed replicated tables spread retry timing and error details across several visually heavy blocks. ## What is the new behavior? Condenses retry timing and failure details into a clearer table-level presentation without changing retry behaviour or pipeline mutations. | Before | After | | --- | --- | | <img width="1842" height="594" alt="CleanShot 2026-09-16 at 12 55 52@2x" src="https://github.com/user-attachments/assets/fb6f6a3f-2e81-411e-9d49-ed4cf8cc66e7" /> | <img width="1824" height="328" alt="CleanShot 2026-09-16 at 15 16 21@2x" src="https://github.com/user-attachments/assets/bad558c4-2d4c-4d1b-bb68-32ad4d5b348f" /> | | _Not applicable._ | <img width="832" height="662" alt="CleanShot 2026-09-16 at 15 16 29@2x" src="https://github.com/user-attachments/assets/540a5d55-f99f-4c1e-9a57-5ab2ac31e44e" /> | This is an independent slice extracted from #49630. The related review series is #50443, this PR, #50445, #50446, then #49630. ## To test 1. Open `/project/<ref>/database/replication` and select a pipeline with a failed table. 2. Confirm the table row presents its failure and retry timing without expanding the row unnecessarily. 3. Open the error details dialog and confirm the underlying error remains available. This is difficult to test unless you have a properly-failing table. You can instead do the following locally: 1. Check out `dnywh/tmp/pipelines-running-fixture`. 2. Open `/project/<ref>/database/replication/<pipelineId>`. 3. Use the floating pipeline-state switcher in the bottom-right. 4. Select _Running, some tables errored_.
36 lines
1.1 KiB
TypeScript
36 lines
1.1 KiB
TypeScript
import dayjs from 'dayjs'
|
|
import { useEffect, useState } from 'react'
|
|
|
|
interface RetryCountdownProps {
|
|
/** RFC 3339 timestamp of the next automatic retry */
|
|
nextRetryTime: string
|
|
}
|
|
|
|
const formatRemaining = (milliseconds: number) => {
|
|
const duration = dayjs.duration(milliseconds)
|
|
if (duration.asHours() >= 1) return `${Math.floor(duration.asHours())}h ${duration.minutes()}m`
|
|
if (duration.asMinutes() >= 1) return `${duration.minutes()}m ${duration.seconds()}s`
|
|
return `${duration.seconds()}s`
|
|
}
|
|
|
|
export const RetryCountdown = ({ nextRetryTime }: RetryCountdownProps) => {
|
|
const target = new Date(nextRetryTime).getTime()
|
|
const [remaining, setRemaining] = useState(() => target - Date.now())
|
|
|
|
useEffect(() => {
|
|
if (Number.isNaN(target)) return
|
|
const tick = () => setRemaining(target - Date.now())
|
|
tick()
|
|
const interval = setInterval(tick, 1000)
|
|
return () => clearInterval(interval)
|
|
}, [target])
|
|
|
|
if (Number.isNaN(target)) return <>Retry time is invalid.</>
|
|
|
|
return (
|
|
<span role="status" aria-live="polite">
|
|
{remaining <= 0 ? 'Retrying now…' : `Retrying in ${formatRemaining(remaining)}…`}
|
|
</span>
|
|
)
|
|
}
|