Files
windmill/frontend/src/lib/components/scriptEditor/LogPanel.svelte
T
ebfde197fc fix(ui): capture v2 UX (#4954)
* feat: captures

* flow UI and improvements

* fix: build

* fix sqlx

* Move Capture WIP

* Add capture to webhook and websocket

* Move connection status viewer in the head

* change trigger section label

* Add popover capture picker using melt ui

* Add shortcut to triggers capture from input form

* remove capture tab in input

* Allways show capture

* remove useless logs

* Add email capture

* Add kafka capture into triggers

* Add edit option in capture table

* use light header for arg input

* Add prefilled group id

* Change button label

* fix logic

* Open resource drawer if prototype has fields

* fix default completion

* Change name Prototype

* Dissociate Editor Mode for triggers

* Fix bug for script

* Fix apply args

* fix apply schema

* Add capture table to script

* fix apply args

* Add capture button for script

* Delete capture tab

* Set capture on when opening triggers capture

* fix connection indicator

* fix minor issues

* Add preprocessor logic

* Use slot in log Panel for captures

* handle capture refresh in script

* Delete capture tab from script editor

* reset kafka resource on toggle static

* fix minor issue

* Allow resource in kafka capture

* use simple capture button in flow

* Remove capture panel

* Polish route trigger editor

* Fix resource saving

* Remove excessive padding

* merge nits

* Add history tab

# Conflicts:
#	frontend/src/lib/components/EditableSchemaForm.svelte

* add workflow_dispatch to build

* add workflow_dispatch to build

* Move input copy from in a panel

* better capture UI

* Add smoothing animation on open input editor

* fix bad wrapping in input editor menu

* clean code

* restore captureTable

* clean

* fix sqlx

* fix build

* fix build

* build

* make initial_messages optional in line with db

* fix npm check

* better handle args for capture webhook and http

* add capture pagination

* Add capture table

* Add table for history inputs

* nit

* simplify capture table

* stop capturing on closing capture section

* show only 10 items per page for captures

* set focus to logs on script test

* Fix collapsable section

* nit

* open captures when applying args from triggers

* update edit input drawer

* fix all EditableSchemaForm

* fix panel init animation

* Add capture popover

* change open tab button

* clean code

* fix bad table display

* Fix JSON input bad sync

* make capture icon bigger

* nit

* nit

* revert unwanted change

* Change capture name and nits

* infinite scroll and cleaning

* Add toast when failing to retreive first step input

* add icons to dropdown edit input list

* remove unused log

* Add click outside to all schema pickers

* fix click oustide

* Fix migrations

* solve run button not refreshing properly

* Add a capture drawer

* change apply args and schema logic

* change apply args and schema logic

* fix toggle display

* fix schema display

* improve preview mode

* Add first step panel

* nit

* clean

* Add hint for captures

* clean

* nit

* nit

* fix script node page height

* improve animation on redirecting to capture

* improve saved input picker

* nit

* add Json placeholder

* nit

* add list animations

* delete useless logs

* add animated list to captures history and saved inputs

* Delete unused component

* clean

* nit

* clean code

* improve tab scroll experience

* nit

* nits

* nits

* fix script args update

* nit

* nit: include preview in history inputs in edit mode

---------

Co-authored-by: HugoCasa <hugo@casademont.ch>
Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2025-01-13 14:45:38 +01:00

269 lines
8.2 KiB
Svelte

<script lang="ts">
import {
type CompletedJob,
type Job,
JobService,
OpenAPI,
type Preview,
type WorkflowStatus
} from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { base } from '$lib/base'
import { displayDate } from '$lib/utils'
import Tabs from '../common/tabs/Tabs.svelte'
import Tab from '../common/tabs/Tab.svelte'
import DisplayResult from '../DisplayResult.svelte'
import Drawer from '../common/drawer/Drawer.svelte'
import DrawerContent from '../common/drawer/DrawerContent.svelte'
import HighlightCode from '../HighlightCode.svelte'
import LogViewer from '../LogViewer.svelte'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import SplitPanesWrapper from '../splitPanes/SplitPanesWrapper.svelte'
import { CheckCircle2, Loader2, XCircle } from 'lucide-svelte'
import type Editor from '../Editor.svelte'
import type DiffEditor from '../DiffEditor.svelte'
import ScriptFix from '../copilot/ScriptFix.svelte'
import Cell from '../table/Cell.svelte'
import DataTable from '../table/DataTable.svelte'
import Head from '../table/Head.svelte'
import WorkflowTimeline from '../WorkflowTimeline.svelte'
import Tooltip from '$lib/components/Tooltip.svelte'
export let lang: Preview['language'] | undefined
export let previewIsLoading = false
export let previewJob: Job | undefined
export let pastPreviews: CompletedJob[] = []
export let editor: Editor | undefined = undefined
export let diffEditor: DiffEditor | undefined = undefined
export let args: Record<string, any> | undefined = undefined
export let workspace: string | undefined = undefined
export let showCaptures: boolean = false
type DrawerContent = {
mode: 'json' | Preview['language'] | 'plain'
title: string
content: any
}
let selectedTab = 'logs'
let drawerOpen: boolean = false
let drawerContent: DrawerContent | undefined = undefined
export function setFocusToLogs() {
selectedTab = 'logs'
}
function openDrawer(newContent: DrawerContent) {
drawerContent = newContent
drawerOpen = true
}
function closeDrawer() {
drawerOpen = false
}
function asWorkflowStatus(x: any): Record<string, WorkflowStatus> {
return x as Record<string, WorkflowStatus>
}
let forceJson = false
</script>
<Drawer bind:open={drawerOpen} size="800px">
<DrawerContent title={drawerContent?.title} on:close={() => closeDrawer()}>
{#if drawerContent?.mode === 'json'}
<DisplayResult
workspaceId={previewJob?.workspace_id}
jobId={previewJob?.id}
result={drawerContent.content}
/>
{:else if drawerContent?.mode === 'plain'}
<pre
class="overflow-x-auto break-words relative h-full m-2 text-xs bg-surface shadow-inner p-2"
>{drawerContent?.content}
</pre>
{:else if drawerContent?.mode}
<HighlightCode language={drawerContent?.mode} code={drawerContent?.content} />
{/if}
</DrawerContent>
</Drawer>
<div class="h-full flex flex-col">
<Tabs bind:selected={selectedTab} class="pt-1" wrapperClass="flex-none">
<Tab value="logs" size="xs">Logs & Result</Tab>
<Tab value="history" size="xs">History</Tab>
{#if showCaptures}
<Tab value="captures" size="xs">Trigger captures</Tab>
{/if}
<svelte:fragment slot="content">
<div class="grow min-h-0">
{#if selectedTab === 'logs'}
<SplitPanesWrapper>
<Splitpanes horizontal>
{#if previewJob?.is_flow_step == false && previewJob?.flow_status && !(typeof previewJob.flow_status == 'object' && '_metadata' in previewJob.flow_status)}
<Pane class="relative">
<WorkflowTimeline
flow_status={asWorkflowStatus(previewJob.flow_status)}
flowDone={previewJob.type == 'CompletedJob'}
/>
</Pane>
{/if}
<Pane class="relative">
<LogViewer
jobId={previewJob?.id}
duration={previewJob?.['duration_ms']}
mem={previewJob?.['mem_peak']}
content={previewJob?.logs}
isLoading={previewJob?.['running'] == false && previewIsLoading}
tag={previewJob?.tag}
/>
</Pane>
<Pane>
<slot />
{#if previewJob != undefined && 'result' in previewJob}
<div class="relative w-full h-full p-2">
<div class="relative">
<DisplayResult
bind:forceJson
workspaceId={previewJob?.workspace_id}
jobId={previewJob?.id}
result={previewJob.result}
>
<svelte:fragment slot="copilot-fix">
{#if lang && editor && diffEditor && args && previewJob?.result && typeof previewJob?.result == 'object' && `error` in previewJob?.result && previewJob?.result.error}
<ScriptFix
error={JSON.stringify(previewJob.result.error)}
{lang}
{editor}
{diffEditor}
{args}
/>
{/if}
</svelte:fragment>
</DisplayResult>
</div>
</div>
{:else}
<div class="text-sm text-tertiary p-2 flex justify-between items-center">
<span>
{#if previewIsLoading}
<Loader2 class="animate-spin" />
{:else}
Test to see the result here
{/if}
</span>
<Tooltip
documentationLink="https://www.windmill.dev/docs/core_concepts/rich_display_rendering"
>
The result renderer in Windmill supports rich display rendering, allowing you
to customize the display format of your results.
</Tooltip>
</div>
{/if}
</Pane>
</Splitpanes>
</SplitPanesWrapper>
{/if}
{#if selectedTab === 'history'}
<div>
<DataTable size="xs" noBorder>
<Head>
<tr>
<Cell first>Id</Cell>
<Cell>Created at</Cell>
<Cell>Success</Cell>
<Cell>Result</Cell>
<Cell>Code</Cell>
<Cell last>Logs</Cell>
</tr>
</Head>
<tbody class="divide-y">
{#each pastPreviews as { id, created_at, success }}
<tr>
<Cell first>
<a
class="pr-3"
href="{base}/run/{id}?workspace={workspace ?? $workspaceStore}"
target="_blank">{id.substring(30)}</a
>
</Cell>
<Cell>{displayDate(created_at)}</Cell>
<Cell>
{#if success}
<CheckCircle2 size={10} class="text-green-600" />
{:else}
<XCircle size={10} class="text-red-700" />
{/if}
</Cell>
<Cell>
<button
class="text-xs"
on:click|preventDefault={() => {
openDrawer({ mode: 'json', content: undefined, title: 'Result' })
JobService.getCompletedJobResult({
workspace: workspace ?? $workspaceStore ?? 'NO_W',
id
}).then((res) => {
drawerContent && (drawerContent.content = res)
})
}}
>
See Result
</button>
</Cell>
<Cell>
<button
class="text-xs"
on:click|preventDefault={async () => {
const code = (
await JobService.getCompletedJob({
workspace: workspace ?? $workspaceStore ?? 'NO_W',
id
})
).raw_code
openDrawer({
mode: lang ?? 'plain',
content: String(code),
title: `Code ${lang}`
})
}}
>
View code
</button>
</Cell>
<Cell last>
<button
class="text-xs"
on:click|preventDefault={async () => {
const logs = await (
await fetch(
OpenAPI.BASE +
`/w/${workspace ?? $workspaceStore}/jobs_u/get_logs/${id}`
)
).text()
console.log(logs)
openDrawer({
mode: 'plain',
content: String(logs),
title: `Logs for ${id}`
})
}}
>
View logs
</button>
</Cell>
</tr>
{/each}
</tbody>
</DataTable>
</div>
{/if}
{#if selectedTab === 'captures'}
<slot name="capturesTab" />
{/if}
</div>
</svelte:fragment>
</Tabs>
</div>