mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-07 08:02:40 +00:00
feat: add filter jobs by args or result
This commit is contained in:
@@ -3174,6 +3174,8 @@ paths:
|
||||
- $ref: "#/components/parameters/JobKinds"
|
||||
- $ref: "#/components/parameters/Suspended"
|
||||
- $ref: "#/components/parameters/Running"
|
||||
- $ref: "#/components/parameters/ArgsFilter"
|
||||
- $ref: "#/components/parameters/ResultFilter"
|
||||
responses:
|
||||
"200":
|
||||
description: All available queued jobs
|
||||
@@ -3202,6 +3204,8 @@ paths:
|
||||
- $ref: "#/components/parameters/CreatedAfter"
|
||||
- $ref: "#/components/parameters/Success"
|
||||
- $ref: "#/components/parameters/JobKinds"
|
||||
- $ref: "#/components/parameters/ArgsFilter"
|
||||
- $ref: "#/components/parameters/ResultFilter"
|
||||
- name: is_skipped
|
||||
description: is the job skipped
|
||||
in: query
|
||||
@@ -3238,6 +3242,8 @@ paths:
|
||||
- $ref: "#/components/parameters/CreatedBefore"
|
||||
- $ref: "#/components/parameters/CreatedAfter"
|
||||
- $ref: "#/components/parameters/JobKinds"
|
||||
- $ref: "#/components/parameters/ArgsFilter"
|
||||
- $ref: "#/components/parameters/ResultFilter"
|
||||
- name: is_skipped
|
||||
description: is the job skipped
|
||||
in: query
|
||||
@@ -4646,6 +4652,19 @@ components:
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
ArgsFilter:
|
||||
name: args
|
||||
description: filter on jobs containing those args as a json subset (@> in postgres)
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
|
||||
ResultFilter:
|
||||
name: result
|
||||
description: filter on jobs containing those result as a json subset (@> in postgres)
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
After:
|
||||
name: after
|
||||
description: filter on created after (exclusive) timestamp
|
||||
|
||||
@@ -334,6 +334,8 @@ pub struct ListQueueQuery {
|
||||
pub order_desc: Option<bool>,
|
||||
pub job_kinds: Option<String>,
|
||||
pub suspended: Option<bool>,
|
||||
// filter by matching a subset of the args using base64 encoded json subset
|
||||
pub args: Option<String>,
|
||||
}
|
||||
|
||||
fn list_queue_jobs_query(w_id: &str, lq: &ListQueueQuery, fields: &[&str]) -> SqlBuilder {
|
||||
@@ -376,6 +378,7 @@ fn list_queue_jobs_query(w_id: &str, lq: &ListQueueQuery, fields: &[&str]) -> Sq
|
||||
sqlb.and_where_eq("suspend", 0);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(jk) = &lq.job_kinds {
|
||||
sqlb.and_where_in(
|
||||
"job_kind",
|
||||
@@ -383,6 +386,10 @@ fn list_queue_jobs_query(w_id: &str, lq: &ListQueueQuery, fields: &[&str]) -> Sq
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(args) = &lq.args {
|
||||
sqlb.and_where("args @> ?".bind(&args.replace("'", "''")));
|
||||
}
|
||||
|
||||
sqlb
|
||||
}
|
||||
|
||||
@@ -462,6 +469,7 @@ async fn list_jobs(
|
||||
order_desc: Some(true),
|
||||
job_kinds: lq.job_kinds,
|
||||
suspended: lq.suspended,
|
||||
args: lq.args,
|
||||
},
|
||||
&[
|
||||
"'QueuedJob' as typ",
|
||||
@@ -1136,15 +1144,15 @@ where
|
||||
struct InPayload {
|
||||
payload: Option<String>,
|
||||
}
|
||||
|
||||
fn decode_payload<D: DeserializeOwned, T: AsRef<[u8]>>(t: T) -> anyhow::Result<D> {
|
||||
let vec = base64::engine::general_purpose::URL_SAFE
|
||||
.decode(t)
|
||||
.context("invalid base64")?;
|
||||
serde_json::from_slice(vec.as_slice()).context("invalid json")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_payload<D: DeserializeOwned, T: AsRef<[u8]>>(t: T) -> anyhow::Result<D> {
|
||||
let vec = base64::engine::general_purpose::URL_SAFE
|
||||
.decode(t)
|
||||
.context("invalid base64")?;
|
||||
serde_json::from_slice(vec.as_slice()).context("invalid json")
|
||||
}
|
||||
pub async fn run_flow_by_path(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
@@ -1706,6 +1714,15 @@ fn list_completed_jobs_query(
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(args) = &lq.args {
|
||||
sqlb.and_where("args @> ?".bind(&args.replace("'", "''")));
|
||||
}
|
||||
|
||||
if let Some(result) = &lq.result {
|
||||
sqlb.and_where("result @> ?".bind(&result.replace("'", "''")));
|
||||
}
|
||||
|
||||
tracing::info!("{:?}", sqlb.sql());
|
||||
sqlb
|
||||
}
|
||||
#[derive(Deserialize, Clone)]
|
||||
@@ -1723,6 +1740,10 @@ pub struct ListCompletedQuery {
|
||||
pub is_skipped: Option<bool>,
|
||||
pub is_flow_step: Option<bool>,
|
||||
pub suspended: Option<bool>,
|
||||
// filter by matching a subset of the args using base64 encoded json subset
|
||||
pub args: Option<String>,
|
||||
// filter by matching a subset of the result using base64 encoded json subset
|
||||
pub result: Option<String>,
|
||||
}
|
||||
|
||||
async fn list_completed_jobs(
|
||||
|
||||
@@ -2,13 +2,18 @@
|
||||
import { ChevronDown, ChevronUp } from 'lucide-svelte'
|
||||
import { slide } from 'svelte/transition'
|
||||
import Button from './common/button/Button.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
|
||||
export let text: string
|
||||
export let tooltip: string | undefined = undefined
|
||||
export let view = false
|
||||
</script>
|
||||
|
||||
<Button color="light" on:click={() => (view = !view)} variant="border"
|
||||
>{text}
|
||||
{#if tooltip}
|
||||
<Tooltip wrapperClass="mx-1">{tooltip}</Tooltip>
|
||||
{/if}
|
||||
{#if !view}<ChevronDown />{:else}<ChevronUp />{/if}</Button
|
||||
>
|
||||
{#if view}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
|
||||
export let code: string
|
||||
export let value: any
|
||||
export let value: any = undefined
|
||||
export let error = ''
|
||||
|
||||
function parseJson() {
|
||||
try {
|
||||
@@ -11,7 +12,6 @@
|
||||
error = e.message
|
||||
}
|
||||
}
|
||||
let error = ''
|
||||
$: code && parseJson()
|
||||
</script>
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
import Icon from 'svelte-awesome'
|
||||
import AutoComplete from 'simple-svelte-autocomplete'
|
||||
import NoItemFound from '$lib/components/home/NoItemFound.svelte'
|
||||
import Slider from '$lib/components/Slider.svelte'
|
||||
import JsonEditor from '$lib/components/apps/editor/settingsPanel/inputEditor/JsonEditor.svelte'
|
||||
|
||||
let jobs: Job[] | undefined
|
||||
let error: Error | undefined
|
||||
@@ -55,6 +57,16 @@
|
||||
|
||||
$: ($workspaceStore && loadJobs(createdBefore)) || (path && success && isSkipped && jobKinds)
|
||||
|
||||
let filterTimeout: NodeJS.Timeout | undefined = undefined
|
||||
function debounceSyncer() {
|
||||
filterTimeout && clearTimeout(filterTimeout)
|
||||
filterTimeout = setTimeout(() => {
|
||||
loadJobs(createdBefore)
|
||||
}, 500)
|
||||
}
|
||||
|
||||
$: (true || argFilter || resultFilter) && debounceSyncer()
|
||||
|
||||
async function fetchJobs(
|
||||
createdBefore: string | undefined,
|
||||
createdAfter: string | undefined
|
||||
@@ -67,7 +79,13 @@
|
||||
jobKinds,
|
||||
success,
|
||||
isSkipped,
|
||||
isFlowStep: jobKindsCat != 'all' ? false : undefined
|
||||
isFlowStep: jobKindsCat != 'all' ? false : undefined,
|
||||
args:
|
||||
argFilter && argFilter != '{}' && argFilter != '' && argError == '' ? argFilter : undefined,
|
||||
result:
|
||||
resultFilter && resultFilter != '{}' && resultFilter != '' && resultError == ''
|
||||
? resultFilter
|
||||
: undefined
|
||||
})
|
||||
}
|
||||
|
||||
@@ -89,7 +107,7 @@
|
||||
}
|
||||
|
||||
async function syncer() {
|
||||
if (jobs && createdBefore === undefined) {
|
||||
if (sync && jobs && createdBefore === 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
|
||||
@@ -110,9 +128,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
let sync = true
|
||||
onMount(() => {
|
||||
loadPaths()
|
||||
intervalId = setInterval(syncer, 5000)
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.hidden) {
|
||||
sync = false
|
||||
} else {
|
||||
sync = true
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
let paths: string[] = []
|
||||
@@ -146,6 +172,12 @@
|
||||
function onSearchPathChange() {
|
||||
goto(`/runs/${searchPath}?${$page.url.searchParams.toString()}`)
|
||||
}
|
||||
|
||||
let argFilter: any = undefined
|
||||
let resultFilter: any = undefined
|
||||
|
||||
let argError = ''
|
||||
let resultError = ''
|
||||
</script>
|
||||
|
||||
<CenteredPage>
|
||||
@@ -210,28 +242,47 @@
|
||||
<input type="text" value={maxTs ?? 'zoom x axis to set max'} disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row gap-x-2 mb-2 w-full">
|
||||
{#key path}
|
||||
<AutoComplete
|
||||
items={paths}
|
||||
value={path}
|
||||
bind:selectedItem={searchPath}
|
||||
placeholder="Search by path of script/flow"
|
||||
/>
|
||||
{/key}
|
||||
<Button
|
||||
variant="border"
|
||||
on:click={() => {
|
||||
minTs = undefined
|
||||
maxTs = undefined
|
||||
goto('/runs?' + $page.url.searchParams.toString())
|
||||
fetchJobs(createdBefore, undefined)
|
||||
}}
|
||||
size="xs"
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-2 mb-2 w-full flex-wrap">
|
||||
<div>
|
||||
<div class="flex flex-row gap-x-2">
|
||||
{#key path}
|
||||
<AutoComplete
|
||||
items={paths}
|
||||
value={path}
|
||||
bind:selectedItem={searchPath}
|
||||
placeholder="Search by path"
|
||||
/>
|
||||
{/key}
|
||||
<Button
|
||||
variant="border"
|
||||
on:click={() => {
|
||||
minTs = undefined
|
||||
maxTs = undefined
|
||||
goto('/runs?' + $page.url.searchParams.toString())
|
||||
fetchJobs(createdBefore, undefined)
|
||||
}}
|
||||
size="xs"
|
||||
>
|
||||
<Icon data={faSearchMinus} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
><Slider
|
||||
text="Filter by args"
|
||||
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"
|
||||
tooltip={'Filter by a json being a subset of the result. Try \'{"foo": "bar"}\''}
|
||||
><JsonEditor bind:error={resultError} bind:code={resultFilter} /></Slider
|
||||
></div
|
||||
>
|
||||
<Icon data={faSearchMinus} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Skeleton loading={!jobs} layout={[[6], 1, [6], 1, [6], 1, [6], 1, [6]]} />
|
||||
|
||||
{#if jobs}
|
||||
|
||||
Reference in New Issue
Block a user