mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-08 16:03:27 +00:00
fix: seed runs page filter defaults through the url so they survive sync (#11005)
Claude-Session: https://claude.ai/code/session_017KKZCLrTrWAqSeGjTzVtP2 Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ee9e550a48
commit
f381acdb37
@@ -112,17 +112,37 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a URL-synced filter instance that automatically syncs with URL search parameters
|
||||
* Creates a URL-synced filter instance that automatically syncs with URL search parameters.
|
||||
*
|
||||
* `initial` supplies defaults for keys the URL doesn't carry (a route segment, a persisted
|
||||
* toggle); the returned `seed` re-applies them after a navigation has rewritten the query.
|
||||
*/
|
||||
export function useUrlSyncedFilterInstance<T extends FilterSchemaRec>(
|
||||
schemaRec: T
|
||||
): { val: Partial<FilterInstanceRec<T>> } {
|
||||
schemaRec: T,
|
||||
initial?: Partial<FilterInstanceRec<T>>
|
||||
): {
|
||||
val: Partial<FilterInstanceRec<T>>
|
||||
seed: (values: Partial<FilterInstanceRec<T>>) => void
|
||||
} {
|
||||
// Build the Zod schema from the filter schema
|
||||
const zodSchema = filterSchemaRecToZodSchema(schemaRec)
|
||||
|
||||
// Create URL-synced search params
|
||||
const urlFilter = useSearchParams(zodSchema) as Record<string, unknown>
|
||||
|
||||
// A default has to arrive as a URL param: the URL→instance effect below drops whatever the
|
||||
// URL lacks, so a value written to the instance is undone on the next sync. Going through
|
||||
// urlFilter rather than straight to history keeps the search-param cells in step, so it
|
||||
// does not matter whether a popstate follows.
|
||||
function seed(values: Partial<FilterInstanceRec<T>>) {
|
||||
const sp = new URLSearchParams(window.location.search)
|
||||
for (const [key, value] of Object.entries(values) as [string, unknown][]) {
|
||||
if (value === undefined || value === null || sp.has(key)) continue
|
||||
urlFilter[key] = value instanceof Date ? value.toISOString() : value
|
||||
}
|
||||
}
|
||||
if (initial) seed(initial)
|
||||
|
||||
// Create the filter instance object
|
||||
const filterInstance: { val: Partial<FilterInstanceRec<T>> } = $state({ val: {} })
|
||||
|
||||
@@ -173,7 +193,15 @@
|
||||
})
|
||||
}
|
||||
|
||||
return filterInstance
|
||||
return {
|
||||
get val() {
|
||||
return filterInstance.val
|
||||
},
|
||||
set val(v: Partial<FilterInstanceRec<T>>) {
|
||||
filterInstance.val = v
|
||||
},
|
||||
seed
|
||||
}
|
||||
}
|
||||
|
||||
function filterToText<F extends FilterSchema>(filter: FilterInstance<F>, schema: F): string {
|
||||
|
||||
@@ -84,6 +84,8 @@
|
||||
initialPath?: string
|
||||
}
|
||||
|
||||
let { initialPath }: Props = $props()
|
||||
|
||||
let paths: string[] = $state([])
|
||||
let usernames: string[] = $state([])
|
||||
let folders: string[] = $state([])
|
||||
@@ -100,25 +102,30 @@
|
||||
let perPage = useLocalStorageValue('runs_per_page', 1000, 'number')
|
||||
let showSchedulesStorage = useLocalStorageValue('runs_show_schedules', true, 'boolean')
|
||||
let showFutureJobsStorage = useLocalStorageValue('runs_show_future_jobs', true, 'boolean')
|
||||
let filters = useUrlSyncedFilterInstance(untrack(() => runsFilterSearchbarSchema))
|
||||
function filterSeeds() {
|
||||
return {
|
||||
path: initialPath || undefined,
|
||||
job_trigger_kind: showSchedulesStorage.val === false ? ('!schedule' as const) : undefined,
|
||||
show_future_jobs: showFutureJobsStorage.val === false ? false : undefined
|
||||
}
|
||||
}
|
||||
|
||||
let { initialPath }: Props = $props()
|
||||
let filters = useUrlSyncedFilterInstance(
|
||||
untrack(() => runsFilterSearchbarSchema),
|
||||
untrack(filterSeeds)
|
||||
)
|
||||
|
||||
// `runs/[...path]` is a single route, so a navigation between its URLs — the sidebar's own
|
||||
// "Runs" entry, `/runs/<path>` → `/runs`, Back — rewrites the query without remounting, and
|
||||
// what was seeded at mount is gone. Re-apply it. Editing a filter writes with `replaceState`,
|
||||
// which never reaches `page.url`, so a filter the user clears stays cleared.
|
||||
$effect(() => {
|
||||
page.url.href
|
||||
untrack(() => filters.seed(filterSeeds()))
|
||||
})
|
||||
|
||||
let batchRerunOptionsIsOpen = $state(false)
|
||||
|
||||
// Initialize path filter from route param if provided and not already set via query params
|
||||
if (untrack(() => initialPath) && !filters.val.path) {
|
||||
filters.val.path = untrack(() => initialPath)
|
||||
}
|
||||
|
||||
// Apply persistent toggle values from local storage if URL doesn't specify them
|
||||
if (!page.url.searchParams.has('job_trigger_kind') && showSchedulesStorage.val === false) {
|
||||
filters.val.job_trigger_kind = '!schedule'
|
||||
}
|
||||
if (!page.url.searchParams.has('show_future_jobs') && showFutureJobsStorage.val === false) {
|
||||
filters.val.show_future_jobs = false
|
||||
}
|
||||
|
||||
// Sync toggle state back to local storage when filters change
|
||||
$effect(() => {
|
||||
if (!filters.val.job_trigger_kind || filters.val.job_trigger_kind === '!schedule') {
|
||||
|
||||
Reference in New Issue
Block a user