Skip to content

Commit 611d5e8

Browse files
committed
feat: flow history picker for flow status + load last flow state
1 parent 6d9edc8 commit 611d5e8

13 files changed

+280
-130
lines changed

frontend/src/lib/components/FlowBuilder.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,6 +1446,7 @@
14461446
showCaptureHint.set(true)
14471447
}}
14481448
bind:this={flowPreviewButtons}
1449+
{loading}
14491450
/>
14501451
<Button
14511452
loading={loadingDraft}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<script lang="ts">
2+
import { HistoryIcon } from 'lucide-svelte'
3+
import { createEventDispatcher } from 'svelte'
4+
import PopoverV2 from '$lib/components/meltComponents/Popover.svelte'
5+
import HistoricInputs from './HistoricInputs.svelte'
6+
import { workspaceStore } from '$lib/stores'
7+
import { JobService } from '$lib/gen'
8+
9+
export let path: string
10+
export let selected: string | undefined = undefined
11+
const dispatch = createEventDispatcher()
12+
13+
async function loadInitial() {
14+
let jobs = await JobService.listJobs({
15+
workspace: $workspaceStore!,
16+
scriptPathExact: path,
17+
jobKinds: ['flow', 'flowpreview'].join(','),
18+
page: 1,
19+
perPage: 1
20+
})
21+
if (jobs.length > 0) {
22+
dispatch('select', { jobId: jobs[0].id, initial: true })
23+
}
24+
}
25+
26+
$: $workspaceStore && loadInitial()
27+
</script>
28+
29+
<PopoverV2 closeButton={false}>
30+
<svelte:fragment slot="trigger">
31+
<HistoryIcon size={14} />
32+
</svelte:fragment>
33+
<svelte:fragment slot="content">
34+
<div class="p-2 h-[400px] overflow-hidden w-80 border shadow-sm">
35+
<HistoricInputs
36+
on:select={(e) => {
37+
if (e.detail) {
38+
dispatch('select', { jobId: e.detail?.jobId, initial: false })
39+
} else {
40+
dispatch('unselect')
41+
}
42+
}}
43+
{selected}
44+
runnableId={path}
45+
runnableType={'FlowPath'}
46+
/>
47+
</div>
48+
</svelte:fragment>
49+
</PopoverV2>

frontend/src/lib/components/FlowPreviewContent.svelte

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import InputSelectedBadge from './schema/InputSelectedBadge.svelte'
1717
import Toggle from './Toggle.svelte'
1818
import JsonInputs from './JsonInputs.svelte'
19+
import FlowHistoryJobPicker from './FlowHistoryJobPicker.svelte'
1920
2021
export let previewMode: 'upTo' | 'whole'
2122
export let open: boolean
@@ -54,6 +55,11 @@
5455
} = getContext<FlowEditorContext>('FlowEditorContext')
5556
const dispatch = createEventDispatcher()
5657
58+
let renderCount: number = 0
59+
let initial: boolean = false
60+
let schemaFormWithArgPicker: SchemaFormWithArgPicker | undefined = undefined
61+
let currentJobId: string | undefined = undefined
62+
5763
function extractFlow(previewMode: 'upTo' | 'whole'): OpenFlow {
5864
if (previewMode === 'whole') {
5965
return $flowStore
@@ -74,6 +80,9 @@
7480
args: Record<string, any>,
7581
restartedFrom: RestartedFrom | undefined
7682
) {
83+
if (initial) {
84+
initial = false
85+
}
7786
try {
7887
lastPreviewFlow = JSON.stringify($flowStore)
7988
jobProgressReset()
@@ -89,6 +98,7 @@
8998
isRunning = false
9099
jobId = undefined
91100
}
101+
schemaFormWithArgPicker?.refreshHistory()
92102
}
93103
94104
function onKeyDown(event: KeyboardEvent) {
@@ -159,9 +169,6 @@
159169
}
160170
161171
$: selectedJobStep !== undefined && onSelectedJobStepChange()
162-
163-
let renderCount: number = 0
164-
let schemaFormWithArgPicker: SchemaFormWithArgPicker | undefined = undefined
165172
</script>
166173

167174
<svelte:window on:keydown={onKeyDown} />
@@ -389,8 +396,43 @@
389396
{/if}
390397
</SchemaFormWithArgPicker>
391398
</div>
392-
<div class="pt-4 flex flex-col grow">
399+
<div class="pt-4 flex flex-col grow relative">
400+
<div
401+
class="absolute top-[22px] right-2 border p-1.5 hover:bg-surface-hover rounded-md center-center"
402+
>
403+
<FlowHistoryJobPicker
404+
on:select={(e) => {
405+
if (!currentJobId) {
406+
currentJobId = jobId
407+
}
408+
const detail = e.detail
409+
initial = detail.initial
410+
jobId = detail.jobId
411+
}}
412+
on:unselect={() => {
413+
jobId = currentJobId
414+
currentJobId = undefined
415+
}}
416+
path={initialPath == '' ? $pathStore : initialPath}
417+
/>
418+
</div>
393419
{#if jobId}
420+
{#if initial}
421+
<!-- svelte-ignore a11y-click-events-have-key-events -->
422+
<!-- svelte-ignore a11y-no-static-element-interactions -->
423+
<div
424+
on:click={() => {
425+
initial = false
426+
}}
427+
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"
428+
>
429+
<div class="text-center text-primary text-lg py-2 pt-20"
430+
><span class="font-bold border p-2 bg-surface-secondary rounded-md"
431+
>Previous run of this flow from history</span
432+
></div
433+
>
434+
</div>
435+
{/if}
394436
<FlowStatusViewer
395437
hideDownloadInGraph={customUi?.downloadLogs === false}
396438
wideResults

frontend/src/lib/components/FlowStatusViewerInner.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,8 @@
933933
<Tab value="graph"><span class="font-semibold text-md">Graph</span></Tab>
934934
<Tab value="sequence"><span class="font-semibold">Details</span></Tab>
935935
</Tabs>
936+
{:else}
937+
<div class="h-[30px]" />
936938
{/if}
937939
{/if}
938940
<div class="{selected != 'sequence' ? 'hidden' : ''} max-w-7xl mx-auto">
Lines changed: 25 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,23 @@
11
<script lang="ts">
2-
import { InputService, type RunnableType, type Job } from '$lib/gen/index.js'
3-
import { workspaceStore } from '$lib/stores.js'
2+
import { type RunnableType, type Job } from '$lib/gen/index.js'
43
import { sendUserToast } from '$lib/utils.js'
5-
import JobSchemaPicker from '$lib/components/schema/JobSchemaPicker.svelte'
64
import RunningJobSchemaPicker from '$lib/components/schema/RunningJobSchemaPicker.svelte'
75
import { createEventDispatcher, onDestroy } from 'svelte'
86
import JobLoader from './runs/JobLoader.svelte'
97
import { DataTable } from '$lib/components/table'
10-
import InfiniteList from './InfiniteList.svelte'
8+
import HistoricList from './HistoricList.svelte'
119
1210
export let runnableId: string | undefined = undefined
1311
export let runnableType: RunnableType | undefined = undefined
1412
export let loading: boolean = false
13+
export let selected: string | undefined = undefined
1514
15+
let historicList: HistoricList | undefined = undefined
1616
const dispatch = createEventDispatcher()
1717
1818
let jobs: Job[] = []
1919
let hasMoreCurrentRuns = false
2020
let page = 1
21-
let infiniteList: InfiniteList | undefined = undefined
22-
let loadInputsPageFn: ((page: number, perPage: number) => Promise<any>) | undefined = undefined
23-
24-
let cachedArgs: Record<string, any> = {}
25-
function initLoadInputs() {
26-
loadInputsPageFn = async (page: number, perPage: number) => {
27-
const inputs = await InputService.getInputHistory({
28-
workspace: $workspaceStore!,
29-
runnableId,
30-
runnableType,
31-
page,
32-
perPage,
33-
includePreview: true
34-
})
35-
36-
const inputsWithPayload = await Promise.all(
37-
inputs.map(async (input) => {
38-
if (cachedArgs[input.id]) {
39-
return {
40-
...input,
41-
payloadData: cachedArgs[input.id]
42-
}
43-
}
44-
const payloadData = await loadArgsFromHistory(input.id, undefined, false)
45-
if (payloadData === 'WINDMILL_TOO_BIG') {
46-
return {
47-
...input,
48-
payloadData: 'WINDMILL_TOO_BIG',
49-
getFullPayload: () => loadArgsFromHistory(input.id, undefined, true)
50-
}
51-
}
52-
cachedArgs[input.id] = payloadData
53-
return {
54-
...input,
55-
payloadData
56-
}
57-
})
58-
)
59-
return inputsWithPayload
60-
}
61-
infiniteList?.setLoader(loadInputsPageFn)
62-
}
6321
6422
async function handleSelected(data: any) {
6523
if (selected === data.id) {
@@ -69,33 +27,16 @@
6927
selected = data.id
7028
if (data.payloadData === 'WINDMILL_TOO_BIG') {
7129
const fullPayload = await data.getFullPayload?.()
72-
dispatch('select', fullPayload)
30+
dispatch('select', { args: fullPayload, jobId: data.id })
7331
} else {
74-
dispatch('select', structuredClone(data.payloadData))
32+
dispatch('select', { args: structuredClone(data.payloadData), jobId: data.id })
7533
}
7634
}
7735
78-
let selected: string | undefined = undefined
79-
8036
onDestroy(() => {
8137
resetSelected(true)
8238
})
8339
84-
async function loadArgsFromHistory(
85-
id: string | undefined,
86-
input: boolean | undefined,
87-
allowLarge: boolean
88-
): Promise<any> {
89-
if (!id) return
90-
const payloadData = await InputService.getArgsFromHistoryOrSavedInput({
91-
jobOrInputId: id,
92-
workspace: $workspaceStore!,
93-
input,
94-
allowLarge
95-
})
96-
return payloadData
97-
}
98-
9940
function handleKeydown(event: KeyboardEvent) {
10041
if (event.key === 'Escape' && selected) {
10142
resetSelected(true)
@@ -113,21 +54,26 @@
11354
let jobHovered: string | undefined = undefined
11455
11556
export function refresh() {
116-
if (infiniteList) {
117-
infiniteList.loadData('refresh')
118-
}
57+
historicList?.refresh()
11958
}
12059
12160
export function resetSelected(dispatchEvent?: boolean) {
122-
console.log('resetSelected')
12361
selected = undefined
12462
if (dispatchEvent) {
12563
dispatch('select', undefined)
12664
}
12765
}
12866
129-
$: !loading && refresh()
130-
$: $workspaceStore && runnableId && runnableType && infiniteList && initLoadInputs()
67+
function getJobKinds(runnableType: RunnableType | undefined) {
68+
if (runnableType === 'FlowPath') {
69+
return 'flow,flowpreview'
70+
} else if (runnableType === 'ScriptPath') {
71+
return 'script,preview'
72+
} else if (runnableType === 'ScriptHash') {
73+
return 'script,preview'
74+
}
75+
return 'all'
76+
}
13177
</script>
13278

13379
<svelte:window on:keydown={handleKeydown} />
@@ -137,7 +83,7 @@
13783
bind:jobs
13884
path={runnableId}
13985
isSkipped={false}
140-
jobKindsCat="all"
86+
jobKinds={getJobKinds(runnableType)}
14187
user={null}
14288
label={null}
14389
folder={null}
@@ -153,7 +99,7 @@
15399
/>
154100
{/if}
155101

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

189135
<div class="min-h-0 grow" data-schema-picker>
190-
<InfiniteList
191-
bind:this={infiniteList}
192-
selectedItemId={selected}
136+
<HistoricList
137+
bind:this={historicList}
193138
on:error={(e) => handleError(e.detail)}
194139
on:select={(e) => handleSelected(e.detail)}
195-
>
196-
<svelte:fragment slot="columns">
197-
<colgroup>
198-
<col class="w-8" />
199-
<col class="w-16" />
200-
<col />
201-
</colgroup>
202-
</svelte:fragment>
203-
<svelte:fragment let:item let:hover>
204-
<JobSchemaPicker
205-
job={item}
206-
selected={selected === item.id}
207-
hovering={hover}
208-
payloadData={item.payloadData}
209-
/>
210-
</svelte:fragment>
211-
<svelte:fragment slot="empty">
212-
<div class="text-center text-tertiary text-xs py-2">
213-
{runnableId ? 'No previous inputs' : 'Save draft to see previous runs'}
214-
</div>
215-
</svelte:fragment>
216-
</InfiniteList>
140+
{runnableId}
141+
{runnableType}
142+
{selected}
143+
/>
217144
</div>
218145
</div>

0 commit comments

Comments
 (0)