Skip to content

Commit

Permalink
feat: flow history picker for flow status + load last flow state
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenfiszel committed Feb 1, 2025
1 parent 6d9edc8 commit 611d5e8
Show file tree
Hide file tree
Showing 13 changed files with 280 additions and 130 deletions.
1 change: 1 addition & 0 deletions frontend/src/lib/components/FlowBuilder.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -1446,6 +1446,7 @@
showCaptureHint.set(true)
}}
bind:this={flowPreviewButtons}
{loading}
/>
<Button
loading={loadingDraft}
Expand Down
49 changes: 49 additions & 0 deletions frontend/src/lib/components/FlowHistoryJobPicker.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<script lang="ts">
import { HistoryIcon } from 'lucide-svelte'
import { createEventDispatcher } from 'svelte'
import PopoverV2 from '$lib/components/meltComponents/Popover.svelte'
import HistoricInputs from './HistoricInputs.svelte'
import { workspaceStore } from '$lib/stores'
import { JobService } from '$lib/gen'
export let path: string
export let selected: string | undefined = undefined
const dispatch = createEventDispatcher()
async function loadInitial() {
let jobs = await JobService.listJobs({
workspace: $workspaceStore!,
scriptPathExact: path,
jobKinds: ['flow', 'flowpreview'].join(','),
page: 1,
perPage: 1
})
if (jobs.length > 0) {
dispatch('select', { jobId: jobs[0].id, initial: true })
}
}
$: $workspaceStore && loadInitial()
</script>

<PopoverV2 closeButton={false}>
<svelte:fragment slot="trigger">
<HistoryIcon size={14} />
</svelte:fragment>
<svelte:fragment slot="content">
<div class="p-2 h-[400px] overflow-hidden w-80 border shadow-sm">
<HistoricInputs
on:select={(e) => {
if (e.detail) {
dispatch('select', { jobId: e.detail?.jobId, initial: false })
} else {
dispatch('unselect')
}
}}
{selected}
runnableId={path}
runnableType={'FlowPath'}
/>
</div>
</svelte:fragment>
</PopoverV2>
50 changes: 46 additions & 4 deletions frontend/src/lib/components/FlowPreviewContent.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import InputSelectedBadge from './schema/InputSelectedBadge.svelte'
import Toggle from './Toggle.svelte'
import JsonInputs from './JsonInputs.svelte'
import FlowHistoryJobPicker from './FlowHistoryJobPicker.svelte'
export let previewMode: 'upTo' | 'whole'
export let open: boolean
Expand Down Expand Up @@ -54,6 +55,11 @@
} = getContext<FlowEditorContext>('FlowEditorContext')
const dispatch = createEventDispatcher()
let renderCount: number = 0
let initial: boolean = false
let schemaFormWithArgPicker: SchemaFormWithArgPicker | undefined = undefined
let currentJobId: string | undefined = undefined
function extractFlow(previewMode: 'upTo' | 'whole'): OpenFlow {
if (previewMode === 'whole') {
return $flowStore
Expand All @@ -74,6 +80,9 @@
args: Record<string, any>,
restartedFrom: RestartedFrom | undefined
) {
if (initial) {
initial = false
}
try {
lastPreviewFlow = JSON.stringify($flowStore)
jobProgressReset()
Expand All @@ -89,6 +98,7 @@
isRunning = false
jobId = undefined
}
schemaFormWithArgPicker?.refreshHistory()
}
function onKeyDown(event: KeyboardEvent) {
Expand Down Expand Up @@ -159,9 +169,6 @@
}
$: selectedJobStep !== undefined && onSelectedJobStepChange()
let renderCount: number = 0
let schemaFormWithArgPicker: SchemaFormWithArgPicker | undefined = undefined
</script>

<svelte:window on:keydown={onKeyDown} />
Expand Down Expand Up @@ -389,8 +396,43 @@
{/if}
</SchemaFormWithArgPicker>
</div>
<div class="pt-4 flex flex-col grow">
<div class="pt-4 flex flex-col grow relative">
<div
class="absolute top-[22px] right-2 border p-1.5 hover:bg-surface-hover rounded-md center-center"
>
<FlowHistoryJobPicker
on:select={(e) => {
if (!currentJobId) {
currentJobId = jobId
}
const detail = e.detail
initial = detail.initial
jobId = detail.jobId
}}
on:unselect={() => {
jobId = currentJobId
currentJobId = undefined
}}
path={initialPath == '' ? $pathStore : initialPath}
/>
</div>
{#if jobId}
{#if initial}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
on:click={() => {
initial = false
}}
class="cursor-pointer h-full hover:bg-gray-500/20 dark:hover:bg-gray-500/20 dark:bg-gray-500/80 rounded bg-gray-500/40 absolute top-0 left-0 w-full z-50"
>
<div class="text-center text-primary text-lg py-2 pt-20"
><span class="font-bold border p-2 bg-surface-secondary rounded-md"
>Previous run of this flow from history</span
></div
>
</div>
{/if}
<FlowStatusViewer
hideDownloadInGraph={customUi?.downloadLogs === false}
wideResults
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lib/components/FlowStatusViewerInner.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,8 @@
<Tab value="graph"><span class="font-semibold text-md">Graph</span></Tab>
<Tab value="sequence"><span class="font-semibold">Details</span></Tab>
</Tabs>
{:else}
<div class="h-[30px]" />
{/if}
{/if}
<div class="{selected != 'sequence' ? 'hidden' : ''} max-w-7xl mx-auto">
Expand Down
123 changes: 25 additions & 98 deletions frontend/src/lib/components/HistoricInputs.svelte
Original file line number Diff line number Diff line change
@@ -1,65 +1,23 @@
<script lang="ts">
import { InputService, type RunnableType, type Job } from '$lib/gen/index.js'
import { workspaceStore } from '$lib/stores.js'
import { type RunnableType, type Job } from '$lib/gen/index.js'
import { sendUserToast } from '$lib/utils.js'
import JobSchemaPicker from '$lib/components/schema/JobSchemaPicker.svelte'
import RunningJobSchemaPicker from '$lib/components/schema/RunningJobSchemaPicker.svelte'
import { createEventDispatcher, onDestroy } from 'svelte'
import JobLoader from './runs/JobLoader.svelte'
import { DataTable } from '$lib/components/table'
import InfiniteList from './InfiniteList.svelte'
import HistoricList from './HistoricList.svelte'
export let runnableId: string | undefined = undefined
export let runnableType: RunnableType | undefined = undefined
export let loading: boolean = false
export let selected: string | undefined = undefined
let historicList: HistoricList | undefined = undefined
const dispatch = createEventDispatcher()
let jobs: Job[] = []
let hasMoreCurrentRuns = false
let page = 1
let infiniteList: InfiniteList | undefined = undefined
let loadInputsPageFn: ((page: number, perPage: number) => Promise<any>) | undefined = undefined
let cachedArgs: Record<string, any> = {}
function initLoadInputs() {
loadInputsPageFn = async (page: number, perPage: number) => {
const inputs = await InputService.getInputHistory({
workspace: $workspaceStore!,
runnableId,
runnableType,
page,
perPage,
includePreview: true
})
const inputsWithPayload = await Promise.all(
inputs.map(async (input) => {
if (cachedArgs[input.id]) {
return {
...input,
payloadData: cachedArgs[input.id]
}
}
const payloadData = await loadArgsFromHistory(input.id, undefined, false)
if (payloadData === 'WINDMILL_TOO_BIG') {
return {
...input,
payloadData: 'WINDMILL_TOO_BIG',
getFullPayload: () => loadArgsFromHistory(input.id, undefined, true)
}
}
cachedArgs[input.id] = payloadData
return {
...input,
payloadData
}
})
)
return inputsWithPayload
}
infiniteList?.setLoader(loadInputsPageFn)
}
async function handleSelected(data: any) {
if (selected === data.id) {
Expand All @@ -69,33 +27,16 @@
selected = data.id
if (data.payloadData === 'WINDMILL_TOO_BIG') {
const fullPayload = await data.getFullPayload?.()
dispatch('select', fullPayload)
dispatch('select', { args: fullPayload, jobId: data.id })
} else {
dispatch('select', structuredClone(data.payloadData))
dispatch('select', { args: structuredClone(data.payloadData), jobId: data.id })
}
}
let selected: string | undefined = undefined
onDestroy(() => {
resetSelected(true)
})
async function loadArgsFromHistory(
id: string | undefined,
input: boolean | undefined,
allowLarge: boolean
): Promise<any> {
if (!id) return
const payloadData = await InputService.getArgsFromHistoryOrSavedInput({
jobOrInputId: id,
workspace: $workspaceStore!,
input,
allowLarge
})
return payloadData
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Escape' && selected) {
resetSelected(true)
Expand All @@ -113,21 +54,26 @@
let jobHovered: string | undefined = undefined
export function refresh() {
if (infiniteList) {
infiniteList.loadData('refresh')
}
historicList?.refresh()
}
export function resetSelected(dispatchEvent?: boolean) {
console.log('resetSelected')
selected = undefined
if (dispatchEvent) {
dispatch('select', undefined)
}
}
$: !loading && refresh()
$: $workspaceStore && runnableId && runnableType && infiniteList && initLoadInputs()
function getJobKinds(runnableType: RunnableType | undefined) {
if (runnableType === 'FlowPath') {
return 'flow,flowpreview'
} else if (runnableType === 'ScriptPath') {
return 'script,preview'
} else if (runnableType === 'ScriptHash') {
return 'script,preview'
}
return 'all'
}
</script>

<svelte:window on:keydown={handleKeydown} />
Expand All @@ -137,7 +83,7 @@
bind:jobs
path={runnableId}
isSkipped={false}
jobKindsCat="all"
jobKinds={getJobKinds(runnableType)}
user={null}
label={null}
folder={null}
Expand All @@ -153,7 +99,7 @@
/>
{/if}

<div class="h-full w-full flex flex-col gap-4">
<div class="h-full max-h-full min-h-0 w-full flex flex-col gap-4">
<div class="grow-0" data-schema-picker>
<DataTable size="xs" bind:currentPage={page} hasMore={hasMoreCurrentRuns} tableFixed={true}>
{#if loading && (jobs == undefined || jobs?.length == 0)}
Expand Down Expand Up @@ -187,32 +133,13 @@
</div>

<div class="min-h-0 grow" data-schema-picker>
<InfiniteList
bind:this={infiniteList}
selectedItemId={selected}
<HistoricList
bind:this={historicList}
on:error={(e) => handleError(e.detail)}
on:select={(e) => handleSelected(e.detail)}
>
<svelte:fragment slot="columns">
<colgroup>
<col class="w-8" />
<col class="w-16" />
<col />
</colgroup>
</svelte:fragment>
<svelte:fragment let:item let:hover>
<JobSchemaPicker
job={item}
selected={selected === item.id}
hovering={hover}
payloadData={item.payloadData}
/>
</svelte:fragment>
<svelte:fragment slot="empty">
<div class="text-center text-tertiary text-xs py-2">
{runnableId ? 'No previous inputs' : 'Save draft to see previous runs'}
</div>
</svelte:fragment>
</InfiniteList>
{runnableId}
{runnableType}
{selected}
/>
</div>
</div>
Loading

0 comments on commit 611d5e8

Please sign in to comment.