mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-21 08:03:33 +00:00
fix: spell the schedule pause time the way the picker writes it (#11875)
* fix: make the schedule editor's saved baseline reactive * fix: spell the schedule pause time the way the picker writes it * fix: read a zone-less naive date-time as wall-clock time --------- Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { createDispatcherIfMounted } from '$lib/createDispatcherIfMounted'
|
||||
import TextInput from './text_input/TextInput.svelte'
|
||||
import { parseDateTime } from './dateTimeInput'
|
||||
// import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
|
||||
interface Props {
|
||||
@@ -45,7 +46,7 @@
|
||||
// let format: 'local' | 'utc' = 'local'
|
||||
|
||||
function parseValue(value: string | null | undefined = undefined) {
|
||||
let dateFromValue: Date | undefined = value ? new Date(value) : undefined
|
||||
let dateFromValue: Date | undefined = value ? parseDateTime(value, timezone) : undefined
|
||||
if (!isValidDate(dateFromValue)) {
|
||||
date = undefined
|
||||
time = '12:00'
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { expect, it } from 'vitest'
|
||||
import { parseDateTime } from './dateTimeInput'
|
||||
|
||||
it('reads a zone-less naive value as its digits, outside UTC too', () => {
|
||||
const tz = process.env.TZ
|
||||
process.env.TZ = 'Europe/Paris'
|
||||
try {
|
||||
expect(parseDateTime('2027-09-09T10:00', 'naive').getUTCHours()).toBe(10)
|
||||
expect(parseDateTime('2027-09-09T10:00', 'local').getUTCHours()).toBe(8)
|
||||
} finally {
|
||||
if (tz === undefined) delete process.env.TZ
|
||||
else process.env.TZ = tz
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,5 @@
|
||||
/** A naive value's digits are its time: `Date` would read a zone-less date-time as local time */
|
||||
export function parseDateTime(value: string, timezone: 'naive' | 'local'): Date {
|
||||
const zoneless = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/.test(value)
|
||||
return new Date(timezone === 'naive' && zoneless ? value.replace(' ', 'T') + 'Z' : value)
|
||||
}
|
||||
@@ -103,7 +103,7 @@
|
||||
let loading = $state(false)
|
||||
let drawerLoading = $state(true)
|
||||
let showLoading = $state(false)
|
||||
let initialConfig: Record<string, any> | undefined = undefined
|
||||
let initialConfig = $state<Record<string, any> | undefined>(undefined)
|
||||
let extraPerms: Record<string, boolean> = $state({})
|
||||
// Path the permissions above were loaded for — the verdict is about the schedule as
|
||||
// stored, not about a rename being typed into the form. `undefined` until a config has
|
||||
@@ -281,7 +281,7 @@
|
||||
schedule = s?.schedule ?? '0 0 12 * *'
|
||||
initialSchedule = schedule
|
||||
timezone = s?.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
paused_until = s?.paused_until ?? undefined
|
||||
paused_until = asPicked(s?.paused_until)
|
||||
showPauseUntil = paused_until !== undefined
|
||||
summary = s?.summary ?? ''
|
||||
labels = s?.labels ?? undefined
|
||||
@@ -372,6 +372,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
// The draft check compares as text and the picker writes `.000Z`: spell the stored time
|
||||
// the same, or a time changed and changed back reads as unsaved (`…:00Z` vs `…:00.000Z`).
|
||||
function asPicked(time: string | null | undefined): string | undefined {
|
||||
return time ? new Date(time).toISOString() : undefined
|
||||
}
|
||||
|
||||
async function loadScheduleCfg(cfg: Record<string, any>): Promise<void> {
|
||||
loading = true
|
||||
|
||||
@@ -382,7 +388,7 @@
|
||||
schedule = cfg.schedule
|
||||
initialSchedule = schedule
|
||||
timezone = cfg.timezone
|
||||
paused_until = cfg.paused_until
|
||||
paused_until = asPicked(cfg.paused_until)
|
||||
showPauseUntil = paused_until !== undefined
|
||||
summary = cfg.summary ?? ''
|
||||
labels = cfg.labels ?? undefined
|
||||
|
||||
@@ -47,6 +47,8 @@ export interface TriggerDraftSyncOptions {
|
||||
/**
|
||||
* The deployed baseline the editor's dirty check compares against —
|
||||
* `originalConfig` for TriggerCrud editors, `initialConfig` for Schedule.
|
||||
* Must read `$state`: the baseline is cached, so a plain `let` set by a later
|
||||
* open stays stale and the "unsaved changes" banner never shows.
|
||||
*/
|
||||
deployed: () => Cfg | undefined
|
||||
}
|
||||
@@ -61,8 +63,8 @@ export interface TriggerDraftSync {
|
||||
*/
|
||||
readonly hasDraft: boolean
|
||||
/**
|
||||
* Whether a deployed baseline exists (banner can appear). Reactive, unlike
|
||||
* reading `deployed` directly (a plain `let`), so callers can reserve its slot.
|
||||
* Whether a deployed baseline exists (banner can appear), so callers can
|
||||
* reserve its slot.
|
||||
*/
|
||||
readonly hasBaseline: boolean
|
||||
/** The deployed baseline the dirty check compares against. */
|
||||
@@ -172,8 +174,7 @@ export function useTriggerDraftSync(opts: TriggerDraftSyncOptions): TriggerDraft
|
||||
!opts.drawerLoading() && baseline != null && cfgDiffers(opts.getCfg() as Cfg, baseline as Cfg)
|
||||
)
|
||||
|
||||
// Reactive "banner is possible" — depends on `drawerLoading()` so it
|
||||
// re-evaluates (and re-reads the plain-`let` baseline) once the load settles.
|
||||
// "Banner is possible" — false until the load settles.
|
||||
const hasBaseline = $derived(!opts.drawerLoading() && opts.deployed() != null)
|
||||
|
||||
/** `auto: true` marks a discard from the reactive persist-effect (not an
|
||||
@@ -313,8 +314,7 @@ export function useTriggerDraftSync(opts: TriggerDraftSyncOptions): TriggerDraft
|
||||
const deployed = opts.deployed()
|
||||
if (deployed == null) return
|
||||
// The deployed object too, so `resetToDeployed` restores the new value;
|
||||
// `settledBaseline` because it shadows the deployed object and is the
|
||||
// reactive side (an editor's `deployed` may be a plain `let`).
|
||||
// `settledBaseline` because it shadows the deployed object.
|
||||
Object.assign(deployed, patch)
|
||||
settledBaseline = { ...snapshotCfg(settledBaseline ?? deployed), ...patch }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user