mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-17 16:03:06 +00:00
* fix: show when the last git auto-pull status was recorded * chore: bump ee-repo-ref for the auto-pull status fix * fix: show the git auto-pull status age with TimeAgo instead of a year-less date * test: pin that a stale auto-pull recovery cannot overwrite a newer state * chore: bump ee-repo-ref for the conditional auto-pull recovery * fix: keep TimeAgo counting past the first hour in noSeconds mode * chore: bump ee-repo-ref for the clear_auto_pull_failure contract note * fix: guard TimeAgo's boundary scheduler against invalid dates and pin same-head newer failures * chore: bump ee-repo-ref for the timestamp-guarded auto-pull recovery * test: cover a same-second newer failure surviving a stale auto-pull recovery * chore: bump ee-repo-ref for the whole-failure recovery match * test: name the recovery helper after its input, not its staleness * chore: update ee-repo-ref to c6df9fdd9826efb40d3586a9f97d17dee98ac6ef This commit updates the EE repository reference after PR #793 was merged in windmill-ee-private. Previous ee-repo-ref: 6aff80b80cae4944a4a78a6b9244019bc37f368b New ee-repo-ref: c6df9fdd9826efb40d3586a9f97d17dee98ac6ef Automated by sync-ee-ref workflow. --------- Co-authored-by: Ruben Fiszel <ruben@windmill.dev> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
198 lines
5.6 KiB
Svelte
198 lines
5.6 KiB
Svelte
<script lang="ts">
|
|
import { getDbClockNow } from '$lib/forLater'
|
|
import { displayDate } from '$lib/utils'
|
|
import { onMount, untrack } from 'svelte'
|
|
|
|
interface Props {
|
|
date: string
|
|
agoOnlyIfRecent?: boolean
|
|
noDate?: boolean
|
|
isRecent?: boolean
|
|
noSeconds?: boolean
|
|
/** Single-unit form ("2m", "3h", "5d") for rows too narrow for "2 mins ago". */
|
|
compact?: boolean
|
|
}
|
|
|
|
let {
|
|
date,
|
|
agoOnlyIfRecent = false,
|
|
noDate = false,
|
|
isRecent = $bindable(true),
|
|
noSeconds = false,
|
|
compact = false
|
|
}: Props = $props()
|
|
|
|
let computedTimeAgo: string | undefined = $state(undefined)
|
|
|
|
let interval
|
|
|
|
onMount(() => {
|
|
// compact and noSeconds schedule themselves below; they need no fixed rate.
|
|
if (compact || noSeconds) return
|
|
|
|
interval = setInterval(() => {
|
|
computeDate()
|
|
if (!isRecent) {
|
|
clearInterval(interval)
|
|
interval = undefined
|
|
}
|
|
}, 1000)
|
|
|
|
// Add explicit cleanup
|
|
return () => {
|
|
interval && clearInterval(interval)
|
|
}
|
|
})
|
|
|
|
// Waking on the boundary of the unit on screen, rather than at a fixed rate: `2h` only
|
|
// changes on the hour, and a row that reads `5d` must not hold a 1s timer to find that
|
|
// out. Re-armed when `date` changes, so an item edited to now leaves its day-long wait.
|
|
// noSeconds rides the same schedule: its relative forms keep counting hours and days,
|
|
// so a fixed-rate timer that stops once the date is an hour old would freeze them.
|
|
$effect(() => {
|
|
if (!compact && !noSeconds) return
|
|
const at = date
|
|
// An absent or unparsable date has no boundary to wait for: the delay below would
|
|
// be NaN, which setTimeout runs immediately, and the tick would re-arm itself in
|
|
// a tight loop.
|
|
if (Number.isNaN(new Date(at).getTime())) return
|
|
let handle: ReturnType<typeof setTimeout> | undefined
|
|
const tick = () => {
|
|
computeDate()
|
|
handle = setTimeout(tick, nextUnitBoundaryMs(at))
|
|
}
|
|
handle = setTimeout(tick, nextUnitBoundaryMs(at))
|
|
return () => {
|
|
handle && clearTimeout(handle)
|
|
}
|
|
})
|
|
|
|
function nextUnitBoundaryMs(dateString: string): number {
|
|
const secs = secondsAgo(new Date(dateString))
|
|
const left =
|
|
secs < 60
|
|
? 1
|
|
: secs < 3600
|
|
? 60 - (secs % 60)
|
|
: secs < 86_400
|
|
? 3600 - (secs % 3600)
|
|
: 86_400 - (secs % 86_400)
|
|
return left * 1000
|
|
}
|
|
|
|
async function computeDate() {
|
|
computedTimeAgo = await displayDaysAgo(date)
|
|
}
|
|
|
|
function isToday(someDate: Date): boolean {
|
|
const today = getDbClockNow()
|
|
return (
|
|
someDate.getDate() == today.getDate() &&
|
|
someDate.getMonth() == today.getMonth() &&
|
|
someDate.getFullYear() == today.getFullYear()
|
|
)
|
|
}
|
|
|
|
function daysAgo(someDate: Date): number {
|
|
const today = getDbClockNow()
|
|
return Math.floor((today.getTime() - someDate.getTime()) / 86400000)
|
|
}
|
|
|
|
function secondsAgo(date: Date) {
|
|
return Math.max(0, Math.floor((getDbClockNow().getTime() - date.getTime()) / 1000))
|
|
}
|
|
|
|
function minutesAgo(date: Date) {
|
|
return Math.floor(secondsAgo(date) / 60)
|
|
}
|
|
|
|
function hoursAgo(date: Date) {
|
|
return Math.floor(minutesAgo(date) / 60)
|
|
}
|
|
|
|
function isLessThanMonthAgo(date: Date): boolean {
|
|
const today = getDbClockNow()
|
|
const monthAgo = new Date(today)
|
|
monthAgo.setMonth(monthAgo.getMonth() - 1)
|
|
return date.getTime() >= monthAgo.getTime()
|
|
}
|
|
|
|
async function displayDaysAgo(dateString: string): Promise<string> {
|
|
const date = new Date(dateString)
|
|
|
|
if (compact) {
|
|
// The s/m/h/d ladder the hand-rolled `ago()` helpers around the codebase use
|
|
// (PipelineEventLog, PipelineActivityPanel, IndexerMemorySettings): seconds stay
|
|
// meaningful right up to the minute mark.
|
|
const secs = secondsAgo(date)
|
|
if (secs < 60) return `${secs}s`
|
|
const mins = minutesAgo(date)
|
|
if (mins < 60) return `${mins}m`
|
|
const hours = hoursAgo(date)
|
|
if (hours < 24) return `${hours}h`
|
|
// Days all the way up, never an absolute date: callers read as "<slot> ago", and
|
|
// the hand-rolled ago() helpers this mirrors have no date fallback either.
|
|
return `${daysAgo(date)}d`
|
|
}
|
|
|
|
// New noSeconds mode
|
|
if (noSeconds) {
|
|
const mins = minutesAgo(date)
|
|
const hours = hoursAgo(date)
|
|
const dAgo = daysAgo(date)
|
|
|
|
// If less than an hour ago -> "x min ago"
|
|
if (mins < 60) {
|
|
return `${mins} min${mins !== 1 ? 's' : ''} ago`
|
|
}
|
|
// If less than a day ago -> "x hours ago"
|
|
else if (hours < 24) {
|
|
isRecent = false
|
|
return `${hours} hour${hours !== 1 ? 's' : ''} ago`
|
|
}
|
|
// If less than a month ago -> "x days ago"
|
|
else if (isLessThanMonthAgo(date)) {
|
|
return `${dAgo} day${dAgo !== 1 ? 's' : ''} ago`
|
|
}
|
|
// If more than a month ago -> "the mm/dd/year"
|
|
else {
|
|
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
|
const day = date.getDate().toString().padStart(2, '0')
|
|
const year = date.getFullYear()
|
|
return `the ${month}/${day}/${year}`
|
|
}
|
|
}
|
|
|
|
// Original mode
|
|
const nbSecondsAgo = secondsAgo(date)
|
|
if (nbSecondsAgo < 600) {
|
|
return `${nbSecondsAgo}s ago`
|
|
} else if (isToday(date)) {
|
|
isRecent = false
|
|
return `today at ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`
|
|
} else {
|
|
isRecent = false
|
|
let dAgo = daysAgo(date)
|
|
if (dAgo == 0) {
|
|
return `yesterday at ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`
|
|
} else if (dAgo > 7 && !noDate) {
|
|
return `${dAgo + 1} days ago at ${date.toLocaleTimeString([], {
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
})}`
|
|
} else {
|
|
return displayDate(dateString, false, !noDate)
|
|
}
|
|
}
|
|
}
|
|
$effect(() => {
|
|
date && untrack(() => computeDate())
|
|
})
|
|
</script>
|
|
|
|
{#if computedTimeAgo && (!agoOnlyIfRecent || isRecent)}
|
|
{computedTimeAgo}
|
|
{:else}
|
|
{displayDate(date)}
|
|
{/if}
|