feat: add native result streaming (#6242)

* feat: add stream output feature to SSE job updates

Adds stream_output field to JobUpdate struct that extracts log lines
starting with '[wm_stream]:' from job logs. Regular logs now exclude
stream lines, which are captured separately for specialized handling.

- Added stream_output: Option<String> field to JobUpdate struct
- Created extract_stream_output_from_logs() function to filter stream lines
- Modified get_job_update_data() to use stream extraction logic
- SSE clients now receive both new_logs and stream_output in job updates

Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com>

* feat: rename stream_output to stream and handle newlines as \n

- Renamed `stream_output` field to `stream` in JobUpdate struct
- Updated extract_stream_output_from_logs to extract_stream_from_logs
- Changed stream output to join with literal \n instead of actual newlines
- Stream lines are properly excluded from regular new_logs

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Ruben Fiszel <rubenfiszel@users.noreply.github.com>

* decision tree nits

* push ee ref

* push ee ref

* fix: fix id renaming in apps

* remove duplicate caching (#6285)

* feat: migrate audit log ids to bigints (blocking migration for EE)

* fix(mcp): add proper check for mcp routes (#6282)

* add proper check for mcp routes

* cleaner

* apply to flow

* fix add checks scopes

---------

Co-authored-by: dieriba <dieriba.pro@gmail.com>

* chore(main): release 1.514.0 (#6283)

* chore(main): release 1.514.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>

* fix: pin tokio to 1.46.1 and aws-sdks-ts

* pin rustls to 0.23.29 + pin aws-sdk

* chore(main): release 1.514.1 (#6288)

* chore(main): release 1.514.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>

* fix: improve docker logs collection in docker mode

* support $res: string in form inputs of arrays

* fix import nit

* fix: fix DynSelect

* nits

* fix: resource-type-ts-parser (#6289)

* fix: resource types as arg in typescript handle imported defined types

* Update nix flake (#6291)

* merge

* Small UI fixes (#6294)

* fix step history not refreshing with staticInputs

* fix array of obj not showing up in json editor in test this step

* datatable scales correctly in DisplayResult and scrolling is much more usable

* avoid next button disapearing and changing layout / hurting ux

* nits

* fix bug when renaming module A to B then module C to A, C takes the schema of A

* fix bug with comments in sql repl

* fix aggrid theme randomly not loading

* bindable script

* better delete button in db manager

* property select doesnt exist

* fix all warnings

* delete $flowStateStore[id] on delete

* feat(cli): generate cursor rules on init (#6270)

* create cursor rules on init

* change gen

* add missing resource-type command

* add resource type command in guidance

* add schema option

* revert

* nit

* nit

* add flow guidance

* nit

* chore(main): release 1.515.0 (#6292)

* chore(main): release 1.515.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>

* fix: improved logs for script

* nits logs

* chore(main): release 1.515.1 (#6295)

* chore(main): release 1.515.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>

* merge

* even more indexer tracings

* add more tracing logs

* feat: prevent too large results (>500Mb) from OOMing database

* nit naming

* feat: add CA certificate update at startup via environment variable (#6280)

* feat: add CA certificate update at startup via environment variable

Add support for running 'update-ca-certificates' at binary startup
when RUN_UPDATE_CA_CERTIFICATE_AT_START environment variable is set to "true".

- Check for RUN_UPDATE_CA_CERTIFICATE_AT_START env var on startup
- Execute update-ca-certificates command if env var is set to "true"
- Log success/failure appropriately with tracing
- Continue startup even if CA certificate update fails
- Non-blocking implementation with proper error handling

Fixes #6279

Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com>

* refactor: extract CA certificate update logic into separate function

Extract the CA certificate update logic from windmill_main() into a
dedicated update_ca_certificates_if_requested() function for better
code organization and maintainability.

Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com>

* improvements

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com>
Co-authored-by: Alexander Petric <alpetric@users.noreply.github.com>
Co-authored-by: Alexander Petric <alex@windmill.dev>

* fix: indexer collection of job logs before indexing (#6300)

* Add flume as dependecy for indexer

* Update ee-repo-ref

* Remove flags from cargo.toml

* Update ee-repo-ref

* Update ee-repo-ref

* fix rust sdk build error (#6305)

Signed-off-by: pyranota <pyra@duck.com>

* fix broken audit logs filter (#6304)

* rename to from to

* goto fix

* default to false if field not present operator settings (#6301)

* git sync UI improvements (#6303)

* ui improvements round 1

* modal cleanup

* init

* UI refactor

* UI cleanup + refactor

* legacy cleanup

* success model -> github actions, non-ee warnings

* sqlx

* npm check

* ee warning everywhere

* last comments

* formatting

* no hardcoded theme

* claude review improvemenets

* fix: no process relative imports for scripts with codebase

* fix: sqs oidc authentication disconnect #6307

* handle metadata for new scripts happen after commit

* handle_deployment_metadata in a task

* nits

* chore: add windmill-utils-internal package (#6299)

* add utils package

* naming

* cleaning

* add docs

* remove log

* use autogenerated types

* remove old

* fix

* cleaning

* add docs

* chore(main): release 1.516.0 (#6298)

* chore(main): release 1.516.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>

* merge

* indexer improvements

* upgrade tantivy to 0.24.2

* use tantivy fork

* nit warnings

* fix oss build

* improve indexer

* chore: use windmill-utils-internal for cli (#6297)

* add utils package

* naming

* cleaning

* simplify assignPath

* rename old files

* same for locks

* create on confirm

* default true

* use replaceinlinescripts from utils

* use extractscriptfromflows

* make it compile

* cleaning

* use argsigtojson

* fix

* fix missing await

* cleaner

* cleaning

* cleaning

* use in frontend

* add docs

* testing

* remove log

* use autogenerated types

* remove old

* fix

* cleaning

* adapt usage

* draft

* better build script

* fix build

* revert to default creation

* add docs

* remove and rename

* make everything work

* add await

* only if not installed

* add vs code setting

* add to publish action

* fix bc

* safer use of sep

* fix

* do not rename on push

* no publish on release

* use published package on frontend

* nit

* Add dependencies to run sqlx prepare to nix flake (#6309)

* feat(cli): wmill-lock.yaml v2 for easier git merge diffs

* merge

* merge

* all

* all

* rm warnings

* fix styling on aichatinput (#6312)

* fix: use with_capacity back presusre for tantivy directory multipart writes (#6313)

* use with capacity for tantivy directory multi part uploads

* Update ee repo ref

* Update ee-repo-ref

* Update ee-repo-ref

* chore(main): release 1.517.0 (#6310)

* chore(main): release 1.517.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>

* fix typo on cli build (#6314)

* cleanup

* feat(utils): add flow.yaml validation function (#6316)

* add validateflow function

* cleaner code

* preprocess json

* cleaning

* create specific package

* cleaning

* add tests

* fix: cleanup concurrency_counter automatically + remove orphans keys automatically

* fix: add disabled support to resource picker in schema forms

* fix: add wm_labels to tracing spans

* all

* merge

* all

* fix: delete empty git connection (#6318)

* fix checks

* bun handling

* all

* all?

* all

* all

* update

* all

* update

* check

* fix history

* all

* all

* all

* Remove leftover debug tracing statements

- Remove commented debug trace in jobs.rs for stream output
- Remove commented debug trace in result_stream.rs for stream processing

Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com>

* fix test

* all

* handle iter

* fix

---------

Signed-off-by: pyranota <pyra@duck.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: centdix <40307056+centdix@users.noreply.github.com>
Co-authored-by: dieriba <dieriba.pro@gmail.com>
Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
Co-authored-by: wendrul <53628737+wendrul@users.noreply.github.com>
Co-authored-by: Diego Imbert <70353967+diegoimbert@users.noreply.github.com>
Co-authored-by: Alexander Petric <alpetric@users.noreply.github.com>
Co-authored-by: Alexander Petric <alex@windmill.dev>
Co-authored-by: pyranota <92104930+pyranota@users.noreply.github.com>
This commit is contained in:
claude[bot]
2025-08-06 22:40:42 +00:00
committed by GitHub
co-authored by Ruben Fiszel Ruben Fiszel centdix dieriba rubenfiszel wendrul Diego Imbert Alexander Petric Alexander Petric pyranota
parent 138a2a6379
commit 161dbbc6d6
63 changed files with 979 additions and 515 deletions
@@ -39,6 +39,7 @@
import { getContext, hasContext, createEventDispatcher, onDestroy } from 'svelte'
import { toJsonStr } from '$lib/utils'
import { userStore } from '$lib/stores'
import ResultStreamDisplay from './ResultStreamDisplay.svelte'
const IMG_MAX_SIZE = 10000000
const TABLE_MAX_SIZE = 5000000
@@ -83,12 +84,14 @@
noControls?: boolean
drawerOpen?: boolean
nodeId?: string | undefined
loading?: boolean | undefined
language?: string | undefined
appPath?: string | undefined
customUi?: DisplayResultUi | undefined
isTest?: boolean
externalToolbarAvailable?: boolean
forceJson?: boolean
result_stream?: string | undefined
fixTableSizingToParent?: boolean
copilot_fix?: import('svelte').Snippet
children?: import('svelte').Snippet
@@ -111,9 +114,11 @@
isTest = true,
externalToolbarAvailable = false,
forceJson = $bindable(false),
result_stream = undefined,
fixTableSizingToParent = false,
copilot_fix,
children
children,
loading = false
}: Props = $props()
let enableHtml = $state(false)
let s3FileDisplayRawMode = $state(false)
@@ -487,7 +492,15 @@
</script>
<HighlightTheme />
{#if is_render_all}
{#if result_stream && result == undefined}
<div class="flex flex-col w-full gap-2">
<div class="flex items-center gap-2 text-tertiary">
<Loader2 class="animate-spin" size={16} /> Streaming result
</div>
<ResultStreamDisplay {result_stream} />
</div>
{:else if is_render_all}
<div class="flex flex-col w-full gap-2">
{#if !noControls}
<div class="text-tertiary text-sm">
@@ -690,7 +703,7 @@
{:else if !forceJson && resultKind === 'plain'}<div class="h-full text-2xs"
><pre class="whitespace-pre-wrap"
>{typeof result === 'string' ? result : result?.['result']}</pre
>{#if !noControls}
>{#if !noControls && !loading}
<div class="flex">
<Button
on:click={() =>
@@ -14,6 +14,7 @@
interface Props {
waitingForExecutor?: boolean
result: any
result_stream?: string
logs: string | undefined
col?: boolean
noBorder?: boolean
@@ -31,6 +32,7 @@
let {
waitingForExecutor = false,
result,
result_stream,
logs = $bindable(),
col = false,
noBorder = false,
@@ -105,8 +107,8 @@
>
<div class="bg-surface {col ? '' : 'max-h-80'} p-1 overflow-auto relative">
<span class="text-tertiary">Result</span>
{#if result !== undefined}
<DisplayResult {workspaceId} {jobId} {filename} {result} />
{#if result !== undefined || result_stream !== undefined}
<DisplayResult {workspaceId} {jobId} {filename} {result} {result_stream} />
{:else if loading}
<Loader2 class="animate-spin" />
{:else}
@@ -20,6 +20,7 @@
suspendStatus: Writable<Record<string, { job: Job; nb: number }>>
hideJobId?: boolean
extra?: import('svelte').Snippet
result_streams?: Record<string, string | undefined>
}
let {
@@ -32,7 +33,8 @@
innerModules,
suspendStatus,
hideJobId,
extra
extra,
result_streams
}: Props = $props()
</script>
@@ -105,6 +107,9 @@
<Loader2 class="animate-spin mt-0.5" /></span
></div
>
{#if mod.job && result_streams?.[mod.job]}
<pre class="text-xs text-primary">{result_streams?.[mod.job]}</pre>
{/if}
{/if}
{/each}
</div>
@@ -83,10 +83,17 @@
subflowParentsDurationStatuses?: Writable<Record<string, DurationStatus>>[]
isForloopSelected?: boolean
parentRecursiveRefresh?: Record<string, (clear, root) => Promise<void>>
job?: Job | undefined
job?: (Job & { result_stream?: string }) | undefined
rightColumnSelect?: 'timeline' | 'node_status' | 'node_definition' | 'user_states'
localModuleStates?: Writable<Record<string, GraphModuleState>>
localDurationStatuses?: Writable<Record<string, DurationStatus>>
onResultStreamUpdate?: ({
jobId,
result_stream
}: {
jobId: string
result_stream?: string
}) => void
customUi?: {
tagLabel?: string | undefined
}
@@ -119,8 +126,24 @@
rightColumnSelect = $bindable('timeline'),
localModuleStates = writable({}),
localDurationStatuses = writable({}),
customUi
customUi,
onResultStreamUpdate = undefined
}: Props = $props()
let resultStreams: Record<string, string | undefined> = $state({})
if (onResultStreamUpdate == undefined) {
onResultStreamUpdate = ({
jobId,
result_stream
}: {
jobId: string
result_stream?: string
}) => {
resultStreams[jobId] = result_stream
}
}
let recursiveRefresh: Record<string, (clear, root) => Promise<void>> = $state({})
// Add support for the input args assets shown as an asset node
@@ -516,6 +539,9 @@
jobLoader?.watchJob(jobId, {
change(newJob) {
setJob(newJob, true)
},
resultStreamUpdate({ id, result_stream }: { id: string; result_stream?: string }) {
onResultStreamUpdate?.({ jobId: id, result_stream })
}
})
}
@@ -959,6 +985,7 @@
<DisplayResult
workspaceId={job?.workspace_id}
{jobId}
result_stream={job?.result_stream}
result={jobResults}
language={job?.language}
/>
@@ -976,6 +1003,7 @@
{innerModules}
{suspendStatus}
{hideJobId}
result_streams={resultStreams}
/>
</div>
{/if}
@@ -1067,6 +1095,7 @@
storedListJobs[j] = job
innerJobLoaded(job, j, false, force)
}}
{onResultStreamUpdate}
/>
</div>
{/if}
@@ -1142,6 +1171,7 @@
{reducedPolling}
{workspaceId}
jobId={failedRetry}
{onResultStreamUpdate}
/>
</div>
{/each}
@@ -1174,6 +1204,7 @@
let { force, job } = e.detail
onJobsLoaded(mod, job, force)
}}
{onResultStreamUpdate}
/>
{:else if mod.flow_jobs?.length == 0 && mod.job == '00000000-0000-0000-0000-000000000000'}
<div class="text-secondary">no subflow (empty loop?)</div>
@@ -1205,6 +1236,7 @@
let { job, force } = e.detail
onJobsLoaded(mod, job, force)
}}
{onResultStreamUpdate}
/>
{/if}
{:else}
@@ -1428,6 +1460,7 @@
waitingForExecutor={node.type == 'WaitingForExecutor'}
refreshLog={node.type == 'InProgress'}
col
result_stream={resultStreams[node.job_id ?? '']}
result={node.result}
tag={node.tag}
logs={node.logs}
+53 -7
View File
@@ -23,11 +23,12 @@
cancel?: ({ id }: { id: string }) => void
started?: ({ id }: { id: string }) => void
running?: ({ id }: { id: string }) => void
resultStreamUpdate?: ({ id, result_stream }: { id: string; result_stream?: string }) => void
}
interface Props {
isLoading?: boolean
job?: Job | undefined
job?: (Job & { result_stream?: string }) | undefined
noCode?: boolean
noLogs?: boolean
workspaceOverride?: string | undefined
@@ -35,7 +36,6 @@
allowConcurentRequests?: boolean
jobUpdateLastFetch?: Date | undefined
toastError?: boolean
lazyLogs?: boolean
onlyResult?: boolean
// If you want to find out progress of subjobs of a flow, check job.flow_status.progress
scriptProgress?: number | undefined
@@ -52,7 +52,6 @@
notfound = $bindable(false),
jobUpdateLastFetch = $bindable(undefined),
toastError = false,
lazyLogs = false,
onlyResult = false,
scriptProgress = $bindable(undefined),
noLogs = false,
@@ -74,6 +73,7 @@
let errorIteration = 0
let logOffset = 0
let resultStreamOffset = 0
let lastCallbacks: Callbacks | undefined = undefined
let finished: string[] = []
@@ -194,6 +194,9 @@
if (logOffset == 0) {
logOffset = job?.logs?.length ? job.logs?.length + 1 : 0
}
if (resultStreamOffset == 0) {
resultStreamOffset = job?.result_stream?.length ? job.result_stream?.length + 1 : 0
}
}
export async function getLogs() {
if (job) {
@@ -285,6 +288,7 @@
let startedWatchingJob: number | undefined = undefined
export async function watchJob(testId: string, callbacks?: Callbacks) {
logOffset = 0
resultStreamOffset = 0
syncIteration = 0
errorIteration = 0
currentId = testId
@@ -334,7 +338,7 @@
function updateJobFromProgress(
previewJobUpdates: GetJobUpdatesResponse,
job: Job,
job: Job & { result_stream?: string },
callbacks: Callbacks | undefined
) {
// Clamp number between two values with the following line:
@@ -357,10 +361,26 @@
}
}
if (previewJobUpdates.new_result_stream) {
if (!job.result_stream) {
job.result_stream = previewJobUpdates.new_result_stream
} else {
job.result_stream = job.result_stream.concat(previewJobUpdates.new_result_stream)
}
callbacks?.resultStreamUpdate?.({
id: job.id,
result_stream: job.result_stream
})
}
if (previewJobUpdates.log_offset) {
logOffset = previewJobUpdates.log_offset ?? 0
}
if (previewJobUpdates.stream_offset) {
resultStreamOffset = previewJobUpdates.stream_offset ?? 0
}
if (previewJobUpdates.flow_status) {
job.flow_status = previewJobUpdates.flow_status as FlowStatus
}
@@ -415,7 +435,7 @@
job = await JobService.getJob({
workspace: workspace!,
id,
noLogs: lazyLogs || onlyResult || noLogs,
noLogs: onlyResult || noLogs,
noCode
})
}
@@ -504,6 +524,7 @@
callbacks?: Callbacks
): Promise<boolean> {
let isCompleted = false
let resultOnlyResultStream: string = ''
if (isCurrentJob(id)) {
try {
// First load the job to get initial state
@@ -511,11 +532,18 @@
job = await JobService.getJob({
workspace: workspace!,
id,
noLogs: lazyLogs || noLogs,
noLogs: noLogs,
noCode
})
}
if (!onlyResult) {
callbacks?.resultStreamUpdate?.({
id,
result_stream: undefined
})
}
// If job is already completed, don't start SSE
if (job?.type === 'CompletedJob') {
isCompleted = true
@@ -555,6 +583,12 @@
if (startedWatchingJob && startedWatchingJob > Date.now() - 5000) {
params.set('fast', 'true')
}
if (noLogs) {
params.set('no_logs', 'true')
}
if (resultStreamOffset) {
params.set('stream_offset', resultStreamOffset.toString())
}
const sseUrl = `/api/w/${workspace}/jobs_u/getupdate_sse/${id}?${params.toString()}`
@@ -598,6 +632,17 @@
callbacks?.running?.({ id })
}
if (onlyResult && previewJobUpdates.new_result_stream) {
resultOnlyResultStream = resultOnlyResultStream.concat(
previewJobUpdates.new_result_stream
)
// console.log('resultOnlyResultStream', resultOnlyResultStream)
callbacks?.resultStreamUpdate?.({
id,
result_stream: resultOnlyResultStream
})
}
// Check if job is completed
if (previewJobUpdates.completed) {
currentEventSource?.close()
@@ -611,8 +656,9 @@
})
clearCurrentId()
} else {
const njob = previewJobUpdates.job as Job
const njob = previewJobUpdates.job as Job & { result_stream?: string }
njob.logs = job?.logs ?? ''
njob.result_stream = job?.result_stream ?? ''
job = njob
onJobCompleted(id, job, callbacks)
}
@@ -0,0 +1,5 @@
<script lang="ts">
let { result_stream } = $props()
</script>
<pre class="text-xs">{result_stream}</pre>
@@ -26,6 +26,7 @@
configuration: RichConfigurations
}
let result_stream: string | undefined = $state(undefined)
let {
id,
componentInput,
@@ -57,6 +58,7 @@
})
let css = $state(initCss($app.css?.displaycomponent, customCss))
let loading = $state(false)
</script>
{#each Object.keys(components['displaycomponent'].initialData.configuration) as key (key)}
@@ -78,7 +80,15 @@
/>
{/each}
<RunnableWrapper {outputs} {render} {componentInput} {id} bind:initializing bind:result>
<RunnableWrapper
{outputs}
{render}
{componentInput}
{id}
bind:initializing
bind:result
bind:loading
>
<div class="flex flex-col w-full h-full component-wrapper">
<div
class={twMerge(
@@ -103,8 +113,10 @@
)}
>
<DisplayResult
{loading}
workspaceId={workspace}
{result}
{result_stream}
{requireHtmlApproval}
disableExpand={resolvedConfig?.hideDetails}
appPath={$userStore ? undefined : $appPath}
@@ -18,7 +18,6 @@
getContext<AppViewerContext>('AppViewerContext')
let result: any = $state(noBackend ? runnable.noBackendValue : undefined)
export function onSuccess() {
if (runnable.recomputeIds) {
runnable.recomputeIds.forEach((id) => $runnableComponents?.[id]?.cb?.map((cb) => cb()))
@@ -38,6 +38,7 @@
extraQueryParams?: Record<string, any>
autoRefresh?: boolean
result?: any
result_stream?: string
forceSchemaDisplay?: boolean
wrapperClass?: string
wrapperStyle?: string
@@ -72,6 +73,7 @@
extraQueryParams = {},
autoRefresh = true,
result = $bindable(undefined),
result_stream = $bindable(undefined),
forceSchemaDisplay = false,
wrapperClass = '',
wrapperStyle = '',
@@ -226,6 +228,15 @@
loading = false
dispatch('done', { id, result })
},
resultStreamUpdate({
id,
result_stream: nresult_stream
}: {
id: string
result_stream?: string
}) {
setResult(nresult_stream, id)
},
cancel({ id }: { id: string }) {
onCancel?.()
let jobId = id
@@ -42,11 +42,12 @@
<div class="break-words relative h-full px-1">
<DisplayResult bind:drawerOpen={resultDrawerOpen} result={frontendJob} />
</div>
{:else if testJob != undefined && 'result' in testJob && testJob.result != undefined}
{:else if testJob != undefined && (testJob.type == 'CompletedJob' || testJob.result_stream)}
<div class="break-words relative h-full px-1">
<DisplayResult
bind:drawerOpen={resultDrawerOpen}
workspaceId={testJob?.workspace_id}
result_stream={testJob?.result_stream}
jobId={testJob?.id}
result={testJob.result}
language={testJob?.language}
@@ -15,6 +15,7 @@
children?: import('svelte').Snippet<[any]>
content?: import('svelte').Snippet
onSelectedChange?: (value: string) => void
onTabClick?: (value: string) => void
}
let {
@@ -27,7 +28,8 @@
values = undefined,
children,
content,
onSelectedChange
onSelectedChange,
onTabClick
}: Props = $props()
const selectedStore = writable(selected)
@@ -37,6 +39,7 @@
update: (value: string) => {
selectedStore.set(value)
selected = value
onTabClick?.(value)
},
hashNavigation
})
@@ -55,9 +58,11 @@
}
}
}
$effect(() => {
selected && untrack(() => updateSelected())
})
$effect(() => {
$selectedStore && untrack(() => onSelectedChange?.($selectedStore))
})
@@ -111,7 +111,6 @@
<Button
{...btn.buttonProps}
startIcon={{ icon: btn.buttonProps.startIcon }}
on:click={btn.buttonProps.onClick}
btnClasses="hidden md:flex items-center gap-1"
>
{btn.label}
@@ -119,7 +118,6 @@
<Button
{...btn.buttonProps}
startIcon={{ icon: btn.buttonProps.startIcon }}
on:click={btn.buttonProps.onClick}
iconOnly
btnClasses="flex md:hidden items-center gap-1"
>
+2 -1
View File
@@ -13,7 +13,8 @@ export const forbiddenIds: string[] = [
'new',
'in',
'failure',
'preprocessor'
'preprocessor',
'as'
]
export function numberToChars(n: number) {
@@ -20,7 +20,11 @@
<div
class={twMerge(
'rounded-full w-2 h-2',
'success' in job && job.success ? 'bg-green-400' : 'bg-red-400'
'success' in job && job.success
? 'bg-green-400'
: job.type === 'CompletedJob'
? 'bg-red-400'
: 'bg-yellow-400'
)}
title={'success' in job && job.success ? 'Success' : 'Failed'}
></div>
@@ -57,7 +57,7 @@
disableMock?: boolean
disableHistory?: boolean
lastJob?: Job
testJob?: Job
testJob?: Job & { result_stream?: string }
derivedHistoryOpen?: boolean // derived from historyOpen
historyOffset?: any
clazz?: string
@@ -116,7 +116,7 @@
workspace_id: string
success: boolean
}
) & { preview?: boolean })
) & { preview?: boolean; result_stream?: string; result?: unknown })
| undefined
let jsonView = $state(false)
@@ -135,9 +135,9 @@
}
function selectJob(nJob: SelectedJob | undefined) {
if (nJob && 'result' in nJob) {
if (nJob && (nJob.result_stream || nJob.type == 'CompletedJob')) {
selectedJob = nJob
} else if (job && 'result' in job) {
} else if (job && (job.result_stream || job.type == 'CompletedJob')) {
selectedJob = job
} else {
selectedJob = undefined
@@ -145,7 +145,7 @@
}
$effect(() => {
if (!job || !('result' in job)) {
if (!job || !(job.result_stream || job.type == 'CompletedJob')) {
return
}
selectJob(job)
@@ -266,11 +266,13 @@
return { ...lastJob, preview: false }
}
return undefined
})
}) as SelectedJob | undefined
let popoverHeight = $derived(customHeight ?? (clientHeight > 0 ? clientHeight : 0))
const isLoadingAndNotMock = $derived(isLoading && !mock?.enabled)
const isLoadingAndNotMock = $derived(
isLoading && job?.result_stream === undefined && !mock?.enabled
)
const copilot_fix_render = $derived(copilot_fix)
</script>
@@ -650,6 +652,7 @@
bind:forceJson
workspaceId={undefined}
jobId={undefined}
result_stream={undefined}
result={mock?.return_value}
externalToolbarAvailable
fixTableSizingToParent
@@ -669,7 +672,7 @@
pureViewer={false}
/>
{/if}
{:else if selectedJob != undefined && 'result' in selectedJob}
{:else if selectedJob != undefined && (selectedJob.result_stream || selectedJob.type == 'CompletedJob')}
{#if fullResult}
<div class="break-words relative h-full">
{#key selectedJob}
@@ -679,6 +682,7 @@
workspaceId={selectedJob?.workspace_id}
jobId={selectedJob?.id}
result={selectedJob?.result}
result_stream={selectedJob?.result_stream}
externalToolbarAvailable
fixTableSizingToParent
on:toolbar-location-changed={({ detail }) => {
@@ -28,7 +28,7 @@
let { id, blankLink = false, workspace }: Props = $props()
let job: Job | undefined = $state(undefined)
let job: (Job & { result_stream?: string }) | undefined = $state(undefined)
let result: any = $state()
@@ -82,7 +82,7 @@
let jobLoader: JobLoader | undefined = $state(undefined)
</script>
<JobLoader lazyLogs workspaceOverride={workspace} bind:job={currentJob} bind:this={jobLoader} />
<JobLoader noLogs workspaceOverride={workspace} bind:job={currentJob} bind:this={jobLoader} />
<div class="p-4 flex flex-col gap-2 items-start h-full">
{#if job}
@@ -239,7 +239,7 @@
{:else}
<Skeleton layout={[[5]]} />
{/if}
{:else if job !== undefined && 'result' in job && job.result !== undefined}
{:else if job !== undefined && (job.result_stream || (job.type == 'CompletedJob' && job.result !== undefined))}
<DisplayResult
workspaceId={job?.workspace_id}
jobId={job?.id}
@@ -36,7 +36,7 @@
interface Props {
lang: Preview['language'] | undefined
previewIsLoading?: boolean
previewJob: Job | undefined
previewJob: (Job & { result_stream?: string; result?: any; success?: boolean }) | undefined
pastPreviews?: CompletedJob[]
editor?: Editor | undefined
diffEditor?: DiffEditor | undefined
@@ -102,6 +102,7 @@
result={drawerContent.content}
customUi={customUi?.displayResult}
language={lang}
result_stream={previewJob?.result_stream}
/>
{:else if drawerContent?.mode === 'plain'}
<pre
@@ -113,7 +114,6 @@
{/if}
</DrawerContent>
</Drawer>
<div class="h-full flex flex-col">
<Tabs bind:selected={selectedTab} class="pt-1" wrapperClass="flex-none">
<Tab value="logs" size="xs">Logs & Result</Tab>
@@ -151,7 +151,7 @@
</Pane>
<Pane>
{@render children?.()}
{#if previewJob != undefined && 'result' in previewJob}
{#if previewJob != undefined && (previewJob.result_stream || previewJob.result)}
<div class="relative w-full h-full p-2">
<div class="relative h-full">
<DisplayResult
@@ -161,6 +161,7 @@
result={previewJob.result}
customUi={customUi?.displayResult}
language={lang}
result_stream={previewJob?.result_stream}
fixTableSizingToParent
>
{#snippet copilot_fix()}
@@ -68,7 +68,7 @@
import JobArgs from '$lib/components/JobArgs.svelte'
import FlowProgressBar from '$lib/components/flows/FlowProgressBar.svelte'
import JobProgressBar from '$lib/components/jobs/JobProgressBar.svelte'
import Tabs from '$lib/components/common/tabs/Tabs.svelte'
import Tabs from '$lib/components/common/tabs/TabsV2.svelte'
import Badge from '$lib/components/common/badge/Badge.svelte'
import { goto } from '$lib/navigation'
import { sendUserToast } from '$lib/toast'
@@ -97,7 +97,7 @@
} from '$lib/components/flows/FlowAssetsHandler.svelte'
import JobAssetsViewer from '$lib/components/assets/JobAssetsViewer.svelte'
let job: Job | undefined = $state()
let job: (Job & { result?: any; result_stream?: string }) | undefined = $state()
let jobUpdateLastFetch: Date | undefined = $state()
let scriptProgress: number | undefined = $state(undefined)
@@ -123,6 +123,8 @@
let lastJobId: string | undefined = $state(undefined)
let concurrencyKey: string | undefined = $state(undefined)
let manuallySetLogs: boolean = $state(false)
setContext(
'FlowGraphAssetContext',
initFlowGraphAssetsCtx({ getModules: () => job?.raw_flow?.modules ?? [] })
@@ -173,7 +175,7 @@
// If we get results, focus on that tab. Else, focus on logs
function initView(): void {
if (job && 'result' in job && job.result != undefined) {
if (job && (job.result || job.result_stream)) {
viewTab = 'result'
} else if (viewTab == 'result') {
viewTab = 'logs'
@@ -182,6 +184,11 @@
async function getJob() {
await jobLoader?.watchJob($page.params.run, {
change(job: Job & { result_stream?: string }) {
if (!manuallySetLogs && viewTab == 'logs' && job.result_stream) {
viewTab = 'result'
}
},
done(job) {
if (job?.['result'] != undefined) {
viewTab = 'result'
@@ -366,13 +373,7 @@
runImmediatelyLoading = false
}
}
$effect(() => {
job?.logs == undefined &&
job &&
viewTab == 'logs' &&
isNotFlow(job?.job_kind) &&
jobLoader?.getLogs()
})
$effect(() => {
job?.id && lastJobId !== job.id && untrack(() => getConcurrencyKey(job))
})
@@ -427,7 +428,6 @@
{/if}
{#if !job || (job?.job_kind != 'flow' && job?.job_kind != 'flownode' && job?.job_kind != 'flowpreview')}
<JobLoader
lazyLogs
bind:scriptProgress
bind:this={jobLoader}
bind:isLoading={testIsLoading}
@@ -933,7 +933,12 @@
{/if}
<!-- Logs and outputs-->
<div class="mr-2 sm:mr-0 mt-12">
<Tabs bind:selected={viewTab}>
<Tabs
bind:selected={viewTab}
onTabClick={(value) => {
manuallySetLogs = value == 'logs'
}}
>
<Tab value="result">Result</Tab>
<Tab value="logs">Logs</Tab>
<Tab value="stats">Metrics</Tab>
@@ -975,9 +980,10 @@
<div class="w-full">
<MemoryFootprintViewer jobId={job.id} bind:jobUpdateLastFetch />
</div>
{:else if job !== undefined && 'result' in job && job.result !== undefined}
{:else if job !== undefined && (job.result || job.result_stream)}
<DisplayResult
workspaceId={job?.workspace_id}
result_stream={job.result_stream}
jobId={job?.id}
result={job.result}
language={job?.language}