mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-23 08:00:45 +00:00
fix(frontend): Display transformer errors (#1971)
* fix(frontend): Display transformer errors * feat(frontend): Display both script and transformer results * feat(frontend): Merge main + remove debug code * feat(frontend): refactor runnableComponent * feat(frontend): fix empty object * feat(frontend): improve error popover
This commit is contained in:
@@ -217,7 +217,9 @@
|
||||
href="data:application/octet-stream;base64,{result.file}">Download</a
|
||||
>
|
||||
</div>
|
||||
{:else if !forceJson && resultKind == 'error'}<div class="flex flex-col items-start">
|
||||
{:else if !forceJson && resultKind == 'error' && result?.error}<div
|
||||
class="flex flex-col items-start"
|
||||
>
|
||||
<span class="text-red-500 font-semibold text-sm whitespace-pre-wrap"
|
||||
>{#if result.error.name || result.error.message}{result.error.name}: {result.error
|
||||
.message}{:else}{JSON.stringify(result.error, null, 4)}{/if}</span
|
||||
|
||||
@@ -151,7 +151,14 @@
|
||||
if (currentId === id) {
|
||||
job = { ...maybe_job, id }
|
||||
await tick()
|
||||
dispatch('done', job)
|
||||
if ('error' in job ?? {}) {
|
||||
dispatch('doneError', {
|
||||
id,
|
||||
error: job.result.error
|
||||
})
|
||||
} else {
|
||||
dispatch('done', job)
|
||||
}
|
||||
currentId = undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,26 +186,14 @@
|
||||
$worldStore,
|
||||
$runnableComponents
|
||||
)
|
||||
await setResult(r, undefined)
|
||||
|
||||
$state = $state
|
||||
const job = generateNextFrontendJobId()
|
||||
const njobs = [{ job, component: id, result: r }, ...$jobs]
|
||||
$jobs = $jobs?.length > 100 ? njobs.slice(0, 100) : njobs
|
||||
await setResult(r, job)
|
||||
$state = $state
|
||||
} catch (e) {
|
||||
sendUserToast(`Error running frontend script ${id}: ` + e.message, true)
|
||||
|
||||
// Manually add a fake job to the job list to show the error
|
||||
|
||||
const job = generateNextFrontendJobId()
|
||||
const error = e.body ?? e.message
|
||||
|
||||
$errorByComponent[job] = {
|
||||
error,
|
||||
componentId: id
|
||||
}
|
||||
|
||||
$jobs = [{ job, component: id, error }, ...$jobs]
|
||||
await setResult({ error: { message: e.body ?? e.message } }, job)
|
||||
}
|
||||
loading = false
|
||||
donePromise?.()
|
||||
@@ -227,7 +215,7 @@
|
||||
}
|
||||
|
||||
try {
|
||||
let njob = await resultJobLoader?.abstractRun(async () => {
|
||||
await resultJobLoader?.abstractRun(async () => {
|
||||
const nonStaticRunnableInputs = {}
|
||||
const staticRunnableInputs = {}
|
||||
for (const k of Object.keys(fields ?? {})) {
|
||||
@@ -272,12 +260,8 @@
|
||||
requestBody
|
||||
})
|
||||
})
|
||||
if (njob) {
|
||||
const njobs = [{ job: njob, component: id }, ...$jobs]
|
||||
$jobs = $jobs?.length > 100 ? njobs.slice(0, 100) : njobs
|
||||
}
|
||||
} catch (e) {
|
||||
setResult({ error: e.body ?? e.message }, undefined)
|
||||
updateResult({ error: e.body ?? e.message })
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
@@ -290,7 +274,7 @@
|
||||
executeComponent()
|
||||
}
|
||||
} catch (e) {
|
||||
setResult({ error: e.body ?? e.message }, undefined)
|
||||
updateResult({ error: e.body ?? e.message })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,14 +289,50 @@
|
||||
outputs.jobId?.set(jobId)
|
||||
}
|
||||
|
||||
async function setResult(res: any, jobId: string | undefined) {
|
||||
dispatch('done')
|
||||
const hasRes = res !== undefined && res !== null
|
||||
function recordJob(
|
||||
jobId: string,
|
||||
result?: string,
|
||||
error?: string,
|
||||
transformer?: { result?: string; error?: string }
|
||||
) {
|
||||
const job = {
|
||||
...(result ? { result } : {}),
|
||||
...(error ? { error } : {}),
|
||||
...(transformer ? { transformer } : {}),
|
||||
job: jobId,
|
||||
component: id
|
||||
}
|
||||
|
||||
if (error) {
|
||||
recordError(error, jobId)
|
||||
} else if (job?.transformer?.error) {
|
||||
recordError(job.transformer.error, jobId)
|
||||
}
|
||||
|
||||
const njobs = [job, ...$jobs]
|
||||
// Only keep the last 100 jobs
|
||||
$jobs = $jobs?.length > 100 ? njobs.slice(0, 100) : njobs
|
||||
}
|
||||
|
||||
function getResultErrors(result: any | any[]): string | undefined {
|
||||
const errorAsArray = Array.isArray(result) ? result.flat() : [result]
|
||||
const hasErrors = errorAsArray.some((r) => r?.error)
|
||||
|
||||
if (!hasErrors) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return errorAsArray
|
||||
.map((r) => r?.error?.message)
|
||||
.filter(Boolean)
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
async function runTransformer(res) {
|
||||
if (transformer) {
|
||||
try {
|
||||
let raw = $worldStore.newOutput(id, 'raw', res)
|
||||
res = await eval_like(
|
||||
const transformerResult = await eval_like(
|
||||
transformer.content,
|
||||
computeGlobalContext($worldStore, {
|
||||
iter: iterContext ? $iterContext : undefined,
|
||||
@@ -326,9 +346,10 @@
|
||||
$worldStore,
|
||||
$runnableComponents
|
||||
)
|
||||
raw.set(res)
|
||||
raw.set(transformerResult)
|
||||
return transformerResult
|
||||
} catch (err) {
|
||||
res = {
|
||||
return {
|
||||
error: {
|
||||
name: 'TransformerError',
|
||||
message: 'An error occured in the transformer',
|
||||
@@ -336,52 +357,51 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasRes && res === undefined) {
|
||||
res = {
|
||||
error: {
|
||||
name: 'TransformerError',
|
||||
message: 'An error occured in the transformer',
|
||||
stack: 'Transformer returned undefined'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// console.log('setr', id)
|
||||
|
||||
function updateResult(res) {
|
||||
outputs.result?.set(res)
|
||||
|
||||
result = res
|
||||
}
|
||||
|
||||
// Flows with loops can have multiple results
|
||||
const errorAsArray = Array.isArray(result) ? result.flat() : [result]
|
||||
async function setResult(res: any, jobId: string | undefined) {
|
||||
dispatch('done')
|
||||
const hasRes = res !== undefined && res !== null
|
||||
|
||||
// As soon as we have an error, we consider the component errored
|
||||
const hasErrors = errorAsArray.some((r) => r?.error)
|
||||
|
||||
if (hasErrors) {
|
||||
const errorMessages = errorAsArray
|
||||
.map((r) => r?.error?.message)
|
||||
.filter(Boolean)
|
||||
.join('\n')
|
||||
|
||||
jobId && recordError(errorMessages, jobId)
|
||||
|
||||
dispatch('handleError', errorMessages)
|
||||
} else {
|
||||
dispatch('success')
|
||||
if (!jobId && !hasRes) {
|
||||
return
|
||||
}
|
||||
|
||||
const previousJobId = Object.keys($errorByComponent).find(
|
||||
(key) => $errorByComponent[key].componentId === id
|
||||
)
|
||||
const errors = getResultErrors(res)
|
||||
|
||||
if (previousJobId && !hasErrors) {
|
||||
delete $errorByComponent[previousJobId]
|
||||
$errorByComponent = $errorByComponent
|
||||
console.log('errors', errors)
|
||||
|
||||
if (errors) {
|
||||
const transformerResult = transformer
|
||||
? { error: 'Transformer could not be run because of previous errors' }
|
||||
: undefined
|
||||
|
||||
recordJob(jobId!, undefined, errors, transformerResult)
|
||||
updateResult(res)
|
||||
dispatch('handleError', errors)
|
||||
return
|
||||
}
|
||||
|
||||
const transformerResult = await runTransformer(res)
|
||||
|
||||
if (transformerResult?.error) {
|
||||
recordJob(jobId!, res, undefined, transformerResult)
|
||||
updateResult(transformerResult)
|
||||
dispatch('handleError', transformerResult.error)
|
||||
return
|
||||
}
|
||||
|
||||
updateResult(transformerResult ?? res)
|
||||
recordJob(jobId!, result, undefined, transformerResult)
|
||||
$errorByComponent = clearErrorByComponentId(id, $errorByComponent)
|
||||
|
||||
dispatch('success')
|
||||
donePromise?.()
|
||||
}
|
||||
|
||||
@@ -477,7 +497,7 @@
|
||||
loading = false
|
||||
}}
|
||||
on:doneError={(e) => {
|
||||
setResult({ error: e.detail }, e.detail.id)
|
||||
setResult({ error: e.detail.error }, e.detail.id)
|
||||
loading = false
|
||||
}}
|
||||
bind:this={resultJobLoader}
|
||||
@@ -514,10 +534,17 @@
|
||||
<Alert type="error" title="Error during execution">
|
||||
<div class="flex flex-col gap-2">
|
||||
An error occured, please contact the app author.
|
||||
|
||||
{#if lastJobId && $errorByComponent[lastJobId]?.error}
|
||||
<div class="font-bold">{$errorByComponent[lastJobId]?.error}</div>
|
||||
{/if}
|
||||
<a
|
||||
href={`/run/${lastJobId}?workspace=${workspace}`}
|
||||
class="font-semibold text-red-800 underline">Job id: {lastJobId}</a
|
||||
class="font-semibold text-red-800 underline"
|
||||
target="_blank"
|
||||
>
|
||||
Job id: {lastJobId}
|
||||
</a>
|
||||
</div>
|
||||
</Alert>
|
||||
</div>
|
||||
|
||||
@@ -632,6 +632,7 @@
|
||||
<Skeleton layout={[[40]]} />
|
||||
{/if}
|
||||
{:else}
|
||||
{@const jobResult = $jobs.find((j) => j.job == selectedJobId)}
|
||||
<div class="flex flex-col h-full w-full gap-4 p-2 mb-4">
|
||||
{#if job?.['running']}
|
||||
<div class="flex flex-row-reverse w-full">
|
||||
@@ -673,6 +674,28 @@
|
||||
</div>
|
||||
{/if}
|
||||
</Pane>
|
||||
{#if jobResult?.transformer}
|
||||
<Pane size={50} minSize={10} class="text-sm text-secondary p-2">
|
||||
<div class="font-bold mb-4">Transformer results</div>
|
||||
{#if job != undefined && 'result' in job && job.result != undefined}
|
||||
<pre class="overflow-x-auto break-words relative h-full px-2">
|
||||
<DisplayResult
|
||||
workspaceId={$workspaceStore}
|
||||
jobId={selectedJobId}
|
||||
result={jobResult?.transformer}
|
||||
/>
|
||||
</pre>
|
||||
{:else if testIsLoading}
|
||||
<div class="p-2"><Loader2 class="animate-spin" /> </div>
|
||||
{:else if job != undefined && 'result' in job && job?.['result'] == undefined}
|
||||
<div class="p-2 text-tertiary">Result is undefined</div>
|
||||
{:else}
|
||||
<div class="p-2 text-tertiary">
|
||||
<Loader2 size={14} class="animate-spin mr-2" />
|
||||
</div>
|
||||
{/if}
|
||||
</Pane>
|
||||
{/if}
|
||||
</Splitpanes>
|
||||
{:else}
|
||||
<div class="mt-10" />
|
||||
|
||||
@@ -186,7 +186,15 @@ export type AppViewerContext = {
|
||||
workspace: string
|
||||
onchange: (() => void) | undefined
|
||||
isEditor: boolean
|
||||
jobs: Writable<{ job: string; component: string; result?: string; error?: string }[]>
|
||||
jobs: Writable<
|
||||
{
|
||||
job: string
|
||||
component: string
|
||||
result?: string
|
||||
error?: string
|
||||
transformer?: { result?: string; error?: string }
|
||||
}[]
|
||||
>
|
||||
noBackend: boolean
|
||||
errorByComponent: Writable<Record<string, { error: string; componentId: string }>>
|
||||
openDebugRun: Writable<((componentID: string) => void) | undefined>
|
||||
|
||||
Reference in New Issue
Block a user