mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 16:02:19 +00:00
feat(frontend): make runs filters synced with query args
This commit is contained in:
@@ -3168,8 +3168,8 @@ paths:
|
||||
- $ref: "#/components/parameters/ScriptExactPath"
|
||||
- $ref: "#/components/parameters/ScriptStartPath"
|
||||
- $ref: "#/components/parameters/ScriptExactHash"
|
||||
- $ref: "#/components/parameters/CreatedBefore"
|
||||
- $ref: "#/components/parameters/CreatedAfter"
|
||||
- $ref: "#/components/parameters/StartedBefore"
|
||||
- $ref: "#/components/parameters/StartedAfter"
|
||||
- $ref: "#/components/parameters/Success"
|
||||
- $ref: "#/components/parameters/JobKinds"
|
||||
- $ref: "#/components/parameters/Suspended"
|
||||
@@ -3200,8 +3200,8 @@ paths:
|
||||
- $ref: "#/components/parameters/ScriptExactPath"
|
||||
- $ref: "#/components/parameters/ScriptStartPath"
|
||||
- $ref: "#/components/parameters/ScriptExactHash"
|
||||
- $ref: "#/components/parameters/CreatedBefore"
|
||||
- $ref: "#/components/parameters/CreatedAfter"
|
||||
- $ref: "#/components/parameters/StartedBefore"
|
||||
- $ref: "#/components/parameters/StartedAfter"
|
||||
- $ref: "#/components/parameters/Success"
|
||||
- $ref: "#/components/parameters/JobKinds"
|
||||
- $ref: "#/components/parameters/ArgsFilter"
|
||||
@@ -3239,8 +3239,8 @@ paths:
|
||||
- $ref: "#/components/parameters/ScriptExactPath"
|
||||
- $ref: "#/components/parameters/ScriptStartPath"
|
||||
- $ref: "#/components/parameters/ScriptExactHash"
|
||||
- $ref: "#/components/parameters/CreatedBefore"
|
||||
- $ref: "#/components/parameters/CreatedAfter"
|
||||
- $ref: "#/components/parameters/StartedBefore"
|
||||
- $ref: "#/components/parameters/StartedAfter"
|
||||
- $ref: "#/components/parameters/JobKinds"
|
||||
- $ref: "#/components/parameters/ArgsFilter"
|
||||
- $ref: "#/components/parameters/ResultFilter"
|
||||
@@ -4620,15 +4620,15 @@ components:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
CreatedBefore:
|
||||
name: created_before
|
||||
StartedBefore:
|
||||
name: started_before
|
||||
description: filter on created before (inclusive) timestamp
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
CreatedAfter:
|
||||
name: created_after
|
||||
StartedAfter:
|
||||
name: started_after
|
||||
description: filter on created after (exclusive) timestamp
|
||||
in: query
|
||||
schema:
|
||||
|
||||
@@ -327,8 +327,8 @@ pub struct ListQueueQuery {
|
||||
pub script_path_exact: Option<String>,
|
||||
pub script_hash: Option<String>,
|
||||
pub created_by: Option<String>,
|
||||
pub created_before: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub created_after: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub started_before: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub started_after: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub running: Option<bool>,
|
||||
pub parent_job: Option<String>,
|
||||
pub order_desc: Option<bool>,
|
||||
@@ -364,11 +364,11 @@ fn list_queue_jobs_query(w_id: &str, lq: &ListQueueQuery, fields: &[&str]) -> Sq
|
||||
if let Some(pj) = &lq.parent_job {
|
||||
sqlb.and_where_eq("parent_job", "?".bind(pj));
|
||||
}
|
||||
if let Some(dt) = &lq.created_before {
|
||||
sqlb.and_where_lt("created_at", format!("to_timestamp({})", dt.timestamp()));
|
||||
if let Some(dt) = &lq.started_before {
|
||||
sqlb.and_where_le("started_at", format!("to_timestamp({})", dt.timestamp()));
|
||||
}
|
||||
if let Some(dt) = &lq.created_after {
|
||||
sqlb.and_where_gt("created_at", format!("to_timestamp({})", dt.timestamp()));
|
||||
if let Some(dt) = &lq.started_after {
|
||||
sqlb.and_where_ge("started_at", format!("to_timestamp({})", dt.timestamp()));
|
||||
}
|
||||
|
||||
if let Some(s) = &lq.suspended {
|
||||
@@ -462,8 +462,8 @@ async fn list_jobs(
|
||||
script_path_exact: lq.script_path_exact,
|
||||
script_hash: lq.script_hash,
|
||||
created_by: lq.created_by,
|
||||
created_before: lq.created_before,
|
||||
created_after: lq.created_after,
|
||||
started_before: lq.started_before,
|
||||
started_after: lq.started_after,
|
||||
running: None,
|
||||
parent_job: lq.parent_job,
|
||||
order_desc: Some(true),
|
||||
@@ -1695,11 +1695,11 @@ fn list_completed_jobs_query(
|
||||
if let Some(pj) = &lq.parent_job {
|
||||
sqlb.and_where_eq("parent_job", "?".bind(pj));
|
||||
}
|
||||
if let Some(dt) = &lq.created_before {
|
||||
sqlb.and_where_lt("created_at", format!("to_timestamp({})", dt.timestamp()));
|
||||
if let Some(dt) = &lq.started_before {
|
||||
sqlb.and_where_le("started_at", format!("to_timestamp({})", dt.timestamp()));
|
||||
}
|
||||
if let Some(dt) = &lq.created_after {
|
||||
sqlb.and_where_gt("created_at", format!("to_timestamp({})", dt.timestamp()));
|
||||
if let Some(dt) = &lq.started_after {
|
||||
sqlb.and_where_ge("started_at", format!("to_timestamp({})", dt.timestamp()));
|
||||
}
|
||||
if let Some(sk) = &lq.is_skipped {
|
||||
sqlb.and_where_eq("is_skipped", sk);
|
||||
@@ -1731,8 +1731,8 @@ pub struct ListCompletedQuery {
|
||||
pub script_path_exact: Option<String>,
|
||||
pub script_hash: Option<String>,
|
||||
pub created_by: Option<String>,
|
||||
pub created_before: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub created_after: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub started_before: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub started_after: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub success: Option<bool>,
|
||||
pub parent_job: Option<String>,
|
||||
pub order_desc: Option<bool>,
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
label: 'Failed',
|
||||
data:
|
||||
failed?.map((job) => ({
|
||||
x: job.created_at as any,
|
||||
x: job.started_at as any,
|
||||
y: job.duration_ms,
|
||||
id: job.id,
|
||||
path: job.script_path
|
||||
@@ -57,7 +57,7 @@
|
||||
label: 'Successful',
|
||||
data:
|
||||
success?.map((job) => ({
|
||||
x: job.created_at as any,
|
||||
x: job.started_at as any,
|
||||
y: job.duration_ms,
|
||||
id: job.id,
|
||||
path: job.script_path
|
||||
@@ -71,7 +71,10 @@
|
||||
enabled: true,
|
||||
modifierKey: 'ctrl' as 'ctrl',
|
||||
onPanComplete: ({ chart }) => {
|
||||
dispatch('zoom', { min: new Date(chart.scales.x.min), max: new Date(chart.scales.x.max) })
|
||||
dispatch('zoom', {
|
||||
min: addSeconds(new Date(chart.scales.x.min), -1),
|
||||
max: addSeconds(new Date(chart.scales.x.max), 1)
|
||||
})
|
||||
}
|
||||
},
|
||||
zoom: {
|
||||
@@ -80,7 +83,10 @@
|
||||
},
|
||||
mode: 'x' as 'x',
|
||||
onZoom: ({ chart }) => {
|
||||
dispatch('zoom', { min: new Date(chart.scales.x.min), max: new Date(chart.scales.x.max) })
|
||||
dispatch('zoom', {
|
||||
min: addSeconds(new Date(chart.scales.x.min), -1),
|
||||
max: addSeconds(new Date(chart.scales.x.max), 1)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,6 +94,22 @@
|
||||
function getPath(x: any): string {
|
||||
return x.path
|
||||
}
|
||||
|
||||
$: minTime = getMinTime(jobs)
|
||||
|
||||
function addSeconds(date: Date, seconds: number): Date {
|
||||
date.setTime(date.getTime() + seconds * 1000)
|
||||
return date
|
||||
}
|
||||
function getMinTime(jobs: CompletedJob[] | undefined): Date {
|
||||
return addSeconds(new Date(jobs?.[jobs?.length - 1]?.started_at ?? new Date().toString()), -15)
|
||||
}
|
||||
|
||||
$: maxTime = getMaxTime(jobs)
|
||||
|
||||
function getMaxTime(jobs: CompletedJob[] | undefined): Date {
|
||||
return addSeconds(new Date(jobs?.[0]?.started_at ?? new Date().toString()), 15)
|
||||
}
|
||||
</script>
|
||||
|
||||
<Scatter
|
||||
@@ -115,7 +137,8 @@
|
||||
display: false
|
||||
},
|
||||
type: 'time',
|
||||
min: jobs?.[jobs?.length - 1]?.created_at ?? new Date().toString()
|
||||
min: minTime,
|
||||
max: maxTime
|
||||
},
|
||||
y: {
|
||||
grid: {
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
let summaryC: HTMLInputElement | undefined = undefined
|
||||
let pathC: Path | undefined = undefined
|
||||
|
||||
$: setQueryWithoutLoad($page.url, 'state', encodeState(script))
|
||||
$: setQueryWithoutLoad($page.url, [{ key: 'state', value: encodeState(script) }])
|
||||
$: step = Number($page.url.searchParams.get('step')) || 1
|
||||
|
||||
if (script.content == '') {
|
||||
|
||||
@@ -297,17 +297,25 @@ export async function setQuery(url: URL, key: string, value: string): Promise<vo
|
||||
}
|
||||
|
||||
let debounced: NodeJS.Timeout | undefined = undefined
|
||||
export function setQueryWithoutLoad(url: URL, key: string, value: string): void {
|
||||
export function setQueryWithoutLoad(url: URL, args: { key: string, value: string | null | undefined }[], bounceTime?: number): void {
|
||||
debounced && clearTimeout(debounced)
|
||||
debounced = setTimeout(() => {
|
||||
const nurl = new URL(url.toString())
|
||||
nurl.searchParams.set(key, value)
|
||||
console.log(url.toString())
|
||||
for (const { key, value } of args) {
|
||||
if (value) {
|
||||
nurl.searchParams.set(key, value)
|
||||
} else {
|
||||
nurl.searchParams.delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
history.replaceState(history.state, '', nurl.toString())
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}, 200)
|
||||
}, bounceTime ?? 200)
|
||||
}
|
||||
|
||||
export function groupBy<T>(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
import { JobService, Job, CompletedJob, ScriptService, FlowService } from '$lib/gen'
|
||||
import { setQuery } from '$lib/utils'
|
||||
import { setQuery, setQueryWithoutLoad } from '$lib/utils'
|
||||
|
||||
import { page } from '$app/stores'
|
||||
import { sendUserToast } from '$lib/utils'
|
||||
@@ -24,7 +24,6 @@
|
||||
let jobs: Job[] | undefined
|
||||
let error: Error | undefined
|
||||
let intervalId: NodeJS.Timer | undefined
|
||||
let createdBefore: string | undefined = $page.url.searchParams.get('createdBefore') ?? undefined
|
||||
|
||||
let success: boolean | undefined =
|
||||
$page.url.searchParams.get('success') != undefined
|
||||
@@ -35,6 +34,11 @@
|
||||
? $page.url.searchParams.get('is_skipped') == 'true'
|
||||
: false
|
||||
|
||||
let argFilter: any = $page.url.searchParams.get('arg') ?? undefined
|
||||
let resultFilter: any = $page.url.searchParams.get('result') ?? undefined
|
||||
let minTs = $page.url.searchParams.get('min_ts') ?? undefined
|
||||
let maxTs = $page.url.searchParams.get('max_ts') ?? undefined
|
||||
|
||||
let nbOfJobs = 30
|
||||
|
||||
$: path = $page.params.path
|
||||
@@ -55,26 +59,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: ($workspaceStore && loadJobs(createdBefore)) || (path && success && isSkipped && jobKinds)
|
||||
$: ($workspaceStore && loadJobs()) || (path && success && isSkipped && jobKinds)
|
||||
|
||||
let filterTimeout: NodeJS.Timeout | undefined = undefined
|
||||
function debounceSyncer() {
|
||||
filterTimeout && clearTimeout(filterTimeout)
|
||||
filterTimeout = setTimeout(() => {
|
||||
loadJobs(createdBefore)
|
||||
}, 500)
|
||||
loadJobs()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
$: (true || argFilter || resultFilter) && debounceSyncer()
|
||||
|
||||
async function fetchJobs(
|
||||
createdBefore: string | undefined,
|
||||
createdAfter: string | undefined
|
||||
startedBefore: string | undefined,
|
||||
startedAfter: string | undefined
|
||||
): Promise<Job[]> {
|
||||
return JobService.listJobs({
|
||||
workspace: $workspaceStore!,
|
||||
createdBefore,
|
||||
createdAfter,
|
||||
startedBefore,
|
||||
startedAfter,
|
||||
scriptPathExact: path === '' ? undefined : path,
|
||||
jobKinds,
|
||||
success,
|
||||
@@ -89,9 +93,10 @@
|
||||
})
|
||||
}
|
||||
|
||||
async function loadJobs(createdBefore: string | undefined): Promise<void> {
|
||||
async function loadJobs(): Promise<void> {
|
||||
jobs = undefined
|
||||
try {
|
||||
const newJobs = await fetchJobs(createdBefore, undefined)
|
||||
const newJobs = await fetchJobs(maxTs, minTs)
|
||||
jobs = newJobs
|
||||
} catch (err) {
|
||||
sendUserToast(`There was a problem fetching jobs: ${err}`, true)
|
||||
@@ -107,7 +112,7 @@
|
||||
}
|
||||
|
||||
async function syncer() {
|
||||
if (sync && jobs && createdBefore === undefined) {
|
||||
if (sync && jobs && maxTs == undefined) {
|
||||
const reversedJobs = [...jobs].reverse()
|
||||
const lastIndex = reversedJobs.findIndex((x) => x.type == Job.type.QUEUED_JOB) - 1
|
||||
let ts = lastIndex >= 0 ? reversedJobs[lastIndex].created_at : undefined
|
||||
@@ -148,11 +153,23 @@
|
||||
const npaths_flows = await FlowService.listFlowPaths({ workspace: $workspaceStore ?? '' })
|
||||
paths = npaths_scripts.concat(npaths_flows).sort()
|
||||
}
|
||||
async function syncCatWithURL() {
|
||||
await setQuery($page.url, 'job_kinds', jobKindsCat)
|
||||
|
||||
function syncWithUrl(arg: string, value: string) {
|
||||
setQueryWithoutLoad($page.url, [{ key: arg, value }])
|
||||
}
|
||||
|
||||
$: jobKindsCat && syncCatWithURL()
|
||||
async function syncTsWithURL(minTs?: string, maxTs?: string) {
|
||||
console.log(minTs, maxTs)
|
||||
setQueryWithoutLoad($page.url, [
|
||||
{ key: 'min_ts', value: minTs },
|
||||
{ key: 'max_ts', value: maxTs }
|
||||
])
|
||||
}
|
||||
|
||||
$: syncWithUrl('job_kinds', jobKindsCat)
|
||||
$: syncWithUrl('arg', argFilter)
|
||||
$: syncWithUrl('result', resultFilter)
|
||||
$: syncTsWithURL(minTs, maxTs)
|
||||
|
||||
$: completedJobs =
|
||||
jobs?.filter((x) => x.type == 'CompletedJob').map((x) => x as CompletedJob) ?? []
|
||||
@@ -164,8 +181,6 @@
|
||||
})
|
||||
let searchPath = ''
|
||||
$: searchPath = path
|
||||
let minTs = undefined
|
||||
let maxTs = undefined
|
||||
|
||||
$: searchPath && onSearchPathChange()
|
||||
|
||||
@@ -173,9 +188,6 @@
|
||||
goto(`/runs/${searchPath}?${$page.url.searchParams.toString()}`)
|
||||
}
|
||||
|
||||
let argFilter: any = undefined
|
||||
let resultFilter: any = undefined
|
||||
|
||||
let argError = ''
|
||||
let resultError = ''
|
||||
</script>
|
||||
@@ -254,12 +266,14 @@
|
||||
/>
|
||||
{/key}
|
||||
<Button
|
||||
title="Clear path and time filters"
|
||||
variant="border"
|
||||
on:click={() => {
|
||||
on:click={async () => {
|
||||
minTs = undefined
|
||||
maxTs = undefined
|
||||
goto('/runs?' + $page.url.searchParams.toString())
|
||||
fetchJobs(createdBefore, undefined)
|
||||
jobs = undefined
|
||||
await goto('/runs?' + $page.url.searchParams.toString())
|
||||
loadJobs()
|
||||
}}
|
||||
size="xs"
|
||||
>
|
||||
@@ -269,14 +283,14 @@
|
||||
</div>
|
||||
<div
|
||||
><Slider
|
||||
text="Filter by args"
|
||||
text="Filter by args {argFilter ? '(set)' : ''}"
|
||||
tooltip={'Filter by a json being a subset of the args. Try \'{"foo": "bar"}\''}
|
||||
><JsonEditor bind:error={argError} bind:code={argFilter} /></Slider
|
||||
></div
|
||||
>
|
||||
<div
|
||||
><Slider
|
||||
text="Filter by result"
|
||||
text="Filter by result {resultFilter ? '(set)' : ''}"
|
||||
tooltip={'Filter by a json being a subset of the result. Try \'{"foo": "bar"}\''}
|
||||
><JsonEditor bind:error={resultError} bind:code={resultFilter} /></Slider
|
||||
></div
|
||||
|
||||
Reference in New Issue
Block a user