feat(frontend): improve display of waiting jobs + schedule filter + suspended jobs on runs page

This commit is contained in:
Ruben Fiszel
2024-08-13 10:41:36 +02:00
parent c702d4e74f
commit 8950aae44e
15 changed files with 323 additions and 147 deletions
@@ -0,0 +1,29 @@
{
"db_name": "PostgreSQL",
"query": "SELECT coalesce(COUNT(*) FILTER(WHERE suspend = 0), 0) as \"database_length!\", coalesce(COUNT(*) FILTER(WHERE suspend > 0), 0) as \"suspended!\" FROM queue WHERE (workspace_id = $1 OR $2) AND scheduled_for <= now() AND running = false",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "database_length!",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "suspended!",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Bool"
]
},
"nullable": [
null,
null
]
},
"hash": "44d0c89afbc472165d80ca7a14b391bc2e5c44e0953103790dfe9375f605ee65"
}
@@ -5,7 +5,7 @@
"columns": [
{
"ordinal": 0,
"name": "bool",
"name": "?column?",
"type_info": "Bool"
}
],
@@ -1,23 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT coalesce(COUNT(*), 0) as \"database_length!\" FROM queue WHERE (workspace_id = $1 OR $2) AND scheduled_for <= now() AND running = false",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "database_length!",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Bool"
]
},
"nullable": [
null
]
},
"hash": "8481d1bd91b7d23f946b4cf0d312acfc0ed4973309b4860e93882a37a21a0bd0"
}
@@ -18,8 +18,8 @@
"Left": []
},
"nullable": [
false,
true
true,
false
]
},
"hash": "b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76"
@@ -1,12 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "SELECT coalesce(COUNT(*), 0) as \"database_length!\" FROM completed_job WHERE workspace_id = $1",
"query": "SELECT coalesce(COUNT(*), 0) as \"database_length!\", null::bigint as suspended FROM completed_job WHERE workspace_id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "database_length!",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "suspended",
"type_info": "Int8"
}
],
"parameters": {
@@ -15,8 +20,9 @@
]
},
"nullable": [
null,
null
]
},
"hash": "f5681df5935ec753f4a6d9ab8b927bbf890f0b4fe2de16dc764b24f24d7bbdca"
"hash": "bf04a017bc55f6e1f5f25f697b3c4828611591c76dd32541735095c010d6cdf2"
}
+3
View File
@@ -5631,6 +5631,8 @@ paths:
properties:
database_length:
type: integer
suspended:
type: integer
required:
- database_length
@@ -5812,6 +5814,7 @@ paths:
- $ref: "#/components/parameters/CreatedOrStartedAfter"
- $ref: "#/components/parameters/CreatedOrStartedAfterCompletedJob"
- $ref: "#/components/parameters/JobKinds"
- $ref: "#/components/parameters/Suspended"
- $ref: "#/components/parameters/ArgsFilter"
- $ref: "#/components/parameters/Tag"
- $ref: "#/components/parameters/ResultFilter"
+3 -3
View File
@@ -1462,7 +1462,6 @@ async fn cancel_selection(
Path(w_id): Path<String>,
Json(jobs): Json<Vec<Uuid>>,
) -> error::JsonResult<Vec<Uuid>> {
let mut tx = user_db.begin(&authed).await?;
let jobs_to_cancel = sqlx::query_scalar!(
"SELECT id FROM queue WHERE id = ANY($1) AND schedule_path IS NULL",
@@ -1508,6 +1507,7 @@ async fn list_filtered_uuids(
#[derive(Serialize, Debug, FromRow)]
struct QueueStats {
database_length: i64,
suspended: Option<i64>,
}
#[derive(Deserialize)]
@@ -1523,7 +1523,7 @@ async fn count_queue_jobs(
Ok(Json(
sqlx::query_as!(
QueueStats,
"SELECT coalesce(COUNT(*), 0) as \"database_length!\" FROM queue WHERE (workspace_id = $1 OR $2) AND scheduled_for <= now() AND running = false",
"SELECT coalesce(COUNT(*) FILTER(WHERE suspend = 0 AND running = false), 0) as \"database_length!\", coalesce(COUNT(*) FILTER(WHERE suspend > 0), 0) as \"suspended!\" FROM queue WHERE (workspace_id = $1 OR $2) AND scheduled_for <= now()",
w_id,
w_id == "admins" && cq.all_workspaces.unwrap_or(false),
)
@@ -1539,7 +1539,7 @@ async fn count_completed_jobs(
Ok(Json(
sqlx::query_as!(
QueueStats,
"SELECT coalesce(COUNT(*), 0) as \"database_length!\" FROM completed_job WHERE workspace_id = $1",
"SELECT coalesce(COUNT(*), 0) as \"database_length!\", null::bigint as suspended FROM completed_job WHERE workspace_id = $1",
w_id
)
.fetch_one(&db)
@@ -20,7 +20,7 @@
export let label: string | null = null
export let folder: string | null
export let path: string | null
export let success: 'success' | 'failure' | 'running' | undefined = undefined
export let success: 'success' | 'suspended' | 'waiting' | 'failure' | 'running' | undefined = undefined
export let isSkipped: boolean = false
export let showSchedules: boolean = true
export let showFutureJobs: boolean = true
@@ -32,6 +32,8 @@
export let maxTs: string | undefined = undefined
export let jobKinds: string = ''
export let queue_count: Tweened<number> | undefined = undefined
export let suspended_count: Tweened<number> | undefined = undefined
export let autoRefresh: boolean = true
export let completedJobs: CompletedJob[] | undefined = undefined
export let externalJobs: Job[] | undefined = undefined
@@ -63,6 +65,7 @@
lookback &&
user &&
folder &&
schedulePath != undefined &&
showFutureJobs != undefined &&
showSchedules != undefined &&
allWorkspaces != undefined &&
@@ -137,17 +140,18 @@
scriptPathStart: scriptPathStart,
jobKinds,
success: success == 'success' ? true : success == 'failure' ? false : undefined,
running: success == 'running' ? true : undefined,
running: (success == 'running' || success == 'suspended' ) ? true : (success == 'waiting' ) ? false : undefined,
isSkipped: isSkipped ? undefined : false,
// isFlowStep: jobKindsCat != 'all' ? false : undefined,
hasNullParent:
scriptPathExact != undefined || scriptPathStart != undefined || jobKinds != 'all'
scriptPathExact != undefined || scriptPathStart != undefined || jobKindsCat != 'all'
? true
: undefined,
label: label === null || label === '' ? undefined : label,
tag: tag === null || tag === '' ? undefined : tag,
isNotSchedule: showSchedules == false ? true : undefined,
scheduledForBeforeNow: showFutureJobs == false ? true : undefined,
suspended: success == 'waiting' ? false : success == 'suspended' ? true : undefined,
scheduledForBeforeNow: showFutureJobs == false || (success == 'waiting' || success == 'suspended') ? true : undefined,
args:
argFilter && argFilter != '{}' && argFilter != '' && argError == ''
? argFilter
@@ -292,16 +296,26 @@
}
async function getCount() {
const qc = (await JobService.getQueueCount({ workspace: $workspaceStore!, allWorkspaces }))
.database_length
const { database_length, suspended} = (await JobService.getQueueCount({ workspace: $workspaceStore!, allWorkspaces }))
if (queue_count) {
queue_count.set(qc)
queue_count.set(database_length)
} else {
queue_count = tweened(qc, { duration: 1000 })
queue_count = tweened(database_length, { duration: 1000 })
}
if (suspended_count) {
suspended_count.set(suspended ?? 0)
} else {
suspended_count = tweened(suspended ?? 0, { duration: 1000 })
}
}
async function syncer() {
if (success == 'waiting') {
minTs = undefined
maxTs = undefined
}
if (loadingFetch) {
return
}
@@ -33,7 +33,7 @@
<div class="text-secondary text-xs">Only showing the first 100 jobs</div>
{/if}
{#each jobs.slice(0, 100) as job}
{#each jobs.slice(0, 1000) as job}
<div class="flex">
<a
target="_blank"
@@ -152,6 +152,7 @@
{job.script_path}
</a>
<Button
title="Filter by path"
size="xs2"
color="light"
on:click={() => {
@@ -163,6 +164,7 @@
{/if}
{#if job.script_path?.startsWith('f/')}
<Button
title="Filter by folder"
size="xs2"
color="light"
on:click={() => {
@@ -255,6 +257,15 @@
{job.schedule_path}
</div>
</Button>
<Button
size="xs2"
color="light"
on:click={() => {
dispatch('filterBySchedule', job.schedule_path)
}}
>
<ListFilter size={10} />
</Button>
</div>
{:else}
<div class="flex flex-row gap-1 items-center">
@@ -4,7 +4,7 @@
import ToggleButtonGroup from '../common/toggleButton-v2/ToggleButtonGroup.svelte'
import Tooltip from '../Tooltip.svelte'
import AutoComplete from 'simple-svelte-autocomplete'
import { AlertCircle, CheckCircle2, ChevronDown, Filter, PlayCircle, X } from 'lucide-svelte'
import { AlertCircle, CheckCircle2, ChevronDown, Filter, Hourglass, PlayCircle, X } from 'lucide-svelte'
import JsonEditor from '../apps/editor/settingsPanel/inputEditor/JsonEditor.svelte'
import Toggle from '../Toggle.svelte'
import Label from '../Label.svelte'
@@ -19,7 +19,7 @@
export let label: string | null = null
export let concurrencyKey: string | null = null
export let tag: string | null = null
export let success: 'running' | 'success' | 'failure' | undefined = undefined
export let success: 'running' | 'waiting' | 'suspended' | 'queued' | 'success' | 'failure' | undefined = undefined
export let isSkipped: boolean | undefined = undefined
export let argFilter: string
export let argError: string
@@ -29,6 +29,7 @@
export let user: string | null = null
export let folder: string | null = null
export let mobile: boolean = false
export let schedulePath: string | undefined
// Autocomplete data
export let paths: string[] = []
@@ -39,35 +40,42 @@
$: displayedLabel = label
$: displayedConcurrencyKey = concurrencyKey
$: displayedTag = tag
$: displayedSchedule = schedulePath
let copyArgFilter = argFilter
let copyResultFilter = resultFilter
export let filterBy: 'path' | 'user' | 'folder' | 'label' | 'concurrencyKey' | 'tag' = 'path'
export let filterBy: 'path' | 'user' | 'folder' | 'label' | 'concurrencyKey' | 'tag' | 'schedulePath' = 'path'
const dispatch = createEventDispatcher()
let manuallySet = false
let autoSet = false
$: {
$: (path || user || folder || label || concurrencyKey || tag || schedulePath) && autosetFilter()
function autosetFilter() {
if (path !== null && path !== '' && filterBy !== 'path') {
manuallySet = true
autoSet = true
filterBy = 'path'
} else if (user !== null && user !== '' && filterBy !== 'user') {
manuallySet = true
autoSet = true
filterBy = 'user'
} else if (folder !== null && folder !== '' && filterBy !== 'folder') {
manuallySet = true
autoSet = true
filterBy = 'folder'
} else if (label !== null && label !== '' && filterBy !== 'label') {
manuallySet = true
autoSet = true
filterBy = 'label'
} else if (concurrencyKey !== null && concurrencyKey !== '' && filterBy !== 'concurrencyKey') {
manuallySet = true
autoSet = true
filterBy = 'concurrencyKey'
} else if (tag !== null && tag !== '' && filterBy !== 'tag') {
manuallySet = true
autoSet = true
filterBy = 'tag'
} else if (schedulePath !== undefined && schedulePath !== '' && filterBy !== 'schedulePath') {
autoSet = true
filterBy = 'schedulePath'
}
}
@@ -75,7 +83,8 @@
let concurrencyKeyTimeout: NodeJS.Timeout | undefined = undefined
let tagTimeout: NodeJS.Timeout | undefined = undefined
</script>
{autoSet}
{filterBy}
<div class="flex gap-4">
{#if !mobile}
<div class="flex gap-2">
@@ -94,15 +103,16 @@
<ToggleButtonGroup
bind:selected={filterBy}
on:selected={() => {
if (!manuallySet) {
if (!autoSet) {
path = null
user = null
folder = null
label = null
concurrencyKey = null
tag = null
schedulePath = undefined
} else {
manuallySet = false
autoSet = false
}
}}
>
@@ -111,7 +121,8 @@
<ToggleButton value="folder" label="Folder" />
<ToggleButtonMore
togglableItems={[
{ label: 'Concurrency key', value: 'concurrencyKey' },
{ label: 'Schedule Path', value: 'schedulePath' },
{ label: 'Concurrency Key', value: 'concurrencyKey' },
{ label: 'Label', value: 'label' },
{ label: 'Tag', value: 'tag' }
]}
@@ -336,6 +347,39 @@
/>
</div>
{/key}
{:else if filterBy === 'schedulePath'}
{#key tag}
<div class="relative">
{#if tag}
<button
class="absolute top-2 right-2 z-50"
on:click={() => {
schedulePath = undefined
dispatch('reset')
}}
>
<X size={14} />
</button>
{/if}
<span class="text-xs absolute -top-4"> Schedule Path </span>
<input
autofocus
type="text"
class="!h-[32px] py-1 !text-xs !w-64"
bind:value={displayedSchedule}
on:keydown={(e) => {
if (tagTimeout) {
clearTimeout(tagTimeout)
}
tagTimeout = setTimeout(() => {
schedulePath = displayedSchedule
}, 1000)
}}
/>
</div>
{/key}
{/if}
</div>
<div class="relative">
@@ -393,6 +437,23 @@
icon={AlertCircle}
iconProps={{ color: success === 'failure' ? 'red' : 'gray' }}
/>
{#if success == 'waiting'}
<ToggleButton
value={'waiting'}
tooltip="Waiting"
class="whitespace-nowrap"
icon={Hourglass}
iconProps={{ color: 'blue'}}
/>
{:else if success == 'suspended'}
<ToggleButton
value={'suspended'}
tooltip="Suspended"
class="whitespace-nowrap"
icon={Hourglass}
iconProps={{ color: 'blue'}}
/>
{/if}
</ToggleButtonGroup>
</div>
{/if}
@@ -419,24 +480,27 @@
<ToggleButtonGroup
bind:selected={filterBy}
on:selected={() => {
if (!manuallySet) {
if (!autoSet) {
path = null
user = null
folder = null
label = null
concurrencyKey = null
tag = null
schedulePath = undefined
} else {
manuallySet = false
autoSet = false
}
}}
>
<ToggleButton value="path" label="Path" />
<ToggleButton value="user" label="User" />
<ToggleButton value="folder" label="Folder" />
<ToggleButton value="schedulePath" label="Schedule" />
<ToggleButton value="concurrencyKey" label="Concurrency" />
<ToggleButton value="tag" label="Tag" />
<ToggleButton value="label" label="Label" />
</ToggleButtonGroup>
</Label>
@@ -1,23 +1,36 @@
<script lang="ts">
import type { Tweened } from 'svelte/motion'
import QueuePopover from './QueuePopover.svelte'
import Popup from '../common/popup/Popup.svelte'
import { createEventDispatcher } from 'svelte'
import { Button } from '../common'
import { FilterX, ListFilter } from 'lucide-svelte'
import Tooltip from '../Tooltip.svelte'
export let queue_count: Tweened<number> | undefined = undefined
export let allWorkspaces: boolean = false
export let suspended_count: Tweened<number> | undefined = undefined
export let success: string | undefined
const dispatch = createEventDispatcher()
</script>
<div class="flex gap-1 relative max-w-36 min-w-[50px] items-baseline">
<div class="text-xs absolute -top-4 truncate">Jobs waiting for a worker</div>
<div class="text-xs absolute -top-4 truncate flex items-baseline gap-1">Waiting for workers <Tooltip small>Jobs waiting for a worker being available to be executed</Tooltip></div>
<div class="mt-1">{queue_count ? ($queue_count ?? 0).toFixed(0) : '...'}</div>
<div class="truncate text-2xs text-blue-400">
<div class="truncate text-2xs !text-secondary mt-0.5">
{#if queue_count && ($queue_count ?? 0) > 0}
<Popup>
<svelte:fragment slot="button">
<span class="text-2xs truncate">jobs</span>
</svelte:fragment>
<QueuePopover {allWorkspaces} />
</Popup>
<Button size="xs2" color="light" on:click={() => dispatch('jobs_waiting')}>{#if success == 'waiting'}<FilterX size={12}></FilterX>{:else}<ListFilter size={12}></ListFilter>{/if}</Button>
{/if}
</div>
</div>
{#if suspended_count && ($suspended_count ?? 0) > 0}
<div class="flex gap-1 relative max-w-36 min-w-[50px] items-baseline ml-20 mr-8">
<div class="text-xs absolute -top-4 truncate flex items-baseline gap-1">Suspended <Tooltip small>Jobs waiting for an event or approval before being resumed</Tooltip></div>
<div class="mt-1">{suspended_count ? ($suspended_count ?? 0).toFixed(0) : '...'}</div>
<div class="truncate text-2xs !text-secondary mt-0.5">
<Button size="xs2" color="light" on:click={() => dispatch('jobs_suspended')}>{#if success == 'waiting'}<FilterX size={12}></FilterX>{:else}<ListFilter size={12}></ListFilter>{/if}</Button>
</div>
</div>
{/if}
@@ -228,73 +228,74 @@
<div class="w-3/12 text-xs font-semibold">Triggered by</div>
</div>
</div>
{#if jobs?.length == 0 && (!showExternalJobs || externalJobs?.length == 0)}
<tr>
<td colspan="4" class="text-center p-8">
<div class="text-xs text-secondary"> No jobs found for the selected filters. </div>
</td>
</tr>
{:else}
<VirtualList
width="100%"
height={tableHeight - header}
itemCount={flatJobs?.length ?? 3}
itemSize={42}
{stickyIndices}
>
<div slot="item" let:index let:style {style} class="w-full">
{#if flatJobs}
{@const jobOrDate = flatJobs[index]}
<VirtualList
width="100%"
height={tableHeight - header}
itemCount={flatJobs?.length ?? 3}
itemSize={42}
{stickyIndices}
>
<div slot="item" let:index let:style {style} class="w-full">
{#if flatJobs}
{@const jobOrDate = flatJobs[index]}
{#if jobOrDate}
{#if jobOrDate?.type === 'date'}
<div class="bg-surface-secondary py-2 border-b font-semibold text-xs pl-5">
{jobOrDate.date}
</div>
{:else}
<div class="flex flex-row items-center h-full w-full">
<RunRow
{containsLabel}
job={jobOrDate.job}
selected={jobOrDate.job.id !== '-' && selectedIds.includes(jobOrDate.job.id)}
{isSelectingJobsToCancel}
on:select={() => {
const jobId = jobOrDate.job.id
if (isSelectingJobsToCancel) {
if (selectedIds.includes(jobOrDate.job.id)) {
selectedIds = selectedIds.filter((id) => id != jobId)
{#if jobOrDate}
{#if jobOrDate?.type === 'date'}
<div class="bg-surface-secondary py-2 border-b font-semibold text-xs pl-5">
{jobOrDate.date}
</div>
{:else}
<div class="flex flex-row items-center h-full w-full">
<RunRow
{containsLabel}
job={jobOrDate.job}
selected={jobOrDate.job.id !== '-' && selectedIds.includes(jobOrDate.job.id)}
{isSelectingJobsToCancel}
on:select={() => {
const jobId = jobOrDate.job.id
if (isSelectingJobsToCancel) {
if (selectedIds.includes(jobOrDate.job.id)) {
selectedIds = selectedIds.filter((id) => id != jobId)
} else {
selectedIds.push(jobId)
selectedIds = selectedIds
}
} else {
selectedIds.push(jobId)
selectedIds = selectedIds
selectedWorkspace = jobOrDate.job.workspace_id
selectedIds = [jobOrDate.job.id]
dispatch('select')
}
} else {
selectedWorkspace = jobOrDate.job.workspace_id
selectedIds = [jobOrDate.job.id]
dispatch('select')
}
}}
{activeLabel}
on:filterByLabel
on:filterByPath
on:filterByUser
on:filterByFolder
on:filterByConcurrencyKey
{containerWidth}
/>
</div>
}}
{activeLabel}
on:filterByLabel
on:filterByPath
on:filterByUser
on:filterByFolder
on:filterByConcurrencyKey
on:filterBySchedule
{containerWidth}
/>
</div>
{/if}
{:else}
{JSON.stringify(jobOrDate)}
{/if}
{:else}
{JSON.stringify(jobOrDate)}
<div class="flex flex-row items-center h-full w-full">
<div class="w-1/12 text-2xs">...</div>
<div class="w-4/12 text-xs">...</div>
<div class="w-4/12 text-xs">...</div>
<div class="w-3/12 text-xs">...</div>
</div>
{/if}
{:else}
<div class="flex flex-row items-center h-full w-full">
<div class="w-1/12 text-2xs">...</div>
<div class="w-4/12 text-xs">...</div>
<div class="w-4/12 text-xs">...</div>
<div class="w-3/12 text-xs">...</div>
</div>
{/if}
</div>
</VirtualList>
</div>
</VirtualList>
{/if}
</div>
{#if jobs?.length == 0 && (!showExternalJobs || externalJobs?.length == 0)}
<tr>
<td colspan="4" class="text-center py-8">
<div class="text-xs text-secondary"> No jobs found for the selected filters. </div>
</td>
</tr>
{/if}
@@ -51,7 +51,7 @@
let concurrencyKey: string | null = $page.url.searchParams.get('concurrency_key')
let tag: string | null = $page.url.searchParams.get('tag')
// Rest of filters handled by RunsFilter
let success: 'running' | 'success' | 'failure' | undefined = ($page.url.searchParams.get(
let success: 'running' | 'suspended' | 'waiting' | 'success' | 'failure' | undefined = ($page.url.searchParams.get(
'success'
) ?? undefined) as 'running' | 'success' | 'failure' | undefined
let isSkipped: boolean | undefined =
@@ -87,6 +87,8 @@
let allWorkspaces = $page.url.searchParams.get('all_workspaces') == 'true' ?? false
let queue_count: Tweened<number> | undefined = undefined
let suspended_count: Tweened<number> | undefined = undefined
let jobKinds: string | undefined = undefined
let loading: boolean = false
let paths: string[] = []
@@ -255,8 +257,6 @@
function reset() {
minTs = undefined
maxTs = undefined
autoRefresh = true
jobs = undefined
completedJobs = undefined
selectedManualDate = 0
@@ -296,6 +296,8 @@
label = null
concurrencyKey = null
tag = null
schedulePath = undefined
}
function filterByUser(e: CustomEvent<string>) {
@@ -305,6 +307,7 @@
label = null
concurrencyKey = null
tag = null
schedulePath = undefined
}
function filterByFolder(e: CustomEvent<string>) {
@@ -314,6 +317,7 @@
label = null
concurrencyKey = null
tag = null
schedulePath = undefined
}
function filterByLabel(e: CustomEvent<string>) {
@@ -323,6 +327,7 @@
label = e.detail
concurrencyKey = null
tag = null
schedulePath = undefined
}
function filterByConcurrencyKey(e: CustomEvent<string>) {
@@ -332,6 +337,7 @@
label = null
concurrencyKey = e.detail
tag = null
schedulePath = undefined
}
function filterByTag(e: CustomEvent<string>) {
@@ -341,6 +347,17 @@
label = null
concurrencyKey = null
tag = e.detail
schedulePath = undefined
}
function filterBySchedule(e: CustomEvent<string>) {
path = null
user = null
folder = null
label = null
concurrencyKey = null
tag = null
schedulePath = e.detail
}
let calendarChangeTimeout: NodeJS.Timeout | undefined = undefined
@@ -381,18 +398,27 @@
scriptPathStart: folder === null || folder === '' ? undefined : `f/${folder}/`,
jobKinds,
success: success == 'success' ? true : success == 'failure' ? false : undefined,
running: success == 'running' ? true : undefined,
running: (success == 'running' || success == 'suspended' ) ? true : (success == 'waiting' ) ? false : undefined,
isSkipped: isSkipped ? undefined : false,
// isFlowStep: jobKindsCat != 'all' ? false : undefined,
hasNullParent:
path != undefined || path != undefined || jobKindsCat != 'all'
? true
: undefined,
label: label === null || label === '' ? undefined : label,
tag: tag === null || tag === '' ? undefined : tag,
isNotSchedule: showSchedules == false ? true : undefined,
scheduledForBeforeNow: showFutureJobs == false ? true : undefined,
suspended: success == 'waiting' ? false : success == 'suspended' ? true : undefined,
scheduledForBeforeNow: showFutureJobs == false || (success == 'waiting' || success == 'suspended') ? true : undefined,
args:
argFilter && argFilter != '{}' && argFilter != '' && argError == '' ? argFilter : undefined,
argFilter && argFilter != '{}' && argFilter != '' && argError == ''
? argFilter
: undefined,
result:
resultFilter && resultFilter != '{}' && resultFilter != '' && resultError == ''
? resultFilter
: undefined,
allWorkspaces: allWorkspaces ? true : undefined,
concurrencyKey: concurrencyKey ?? undefined,
tag: tag ?? undefined
}
selectedFiltersString = JSON.stringify(selectedFilters, null, 4)
@@ -424,6 +450,24 @@
graph === 'ConcurrencyChart' &&
extendedJobs !== undefined &&
extendedJobs.jobs.length + extendedJobs.obscured_jobs.length >= 1000
function jobsFilter(f: 'waiting' | 'suspended') {
path = null
user = null
folder = null
label = null
concurrencyKey = null
schedulePath = undefined
path = null
tag = null
if (success == f) {
success = undefined
} else {
success = f
}
jobKindsCat = 'all'
}
</script>
<JobLoader
@@ -446,6 +490,7 @@
bind:maxTs
{jobKinds}
bind:queue_count
bind:suspended_count
{autoRefresh}
bind:completedJobs
bind:externalJobs
@@ -518,7 +563,7 @@
<svelte:window bind:innerWidth />
{#if innerWidth > 1280}
{#if innerWidth > 900}
<div class="w-full h-screen">
<div class="px-2">
<div class="flex items-center space-x-2 flex-row justify-between">
@@ -556,6 +601,7 @@
bind:resultError
bind:jobKindsCat
bind:allWorkspaces
bind:schedulePath
on:change={reloadJobsWithoutFilterError}
{usernames}
{folders}
@@ -651,7 +697,11 @@
</div>
<div class="flex flex-col gap-1 md:flex-row w-full p-4">
<div class="flex gap-2 grow flex-row">
<RunsQueue {queue_count} {allWorkspaces} />
<RunsQueue {success} {queue_count} {suspended_count} on:jobs_waiting={() => {
jobsFilter('waiting')
}} on:jobs_suspended={() => {
jobsFilter('suspended')
}} />
<div class="flex flex-row">
{#if isSelectingJobsToCancel}
<div class="mt-1 p-2 h-8 flex flex-row items-center gap-1">
@@ -724,7 +774,7 @@
localStorage.setItem('show_schedules_in_run', showSchedules ? 'true' : 'false')
}}
/>
<span class="text-xs absolute -top-4">CRON Schedules</span>
<span class="text-xs absolute -top-4"><span class="hidden xl:inline">CRON</span> Schedules</span>
<Calendar size={16} />
</div>
@@ -843,6 +893,7 @@
on:filterByLabel={filterByLabel}
on:filterByConcurrencyKey={filterByConcurrencyKey}
on:filterByTag={filterByTag}
on:filterBySchedule={filterBySchedule}
/>
{:else}
<div class="gap-1 flex flex-col">
@@ -909,6 +960,7 @@
bind:argError
bind:resultError
bind:allWorkspaces
bind:schedulePath
mobile={true}
on:change={reloadJobsWithoutFilterError}
/>
@@ -992,10 +1044,14 @@
/>
{/if}
</div>
<div class="flex flex-col gap-4 md:flex-row w-full p-4">
<div class="flex flex-col gap-4 md:flex-row w-full p-4 overflow-x-auto">
<div class="flex items-center flex-row gap-2 grow">
{#if queue_count}
<RunsQueue {queue_count} {allWorkspaces} />
<RunsQueue {success} {queue_count} {suspended_count} on:jobs_waiting={() => {
jobsFilter('waiting')
}} on:jobs_suspended={() => {
jobsFilter('suspended')
}} />
{/if}
<div class="flex flex-row">
{#if isSelectingJobsToCancel}
@@ -1062,7 +1118,7 @@
</div>
</div>
<div class="flex gap-2 py-1">
<div class="relative flex gap-2 items-center pr-8 w-40">
<div class="relative flex gap-2 items-center pr-8 w-20">
<Toggle
size="xs"
bind:checked={showSchedules}
@@ -1074,7 +1130,7 @@
<Calendar size={16} />
</div>
<div class="relative flex gap-2 items-center pr-8 w-40">
<div class="relative flex gap-2 items-center pr-8 w-20">
<span class="text-xs absolute -top-4">Planned later</span>
<Toggle
size="xs"
@@ -1093,6 +1149,7 @@
<input
type="text"
class="min-w-10"
value={minTs
? new Date(minTs).toLocaleString()
: 'zoom x axis to set min (drag with ctrl)'}
@@ -1124,6 +1181,7 @@
<div class="flex gap-1 relative w-full">
<span class="text-xs absolute -top-4">Max datetime</span>
<input
class="min-w-10"
type="text"
value={maxTs ? new Date(maxTs).toLocaleString() : 'zoom x axis to set max'}
disabled
@@ -373,7 +373,7 @@
/>
<div class="flex gap-2 items-center justify-end">
<Button
href={`${base}/runs/?schedule_path=${path}`}
href={`${base}/runs/?schedule_path=${path}&show_schedules=true&show_future_jobs=true`}
size="xs"
startIcon={{ icon: List }}
color="light"