feat(frontend): make runs filters synced with query args

This commit is contained in:
Ruben Fiszel
2023-03-11 17:13:49 +01:00
parent d3296c958e
commit ca9ba83f67
6 changed files with 103 additions and 58 deletions
+10 -10
View File
@@ -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:
+14 -14
View File
@@ -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>,
+28 -5
View File
@@ -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 == '') {
+11 -3
View File
@@ -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