add debug Runs to apps

This commit is contained in:
Ruben Fiszel
2023-01-05 08:51:27 +01:00
parent f0a52d2f59
commit 5cad74997d
18 changed files with 274 additions and 69 deletions
-4
View File
@@ -1,7 +1,3 @@
{
auto_https off
}
http://localhost {
bind {$ADDRESS}
reverse_proxy /api/* https://app.windmill.dev {
+1 -1
View File
@@ -143,7 +143,7 @@
}
}
function format() {
export function format() {
if (editor) {
code = getCode()
editor.getAction('editor.action.formatDocument').run()
@@ -7,6 +7,7 @@
import { getScriptByPath, sendUserToast } from '$lib/utils'
import {
faBroom,
faCube,
faDollarSign,
faEye,
@@ -307,6 +308,20 @@
</span>
</Button></div
>
<div>
<Button
btnClasses="!font-medium"
size="xs"
spacingSize="md"
color="light"
on:click={editor.format}
startIcon={{ icon: faBroom }}
>
{#if !iconOnly}
Format (Ctrl+S)
{/if}
</Button></div
>
</div>
<div class="py-1">
<div>
@@ -130,6 +130,15 @@
}, 200)
})
editor.onDidFocusEditorText(() => {
dispatch('focus')
editor.addCommand(KeyMod.CtrlCmd | KeyCode.KeyS, function () {
code = getCode()
shouldBindKey && format && format()
})
})
if (autoHeight) {
let ignoreEvent = false
const updateHeight = () => {
@@ -16,11 +16,15 @@
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
import type { AppInput } from '../../inputType'
import Scatter from 'svelte-chartjs/Scatter.svelte'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
let zoomable = false
let pannable = false
export const staticOutputs: string[] = ['loading', 'result']
ChartJS.register(
@@ -35,28 +39,26 @@
zoomPlugin
)
const zoomOptions = {
pan: {
enabled: true
},
zoom: {
drag: {
enabled: false
},
wheel: {
enabled: true
}
}
}
let result: { data: { x: any[]; y: string[] } } | undefined = undefined
const options = {
$: options = {
responsive: true,
animation: false,
maintainAspectRatio: false,
plugins: {
zoom: zoomOptions
zoom: {
pan: {
enabled: pannable
},
zoom: {
drag: {
enabled: false
},
wheel: {
enabled: zoomable
}
}
}
}
}
@@ -65,6 +67,9 @@
}
</script>
<InputValue {id} input={configuration.zoomable} bind:value={zoomable} />
<InputValue {id} input={configuration.pannable} bind:value={pannable} />
<RunnableWrapper autoRefresh bind:componentInput {id} bind:result>
{#if result}
<Scatter {data} {options} />
@@ -28,6 +28,8 @@
export const staticOutputs: string[] = ['loading', 'result']
let logarithmicScale = false
let zoomable = false
let pannable = false
ChartJS.register(
Title,
@@ -43,20 +45,6 @@
LogarithmicScale
)
const zoomOptions = {
pan: {
enabled: true
},
zoom: {
drag: {
enabled: false
},
wheel: {
enabled: true
}
}
}
let result: { data: { x: any[]; y: string[] } } | undefined = undefined
$: options = {
@@ -64,7 +52,19 @@
animation: false,
maintainAspectRatio: false,
plugins: {
zoom: zoomOptions
zoom: {
pan: {
enabled: pannable
},
zoom: {
drag: {
enabled: false
},
wheel: {
enabled: zoomable
}
}
}
},
scales: {
x: {
@@ -82,6 +82,8 @@
</script>
<InputValue {id} input={configuration.logarithmicScale} bind:value={logarithmicScale} />
<InputValue {id} input={configuration.zoomable} bind:value={zoomable} />
<InputValue {id} input={configuration.pannable} bind:value={pannable} />
<RunnableWrapper autoRefresh bind:componentInput {id} bind:result>
{#if result}
@@ -22,7 +22,7 @@
export let result: any = undefined
export let forceSchemaDisplay: boolean = false
const { worldStore, runnableComponents, workspace, appPath, isEditor } =
const { worldStore, runnableComponents, workspace, appPath, isEditor, jobs } =
getContext<AppEditorContext>('AppEditorContext')
onMount(() => {
@@ -180,7 +180,7 @@
outputs?.loading?.set(true)
await testJobLoader?.abstractRun(() => {
let njob = await testJobLoader?.abstractRun(() => {
const nonStaticRunnableInputs = {}
const staticRunnableInputs = {}
Object.keys(fields ?? {}).forEach((k) => {
@@ -220,6 +220,9 @@
requestBody
})
})
if (njob) {
$jobs = [...$jobs, { job: njob, component: id }]
}
}
export async function runComponent() {
@@ -68,7 +68,8 @@
appPath: path,
workspace: $workspaceStore ?? '',
onchange: () => saveDraft(),
isEditor: true
isEditor: true,
jobs: writable([])
})
let timeout: NodeJS.Timeout | undefined = undefined
@@ -120,7 +121,7 @@
{#if previewing}
<AppPreview
workspace={$workspaceStore ?? ''}
{summary}
summary={$summaryStore}
app={$appStore}
appPath={path}
{breakpoint}
@@ -1,30 +1,42 @@
<script lang="ts">
import { goto } from '$app/navigation'
import { page } from '$app/stores'
import { Alert, Drawer, DrawerContent } from '$lib/components/common'
import { Alert, Badge, Drawer, DrawerContent } from '$lib/components/common'
import Button from '$lib/components/common/button/Button.svelte'
import { dirtyStore } from '$lib/components/common/confirmationModal/dirtyStore'
import Skeleton from '$lib/components/common/skeleton/Skeleton.svelte'
import ToggleButton from '$lib/components/common/toggleButton/ToggleButton.svelte'
import ToggleButtonGroup from '$lib/components/common/toggleButton/ToggleButtonGroup.svelte'
import DisplayResult from '$lib/components/DisplayResult.svelte'
import FlowJobResult from '$lib/components/FlowJobResult.svelte'
import FlowProgressBar from '$lib/components/flows/FlowProgressBar.svelte'
import FlowStatusViewer from '$lib/components/FlowStatusViewer.svelte'
import JobArgs from '$lib/components/JobArgs.svelte'
import LogViewer from '$lib/components/LogViewer.svelte'
import Path from '$lib/components/Path.svelte'
import SplitPanesWrapper from '$lib/components/splitPanes/SplitPanesWrapper.svelte'
import TestJobLoader from '$lib/components/TestJobLoader.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import Tooltip from '$lib/components/Tooltip.svelte'
import { AppService, Policy } from '$lib/gen'
import { AppService, Job, Policy } from '$lib/gen'
import { userStore, workspaceStore } from '$lib/stores'
import { faClipboard, faExternalLink, faSave } from '@fortawesome/free-solid-svg-icons'
import { faBug, faClipboard, faExternalLink, faSave } from '@fortawesome/free-solid-svg-icons'
import {
AlignHorizontalSpaceAround,
Expand,
Eye,
Laptop2,
Loader2,
Pencil,
Smartphone
} from 'lucide-svelte'
import { getContext } from 'svelte'
import { Icon } from 'svelte-awesome'
import { copyToClipboard, sendUserToast } from '../../../utils'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import { classNames, copyToClipboard, sendUserToast } from '../../../utils'
import type { AppComponent, AppEditorContext } from '../types'
import AppExportButton from './AppExportButton.svelte'
import PanelSection from './settingsPanel/common/PanelSection.svelte'
async function hash(message) {
const msgUint8 = new TextEncoder().encode(message) // encode as (utf-8) Uint8Array
@@ -36,7 +48,7 @@
export let policy: Policy
const { app, summary, mode, breakpoint, appPath } =
const { app, summary, mode, breakpoint, appPath, jobs } =
getContext<AppEditorContext>('AppEditorContext')
const loading = {
publish: false,
@@ -47,6 +59,7 @@
let pathError: string | undefined = undefined
let saveDrawerOpen = false
let jobsDrawerOpen = false
let publishDrawerOpen = false
function closeSaveDrawer() {
@@ -144,8 +157,17 @@
loading.save = false
sendUserToast('App saved')
}
let selectedJobId: string | undefined = undefined
let testJobLoader: TestJobLoader
let job: Job | undefined = undefined
let testIsLoading = false
$: selectedJobId && testJobLoader?.watchJob(selectedJobId)
</script>
<TestJobLoader bind:this={testJobLoader} bind:isLoading={testIsLoading} bind:job />
<Drawer bind:open={saveDrawerOpen} size="800px">
<DrawerContent title="Create an App" on:close={() => closeSaveDrawer()}>
<Path
@@ -166,6 +188,91 @@
</DrawerContent>
</Drawer>
<Drawer bind:open={jobsDrawerOpen} size="900px">
<DrawerContent noPadding title="Debug Runs" on:close={() => (jobsDrawerOpen = false)}>
<Splitpanes class="!overflow-visible">
<Pane size={25}>
<PanelSection title="Past Runs" smallPadding>
<div class="flex flex-col gap-2 w-full">
{#if $jobs.length > 0}
<div class="flex gap-2 flex-col ">
{#each $jobs ?? [] as { job, component } (job)}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="{classNames(
'border flex gap-1 truncate justify-between flex-row w-full items-center p-2 rounded-md cursor-pointer hover:bg-blue-50 hover:text-blue-400',
selectedJobId == job ? 'bg-blue-100 text-blue-600' : ''
)},"
on:click={() => (selectedJobId = job)}
>
<span class="text-xs truncate">{job}</span>
<Badge color="dark-indigo">{component}</Badge>
</div>
{/each}
</div>
{:else}
<div class="text-sm text-gray-500">No items</div>
{/if}
</div>
</PanelSection>
</Pane>
<Pane size={75}>
<div class="h-full w-full overflow-hidden">
{#if selectedJobId}
{#if !job}
<Skeleton layout={[[40]]} />
{:else}
<div class="flex flex-col h-full w-full gap-4 mb-4">
{#if job?.['running']}
<div class="flex flex-row-reverse w-full"
><Button
color="red"
variant="border"
on:click={() => testJobLoader?.cancelJob()}>Cancel</Button
>
</div>
{/if}
<div class="p-2">
<JobArgs args={job?.args} />
</div>
{#if job?.job_kind !== 'flow' && job?.job_kind !== 'flowpreview'}
<Splitpanes horizontal class="grow border w-full">
<Pane size={50} minSize={10}>
<LogViewer content={job?.logs} isLoading={testIsLoading} />
</Pane>
<Pane size={50} minSize={10} class="text-sm text-gray-600">
{#if job != undefined && 'result' in job && job.result != undefined}
<pre class="overflow-x-auto break-words relative h-full px-2"
><DisplayResult result={job.result} /></pre
>
{:else if testIsLoading}
<div class="p-2"><Loader2 class="animate-spin" /> </div>
{/if}
</Pane>
</Splitpanes>
{:else}
<div class="mt-10" />
<FlowProgressBar {job} class="py-4" />
<div class="w-full mt-10 mb-20">
<FlowStatusViewer
jobId={job.id}
on:jobsLoaded={({ detail }) => {
job = detail
}}
/>
</div>
{/if}
</div>
{/if}
{:else}
<div class="text-sm p-2 text-gray-500">Select a job to see its details</div>
{/if}
</div>
</Pane>
</Splitpanes>
</DrawerContent>
</Drawer>
<Drawer bind:open={publishDrawerOpen} size="800px">
<DrawerContent title="Publish an App" on:close={() => (publishDrawerOpen = false)}>
{#if appPath == ''}
@@ -267,6 +374,15 @@
</span>
</div>
<div class="flex flex-row grow gap-4 justify-end ">
<Button
on:click={() => (jobsDrawerOpen = true)}
color="light"
size="xs"
variant="border"
startIcon={{ icon: faBug }}
>
Debug Runs
</Button>
<span class="hidden lg:block">
<AppExportButton app={$app} />
</span>
@@ -52,7 +52,8 @@
appPath,
workspace,
onchange: undefined,
isEditor
isEditor,
jobs: writable([])
})
let mounted = false
@@ -127,7 +127,6 @@
rowHeight={36}
cols={columnConfiguration}
fastStart={true}
on:pointerup={({ detail }) => selectComponent(detail.id)}
gap={[4, 2]}
>
{#each $lazyGrid as gridComponent (gridComponent.id)}
@@ -148,11 +147,14 @@
>
{/if}
<div
on:pointerdown={() => {
selectComponent(dataItem.data.id)
}}
class={classNames(
'h-full w-full flex justify-center align-center items-center',
gridComponent.data.card ? 'border border-gray-100' : ''
)}
on:click|preventDefault|capture|once
on:click|preventDefault|capture|once|stopPropagation
>
<ComponentEditor
{pointerdown}
@@ -332,7 +332,20 @@ const display: ComponentSet = {
{
id: 'scatterchartcomponent',
type: 'scatterchartcomponent',
configuration: {},
configuration: {
zoomable: {
type: 'static',
onlyStatic: true,
fieldType: 'boolean',
value: false
},
pannable: {
type: 'static',
onlyStatic: true,
fieldType: 'boolean',
value: false
},
},
componentInput: {
type: 'static',
fieldType: 'array',
@@ -367,7 +380,19 @@ const display: ComponentSet = {
onlyStatic: true,
fieldType: 'boolean',
value: false
}
},
zoomable: {
type: 'static',
onlyStatic: true,
fieldType: 'boolean',
value: false
},
pannable: {
type: 'static',
onlyStatic: true,
fieldType: 'boolean',
value: false
},
},
componentInput: {
type: 'static',
@@ -1,6 +1,7 @@
<script lang="ts">
import { classNames } from '$lib/utils'
import { getContext } from 'svelte'
import { key } from 'svelte-awesome/icons'
import type { AppEditorContext } from '../../types'
import { displayData } from '../../utils'
import PanelSection from '../settingsPanel/common/PanelSection.svelte'
@@ -85,16 +86,19 @@
$connectingInput.hoveredComponent === componentId ? 'outline outline-blue-500' : ''
)}
>
<!-- {#if $selectedComponent === componentId && $connectingInput?.opened}
<div class="absolute top-0 left-0 w-full h-full bg-gray-500 bg-opacity-50 z-10" />
{/if} -->
<ComponentOutputViewer
{outputs}
{componentId}
on:select={({ detail }) => {
connectInput(componentId, detail)
}}
/>
{#key $selectedComponent}
{#key $connectingInput?.opened}
<ComponentOutputViewer
outputs={$connectingInput?.opened && $selectedComponent === componentId
? ['search']
: outputs}
{componentId}
on:select={({ detail }) => {
connectInput(componentId, detail)
}}
/>
{/key}
{/key}
</div>
</div>
{/if}
@@ -73,6 +73,16 @@
</Badge>
{/if}
<Button
variant="border"
size="xs"
color="blue"
on:click={async () => {
editor.format()
}}
>
Format&nbsp;<Tooltip>Ctrl+S</Tooltip>
</Button>
{#if id.startsWith('unused-')}
<Button
size="xs"
@@ -87,7 +97,6 @@
loading={runLoading}
size="xs"
color="blue"
title="Cmd+Enter"
on:click={async () => {
runLoading = true
await $runnableComponents[id]?.()
@@ -95,7 +104,7 @@
}}
>
Run&nbsp;<Tooltip
>Cmd+Enter to run the script and see the result in the component directly</Tooltip
>Ctrl+Enter to run the script and see the result in the component directly</Tooltip
>
</Button>
{/if}
@@ -135,7 +135,7 @@ declare const ${k} = ${JSON.stringify(v)};
<div class="border w-full">
<PanelSection
smallPadding
title={`Runnable inputs (${
title={`Runnable Inputs (${
Object.keys(component.componentInput.fields ?? {}).length
})`}
>
@@ -10,6 +10,8 @@
StaticAppInput,
UserAppInput
} from '../../inputType'
import { getContext } from 'svelte'
import type { AppEditorContext } from '../../types'
export let inputSpecs: Record<
string,
@@ -19,6 +21,8 @@
export let staticOnly: boolean = false
export let shouldCapitalize: boolean = true
export let rowColumns = false
const { connectingInput } = getContext<AppEditorContext>('AppEditorContext')
</script>
{#if inputSpecs}
@@ -27,7 +31,7 @@
{@const input = inputSpecs[inputSpecKey]}
<div class="flex flex-col gap-1">
<div class="flex justify-between items-end gap-1">
<span class="text-xs font-semibold">
<span class="text-sm font-semibold truncate">
{shouldCapitalize ? capitalize(inputSpecKey) : inputSpecKey}
</span>
@@ -39,7 +43,19 @@
</Badge>
{#if !inputSpecs[inputSpecKey].onlyStatic}
<ToggleButtonGroup bind:selected={inputSpecs[inputSpecKey].type}>
<ToggleButtonGroup
bind:selected={inputSpecs[inputSpecKey].type}
on:selected={(e) => {
console.log(inputSpecs[inputSpecKey])
if (e.detail == 'connected' && !inputSpecs[inputSpecKey]['connection']) {
$connectingInput = {
opened: true,
input: undefined,
hoveredComponent: undefined
}
}
}}
>
<ToggleButton
title="Static"
position="left"
@@ -71,7 +87,7 @@
/>
{/if}
<ToggleButton
title="Connected"
title="Connect"
position="right"
value="connected"
startIcon={{ icon: faArrowRight }}
@@ -30,7 +30,7 @@
{#if componentInput.type === 'connected'}
{#if componentInput.connection}
<div class="flex justify-between w-full">
<div class="flex justify-between w-full gap-1">
<span class="text-xs">Status</span>
<Badge color="green">Connected</Badge>
</div>
@@ -56,7 +56,7 @@
Disconnect
</Button>
{:else}
<div class="flex justify-between w-full">
<div class="flex justify-between w-full gap-1">
<span class="text-xs">Status</span>
<Badge color="yellow">Not connected</Badge>
</div>
+2 -1
View File
@@ -145,7 +145,8 @@ export type AppEditorContext = {
appPath: string,
workspace: string,
onchange: (() => void) | undefined,
isEditor: boolean
isEditor: boolean,
jobs: Writable<{ job: string, component: string }[]>
}
export type EditorMode = 'dnd' | 'preview'