From 6bbd9374cbd2c516dd3b56551103fcfeba01f80f Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 11 May 2023 22:13:19 +0200 Subject: [PATCH] fix(frontend): only download result for apps --- backend/windmill-api/openapi.yaml | 24 +++ backend/windmill-api/src/jobs.rs | 29 +++ .../src/lib/components/ResultJobLoader.svelte | 190 ++++++++++++++++++ .../helpers/RunnableComponent.svelte | 95 ++++----- 4 files changed, 293 insertions(+), 45 deletions(-) create mode 100644 frontend/src/lib/components/ResultJobLoader.svelte diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index c0349fd614..43f4e39430 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -3675,6 +3675,30 @@ paths: application/json: schema: {} + /w/{workspace}/jobs/completed/get_result_maybe/{id}: + get: + summary: get completed job result if job is completed + operationId: getCompletedJobResultMaybe + tags: + - job + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/JobId" + responses: + "200": + description: result + content: + application/json: + schema: + type: object + properties: + completed: + type: boolean + result: {} + required: + - completed + - result + /w/{workspace}/jobs/completed/delete/{id}: post: summary: delete completed job (erase content but keep run id) diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index 48f87ea6df..b742b5ca06 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -74,6 +74,10 @@ pub fn workspaced_service() -> Router { .route("/completed/list", get(list_completed_jobs)) .route("/completed/get/:id", get(get_completed_job)) .route("/completed/get_result/:id", get(get_completed_job_result)) + .route( + "/completed/get_result_maybe/:id", + get(get_completed_job_result_maybe), + ) .route("/completed/delete/:id", post(delete_completed_job)) .route("/flow/resume/:id", post(resume_suspended_flow_as_owner)) .route( @@ -2073,6 +2077,31 @@ async fn get_completed_job_result( Ok(Json(result)) } +#[derive(Serialize)] +struct CompletedJobResult { + completed: bool, + result: Option, +} + +async fn get_completed_job_result_maybe( + Extension(db): Extension, + Path((w_id, id)): Path<(String, Uuid)>, +) -> error::JsonResult { + let result_o = sqlx::query_scalar!( + "SELECT result FROM completed_job WHERE id = $1 AND workspace_id = $2", + id, + w_id, + ) + .fetch_optional(&db) + .await?; + + if let Some(result) = result_o { + Ok(Json(CompletedJobResult { completed: true, result })) + } else { + Ok(Json(CompletedJobResult { completed: false, result: None })) + } +} + async fn delete_completed_job( authed: Authed, Extension(user_db): Extension, diff --git a/frontend/src/lib/components/ResultJobLoader.svelte b/frontend/src/lib/components/ResultJobLoader.svelte new file mode 100644 index 0000000000..43ae1be916 --- /dev/null +++ b/frontend/src/lib/components/ResultJobLoader.svelte @@ -0,0 +1,190 @@ + diff --git a/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte b/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte index a145140ac9..cc1e44206e 100644 --- a/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte +++ b/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte @@ -3,8 +3,7 @@ import Alert from '$lib/components/common/alert/Alert.svelte' import LightweightSchemaForm from '$lib/components/LightweightSchemaForm.svelte' import Popover from '$lib/components/Popover.svelte' - import TestJobLoader from '$lib/components/TestJobLoader.svelte' - import { AppService, type CompletedJob } from '$lib/gen' + import { AppService } from '$lib/gen' import { classNames, defaultIfEmptyString, emptySchema, sendUserToast } from '$lib/utils' import { deepEqual } from 'fast-equals' import { Bug } from 'lucide-svelte' @@ -16,6 +15,7 @@ import InputValue from './InputValue.svelte' import RefreshButton from './RefreshButton.svelte' import { clearErrorByComponentId, selectId } from '../../editor/appUtils' + import ResultJobLoader from '$lib/components/ResultJobLoader.svelte' // Component props export let id: string @@ -65,7 +65,6 @@ $runnableComponents = $runnableComponents let args: Record | undefined = undefined - let testIsLoading = false let runnableInputValues: Record = {} let executeTimeout: NodeJS.Timeout | undefined = undefined @@ -96,7 +95,7 @@ } $: (runnableInputValues || extraQueryParams || args) && - testJobLoader && + resultJobLoader && refreshIfAutoRefresh('arg changed') $: refreshOn = @@ -113,8 +112,7 @@ } // Test job internal state - let testJob: CompletedJob | undefined = undefined - let testJobLoader: TestJobLoader | undefined = undefined + let resultJobLoader: ResultJobLoader | undefined = undefined let schemaStripped: Schema | undefined = autoRefresh || forceSchemaDisplay ? emptySchema() : undefined @@ -171,7 +169,7 @@ $worldStore, $runnableComponents ) - await setResult(r) + await setResult(r, undefined) $state = $state } catch (e) { @@ -190,18 +188,20 @@ $jobs = [{ job, component: id, error }, ...$jobs] } loading = false + donePromise?.() return } else if (noBackend) { if (!noToast) { sendUserToast('This app is not connected to a windmill backend, it is a static preview') } + donePromise?.() return } if (runnable?.type === 'runnableByName' && !runnable.inlineScript) { return } - if (!testJobLoader) { + if (!resultJobLoader) { console.warn('No test job loader') return } @@ -209,7 +209,7 @@ loading = true try { - let njob = await testJobLoader?.abstractRun(() => { + let njob = await resultJobLoader?.abstractRun(() => { const nonStaticRunnableInputs = {} const staticRunnableInputs = {} Object.keys(fields ?? {}).forEach((k) => { @@ -255,7 +255,7 @@ $jobs = [{ job: njob, component: id }, ...$jobs] } } catch (e) { - setResult({ error: e.body ?? e.message }) + setResult({ error: e.body ?? e.message }, undefined) loading = false } } @@ -264,36 +264,42 @@ try { await executeComponent() } catch (e) { - setResult({ error: e.body ?? e.message }) + setResult({ error: e.body ?? e.message }, undefined) } } - let lastStartedAt: number = -1 - - function recordError(error: string) { - if (testJob) { - $errorByComponent[testJob.id] = { - error: error, - componentId: id - } + function recordError(error: string, jobId: string) { + $errorByComponent[jobId] = { + error: error, + componentId: id } } - async function setResult(res: any) { + async function setResult(res: any, jobId: string | undefined) { const hasRes = res !== undefined && res !== null if (transformer) { - $worldStore.newOutput(id, 'raw', res) - res = await eval_like( - transformer.content, - computeGlobalContext($worldStore, { result: res }), - false, - $state, - $mode == 'dnd', - $componentControl, - $worldStore, - $runnableComponents - ) + try { + $worldStore.newOutput(id, 'raw', res) + res = await eval_like( + transformer.content, + computeGlobalContext($worldStore, { result: res }), + false, + $state, + $mode == 'dnd', + $componentControl, + $worldStore, + $runnableComponents + ) + } catch (err) { + res = { + error: { + name: 'TransformerError', + message: 'An error occured in the transformer', + stack: err.message + } + } + } if (hasRes && res === undefined) { res = { @@ -312,7 +318,7 @@ result = res if (res?.error) { - recordError(res.error) + jobId && recordError(res.error, jobId) dispatch('handleError', res.error.message) } else { dispatch('success') @@ -346,7 +352,7 @@ executeComponent(true, inlineScript).catch(reject) }) p.cancel = () => { - testJobLoader?.cancelJob() + resultJobLoader?.cancelJob() loading = false rejectCb(new Error('Canceled')) } @@ -371,6 +377,8 @@ delete $runnableComponents[id] $runnableComponents = $runnableComponents }) + + let lastJobId: string | undefined = undefined {#each Object.entries(fields ?? {}) as [key, v] (key)} @@ -396,21 +404,18 @@ {/each} {/if} - { - if (testJob) { - const startedAt = new Date(testJob.started_at).getTime() - if (startedAt > lastStartedAt) { - lastStartedAt = startedAt - setResult(e.detail.result) - } - } + lastJobId = e.detail.id + setResult(e.detail.result, e.detail.id) loading = false }} - bind:isLoading={testIsLoading} - bind:job={testJob} - bind:this={testJobLoader} + on:doneError={(e) => { + setResult({ error: e.detail }, e.detail.id) + loading = false + }} + bind:this={resultJobLoader} /> {#if render} @@ -444,7 +449,7 @@
An error occured, please contact the app author. - Job id: {testJob?.id} + Job id: {lastJobId}