From 4044edf8d682f7c5fe62ccf9f16c7a8ffb7cf44c Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 25 Sep 2022 09:03:51 -0700 Subject: [PATCH] refactor logs panel everywhere applicable (#625) * init * init * progress * progress * refactor logs entirely * fix sqlx --- backend/sqlx-data.json | 23 +++ backend/src/jobs.rs | 9 +- .../src/lib/components/DisplayResult.svelte | 142 ++++++------- .../src/lib/components/FlowPreview.svelte | 78 ------- frontend/src/lib/components/LogViewer.svelte | 28 +++ .../src/lib/components/ModulePreview.svelte | 99 +++++++++ frontend/src/lib/components/RunForm.svelte | 3 +- .../src/lib/components/ScriptEditor.svelte | 191 +++++------------- .../src/lib/components/TestJobLoader.svelte | 123 +++++++++++ .../components/common/button/Button.svelte | 4 +- .../lib/components/common/tabs/Tabs.svelte | 5 + .../flows/content/FlowModule.svelte | 4 +- frontend/src/lib/components/flows/utils.ts | 17 +- .../components/script_editor/LogPanel.svelte | 14 +- frontend/src/routes/run/[...run].svelte | 119 ++++------- 15 files changed, 459 insertions(+), 400 deletions(-) delete mode 100644 frontend/src/lib/components/FlowPreview.svelte create mode 100644 frontend/src/lib/components/LogViewer.svelte create mode 100644 frontend/src/lib/components/ModulePreview.svelte create mode 100644 frontend/src/lib/components/TestJobLoader.svelte diff --git a/backend/sqlx-data.json b/backend/sqlx-data.json index a2cffa499c..667af5ddc4 100644 --- a/backend/sqlx-data.json +++ b/backend/sqlx-data.json @@ -3163,6 +3163,29 @@ }, "query": "\n SELECT SUM(duration_ms)\n FROM completed_job\n WHERE permissioned_as = $1\n AND created_at > NOW() - INTERVAL '1200 seconds'\n AND workspace_id = $2" }, + "e7051b21ffd80de0cb0644a71c89c9f63abed6b446d1e232adbb6931efad6d6d": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Uuid" + } + ], + "nullable": [ + false + ], + "parameters": { + "Left": [ + "Varchar", + "Text", + "Uuid", + "Text" + ] + } + }, + "query": "UPDATE queue SET canceled = true, canceled_by = $1, canceled_reason = $2 WHERE id = $3 AND workspace_id = $4 RETURNING id" + }, "ee99cb974fb35d2d92fa96cecb53b8ad3a729ac710f362dc55a576613b66f9be": { "describe": { "columns": [ diff --git a/backend/src/jobs.rs b/backend/src/jobs.rs index 2497bb33d3..799a5424fd 100644 --- a/backend/src/jobs.rs +++ b/backend/src/jobs.rs @@ -778,7 +778,7 @@ async fn cancel_job( let job_option = sqlx::query_scalar!( "UPDATE queue SET canceled = true, canceled_by = $1, canceled_reason = $2 WHERE id = $3 \ - AND schedule_path IS NULL AND workspace_id = $4 RETURNING id", + AND workspace_id = $4 RETURNING id", &authed.username, reason, id, @@ -808,13 +808,6 @@ async fn cancel_job( "queued job id {} exists but is already completed and cannot be canceled", id )), - Some(Job::QueuedJob(job)) if job.schedule_path.is_some() => { - error::Error::BadRequest(format!( - "queued job id {} exists but has been created by a scheduler - and can only be only canceled by disabling the parent scheduler", - id - )) - } _ => error::Error::NotFound(format!("queued job id {} does not exist", id)), }; Err(err) diff --git a/frontend/src/lib/components/DisplayResult.svelte b/frontend/src/lib/components/DisplayResult.svelte index e2b5445444..8dc6a124eb 100644 --- a/frontend/src/lib/components/DisplayResult.svelte +++ b/frontend/src/lib/components/DisplayResult.svelte @@ -52,74 +52,80 @@ } -{#if result} - {#if typeof result == 'object' && Object.keys(result).length > 0}
- The result keys are: {truncate(Object.keys(result).join(', '), 50)} -
- {/if} - {#if resultKind == 'table-col'} -
- {#each Object.keys(result) as col} -
-
- {col} -
- {#if Array.isArray(result[col])} - {#each result[col] as item} -
- {typeof item === 'string' ? item : JSON.stringify(item)} -
- {/each} - {/if} -
- {/each} -
- {:else if resultKind == 'table-row'}
- - - {#each asListOfList(result) as row} - - {#each row as v} - {truncate(JSON.stringify(v), 200) ?? ''} +
+ {#if result} + {#if typeof result == 'object' && Object.keys(result).length > 0} +
+ The result keys are: {truncate(Object.keys(result).join(', '), 50)} +
{/if}{#if resultKind == 'table-col'} +
+ {#each Object.keys(result) as col} +
+
+ {col} +
+ {#if Array.isArray(result[col])} + {#each result[col] as item} +
+ {typeof item === 'string' ? item : JSON.stringify(item)} +
{/each} - - {/each} - - -
- {:else if resultKind == 'png'} -
- Result is an image: png rendered -
- {:else if resultKind == 'jpeg'} -
- Result is an image: jpeg rendered -
- {:else if resultKind == 'gif'} -
- Result is an image: gif rendered -
- {:else if resultKind == 'file'} -
- Result is a file: Download + {/each} +
+ {:else if resultKind == 'table-row'}
-
- {:else} + + + {#each asListOfList(result) as row} + + {#each row as v} + {truncate(JSON.stringify(v), 200) ?? ''} + {/each} + + {/each} + + +
+ {:else if resultKind == 'png'} +
+ Result is an image: png rendered +
+ {:else if resultKind == 'jpeg'} +
+ Result is an image: jpeg rendered +
+ {:else if resultKind == 'gif'} +
+ Result is an image: gif rendered +
+ {:else if resultKind == 'file'} +
+ Result is a file: Download +
+ {:else} + {/if} {/if} -{/if} +
diff --git a/frontend/src/lib/components/FlowPreview.svelte b/frontend/src/lib/components/FlowPreview.svelte deleted file mode 100644 index 3d701bc96d..0000000000 --- a/frontend/src/lib/components/FlowPreview.svelte +++ /dev/null @@ -1,78 +0,0 @@ - - - - -
- runPreview(args)} - schedulable={false} - buttonText="Test just this step" - detailed={false} - args={stepArgs} - /> -
-
- -
- {#if jobId} - mapJobResultsToFlowState(e.detail, 'justthis', i, j)} - /> - {:else} - No results yet - {/if} -
-
-
diff --git a/frontend/src/lib/components/LogViewer.svelte b/frontend/src/lib/components/LogViewer.svelte new file mode 100644 index 0000000000..c322e1c073 --- /dev/null +++ b/frontend/src/lib/components/LogViewer.svelte @@ -0,0 +1,28 @@ + + +
+
+
{#if content}{content}{:else if isLoading}Waiting for job to start...{:else}No logs is available yet{/if}
+
+
+ Auto scroll + +
+
diff --git a/frontend/src/lib/components/ModulePreview.svelte b/frontend/src/lib/components/ModulePreview.svelte new file mode 100644 index 0000000000..a107de472d --- /dev/null +++ b/frontend/src/lib/components/ModulePreview.svelte @@ -0,0 +1,99 @@ + + + + + +
+ runTest(args)} + schedulable={false} + buttonText="Test just this step" + detailed={false} + args={stepArgs} + /> + {#if testIsLoading} + + {/if} +
+
+ +
+ + + + + +
{#if testJob && 'result' in testJob && testJob.result}
+						{:else if testIsLoading}Waiting for Result...
+						{:else}Test to see result here
+						{/if}
+        
+
+
+
+
+
diff --git a/frontend/src/lib/components/RunForm.svelte b/frontend/src/lib/components/RunForm.svelte index 162337bfd2..6ca6866800 100644 --- a/frontend/src/lib/components/RunForm.svelte +++ b/frontend/src/lib/components/RunForm.svelte @@ -9,8 +9,9 @@ import SvelteMarkdown from 'svelte-markdown' import SchemaForm from './SchemaForm.svelte' import Tooltip from './Tooltip.svelte' + import type { Schema } from '$lib/common' - export let runnable: Script | Flow | undefined + export let runnable: { summary: string; schema: Schema; description: string } export let runAction: (scheduledForStr: string | undefined, args: Record) => void export let buttonText = 'Run' export let schedulable = true diff --git a/frontend/src/lib/components/ScriptEditor.svelte b/frontend/src/lib/components/ScriptEditor.svelte index f44fb10528..787b4a33f8 100644 --- a/frontend/src/lib/components/ScriptEditor.svelte +++ b/frontend/src/lib/components/ScriptEditor.svelte @@ -2,14 +2,13 @@ import type { Schema } from '$lib/common' import { CompletedJob, Job, JobService } from '$lib/gen' import { userStore, workspaceStore } from '$lib/stores' - import { classNames, emptySchema, scriptLangToEditorLang } from '$lib/utils' + import { emptySchema, scriptLangToEditorLang } from '$lib/utils' import { faCheck, faExclamationTriangle, faPlay, faRotateRight } from '@fortawesome/free-solid-svg-icons' - import { onDestroy, onMount } from 'svelte' import Icon from 'svelte-awesome' import Editor from './Editor.svelte' @@ -23,6 +22,7 @@ import { faGithub } from '@fortawesome/free-brands-svg-icons' import EditorBar from './EditorBar.svelte' import Button from './common/button/Button.svelte' + import TestJobLoader from './TestJobLoader.svelte' // Exported export let schema: Schema = emptySchema() @@ -34,22 +34,19 @@ // Internal state let editor: Editor - let logPanel: LogPanel - // Preview args input + let testJobLoader: TestJobLoader + + // Test args input let args: Record = {} let isValid: boolean = true - // Preview - let previewIsLoading = false - let previewIntervalId: NodeJS.Timer - let previewJob: Job | undefined + // Test + let testIsLoading = false + let testJob: Job | undefined let pastPreviews: CompletedJob[] = [] let lastSave: string | null - let syncIteration: number = 0 - let ITERATIONS_BEFORE_SLOW_REFRESH = 100 - $: lastSave = localStorage.getItem(path ?? 'last_save') export function getEditor(): Editor { @@ -59,50 +56,15 @@ function onKeyDown(event: KeyboardEvent) { if ((event.ctrlKey || event.metaKey) && event.key == 'Enter') { event.preventDefault() - runPreview() + runTest() } } - let div: HTMLElement | null = null - - export async function runPreview(): Promise { - try { - if (previewIntervalId) { - clearInterval(previewIntervalId) - } - if (previewIsLoading && previewJob) { - JobService.cancelQueuedJob({ - workspace: $workspaceStore!, - id: previewJob.id, - requestBody: {} - }) - } - previewIsLoading = true - - const previewId = await JobService.runScriptPreview({ - workspace: $workspaceStore!, - requestBody: { - path, - content: editor.getCode(), - args: args, - language: lang - } - }) - previewJob = undefined - loadPreviewJob(previewId) - syncIteration = 0 - previewIntervalId = setInterval(() => { - syncer(previewId) - }, 500) - logPanel?.setFocusToLogs() - //TODO fetch preview, every x time, until it's completed - } catch (err) { - previewIsLoading = false - throw err - } + function runTest() { + testJobLoader.runPreview(path, editor.getCode(), lang, args) } - async function loadPastPreviews(): Promise { + async function loadPastTests(): Promise { pastPreviews = await JobService.listCompletedJobs({ workspace: $workspaceStore!, jobKinds: 'preview', @@ -111,37 +73,6 @@ }) } - async function loadPreviewJob(id: string): Promise { - try { - if (previewJob && `running` in previewJob) { - let previewJobUpdates = await JobService.getJobUpdates({ - workspace: $workspaceStore!, - id, - running: previewJob.running, - logOffset: previewJob.logs?.length ?? 0 - }) - - if (previewJobUpdates.new_logs) { - previewJob.logs = (previewJob.logs ?? '').concat(previewJobUpdates.new_logs) - } - if ((previewJobUpdates.running ?? false) || (previewJobUpdates.completed ?? false)) { - previewJob = await JobService.getJob({ workspace: $workspaceStore!, id }) - } - } else { - previewJob = await JobService.getJob({ workspace: $workspaceStore!, id }) - } - if (previewJob?.type === 'CompletedJob') { - //only CompletedJob has success property - clearInterval(previewIntervalId) - previewIsLoading = false - loadPastPreviews() - } - div?.scroll({ top: div?.scrollHeight, behavior: 'smooth' }) - } catch (err) { - console.error(err) - } - } - async function inferSchema() { let isDefault: string[] = [] Object.entries(args).forEach(([k, v]) => { @@ -161,45 +92,15 @@ } } } - - function syncer(id: string): void { - if (syncIteration > ITERATIONS_BEFORE_SLOW_REFRESH) { - loadPreviewJob(id) - if (previewIntervalId) { - clearInterval(previewIntervalId) - previewIntervalId = setInterval(() => loadPreviewJob(id), 5000) - } - } else { - syncIteration++ - loadPreviewJob(id) - } - } - - let syncCode: NodeJS.Timer - - onMount(() => { - inferSchema() - syncCode = setInterval(() => { - const newCode = editor?.getCode() - if (newCode && code != newCode) { - code = editor.getCode() - } - }, 3000) - }) - - onDestroy(() => { - if (editor) { - code = editor.getCode() - } - if (previewIntervalId) { - clearInterval(previewIntervalId) - } - if (syncCode) { - clearInterval(syncCode) - } - }) + +
@@ -221,21 +122,6 @@ Sync from Github
- -
- -
@@ -251,11 +137,11 @@ }} > { - runPreview() + runTest() }} formatAction={async () => { code = getEditor().getCode() @@ -306,14 +192,41 @@ -
+
+
+ {#if testIsLoading} + + {:else} + + {/if} +
diff --git a/frontend/src/lib/components/TestJobLoader.svelte b/frontend/src/lib/components/TestJobLoader.svelte new file mode 100644 index 0000000000..e2981c84f7 --- /dev/null +++ b/frontend/src/lib/components/TestJobLoader.svelte @@ -0,0 +1,123 @@ + diff --git a/frontend/src/lib/components/common/button/Button.svelte b/frontend/src/lib/components/common/button/Button.svelte index 4e42589525..e2a0b108c9 100644 --- a/frontend/src/lib/components/common/button/Button.svelte +++ b/frontend/src/lib/components/common/button/Button.svelte @@ -3,7 +3,7 @@ import Icon from 'svelte-awesome' export let size: 'xs' | 'sm' | 'md' | 'lg' | 'xl' = 'md' - export let color: 'blue' | 'dark' | 'light' = 'blue' + export let color: 'blue' | 'dark' | 'light' | 'red' = 'blue' export let variant: 'contained' | 'border' = 'contained' export let btnClasses: string = '' export let disabled: boolean = false @@ -17,6 +17,8 @@ switch (color) { case 'blue': return 'bg-blue-500 hover:bg-blue-700 focus:ring-blue-300 text-white ' + case 'red': + return 'bg-red-500 hover:bg-red-700 focus:ring-red-300 text-white ' case 'light': return 'text-gray-800 bg-white hover:bg-gray-100 focus:ring-gray-300' default: diff --git a/frontend/src/lib/components/common/tabs/Tabs.svelte b/frontend/src/lib/components/common/tabs/Tabs.svelte index f8980354e4..e2641db986 100644 --- a/frontend/src/lib/components/common/tabs/Tabs.svelte +++ b/frontend/src/lib/components/common/tabs/Tabs.svelte @@ -8,6 +8,9 @@ + (viewTab = 'result')} + bind:this={testJobLoader} + bind:isLoading={testIsLoading} + bind:job +/> +

@@ -246,7 +208,7 @@ {#if job?.job_kind == 'script'} {#if canWrite(job?.script_path ?? '', {}, $userStore)} Edit @@ -430,21 +392,20 @@ {/if}

-
-
{#if viewTab == 'logs'}{#if job && 'logs' in job && job.logs}{job.logs}
-						{:else if job}No logs are available yet
-						{:else}Loading...{/if}
-					{:else if viewTab == 'code'}
-						{#if job && 'raw_code' in job && job.raw_code}
-							
-						{:else if job}No code is available
-						{:else}Loading...{/if}
-					{:else if job && 'result' in job && job.result}
-					{:else if job}No output is available yet
-					{:else}Loading...
-					{/if}
-			
+
+ {#if viewTab == 'logs'} +
+ +
+ {:else if viewTab == 'code'} + {#if job && 'raw_code' in job && job.raw_code} + + {:else if job}No code is available + {:else}Loading...{/if} + {:else if job && 'result' in job && job.result} + {:else if job}No output is available yet + {:else}Loading... + {/if}
{/if}