feat: add search by args on input history directly

This commit is contained in:
Ruben Fiszel
2025-03-12 16:05:02 +01:00
parent a6956215ec
commit 593dc30bc8
8 changed files with 133 additions and 21 deletions

View File

@@ -10697,6 +10697,7 @@ paths:
- $ref: "#/components/parameters/RunnableTypeQuery"
- $ref: "#/components/parameters/Page"
- $ref: "#/components/parameters/PerPage"
- $ref: "#/components/parameters/ArgsFilter"
- name: include_preview
in: query
schema:

View File

@@ -118,6 +118,7 @@ pub struct CompletedJobMini {
#[derive(Deserialize)]
struct GetInputHistory {
include_preview: Option<bool>,
args: Option<String>,
}
async fn get_input_history(
@@ -132,11 +133,18 @@ async fn get_input_history(
let mut tx = user_db.begin(&authed).await?;
let args_query = if let Some(args) = &g.args {
format!("and args @> '{}'", args)
} else {
"".to_string()
};
let sql = &format!(
"select id, v2_job.created_at, created_by, 'null'::jsonb as args, status = 'success' as success from v2_job JOIN v2_job_completed USING (id) \
where {} = $1 and kind = any($2) and v2_job.workspace_id = $3 AND v2_job_completed.status != 'skipped' \
where {} = $1 and kind = any($2) {args_query} and v2_job.workspace_id = $3 AND v2_job_completed.status != 'skipped' \
order by v2_job.created_at desc limit $4 offset $5",
r.runnable_type.column_name()
r.runnable_type.column_name(),
);
let query = sqlx::query_as::<_, CompletedJobMini>(sql);

View File

@@ -14,6 +14,7 @@
export let showAuthor = false
export let placement: 'bottom-start' | 'top-start' | 'bottom-end' | 'top-end' = 'top-end'
export let limitPayloadSize = false
export let searchArgs: Record<string, any> | undefined = undefined
let historicList: HistoricList | undefined = undefined
const dispatch = createEventDispatcher()
@@ -93,7 +94,7 @@
concurrencyKey={null}
tag={null}
success="running"
argFilter={undefined}
argFilter={searchArgs ? JSON.stringify(searchArgs) : undefined}
bind:loading
syncQueuedRunsCount={false}
refreshRate={10000}
@@ -146,6 +147,7 @@
{showAuthor}
{placement}
{limitPayloadSize}
{searchArgs}
/>
</div>
</div>

View File

@@ -11,6 +11,7 @@
export let showAuthor = false
export let placement: 'bottom-start' | 'top-start' | 'bottom-end' | 'top-end' = 'bottom-start'
export let limitPayloadSize = false
export let searchArgs: Record<string, any> | undefined = undefined
let infiniteList: InfiniteList | undefined = undefined
let loadInputsPageFn: ((page: number, perPage: number) => Promise<any>) | undefined = undefined
@@ -37,7 +38,8 @@
runnableType,
page,
perPage,
includePreview: true
includePreview: true,
args: searchArgs ? JSON.stringify(searchArgs) : undefined
})
const inputsWithPayload = await Promise.all(

View File

@@ -1,13 +1,16 @@
<script lang="ts">
import SavedInputsPicker from '$lib/components/SavedInputsPicker.svelte'
import { createEventDispatcher } from 'svelte'
import { createEventDispatcher, tick } from 'svelte'
import HistoricInputs from '$lib/components/HistoricInputs.svelte'
import { type RunnableType } from '$lib/gen/types.gen.js'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import Section from '$lib/components/Section.svelte'
import SaveInputsButton from '$lib/components/SaveInputsButton.svelte'
import { Button } from './common'
import { ExternalLink } from 'lucide-svelte'
import { ExternalLink, Search } from 'lucide-svelte'
import { Popover } from './meltComponents'
import SchemaForm from './SchemaForm.svelte'
import MultiSelect from './multiselect/MultiSelectWrapper.svelte'
const dispatch = createEventDispatcher()
export let scriptHash: string | null = null
@@ -15,7 +18,7 @@
export let flowPath: string | null = null
export let inputSelected: 'saved' | 'history' | undefined = undefined
export let jsonView: boolean = false
export let schema: any
// Are the current Inputs valid and able to be saved?
export let isValid: boolean
export let args: object
@@ -51,6 +54,19 @@
dispatch('selected_args', savedArgs)
}
}
let searchArgs = {}
let appliedSearchArgs = {}
let searchArgsFields = [] as string[]
$: emptySearchArgs = Object.keys(appliedSearchArgs).length === 0
$: filteredSchema = {
properties: Object.fromEntries(
Object.entries(schema.properties).filter(([key]) => searchArgsFields.includes(key))
)
}
</script>
<div class="min-w-[300px] h-full flex flex-col">
@@ -92,17 +108,92 @@
<Pane class="px-4 py-4 h-full">
<Section label="History" wrapperClass="h-full" small={true}>
<svelte:fragment slot="action">
<Button
size="xs2"
color="light"
btnClasses="!text-tertiary"
endIcon={{ icon: ExternalLink }}
on:click={() => {
window.open(`/runs/${runnableId}`, '_blank')
}}
>
All runs
</Button>
<div class="flex space-x-2">
<Popover
class="w-fit"
usePointerDownOutside
closeOnOtherPopoverOpen
on:click={(e) => {
e.stopPropagation()
}}
floatingConfig={{ placement: 'top-end' }}
>
<svelte:fragment slot="trigger">
<Button
variant="contained"
size="xs2"
color={emptySearchArgs ? 'light' : 'dark'}
nonCaptureEvent
title="Search history"
iconOnly={emptySearchArgs}
endIcon={{ icon: Search }}
>
{emptySearchArgs ? '' : 'Active filters'}
</Button>
</svelte:fragment>
<svelte:fragment slot="content">
<div id="multi-select-search" />
<div class="p-2 overflow-auto max-h-[400px] min-h-[300px] w-[400px]">
<div class="flex items-center flex-wrap gap-x-2 justify-between">
<div class="text-sm text-secondary">Search by args</div>
<div class="flex flex-wrap gap-x-2">
<Button
on:click={async () => {
appliedSearchArgs = structuredClone(searchArgs)
await tick()
historicInputs?.refresh()
}}
endIcon={{ icon: Search }}
variant="contained"
size="xs2"
color="dark">Search</Button
>
{#if !emptySearchArgs}
<Button
on:click={async () => {
searchArgs = {}
appliedSearchArgs = {}
await tick()
historicInputs?.refresh()
}}
variant="contained"
size="xs2"
color="light">Reset filters</Button
>
{/if}
</div>
</div>
<div class="my-2">
<MultiSelect
topPlacement
target="#multi-select-search"
placeholder="arg fields to filter on"
items={Object.keys(schema.properties)}
bind:value={searchArgsFields}
/>
</div>
{#key filteredSchema}
<SchemaForm shouldHideNoInputs schema={filteredSchema} bind:args={searchArgs} />
{/key}
{#if searchArgsFields.length === 0}
<div class="text-secondary text-sm my-8 mx-auto">No filters</div>
{/if}
</div>
</svelte:fragment>
</Popover>
<Button
size="xs2"
color="light"
btnClasses="!text-tertiary"
endIcon={{ icon: ExternalLink }}
on:click={() => {
window.open(`/runs/${runnableId}`, '_blank')
}}
>
All runs
</Button>
</div>
</svelte:fragment>
<HistoricInputs
bind:this={historicInputs}
@@ -112,6 +203,7 @@
if (e.detail) savedInputsPicker?.resetSelected()
selectArgs(e.detail?.args, e.detail ? 'history' : undefined)
}}
searchArgs={emptySearchArgs ? undefined : appliedSearchArgs}
showAuthor
placement="top-end"
/>

View File

@@ -8,13 +8,17 @@
import MultiSelect from '$lib/components/multiselect/MultiSelect.svelte'
import DarkModeObserver from '../DarkModeObserver.svelte'
export let items: any[]
export let value: string[] | undefined = [] as string[]
export let placeholder: string | undefined = undefined
export let target: string | HTMLElement | undefined = undefined
export let topPlacement = false
const [floatingRef, floatingContent] = createFloatingActions({
strategy: 'absolute',
placement: topPlacement ? 'top-start' : 'bottom-start',
middleware: [offset(5), flip(), shift()]
})
export let items: any[]
export let value: string[] | undefined = [] as string[]
let outerDiv: HTMLDivElement | undefined = undefined
let portalRef: HTMLDivElement | undefined = undefined
@@ -56,6 +60,7 @@
--sms-selected-bg={darkMode ? '#c7d2fe' : '#e0e7ff'}
--sms-selected-text-color={darkMode ? '#312e81' : '#3730a3'}
bind:selected={value}
{placeholder}
options={items}
on:close={() => {
open = false
@@ -80,7 +85,7 @@
</div>
</MultiSelect>
</div>
<Portal name="multi-select">
<Portal {target} name="multi-select">
<div use:floatingContent class="z5000" hidden={!open}>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->

View File

@@ -566,6 +566,7 @@
<svelte:fragment slot="save_inputs">
<SavedInputsV2
bind:this={savedInputsV2}
schema={flow?.schema}
{jsonView}
flowPath={flow?.path}
{isValid}

View File

@@ -724,6 +724,7 @@
<svelte:fragment slot="save_inputs">
{#if args}
<SavedInputsV2
schema={script?.schema}
bind:this={savedInputsV2}
scriptPath={script?.path}
scriptHash={topHash}