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:
-
- {:else if resultKind == 'jpeg'}
-
- Result is an image:
-
- {:else if resultKind == 'gif'}
-
- Result is an image:
-
- {: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:
+
+ {:else if resultKind == 'jpeg'}
+
+ Result is an image:
+
+ {:else if resultKind == 'gif'}
+
+ Result is an image:
+
+ {:else if resultKind == 'file'}
+
+ {: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}
+
+ 'Cancel'
+
+ {/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
-
-
- runPreview()}
- disabled={previewIsLoading}
- btnClasses="w-50 ml-1"
- size="xs"
- startIcon={{
- icon: previewIsLoading ? faRotateRight : faPlay,
- classes: classNames(previewIsLoading ? 'animate-spin' : 'animate-none')
- }}
- >
- {previewIsLoading ? 'Running' : 'Run preview (Ctrl+Enter)'}
-
-
@@ -251,11 +137,11 @@
}}
>
{
- runPreview()
+ runTest()
}}
formatAction={async () => {
code = getEditor().getCode()
@@ -306,14 +192,41 @@
-
+
+
+ {#if testIsLoading}
+
+ 'Cancel'
+
+ {:else}
+
+ {testIsLoading ? 'Running' : 'Test (Ctrl+Enter)'}
+
+ {/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}