fix: improve flow status viewer to display the step details on third tab

This commit is contained in:
Ruben Fiszel
2024-06-04 15:07:01 +02:00
parent 0108ba2603
commit f0da67f3b6
13 changed files with 89 additions and 65 deletions
@@ -50,7 +50,7 @@
noGraph ? 'border-0 w-max' : ''
)}
>
<FlowGraphViewerStep {flow} {stepDetail} />
<FlowGraphViewerStep schema={flow.schema} {stepDetail} />
</div>
{/if}
</div>
@@ -2,7 +2,7 @@
import HighlightCode from './HighlightCode.svelte'
import InputTransformsViewer from './InputTransformsViewer.svelte'
import IconedPath from './IconedPath.svelte'
import type { FlowModule, FlowValue } from '$lib/gen'
import type { FlowModule } from '$lib/gen'
import { Badge, Button, Drawer, DrawerContent } from './common'
import { Highlight } from 'svelte-highlight'
import ObjectViewer from './propertyPicker/ObjectViewer.svelte'
@@ -17,12 +17,7 @@
import FlowModuleScript from './flows/content/FlowModuleScript.svelte'
import { Copy } from 'lucide-svelte'
export let flow: {
summary: string
description?: string
value: FlowValue
schema?: any
}
export let schema: any | undefined = undefined
export let stepDetail: FlowModule | string | undefined = undefined
let codeViewer: Drawer
@@ -42,7 +37,7 @@
<IconedPath path={stepDetail?.value?.path ?? ''} />
</a>
</div>
<div class="text-2xs mb-4">
<div class="text-2xs mb-4 mt-2">
<h3 class="mb-2">Step Inputs</h3>
<InputTransformsViewer inputTransforms={stepDetail?.value?.input_transforms ?? {}} />
@@ -59,7 +54,7 @@
</div>
{/if}
{:else if stepDetail.value.type == 'rawscript'}
<div class="text-2xs mb-4">
<div class="text-2xs mb-4 mt-2">
<h3 class="mb-2">Step Inputs</h3>
<InputTransformsViewer inputTransforms={stepDetail?.value?.input_transforms ?? {}} />
</div>
@@ -68,6 +63,16 @@
<span class="!text-xs">
<HighlightCode language={stepDetail.value.language} code={stepDetail.value.content} />
</span>
<h3 class="mb-2">Lockfile</h3>
<div>
{#if stepDetail.value.lock}
<pre class="bg-surface-secondary text-sm p-2 h-full overflow-auto w-full"
>{stepDetail.value.lock}</pre
>
{:else}
<p class="bg-surface-secondary text-sm p-2"> There is no lock file for this script </p>
{/if}
</div>
{/if}
{/if}
</DrawerContent>
@@ -79,11 +84,17 @@
<p class="font-medium text-secondary text-center pt-4 pb-8">
Click on a step to see its details
</p>
<h3 class="mb-2 font-semibold">Flow Inputs</h3>
<SchemaViewer schema={flow?.schema} />
{#if schema}
<h3 class="mb-2 font-semibold">Flow Inputs</h3>
<SchemaViewer {schema} />
{/if}
</div>
{:else if stepDetail == 'Input'}
<SchemaViewer schema={flow?.schema} />
{#if schema}
<SchemaViewer {schema} />
{:else}
<p class="font-medium text-secondary text-center pt-4 pb-8"> No input schema </p>
{/if}
{:else if stepDetail == 'Result'}
<p class="font-medium text-secondary text-center pt-4 pb-8"> End of the flow </p>
{:else if typeof stepDetail != 'string' && stepDetail.value}
@@ -132,12 +143,12 @@
</p>
{:else if stepDetail.value.type == 'rawscript'}
<div class="text-xs">
<h3 class="mb-2 font-semibold">Step Inputs</h3>
<h3 class="mb-2 font-semibold mt-2">Step Inputs</h3>
<InputTransformsViewer inputTransforms={stepDetail?.value?.input_transforms ?? {}} />
</div>
<div>
<div class="mb-2 flex justify-between items-center">
<div class="mb-2 mt-4 flex justify-between items-center">
<h3 class="font-semibold">Code</h3>
<Button size="xs2" color="light" variant="contained" on:click={codeViewer.openDrawer}>
Expand
@@ -150,10 +161,22 @@
class="whitespace-pre-wrap"
/>
</div>
<h3 class="mb-2 mt-4">Lockfile</h3>
<div>
{#if stepDetail.value.lock}
<pre class="bg-surface-secondary text-xs p-2 h-full overflow-auto w-full"
>{stepDetail.value.lock}</pre
>
{:else}
<p class="bg-surface-secondary text-sm p-2">
There is no lockfile for this inline script
</p>
{/if}
</div>
</div>
{:else if stepDetail.value.type == 'script'}
<div class="text-2xs">
<h3 class="mb-2 font-semibold">Step Inputs</h3>
<h3 class="mb-2 font-semibold mt-2">Step Inputs</h3>
<InputTransformsViewer inputTransforms={stepDetail?.value?.input_transforms ?? {}} />
</div>
{#if stepDetail.value.path.startsWith('hub/')}
@@ -64,9 +64,7 @@
</Drawer>
<div
class:border={!noBorder}
class="grid {!col
? 'grid-cols-2'
: 'grid-rows-2'} shadow border border-tertiary-inverse h-full max-h-[70vh]"
class="grid {!col ? 'grid-cols-2' : 'grid-rows-2'} shadow border border-tertiary-inverse h-full"
>
<div class="bg-surface {col ? '' : 'max-h-80'} h-full p-1 overflow-auto relative">
<span class="text-tertiary">Result</span>
@@ -308,7 +308,7 @@
{/if}
<FlowProgressBar {job} bind:reset={jobProgressReset} />
</div>
<div class="overflow-y-auto grow pr-4">
<div class="overflow-y-auto grow flex flex-col pr-4">
<div class="max-h-1/2 overflow-auto border-b">
<SchemaForm
noVariablePicker
@@ -318,9 +318,10 @@
bind:args={$previewArgs}
/>
</div>
<div class="pt-4 grow">
<div class="pt-4 flex flex-col grow">
{#if jobId}
<FlowStatusViewer
wideResults
{flowStateStore}
{jobId}
on:jobsLoaded={({ detail }) => {
@@ -13,6 +13,7 @@
export let selectedJobStep: string | undefined = undefined
export let isOwner = false
export let wideResults = false
let lastJobId: string = jobId
@@ -57,4 +58,5 @@
{jobId}
{workspaceId}
{isOwner}
{wideResults}
/>
@@ -6,7 +6,8 @@
type FlowStatus,
type CompletedJob,
type QueuedJob,
type FlowModuleValue
type FlowModuleValue,
type FlowModule
} from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import FlowJobResult from './FlowJobResult.svelte'
@@ -32,6 +33,7 @@
import { dfs } from './flows/dfs'
import { writable, type Writable } from 'svelte/store'
import Alert from './common/alert/Alert.svelte'
import FlowGraphViewerStep from './FlowGraphViewerStep.svelte'
const dispatch = createEventDispatcher()
@@ -63,6 +65,8 @@
export let childFlow: boolean = false
export let reducedPolling = false
export let wideResults = false
let jobResults: any[] = []
let jobFailures: boolean[] = []
@@ -423,7 +427,7 @@
let flowTimeline: FlowTimeline
let rightColumnSelect: 'timeline' | 'detail' = 'timeline'
let rightColumnSelect: 'timeline' | 'node_status' | 'node_definition' | 'user_states' = 'timeline'
let slicedListJobIds: string[] = []
@@ -449,6 +453,8 @@
jobResults = [...new Array(lenToAdd), ...jobResults]
updateSlicedListJobIds()
}
let stepDetail: FlowModule | string | undefined = undefined
</script>
{#if notAnonynmous}
@@ -456,7 +462,7 @@
As a non logged in user, you can only see jobs ran by anonymous users like you
</Alert>
{:else if job}
<div class="flow-root w-full space-y-4 max-w-7xl mx-auto">
<div class="flow-root w-full space-y-4 {wideResults ? '' : 'max-w-7xl'} mx-auto">
<!-- {#if innerModules.length > 0 && true}
<h3 class="text-md leading-6 font-bold text-primay border-b pb-2">Flow result</h3>
{:else}
@@ -561,7 +567,7 @@
{/if}
{#if render}
{#if innerModules.length > 0 && !isListJob}
<Tabs class="mx-auto max-w-7xl" bind:selected>
<Tabs class="mx-auto {wideResults ? '' : 'max-w-7xl'}" bind:selected>
<Tab value="graph"><span class="font-semibold text-md">Graph</span></Tab>
<Tab value="sequence"><span class="font-semibold">Details</span></Tab>
</Tabs>
@@ -754,8 +760,8 @@
</div>
{#if render}
{#if job.raw_flow && !isListJob}
<div class="{selected != 'graph' ? 'hidden' : ''} mt-4">
<div class="grid grid-cols-3 border">
<div class="{selected != 'graph' ? 'hidden' : ''} grow mt-4">
<div class="grid grid-cols-3 border h-full">
<div class="col-span-2 bg-surface-secondary">
<div class="flex flex-col">
{#each Object.values($retryStatus) as count}
@@ -779,16 +785,22 @@
success={jobId != undefined && isSuccess(job?.['success'])}
flowModuleStates={$localModuleStates}
on:select={(e) => {
rightColumnSelect = 'detail'
if (rightColumnSelect != 'node_definition') {
rightColumnSelect = 'node_status'
}
if (typeof e.detail == 'string') {
if (e.detail == 'Input') {
selectedNode = 'start'
stepDetail = undefined
} else if (e.detail == 'Result') {
selectedNode = 'end'
stepDetail = 'end'
} else {
selectedNode = e.detail
stepDetail = e.detail
}
} else {
stepDetail = e.detail
selectedNode = e.detail.id
}
}}
@@ -801,7 +813,8 @@
>
<Tabs bind:selected={rightColumnSelect}>
<Tab value="timeline"><span class="font-semibold text-md">Timeline</span></Tab>
<Tab value="detail"><span class="font-semibold">Details</span></Tab>
<Tab value="node_status"><span class="font-semibold">Node status</span></Tab>
<Tab value="node_definition"><span class="font-semibold">Node definition</span></Tab>
{#if Object.keys(job?.flow_status?.user_states ?? {}).length > 0}
<Tab value="user_states"><span class="font-semibold">User States</span></Tab>
{/if}
@@ -815,8 +828,8 @@
flowModules={dfs(job.raw_flow?.modules ?? [], (x) => x.id)}
durationStatuses={localDurationStatuses}
/>
{:else if rightColumnSelect == 'detail'}
<div class="pt-2 h-full">
{:else if rightColumnSelect == 'node_status'}
<div class="pt-2 min-h-[80vh]">
{#if selectedNode}
{@const node = $localModuleStates[selectedNode]}
@@ -887,6 +900,8 @@
{:else}<p class="p-2 text-tertiary italic">Select a node to see its details here</p
>{/if}
</div>
{:else if rightColumnSelect == 'node_definition'}
<FlowGraphViewerStep {stepDetail} />
{:else if rightColumnSelect == 'user_states'}
<div class="p-2">
<JobArgs argLabel="Key" args={job?.flow_status?.user_states ?? {}} />
@@ -7,19 +7,11 @@
import FieldHeader from './FieldHeader.svelte'
import { copyToClipboard } from '../utils'
import FlowGraphViewer from './FlowGraphViewer.svelte'
import { setContext } from 'svelte'
import {
SVELVET_CONTEXT_KEY,
type SvelvetSettingsContext
} from './graph/svelvet/container/models'
import { Clipboard } from 'lucide-svelte'
import YAML from 'yaml'
import { yaml } from 'svelte-highlight/languages'
setContext<SvelvetSettingsContext>(SVELVET_CONTEXT_KEY, {
fullHeight: true
})
export let flow: {
summary: string
description?: string
@@ -10,6 +10,7 @@
let code: string
let language: SupportedLanguage
let lock: string | undefined = undefined
let notFound = false
async function loadCode(path: string, hash: string | undefined) {
@@ -20,6 +21,7 @@
: await getScriptByPath(path!)
code = script.content
language = script.language
lock = script.lock
} catch (e) {
notFound = true
console.error(e)
@@ -35,4 +37,9 @@
{:else}
<HighlightCode {language} {code} />
{/if}
{#if lock}
<h3 class="mb-2 mt-6">Lock</h3>
<pre class="bg-surface-secondary text-xs p-2 overflow-auto w-full">{lock}</pre>
{/if}
</div>
@@ -1,6 +0,0 @@
export const SVELVET_CONTEXT_KEY = 'svelvet_settings_context' as const
export type SvelvetSettingsContext = {
/** Sets the height of the canvas to `100%` instead of an arbitrary value in pixels */
fullHeight: boolean
}
@@ -6,12 +6,9 @@
createStoreEmpty,
populateSvelvetStoreFromUserInput
} from '../../store/controllers/storeApi'
import { afterUpdate, onMount, getContext } from 'svelte'
import { afterUpdate, onMount } from 'svelte'
import GraphView from './GraphView.svelte'
import { sanitizeUserNodesAndEdges } from '../controllers/middleware'
import { SVELVET_CONTEXT_KEY, type SvelvetSettingsContext } from '../models'
const settings = getContext<SvelvetSettingsContext | undefined>(SVELVET_CONTEXT_KEY)
export let nodes: UserNodeType[]
export let edges: UserEdgeType[]
@@ -31,7 +28,6 @@
export let download: boolean = false
export let dataflow: boolean = false
export let nodeSelected: boolean = false
const fullHeight = settings?.fullHeight ?? false
// generates a unique string for each svelvet component's unique store instance
// creates a store that uses the unique sting as the key to create and look up the corresponding store
// this way we can have multiple Svelvet Components on the same page and prevent overlap of information
@@ -105,7 +101,7 @@
<!-- Now that a store has been created from the initial nodes and initial edges we drill props from the store down to the D3 GraphView along with the unique key -->
<div
class="Svelvet bg-surface-secondary"
style={`width: ${width}px; height: ${fullHeight ? '100%' : height + 'px'};`}
style={`width: ${width}px; min-height: 100%; height: ${height}px;`}
>
{#if error != ''}
<div class="error text-red-600 center-center p-4">{error}</div>
+6 -3
View File
@@ -118,9 +118,10 @@ export async function getScriptByPath(path: string): Promise<{
tag: string | undefined
concurrent_limit: number | undefined
concurrency_time_window_s: number | undefined
lock?: string
}> {
if (path.startsWith('hub/')) {
const { content, language, schema } = await ScriptService.getHubScriptByPath({ path })
const { content, language, schema, lockfile } = await ScriptService.getHubScriptByPath({ path })
return {
content,
@@ -129,7 +130,8 @@ export async function getScriptByPath(path: string): Promise<{
description: '',
tag: undefined,
concurrent_limit: undefined,
concurrency_time_window_s: undefined
concurrency_time_window_s: undefined,
lock: lockfile
}
} else {
const script = await ScriptService.getScriptByPath({
@@ -143,7 +145,8 @@ export async function getScriptByPath(path: string): Promise<{
description: script.description,
tag: script.tag,
concurrent_limit: script.concurrent_limit,
concurrency_time_window_s: script.concurrency_time_window_s
concurrency_time_window_s: script.concurrency_time_window_s,
lock: script.lock
}
}
}
@@ -451,7 +451,7 @@
<svelte:fragment slot="flow_step">
{#if stepDetail}
<FlowGraphViewerStep {flow} {stepDetail} />
<FlowGraphViewerStep schema={flow.schema} {stepDetail} />
{/if}
</svelte:fragment>
</DetailPageLayout>
@@ -707,16 +707,9 @@
{#if job?.['scheduled_for'] && forLater(job?.['scheduled_for'])}
<div class="max-w-7xl mx-auto w-full px-4 sm:px-0">
<h2 class="mt-10">Scheduled to be executed later: {displayDate(job?.['scheduled_for'])}</h2>
<div class="w-full pt-8">
<LogViewer
jobId={job.id}
isLoading={!(job && 'logs' in job && job.logs)}
content={job?.logs}
tag={job?.tag}
/>
</div>
</div>
{:else if job?.job_kind !== 'flow' && job?.job_kind !== 'flowpreview' && job?.job_kind !== 'singlescriptflow'}
{/if}
{#if job?.job_kind !== 'flow' && job?.job_kind !== 'flowpreview' && job?.job_kind !== 'singlescriptflow'}
<div class="max-w-7xl mx-auto w-full px-4 sm:px-0 mb-10">
{#if job?.flow_status && typeof job.flow_status == 'object' && !('_metadata' in job.flow_status)}
<div class="mt-10" />