fix: runs page date picker query parameter handling (#8120)

* fix: runs page date picker query parameter handling

- Route query params by job status: queue-only statuses (running/suspended/waiting)
  use createdBeforeQueue/createdAfterQueue, completed-only statuses (success/failure)
  skip queue params, and no filter uses both
- Pass completedAfter (extendedMinTs) on initial load to avoid overfetching
- Skip auto-refresh syncer for manual timeframes (fixed past date ranges)
- Debounce param change effect to prevent double API calls
- Remove redundant timeframe dependency from effect (already tracked via filters)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: only show Load more when jobs count >= perPage

- Default lastFetchWentToEnd to true so Load more is hidden until a
  full page is confirmed
- Set lastFetchWentToEnd after initial load, not just after loadExtra
- Add jobs.length >= perPage guard in template to prevent flicker

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ruben Fiszel
2026-02-26 18:24:19 +00:00
committed by GitHub
co-authored by Claude Opus 4.6
parent e671aaedf9
commit 515bc2a93f
2 changed files with 39 additions and 17 deletions
+1 -1
View File
@@ -760,7 +760,7 @@
transformInputSelectedText={(_, v) => `${pluralize(v, 'day')} lookback`}
tooltip={'How far behind the min datetime to start considering jobs for the concurrency graph. Change this value to include jobs started before the set time window for the computation of the graph'}
/>
{:else if !lastFetchWentToEnd}
{:else if !lastFetchWentToEnd && (jobs?.length ?? 0) >= (perPage.val ?? 1000)}
<Button wrapperClasses="ml-2" unifiedSize="md" onClick={() => jobsLoader.loadExtraJobs()}>
Load more
<Tooltip>There are more jobs to load</Tooltip>
@@ -100,7 +100,7 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) {
let queue_count: Tweened<number> | undefined = $state()
let suspended_count: Tweened<number> | undefined = $state()
let loading = $state(false)
let lastFetchWentToEnd = $state(false)
let lastFetchWentToEnd = $state(true)
let completedJobs: CompletedJob[] | undefined = $state()
let externalJobs: Job[] | undefined = $state()
@@ -109,6 +109,7 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) {
let intervalId: ReturnType<typeof setInterval> | undefined = $state()
let sync = true
let paramChangeTimeout: ReturnType<typeof setTimeout> | undefined
function onParamChanges() {
resetJobs()
@@ -180,11 +181,18 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) {
loadingFetch = true
let scriptPathStart = folder == null || folder === '' ? undefined : `f/${folder}/`
let scriptPathExact = path == null || path === '' ? undefined : path
let isQueueOnly = success == 'running' || success == 'suspended' || success == 'waiting'
let isCompletedOnly = success == 'success' || success == 'failure'
let promise = JobService.listJobs({
workspace: currentWorkspace,
completedBefore: completedBefore ?? undefined,
completedAfter: completedAfter ?? undefined,
createdAfterQueue,
completedBefore: isQueueOnly ? undefined : (completedBefore ?? undefined),
completedAfter: isQueueOnly ? undefined : (completedAfter ?? undefined),
createdBeforeQueue: isQueueOnly ? (completedBefore ?? undefined) : undefined,
createdAfterQueue: isCompletedOnly
? undefined
: isQueueOnly
? (completedAfter ?? createdAfterQueue)
: createdAfterQueue,
schedulePath: schedulePath ?? undefined,
scriptPathExact,
createdBy: user == null || user === '' ? undefined : user,
@@ -311,16 +319,20 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) {
const extendedMinTs = subtractDaysFromDateString(minTs, lookback)
if (concurrencyKey == null || concurrencyKey === '') {
return CancelablePromiseUtils.map(fetchJobs(maxTs, null, extendedMinTs), (newJobs) => {
extendedJobs = { jobs: newJobs, obscured_jobs: [] } as ExtendedJobs
return CancelablePromiseUtils.map(
fetchJobs(maxTs, extendedMinTs ?? null, extendedMinTs),
(newJobs) => {
extendedJobs = { jobs: newJobs, obscured_jobs: [] } as ExtendedJobs
// Filter on minTs here and not in the backend
// to get enough data for the concurrency graph
jobs = sortMinDate(minTs, newJobs)
externalJobs = []
computeCompletedJobs()
loading = false
})
// Filter on minTs here and not in the backend
// to get enough data for the concurrency graph
jobs = sortMinDate(minTs, newJobs)
externalJobs = []
computeCompletedJobs()
lastFetchWentToEnd = newJobs.length < perPage
loading = false
}
)
} else {
return CancelablePromiseUtils.map(
fetchExtendedJobs(concurrencyKey, maxTs, extendedMinTs ?? null),
@@ -348,6 +360,7 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) {
externalJobs = computeExternalJobs(newExternalJobs)
}
computeCompletedJobs()
lastFetchWentToEnd = newJobs.length < perPage
loading = false
}
)
@@ -379,6 +392,9 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) {
if (loadingFetch) {
return
}
if (timeframe?.type === 'manual') {
return
}
if (sync) {
if (syncQueuedRunsCount) {
getCount()
@@ -526,12 +542,18 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) {
Object.keys(filters ?? {}).map((k) => filters?.[k as keyof RunsFilterInstance])
currentWorkspace
lookback
timeframe
perPage
showSchedules
showFutureJobs
let p = untrack(() => onParamChanges())
return () => p.cancel()
let p: CancelablePromise<void> | undefined
clearTimeout(paramChangeTimeout)
paramChangeTimeout = setTimeout(() => {
p = untrack(() => onParamChanges())
}, 0)
return () => {
clearTimeout(paramChangeTimeout)
p?.cancel()
}
})
$effect(() => {
;[autoRefresh, refreshRate]