mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 16:02:36 +00:00
feat: split the raw-app runnable viewer and test panel into tabs
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016eu85JrwsztjfXGNQAATRz
This commit is contained in:
co-authored by
Claude Opus 5
parent
ffdf17ef8d
commit
744de141ac
@@ -10,7 +10,7 @@
|
||||
type UserAppInput,
|
||||
type CtxAppInput
|
||||
} from '../apps/inputType'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { createEventDispatcher, untrack } from 'svelte'
|
||||
import RawAppInlineScriptEditor from './RawAppInlineScriptEditor.svelte'
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||
import Tabs from '../common/tabs/Tabs.svelte'
|
||||
@@ -75,7 +75,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
let selectedTab = $state('test')
|
||||
let selectedTab = $state(isRunnableByPath(runnable) ? 'view' : 'test')
|
||||
let args = $state({})
|
||||
let hubFlowPreview: OpenFlow | undefined = $state(undefined)
|
||||
|
||||
@@ -125,6 +125,16 @@
|
||||
}
|
||||
})
|
||||
|
||||
// 'view' only exists in the runnable-by-path layout: land on it there, leave it otherwise
|
||||
let isByPath = $derived(isRunnableByPath(runnable))
|
||||
$effect(() => {
|
||||
if (isByPath) {
|
||||
untrack(() => (selectedTab = 'view'))
|
||||
} else if (untrack(() => selectedTab) == 'view') {
|
||||
untrack(() => (selectedTab = 'test'))
|
||||
}
|
||||
})
|
||||
|
||||
// Helper to get actual ctx value for testing
|
||||
function getCtxValue(expr: string): any {
|
||||
switch (expr) {
|
||||
@@ -199,41 +209,188 @@
|
||||
bind:job={testJob}
|
||||
/>
|
||||
|
||||
{#if isRunnableByPath(runnable) || (isRunnableByName(runnable) && runnable.inlineScript)}
|
||||
{#snippet inputsPanel()}
|
||||
{#if runnable?.fields}
|
||||
<div class="w-full flex flex-col gap-4 p-2">
|
||||
{#each Object.keys(runnable.fields) as k}
|
||||
{@const meta = runnable.fields[k]}
|
||||
<RawAppInputsSpecEditor
|
||||
key={k}
|
||||
bind:componentInput={runnable.fields[k]}
|
||||
{id}
|
||||
shouldCapitalize
|
||||
fieldType={meta?.['fieldType']}
|
||||
subFieldType={meta?.['subFieldType']}
|
||||
format={meta?.['format']}
|
||||
selectOptions={meta?.['selectOptions']}
|
||||
tooltip={meta?.['tooltip']}
|
||||
placeholder={meta?.['placeholder']}
|
||||
customTitle={meta?.['customTitle']}
|
||||
loading={meta?.['loading']}
|
||||
documentationLink={meta?.['documentationLink']}
|
||||
allowTypeChange={meta?.['allowTypeChange']}
|
||||
displayType
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-primary text-xs">No inputs</div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
{#snippet testPanel()}
|
||||
{#if debugMode && isDebuggableScript}
|
||||
<div transition:slide={{ duration: 200 }}>
|
||||
<DebugToolbar
|
||||
connected={$debugState.connected}
|
||||
running={$debugState.running}
|
||||
stopped={$debugState.stopped}
|
||||
breakpointCount={editorDebugState.debugBreakpoints?.size ?? 0}
|
||||
onStart={() => inlineScriptEditor?.startDebugging() ?? Promise.resolve()}
|
||||
onStop={() => inlineScriptEditor?.stopDebugging() ?? Promise.resolve()}
|
||||
onContinue={() => inlineScriptEditor?.continueExecution() ?? Promise.resolve()}
|
||||
onStepOver={() => inlineScriptEditor?.stepOver() ?? Promise.resolve()}
|
||||
onStepIn={() => inlineScriptEditor?.stepIn() ?? Promise.resolve()}
|
||||
onStepOut={() => inlineScriptEditor?.stepOut() ?? Promise.resolve()}
|
||||
onClearBreakpoints={() => inlineScriptEditor?.clearAllBreakpoints()}
|
||||
onExitDebug={() => inlineScriptEditor?.toggleDebugMode()}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<SplitPanesWrapper>
|
||||
<Splitpanes horizontal class="grow">
|
||||
<Pane size={50}>
|
||||
<div class="px-2 py-3 h-full overflow-auto">
|
||||
<div class="mx-auto w-fit">
|
||||
<RunButton
|
||||
isLoading={testIsLoading}
|
||||
onRun={testPreview}
|
||||
onCancel={async () => {
|
||||
if (jobLoader) {
|
||||
await jobLoader.cancelJob()
|
||||
}
|
||||
}}
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
<SchemaForm
|
||||
on:keydownCmdEnter={testPreview}
|
||||
workspace={opWs}
|
||||
disabledArgs={Object.entries(runnable?.fields ?? {})
|
||||
.filter(([_, v]) => v.type == 'static')
|
||||
.map(([k]) => k)}
|
||||
schema={runnable ? getSchema(runnable) : {}}
|
||||
bind:args
|
||||
/>
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane size={50}>
|
||||
{#if showDebugPanel || hasDebugResult}
|
||||
<Splitpanes horizontal class="h-full">
|
||||
<Pane size={50} minSize={15}>
|
||||
<Splitpanes horizontal class="h-full">
|
||||
<Pane size={50} minSize={10}>
|
||||
<LogViewer
|
||||
small
|
||||
content={$debugState.logs}
|
||||
isLoading={$debugState.running && !$debugState.stopped}
|
||||
tag={undefined}
|
||||
/>
|
||||
</Pane>
|
||||
<Pane size={50} minSize={10}>
|
||||
{#if hasDebugResult}
|
||||
<div class="h-full p-2 overflow-auto">
|
||||
<DisplayResult
|
||||
result={$debugState.result}
|
||||
language={runnable?.inlineScript?.language}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="h-full flex items-center justify-center text-sm text-tertiary">
|
||||
{#if $debugState.running && !$debugState.stopped}
|
||||
Running...
|
||||
{:else if $debugState.stopped}
|
||||
Paused at breakpoint
|
||||
{:else}
|
||||
Waiting for debug session
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
</Pane>
|
||||
<Pane size={50} minSize={15}>
|
||||
<DebugPanel
|
||||
stackFrames={$debugState.stackFrames}
|
||||
scopes={$debugState.scopes}
|
||||
variables={$debugState.variables}
|
||||
client={dapClient}
|
||||
bind:selectedFrameId={selectedDebugFrameId}
|
||||
/>
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
{:else if debugMode && isDebuggableScript}
|
||||
<div class="h-full flex items-center justify-center text-sm text-tertiary">
|
||||
Click "Debug" in the toolbar to start debugging
|
||||
</div>
|
||||
{:else}
|
||||
<RunnableJobPanelInner frontendJob={false} {testJob} {testIsLoading} />
|
||||
{/if}
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
</SplitPanesWrapper>
|
||||
{/snippet}
|
||||
|
||||
{#if isRunnableByPath(runnable)}
|
||||
<div class="h-full w-full flex flex-col min-h-0">
|
||||
<Tabs bind:selected={selectedTab}>
|
||||
<Tab value="view" label={runnable.runType == 'flow' ? 'Flow' : 'Script'} />
|
||||
<Tab value="test" label="Test" />
|
||||
<Tab value="inputs" label="Inputs" />
|
||||
{#snippet content()}
|
||||
<div class="grow min-h-0 flex flex-col">
|
||||
{#if selectedTab == 'inputs'}
|
||||
{@render inputsPanel()}
|
||||
{:else if selectedTab == 'test'}
|
||||
{@render testPanel()}
|
||||
{:else}
|
||||
<InlineScriptRunnableByPath
|
||||
rawApps
|
||||
bind:runnable
|
||||
bind:fields={runnable.fields}
|
||||
bind:hubFlowPreview
|
||||
on:fork={(e) => fork(e.detail)}
|
||||
on:delete
|
||||
{id}
|
||||
isLoading={testIsLoading}
|
||||
onRun={testPreview}
|
||||
onCancel={async () => {
|
||||
if (jobLoader) {
|
||||
await jobLoader.cancelJob()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
</Tabs>
|
||||
</div>
|
||||
{:else if isRunnableByName(runnable) && runnable.inlineScript}
|
||||
<Splitpanes>
|
||||
<Pane size={55}>
|
||||
{#if isRunnableByName(runnable)}
|
||||
<RawAppInlineScriptEditor
|
||||
bind:this={inlineScriptEditor}
|
||||
on:createScriptFromInlineScript={() => dispatch('createScriptFromInlineScript', runnable)}
|
||||
{id}
|
||||
bind:inlineScript={runnable.inlineScript}
|
||||
bind:name={runnable.name}
|
||||
bind:fields={runnable.fields}
|
||||
bind:delete_after_secs={runnable.delete_after_secs}
|
||||
onRun={testPreview}
|
||||
on:delete
|
||||
path={appPath}
|
||||
{onSelectionChange}
|
||||
/>
|
||||
{:else if isRunnableByPath(runnable)}
|
||||
<InlineScriptRunnableByPath
|
||||
rawApps
|
||||
bind:runnable
|
||||
bind:fields={runnable.fields}
|
||||
bind:hubFlowPreview
|
||||
on:fork={(e) => fork(e.detail)}
|
||||
on:delete
|
||||
{id}
|
||||
isLoading={testIsLoading}
|
||||
onRun={testPreview}
|
||||
onCancel={async () => {
|
||||
if (jobLoader) {
|
||||
await jobLoader.cancelJob()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
<RawAppInlineScriptEditor
|
||||
bind:this={inlineScriptEditor}
|
||||
on:createScriptFromInlineScript={() => dispatch('createScriptFromInlineScript', runnable)}
|
||||
{id}
|
||||
bind:inlineScript={runnable.inlineScript}
|
||||
bind:name={runnable.name}
|
||||
bind:fields={runnable.fields}
|
||||
bind:delete_after_secs={runnable.delete_after_secs}
|
||||
onRun={testPreview}
|
||||
on:delete
|
||||
path={appPath}
|
||||
{onSelectionChange}
|
||||
/>
|
||||
</Pane>
|
||||
<Pane size={45}>
|
||||
<Tabs bind:selected={selectedTab}>
|
||||
@@ -241,135 +398,9 @@
|
||||
<Tab value="inputs" label="Inputs" />
|
||||
{#snippet content()}
|
||||
{#if selectedTab == 'inputs'}
|
||||
{#if runnable?.fields}
|
||||
<div class="w-full flex flex-col gap-4 p-2">
|
||||
{#each Object.keys(runnable.fields) as k}
|
||||
{@const meta = runnable.fields[k]}
|
||||
<RawAppInputsSpecEditor
|
||||
key={k}
|
||||
bind:componentInput={runnable.fields[k]}
|
||||
{id}
|
||||
shouldCapitalize
|
||||
fieldType={meta?.['fieldType']}
|
||||
subFieldType={meta?.['subFieldType']}
|
||||
format={meta?.['format']}
|
||||
selectOptions={meta?.['selectOptions']}
|
||||
tooltip={meta?.['tooltip']}
|
||||
placeholder={meta?.['placeholder']}
|
||||
customTitle={meta?.['customTitle']}
|
||||
loading={meta?.['loading']}
|
||||
documentationLink={meta?.['documentationLink']}
|
||||
allowTypeChange={meta?.['allowTypeChange']}
|
||||
displayType
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-primary text-xs">No inputs</div>
|
||||
{/if}
|
||||
{:else if selectedTab == 'test'}
|
||||
{#if debugMode && isDebuggableScript}
|
||||
<div transition:slide={{ duration: 200 }}>
|
||||
<DebugToolbar
|
||||
connected={$debugState.connected}
|
||||
running={$debugState.running}
|
||||
stopped={$debugState.stopped}
|
||||
breakpointCount={editorDebugState.debugBreakpoints?.size ?? 0}
|
||||
onStart={() => inlineScriptEditor?.startDebugging() ?? Promise.resolve()}
|
||||
onStop={() => inlineScriptEditor?.stopDebugging() ?? Promise.resolve()}
|
||||
onContinue={() => inlineScriptEditor?.continueExecution() ?? Promise.resolve()}
|
||||
onStepOver={() => inlineScriptEditor?.stepOver() ?? Promise.resolve()}
|
||||
onStepIn={() => inlineScriptEditor?.stepIn() ?? Promise.resolve()}
|
||||
onStepOut={() => inlineScriptEditor?.stepOut() ?? Promise.resolve()}
|
||||
onClearBreakpoints={() => inlineScriptEditor?.clearAllBreakpoints()}
|
||||
onExitDebug={() => inlineScriptEditor?.toggleDebugMode()}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<SplitPanesWrapper>
|
||||
<Splitpanes horizontal class="grow">
|
||||
<Pane size={50}>
|
||||
<div class="px-2 py-3 h-full overflow-auto">
|
||||
<div class="mx-auto w-fit">
|
||||
<RunButton
|
||||
isLoading={testIsLoading}
|
||||
onRun={testPreview}
|
||||
onCancel={async () => {
|
||||
if (jobLoader) {
|
||||
await jobLoader.cancelJob()
|
||||
}
|
||||
}}
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
<SchemaForm
|
||||
on:keydownCmdEnter={testPreview}
|
||||
workspace={opWs}
|
||||
disabledArgs={Object.entries(runnable?.fields ?? {})
|
||||
.filter(([_, v]) => v.type == 'static')
|
||||
.map(([k]) => k)}
|
||||
schema={runnable ? getSchema(runnable) : {}}
|
||||
bind:args
|
||||
/>
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane size={50}>
|
||||
{#if showDebugPanel || hasDebugResult}
|
||||
<Splitpanes horizontal class="h-full">
|
||||
<Pane size={50} minSize={15}>
|
||||
<Splitpanes horizontal class="h-full">
|
||||
<Pane size={50} minSize={10}>
|
||||
<LogViewer
|
||||
small
|
||||
content={$debugState.logs}
|
||||
isLoading={$debugState.running && !$debugState.stopped}
|
||||
tag={undefined}
|
||||
/>
|
||||
</Pane>
|
||||
<Pane size={50} minSize={10}>
|
||||
{#if hasDebugResult}
|
||||
<div class="h-full p-2 overflow-auto">
|
||||
<DisplayResult
|
||||
result={$debugState.result}
|
||||
language={runnable?.inlineScript?.language}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="h-full flex items-center justify-center text-sm text-tertiary"
|
||||
>
|
||||
{#if $debugState.running && !$debugState.stopped}
|
||||
Running...
|
||||
{:else if $debugState.stopped}
|
||||
Paused at breakpoint
|
||||
{:else}
|
||||
Waiting for debug session
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
</Pane>
|
||||
<Pane size={50} minSize={15}>
|
||||
<DebugPanel
|
||||
stackFrames={$debugState.stackFrames}
|
||||
scopes={$debugState.scopes}
|
||||
variables={$debugState.variables}
|
||||
client={dapClient}
|
||||
bind:selectedFrameId={selectedDebugFrameId}
|
||||
/>
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
{:else if debugMode && isDebuggableScript}
|
||||
<div class="h-full flex items-center justify-center text-sm text-tertiary">
|
||||
Click "Debug" in the toolbar to start debugging
|
||||
</div>
|
||||
{:else}
|
||||
<RunnableJobPanelInner frontendJob={false} {testJob} {testIsLoading} />
|
||||
{/if}
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
</SplitPanesWrapper>
|
||||
{@render inputsPanel()}
|
||||
{:else}
|
||||
{@render testPanel()}
|
||||
{/if}
|
||||
{/snippet}
|
||||
</Tabs>
|
||||
|
||||
Reference in New Issue
Block a user