mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-19 08:01:25 +00:00
UX flow improvements
This commit is contained in:
@@ -190,7 +190,9 @@
|
||||
|
||||
<Drawer bind:this={flowViewer} size="1200px">
|
||||
<DrawerContent title="View Graph" on:close={flowViewer.closeDrawer} noPadding>
|
||||
<FlowGraphViewer flow={$flowStore} overflowAuto />
|
||||
<div class="overflow-hidden h-full w-full">
|
||||
<FlowGraphViewer flow={$flowStore} />
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import ObjectViewer from './propertyPicker/ObjectViewer.svelte'
|
||||
import typescript from 'svelte-highlight/languages/typescript'
|
||||
import { cleanExpr } from './flows/utils'
|
||||
import FlowPathViewer from './flows/content/FlowPathViewer.svelte'
|
||||
export let flow: {
|
||||
summary: string
|
||||
description?: string
|
||||
@@ -17,8 +18,11 @@
|
||||
schema?: any
|
||||
}
|
||||
export let overflowAuto = false
|
||||
export let noSide = false
|
||||
|
||||
let stepDetail: FlowModule | undefined = undefined
|
||||
let codeViewer: Drawer
|
||||
let topHeight = 0
|
||||
</script>
|
||||
|
||||
<Drawer bind:this={codeViewer} size="900px">
|
||||
@@ -72,99 +76,106 @@
|
||||
{/if}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
<div class="grid grid-cols-3 w-full h-full">
|
||||
<div class="h-full col-span-2 w-full border border-gray-700" class:overflow-auto={overflowAuto}>
|
||||
<div class="grid grid-cols-3 w-full">
|
||||
<div
|
||||
bind:clientHeight={topHeight}
|
||||
class="{noSide ? 'col-span-3' : 'col-span-2'} w-full border border-gray-400 h-screen"
|
||||
class:overflow-auto={overflowAuto}
|
||||
>
|
||||
<FlowGraph
|
||||
minHeight={topHeight}
|
||||
modules={flow?.value?.modules}
|
||||
failureModule={flow?.value?.failure_module}
|
||||
on:click={(e) => (stepDetail = e.detail)}
|
||||
/>
|
||||
</div>
|
||||
<div class="w-full border-r border-b border-t border-gray-700 min-h-[150px] p-2 overflow-auto">
|
||||
{#if stepDetail == undefined}
|
||||
<span class="font-black text-lg w-full my-4">
|
||||
<span>Click on a step to see its details</span>
|
||||
</span>
|
||||
{:else}
|
||||
<div class="font-black text-lg w-full mb-6"
|
||||
>Step {stepDetail.id ?? ''}<span class="ml-2 font-normal">{stepDetail.summary || ''}</span
|
||||
></div
|
||||
>
|
||||
{#if stepDetail.value.type == 'identity'}
|
||||
<div> An identity step return as output its input </div>
|
||||
{:else if stepDetail.value.type == 'rawscript'}
|
||||
<div class="text-2xs mb-4">
|
||||
<h3 class="mb-2">Step Inputs</h3>
|
||||
<InputTransformsViewer
|
||||
inputTransforms={stepDetail?.value?.input_transforms ??
|
||||
stepDetail?.input_transforms ??
|
||||
{}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h3 class="mb-2"
|
||||
>Code <button class="text-gray-600 text-xs" on:click={codeViewer.openDrawer}
|
||||
>Expand</button
|
||||
>
|
||||
</h3>
|
||||
<span class="!text-xs">
|
||||
<HighlightCode language={stepDetail.value.language} code={stepDetail.value.content} />
|
||||
{#if !noSide}
|
||||
<div class="w-full border-r border-b border-t border-gray-400 min-h-[150px] p-2 overflow-auto">
|
||||
{#if stepDetail == undefined}
|
||||
<span class="font-black text-lg w-full my-4">
|
||||
<span>Click on a step to see its details</span>
|
||||
</span>
|
||||
{:else if stepDetail.value.type == 'script'}
|
||||
<div class="mb-4">
|
||||
<a
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
href={scriptPathToHref(stepDetail?.value?.path ?? '')}
|
||||
class=""
|
||||
>
|
||||
<IconedPath path={stepDetail?.value?.path ?? ''} />
|
||||
</a>
|
||||
</div>
|
||||
<div class="text-2xs mb-4">
|
||||
<h3 class="mb-2">Step Inputs</h3>
|
||||
<InputTransformsViewer
|
||||
inputTransforms={stepDetail?.value?.input_transforms ??
|
||||
stepDetail?.input_transforms ??
|
||||
{}}
|
||||
/>
|
||||
</div>
|
||||
{#if stepDetail.value.path.startsWith('hub/')}
|
||||
<div class="mt-6">
|
||||
<h3
|
||||
>Code <button class="text-gray-600 text-xs" on:click={codeViewer.openDrawer}
|
||||
>Expand</button
|
||||
>
|
||||
</h3>
|
||||
<iframe
|
||||
class="w-full h-full text-sm"
|
||||
title="embedded script from hub"
|
||||
frameborder="0"
|
||||
src="https://hub.windmill.dev/embed/script/{stepDetail.value?.path?.substring(4)}"
|
||||
{:else}
|
||||
<div class="font-black text-lg w-full mb-6"
|
||||
>Step {stepDetail.id ?? ''}<span class="ml-2 font-normal">{stepDetail.summary || ''}</span
|
||||
></div
|
||||
>
|
||||
{#if stepDetail.value.type == 'identity'}
|
||||
<div> An identity step return as output its input </div>
|
||||
{:else if stepDetail.value.type == 'rawscript'}
|
||||
<div class="text-2xs mb-4">
|
||||
<h3 class="mb-2">Step Inputs</h3>
|
||||
<InputTransformsViewer
|
||||
inputTransforms={stepDetail?.value?.input_transforms ??
|
||||
stepDetail?.input_transforms ??
|
||||
{}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{:else if stepDetail.value.type == 'forloopflow'}
|
||||
<p
|
||||
>For loop with iterator: {#if stepDetail.value.iterator.type == 'static'}<ObjectViewer
|
||||
json={stepDetail.value.iterator.value}
|
||||
/>{:else}
|
||||
<span class="text-xs">
|
||||
<Highlight
|
||||
offsetTop={0}
|
||||
language={typescript}
|
||||
code={cleanExpr(stepDetail.value.iterator.expr)}
|
||||
|
||||
<h3 class="mb-2"
|
||||
>Code <button class="text-gray-600 text-xs" on:click={codeViewer.openDrawer}
|
||||
>Expand</button
|
||||
>
|
||||
</h3>
|
||||
<span class="!text-xs">
|
||||
<HighlightCode language={stepDetail.value.language} code={stepDetail.value.content} />
|
||||
</span>
|
||||
{:else if stepDetail.value.type == 'script'}
|
||||
<div class="mb-4">
|
||||
<a
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
href={scriptPathToHref(stepDetail?.value?.path ?? '')}
|
||||
class=""
|
||||
>
|
||||
<IconedPath path={stepDetail?.value?.path ?? ''} />
|
||||
</a>
|
||||
</div>
|
||||
<div class="text-2xs mb-4">
|
||||
<h3 class="mb-2">Step Inputs</h3>
|
||||
<InputTransformsViewer
|
||||
inputTransforms={stepDetail?.value?.input_transforms ??
|
||||
stepDetail?.input_transforms ??
|
||||
{}}
|
||||
/>
|
||||
</div>
|
||||
{#if stepDetail.value.path.startsWith('hub/')}
|
||||
<div class="mt-6">
|
||||
<h3
|
||||
>Code <button class="text-gray-600 text-xs" on:click={codeViewer.openDrawer}
|
||||
>Expand</button
|
||||
>
|
||||
</h3>
|
||||
<iframe
|
||||
class="w-full h-full text-sm"
|
||||
title="embedded script from hub"
|
||||
frameborder="0"
|
||||
src="https://hub.windmill.dev/embed/script/{stepDetail.value?.path?.substring(4)}"
|
||||
/>
|
||||
</span>
|
||||
{/if}</p
|
||||
>
|
||||
{:else if stepDetail.value.type == 'branchall'}
|
||||
<p>Run all branches</p>
|
||||
{:else if stepDetail.value.type == 'branchone'}
|
||||
<p>Run one branch based on a predicate</p>
|
||||
{:else if stepDetail.value.type == 'flow'}
|
||||
<p>Run a workspace flow</p>
|
||||
</div>
|
||||
{/if}
|
||||
{:else if stepDetail.value.type == 'forloopflow'}
|
||||
<p
|
||||
>For loop with iterator: {#if stepDetail.value.iterator.type == 'static'}<ObjectViewer
|
||||
json={stepDetail.value.iterator.value}
|
||||
/>{:else}
|
||||
<span class="text-xs">
|
||||
<Highlight
|
||||
offsetTop={0}
|
||||
language={typescript}
|
||||
code={cleanExpr(stepDetail.value.iterator.expr)}
|
||||
/>
|
||||
</span>
|
||||
{/if}</p
|
||||
>
|
||||
{:else if stepDetail.value.type == 'branchall'}
|
||||
<p>Run all branches</p>
|
||||
{:else if stepDetail.value.type == 'branchone'}
|
||||
<p>Run one branch based on a predicate</p>
|
||||
{:else if stepDetail.value.type == 'flow'}
|
||||
<FlowPathViewer noSide path={stepDetail.value.path} />
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -94,17 +94,12 @@
|
||||
|
||||
<div class="flex divide-y flex-col space-y-2 h-screen bg-white px-6 py-2 w-full">
|
||||
<div class="flex flex-row justify-between w-full items-center gap-x-2">
|
||||
<Button
|
||||
variant="border"
|
||||
size="lg"
|
||||
color="dark"
|
||||
btnClasses="!p-0 !w-16 !h-full"
|
||||
on:click={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
<button
|
||||
on:click={() => dispatch('close')}
|
||||
class="hover:bg-gray-200 bg-gray-100 rounded-full w-8 h-8 flex items-center justify-center transition-all"
|
||||
>
|
||||
<Icon data={faClose} />
|
||||
</Button>
|
||||
<Icon data={faClose} class="text-gray-500" />
|
||||
</button>
|
||||
|
||||
{#if isRunning}
|
||||
<Button
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
import { FlowGraph, type GraphModuleState } from './graph'
|
||||
import ModuleStatus from './ModuleStatus.svelte'
|
||||
import { displayDate, truncateRev } from '$lib/utils'
|
||||
import JobArgs from './JobArgs.svelte'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -338,9 +339,8 @@
|
||||
</div>
|
||||
</div>
|
||||
{#if job.raw_flow && !isListJob}
|
||||
<div class="{selected != 'graph' ? 'hidden' : ''} mx-10 mt-4">
|
||||
<div class="border" />
|
||||
<div class="grid grid-cols-3">
|
||||
<div class="{selected != 'graph' ? 'hidden' : ''} mt-4">
|
||||
<div class="grid grid-cols-3 border border-gray-300">
|
||||
<div class="col-span-2 bg-gray-50">
|
||||
<div class="flex flex-col">
|
||||
{#each Object.values(retry_status) as count}
|
||||
@@ -355,16 +355,30 @@
|
||||
on:click={(e) => {
|
||||
if (e.detail.id) {
|
||||
selectedNode = e.detail.id
|
||||
} else if (e.detail == 'End') {
|
||||
selectedNode = 'end'
|
||||
} else if (e.detail == 'Start') {
|
||||
selectedNode = 'start'
|
||||
}
|
||||
}}
|
||||
modules={job.raw_flow?.modules ?? []}
|
||||
failureModule={job.raw_flow?.failure_module}
|
||||
/>
|
||||
</div>
|
||||
<div class="border-l border-gray-400 pt-1">
|
||||
<div class="border-l border-gray-400 pt-1 overflow-hidden">
|
||||
{#if selectedNode}
|
||||
{@const node = localFlowModuleStates[selectedNode]}
|
||||
{#if node}
|
||||
{#if selectedNode == 'end'}
|
||||
<FlowJobResult noBorder col result={job['result'] ?? {}} logs={job.logs ?? ''} />
|
||||
{:else if selectedNode == 'start'}
|
||||
{#if job.args}
|
||||
<div class="p-2">
|
||||
<JobArgs args={job.args} />
|
||||
</div>
|
||||
{:else}
|
||||
<p class="p-2">No arguments</p>
|
||||
{/if}
|
||||
{:else if node}
|
||||
<div class="px-2 flex gap-2 min-w-0 ">
|
||||
<ModuleStatus type={node.type} scheduled_for={node['scheduled_for']} />
|
||||
{#if node.job_id}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
schema?: any
|
||||
}
|
||||
export let initialOpen: number | undefined = undefined
|
||||
export let noSide = false
|
||||
|
||||
$: flowFiltered = {
|
||||
summary: flow.summary,
|
||||
@@ -76,7 +77,7 @@
|
||||
{:else}
|
||||
<div class="text-gray-700 text-xs italic mb-4">No inputs</div>
|
||||
{/if}
|
||||
<FlowGraphViewer {flow} overflowAuto />
|
||||
<FlowGraphViewer {noSide} {flow} overflowAuto />
|
||||
</div>
|
||||
</TabContent>
|
||||
<TabContent value="json">
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script lang="ts">
|
||||
import Skeleton from '$lib/components/common/skeleton/Skeleton.svelte'
|
||||
import FlowViewer from '$lib/components/FlowViewer.svelte'
|
||||
import FlowGraphViewer from '$lib/components/FlowGraphViewer.svelte'
|
||||
import { FlowService, type Flow } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
|
||||
export let path: string
|
||||
export let noSide = false
|
||||
|
||||
let flow: Flow | undefined = undefined
|
||||
async function loadFlow(path: string) {
|
||||
@@ -14,9 +15,9 @@
|
||||
$: path && loadFlow(path)
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col flex-1 h-full overflow-auto p-2">
|
||||
<div class="flex flex-col flex-1 h-full overflow-auto">
|
||||
{#if flow}
|
||||
<FlowViewer {flow} />
|
||||
<FlowGraphViewer {noSide} {flow} />
|
||||
{:else}
|
||||
<Skeleton layout={[[40]]} />
|
||||
{/if}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
'inputs',
|
||||
'schedules',
|
||||
'failure'
|
||||
].includes($selectedId) || $selectedId.includes('branch')
|
||||
].includes($selectedId) || $selectedId?.includes('branch')
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row-reverse justify-between items-center gap-x-2">
|
||||
@@ -60,7 +60,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Drawer bind:open={previewOpen} size="1200px">
|
||||
<Drawer bind:open={previewOpen} size="75%">
|
||||
<FlowPreviewContent
|
||||
open={previewOpen}
|
||||
bind:previewMode
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
const { select, selectedId } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
|
||||
function onToggle() {
|
||||
if ($flowStore.value.failure_module) {
|
||||
if ($flowStore?.value?.failure_module) {
|
||||
$flowStore.value.failure_module = undefined
|
||||
// By default, we return to settings when disabling the failure module
|
||||
select('settings')
|
||||
@@ -34,7 +34,7 @@
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
on:click={() => {
|
||||
if ($flowStore.value.failure_module) {
|
||||
if ($flowStore?.value?.failure_module) {
|
||||
select('failure')
|
||||
} else {
|
||||
onToggle()
|
||||
@@ -42,7 +42,7 @@
|
||||
}}
|
||||
class={classNames(
|
||||
'border rounded-sm px-2 py-1 bg-white text-sm border-gray-400 cursor-pointer flex flex-col overflow-x-hidden ',
|
||||
$selectedId.includes('failure') ? 'outline outline-offset-1 outline-2 outline-slate-900' : ''
|
||||
$selectedId?.includes('failure') ? 'outline outline-offset-1 outline-2 outline-slate-900' : ''
|
||||
)}
|
||||
>
|
||||
<div class=" flex justify-between items-center flex-wrap">
|
||||
@@ -51,12 +51,12 @@
|
||||
<span class="font-bold text-xs">Error handler</span>
|
||||
</div>
|
||||
<div class="-my-1">
|
||||
<Toggle checked={Boolean($flowStore.value.failure_module)} on:change={onToggle} />
|
||||
<Toggle checked={Boolean($flowStore?.value?.failure_module)} on:change={onToggle} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full truncate block">
|
||||
{#if Boolean($flowStore.value.failure_module)}
|
||||
{#if Boolean($flowStore?.value?.failure_module)}
|
||||
<span>
|
||||
{$flowStore.value.failure_module?.summary ||
|
||||
($flowStore.value.failure_module?.value.type === 'rawscript'
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
let edges: Edge[] = []
|
||||
let width: number, height: number
|
||||
|
||||
let loadedFlows: Record<string, FlowModule[]> = {}
|
||||
let errorHandlers: Record<string, number> = {}
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
@@ -55,13 +54,13 @@
|
||||
}
|
||||
nestedNodes = nodes = []
|
||||
|
||||
nestedNodes.push(createVirtualNode(getParentIds(), 'Flow start'))
|
||||
nestedNodes.push(createVirtualNode(getParentIds(), 'Start'))
|
||||
|
||||
modules.forEach((m) => {
|
||||
const item = getConvertedFlowModule(m)
|
||||
item && nestedNodes.push(item)
|
||||
})
|
||||
nestedNodes.push(createVirtualNode(getParentIds(), 'Flow end'))
|
||||
nestedNodes.push(createVirtualNode(getParentIds(), 'End'))
|
||||
|
||||
if (!flowModuleStates) {
|
||||
if (failureModule) nestedNodes.push(createErrorHandler(failureModule))
|
||||
@@ -430,9 +429,15 @@
|
||||
},
|
||||
width: NODE.width,
|
||||
height: NODE.height,
|
||||
borderColor: '#999',
|
||||
bgColor: '#d4e4ff',
|
||||
parentIds
|
||||
borderColor: selectedNode == label ? 'black' : '#999',
|
||||
bgColor: selectedNode == label ? '#f5f5f5' : '#d4e4ff',
|
||||
parentIds,
|
||||
clickCallback: (node) => {
|
||||
if (!notSelectable) {
|
||||
selectedNode = label
|
||||
}
|
||||
dispatch('click', label)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,7 +464,7 @@
|
||||
width: NODE.width,
|
||||
height: NODE.height,
|
||||
bgColor: selectedNode == mod.id ? '#f5f5f5' : getStateColor(flowModuleStates?.[mod.id]?.type),
|
||||
borderColor: '#999',
|
||||
borderColor: selectedNode == mod.id ? 'black' : '#999',
|
||||
parentIds: parent_module ? [parent_module] : [],
|
||||
clickCallback: (node) => {
|
||||
if (!notSelectable) {
|
||||
|
||||
Reference in New Issue
Block a user