Compare commits
5 Commits
tutorials
...
rf/flowFix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ea08f1e8f | ||
|
|
d302c92766 | ||
|
|
128622117d | ||
|
|
150ce7f731 | ||
|
|
fae406ee8f |
@@ -2,9 +2,10 @@
|
||||
import { Highlight } from 'svelte-highlight'
|
||||
import { json } from 'svelte-highlight/languages'
|
||||
import TableCustom from './TableCustom.svelte'
|
||||
import { truncate } from '$lib/utils'
|
||||
import { Button } from './common'
|
||||
import { copyToClipboard, truncate } from '$lib/utils'
|
||||
import { Button, Drawer, DrawerContent } from './common'
|
||||
import autosize from 'svelte-autosize'
|
||||
import { ClipboardCopy } from 'lucide-svelte'
|
||||
|
||||
export let result: any
|
||||
export let requireHtmlApproval = false
|
||||
@@ -86,16 +87,36 @@
|
||||
return 'json'
|
||||
}
|
||||
let payload = ''
|
||||
|
||||
let jsonViewer: Drawer
|
||||
</script>
|
||||
|
||||
<Drawer bind:this={jsonViewer} size="900px">
|
||||
<DrawerContent title="Expanded Result" on:close={jsonViewer.closeDrawer}>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
on:click={() => copyToClipboard(JSON.stringify(result, null, 4))}
|
||||
color="light"
|
||||
size="xs"
|
||||
>
|
||||
<div class="flex gap-2 items-center">Copy to clipboard <ClipboardCopy /> </div>
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
<Highlight language={json} code={JSON.stringify(result, null, 4).replace(/\\n/g, '\n')} />
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<div class="inline-highlight">
|
||||
{#if result != undefined}
|
||||
{#if resultKind && resultKind != 'json'}
|
||||
<div class="mb-2 text-gray-500 text-sm bg-gray-50/20">
|
||||
as JSON <input type="checkbox" bind:checked={forceJson} /></div
|
||||
>{/if}{#if typeof result == 'object' && Object.keys(result).length > 0}<div
|
||||
class="mb-2 text-sm text-gray-700"
|
||||
>The result keys are: <b>{truncate(Object.keys(result).join(', '), 50)}</b></div
|
||||
class="mb-2 text-sm text-gray-700 relative"
|
||||
>The result keys are: <b>{truncate(Object.keys(result).join(', '), 50)}</b>
|
||||
<div class="text-gray-500 text-sm absolute top-0 right-2">
|
||||
<button on:click={jsonViewer.openDrawer}>Expand JSON</button>
|
||||
</div></div
|
||||
>{/if}{#if !forceJson && resultKind == 'table-col'}<div
|
||||
class="grid grid-flow-col-dense border border-gray-200 rounded-md "
|
||||
>
|
||||
@@ -223,10 +244,8 @@
|
||||
><a rel="noreferrer" target="_blank" href={result['approvalPage']}>Approval Page</a></div
|
||||
>
|
||||
</div>
|
||||
{:else}<Highlight
|
||||
language={json}
|
||||
code={JSON.stringify(result, null, 4).replace(/\\n/g, '\n')}
|
||||
/>
|
||||
{:else}
|
||||
<Highlight language={json} code={JSON.stringify(result, null, 4).replace(/\\n/g, '\n')} />
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="text-gray-500 text-sm">No result</div>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import UnsavedConfirmationModal from './common/confirmationModal/UnsavedConfirmationModal.svelte'
|
||||
import { OFFSET } from './CronInput.svelte'
|
||||
import FlowGraphViewer from './FlowGraphViewer.svelte'
|
||||
import ScriptEditorDrawer from './flows/content/ScriptEditorDrawer.svelte'
|
||||
import FlowEditor from './flows/FlowEditor.svelte'
|
||||
import { flowStateStore } from './flows/flowState'
|
||||
import { flowStore } from './flows/flowStore'
|
||||
@@ -145,6 +146,7 @@
|
||||
|
||||
const scheduleStore = writable<Schedule>({ args: {}, cron: '', enabled: false })
|
||||
const previewArgsStore = writable<Record<string, any>>(initialArgs)
|
||||
const scriptEditorDrawer = writable<ScriptEditorDrawer | undefined>(undefined)
|
||||
|
||||
function select(selectedId: string) {
|
||||
selectedIdStore.set(selectedId)
|
||||
@@ -154,7 +156,8 @@
|
||||
selectedId: selectedIdStore,
|
||||
schedule: scheduleStore,
|
||||
select,
|
||||
previewArgs: previewArgsStore
|
||||
previewArgs: previewArgsStore,
|
||||
scriptEditorDrawer
|
||||
})
|
||||
|
||||
async function loadSchedule() {
|
||||
@@ -181,6 +184,7 @@
|
||||
</script>
|
||||
|
||||
{#if !$userStore?.operator}
|
||||
<ScriptEditorDrawer bind:this={$scriptEditorDrawer} />
|
||||
<UnsavedConfirmationModal />
|
||||
|
||||
<Drawer bind:this={flowViewer} size="75%">
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
</div>
|
||||
<FlowProgressBar {job} bind:reset={jobProgressReset} />
|
||||
|
||||
<div class="overflow-y-auto grow divide-y divide-gray-600 ">
|
||||
<div class="overflow-y-auto grow divide-y divide-gray-600 pr-4">
|
||||
<div class="max-h-1/2 overflow-auto border-b border-gray-700">
|
||||
<SchemaForm
|
||||
noVariablePicker
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
localFlowModuleStates?.[innerModules?.[i - 1]?.id ?? '']?.type ==
|
||||
FlowStatusModule.type.SUCCESS
|
||||
) {
|
||||
localFlowModuleStates[mod.id ?? ''] = { type: mod.type }
|
||||
localFlowModuleStates[mod.id ?? ''] = { type: mod.type, args: job?.args }
|
||||
} else if (
|
||||
mod.type === FlowStatusModule.type.WAITING_FOR_EXECUTOR &&
|
||||
localFlowModuleStates[mod.id ?? '']?.scheduled_for == undefined
|
||||
@@ -109,7 +109,8 @@
|
||||
type: mod.type,
|
||||
scheduled_for: 'scheduled for ' + displayDate(job?.['scheduled_for'], true),
|
||||
job_id: job?.id,
|
||||
parent_module: mod['parent_module']
|
||||
parent_module: mod['parent_module'],
|
||||
args: job?.args
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -329,10 +330,12 @@
|
||||
type: FlowStatusModule.type.IN_PROGRESS,
|
||||
logs: e.detail.logs,
|
||||
job_id: e.detail.id,
|
||||
args: e.detail.args,
|
||||
iteration_total: flowJobIds?.flowJobs.length
|
||||
}
|
||||
} else {
|
||||
localFlowModuleStates[flowJobIds.moduleId] = {
|
||||
args: e.detail.args,
|
||||
type: e.detail.success
|
||||
? FlowStatusModule.type.SUCCESS
|
||||
: FlowStatusModule.type.FAILURE,
|
||||
@@ -397,10 +400,12 @@
|
||||
localFlowModuleStates[mod.id] = {
|
||||
type: FlowStatusModule.type.IN_PROGRESS,
|
||||
logs: e.detail.logs,
|
||||
args: e.detail.args,
|
||||
parent_module: mod['parent_module']
|
||||
}
|
||||
} else {
|
||||
localFlowModuleStates[mod.id] = {
|
||||
args: e.detail.args,
|
||||
type: e.detail.success
|
||||
? FlowStatusModule.type.SUCCESS
|
||||
: FlowStatusModule.type.FAILURE,
|
||||
@@ -497,6 +502,9 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="px-1 border-b border-black">
|
||||
<JobArgs args={node.args} />
|
||||
</div>
|
||||
<FlowJobResult
|
||||
loading={job['running'] == true}
|
||||
noBorder
|
||||
|
||||
@@ -110,6 +110,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
export function focus() {
|
||||
editor?.focus()
|
||||
}
|
||||
|
||||
let width = 0
|
||||
async function loadMonaco() {
|
||||
model = meditor.createModel(code, lang, mUri.parse(uri))
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<script lang="ts">
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import type { Preview } from '$lib/gen'
|
||||
import { faTrash } from '@fortawesome/free-solid-svg-icons'
|
||||
import { createEventDispatcher, getContext, onMount } from 'svelte'
|
||||
import type { AppEditorContext, InlineScript } from '../../types'
|
||||
import { CheckCircle, Code2, Maximize2, Trash2, X } from 'lucide-svelte'
|
||||
import { CheckCircle, Maximize2, Trash2, X } from 'lucide-svelte'
|
||||
import InlineScriptEditorDrawer from './InlineScriptEditorDrawer.svelte'
|
||||
import { inferArgs } from '$lib/infer'
|
||||
import type { Schema } from '$lib/common'
|
||||
|
||||
@@ -27,8 +27,7 @@
|
||||
parentModule.id,
|
||||
$flowStore,
|
||||
$previewArgs,
|
||||
false,
|
||||
true
|
||||
false
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -37,6 +36,7 @@
|
||||
pickableProperties={stepPropPicker.pickableProperties}
|
||||
on:select={({ detail }) => {
|
||||
editor?.insertAtCursor(detail)
|
||||
editor?.focus()
|
||||
}}
|
||||
>
|
||||
<div class="border border-gray-400">
|
||||
|
||||
@@ -33,8 +33,7 @@
|
||||
mod.id,
|
||||
$flowStore,
|
||||
$previewArgs,
|
||||
false,
|
||||
true
|
||||
false
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -60,6 +59,7 @@
|
||||
pickableProperties={stepPropPicker.pickableProperties}
|
||||
on:select={({ detail }) => {
|
||||
editor?.insertAtCursor(detail)
|
||||
editor?.focus()
|
||||
}}
|
||||
>
|
||||
<SimpleEditor
|
||||
|
||||
@@ -12,7 +12,12 @@
|
||||
import FlowCard from '../common/FlowCard.svelte'
|
||||
import FlowModuleHeader from './FlowModuleHeader.svelte'
|
||||
import { flowStateStore } from '../flowState'
|
||||
import { schemaToObject, scriptLangToEditorLang } from '$lib/utils'
|
||||
import {
|
||||
getLatestHashForScript,
|
||||
getScriptByPath,
|
||||
schemaToObject,
|
||||
scriptLangToEditorLang
|
||||
} from '$lib/utils'
|
||||
import PropPickerWrapper from '../propPicker/PropPickerWrapper.svelte'
|
||||
import { afterUpdate, getContext } from 'svelte'
|
||||
import type { FlowEditorContext } from '../types'
|
||||
@@ -71,8 +76,7 @@
|
||||
flowModule.id,
|
||||
$flowStore,
|
||||
$previewArgs,
|
||||
false,
|
||||
true
|
||||
false
|
||||
)
|
||||
|
||||
function onKeyDown(event: KeyboardEvent) {
|
||||
@@ -116,6 +120,8 @@
|
||||
|
||||
let isScript = true
|
||||
$: isScript != (value.type === 'script') && (isScript = value.type === 'script')
|
||||
|
||||
let forceReload = 0
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
@@ -135,6 +141,16 @@
|
||||
flowModule = module
|
||||
$flowStateStore[module.id] = state
|
||||
}}
|
||||
on:reload={async () => {
|
||||
if (flowModule.value.type == 'script') {
|
||||
console.log('reload')
|
||||
if (flowModule.value.hash != undefined) {
|
||||
flowModule.value.hash = await getLatestHashForScript(flowModule.value.path)
|
||||
}
|
||||
forceReload++
|
||||
await reload(flowModule)
|
||||
}
|
||||
}}
|
||||
on:createScriptFromInlineScript={async () => {
|
||||
const [module, state] = await createScriptFromInlineScript(
|
||||
flowModule,
|
||||
@@ -194,7 +210,9 @@
|
||||
/>
|
||||
</div>
|
||||
{:else if value.type === 'script'}
|
||||
<FlowModuleScript path={value.path} hash={value.hash} />
|
||||
{#key forceReload}
|
||||
<FlowModuleScript path={value.path} hash={value.hash} />
|
||||
{/key}
|
||||
{:else if value.type === 'flow'}
|
||||
<FlowPathViewer path={value.path} />
|
||||
{/if}
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
pickableProperties={undefined}
|
||||
on:select={({ detail }) => {
|
||||
editor?.insertAtCursor(detail)
|
||||
editor?.focus()
|
||||
}}
|
||||
>
|
||||
<SimpleEditor
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
<script lang="ts">
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import type { FlowModule } from '$lib/gen'
|
||||
import { faCodeBranch, faSave } from '@fortawesome/free-solid-svg-icons'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { faCodeBranch, faPen, faSave } from '@fortawesome/free-solid-svg-icons'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import { Bed, PhoneIncoming, Repeat, Square } from 'lucide-svelte'
|
||||
import Popover from '../../Popover.svelte'
|
||||
import type { FlowEditorContext } from '../types'
|
||||
import { getLatestHashForScript, sendUserToast } from '$lib/utils'
|
||||
|
||||
export let module: FlowModule
|
||||
const { scriptEditorDrawer } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -62,6 +65,27 @@
|
||||
{/if}
|
||||
{#if module.value.type === 'script'}
|
||||
<div class="w-2" />
|
||||
{#if !module.value.path.startsWith('hub/')}
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
variant="border"
|
||||
on:click={async () => {
|
||||
if (module.value.type == 'script') {
|
||||
const hash = module.value.hash ?? (await getLatestHashForScript(module.value.path))
|
||||
$scriptEditorDrawer?.openDrawer(hash, () => {
|
||||
dispatch('reload')
|
||||
sendUserToast('Script has been updated')
|
||||
})
|
||||
}
|
||||
}}
|
||||
startIcon={{ icon: faPen }}
|
||||
iconOnly={false}
|
||||
disabled={module.value.hash != undefined}
|
||||
>
|
||||
Edit {#if module.value.hash != undefined} (locked hash){/if}
|
||||
</Button>
|
||||
{/if}
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
pickableProperties={undefined}
|
||||
on:select={({ detail }) => {
|
||||
editor?.insertAtCursor(detail)
|
||||
editor?.focus()
|
||||
}}
|
||||
>
|
||||
<InputTransformForm
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
<script lang="ts">
|
||||
import { Button, Drawer, DrawerContent } from '$lib/components/common'
|
||||
import ScriptEditor from '$lib/components/ScriptEditor.svelte'
|
||||
import { ScriptService, type Preview } from '$lib/gen'
|
||||
import { inferArgs } from '$lib/infer'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { emptySchema, getScriptByPath, sendUserToast } from '$lib/utils'
|
||||
import { faSave } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
|
||||
let scriptEditorDrawer: Drawer
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export async function openDrawer(hash: string, cb: () => void): Promise<void> {
|
||||
script = undefined
|
||||
scriptEditorDrawer.openDrawer?.()
|
||||
script = await ScriptService.getScriptByHash({
|
||||
workspace: $workspaceStore!,
|
||||
hash
|
||||
})
|
||||
callback = cb
|
||||
}
|
||||
|
||||
let callback: (() => void) | undefined = undefined
|
||||
let script:
|
||||
| {
|
||||
path: string
|
||||
description: string
|
||||
summary: string
|
||||
hash: string
|
||||
language: Preview.language
|
||||
content: string
|
||||
schema?: any
|
||||
kind: 'script' | 'failure' | 'trigger' | 'command' | 'approval' | undefined
|
||||
}
|
||||
| undefined = undefined
|
||||
|
||||
async function saveScript(): Promise<void> {
|
||||
if (script) {
|
||||
try {
|
||||
script.schema = script.schema ?? emptySchema()
|
||||
try {
|
||||
await inferArgs(script.language, script.content, script.schema)
|
||||
} catch (error) {
|
||||
sendUserToast(
|
||||
`Impossible to infer the schema. Assuming this is a script without main function`,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
await ScriptService.createScript({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
path: script.path,
|
||||
summary: script.summary,
|
||||
description: script.description ?? '',
|
||||
content: script.content,
|
||||
parent_hash: script.hash != '' ? script.hash : undefined,
|
||||
schema: script.schema,
|
||||
is_template: false,
|
||||
language: script.language,
|
||||
kind: script.kind
|
||||
}
|
||||
})
|
||||
callback?.()
|
||||
} catch (error) {
|
||||
sendUserToast(`Impossible to save the script: ${error.body}`, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Drawer bind:this={scriptEditorDrawer} size="1200px">
|
||||
<DrawerContent
|
||||
title="Script Editor"
|
||||
noPadding
|
||||
forceOverflowVisible
|
||||
on:close={() => {
|
||||
scriptEditorDrawer.closeDrawer()
|
||||
}}
|
||||
>
|
||||
{#if script}
|
||||
{#key script.hash}
|
||||
<ScriptEditor
|
||||
noSyncFromGithub
|
||||
lang={script.language}
|
||||
path={script.path}
|
||||
fixedOverflowWidgets={false}
|
||||
bind:code={script.content}
|
||||
bind:schema={script.schema}
|
||||
/>
|
||||
{/key}
|
||||
{:else}
|
||||
<div
|
||||
out:fade={{ duration: 200 }}
|
||||
class="absolute inset-0 center-center flex-col bg-white text-gray-600 border"
|
||||
>
|
||||
<Loader2 class="animate-spin" size={16} />
|
||||
<span class="text-xs mt-1">Loading</span>
|
||||
</div>
|
||||
{/if}
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
on:click={async () => {
|
||||
await saveScript()
|
||||
dispatch('save')
|
||||
scriptEditorDrawer.closeDrawer()
|
||||
}}
|
||||
disabled={!script}
|
||||
startIcon={{ icon: faSave }}>Save</Button
|
||||
>
|
||||
</svelte:fragment>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
@@ -85,7 +85,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
{#if displayLock}
|
||||
<div class="flex flex-row-reverse">
|
||||
<div class="flex flex-row-reverse mb-1">
|
||||
<Toggle
|
||||
bind:checked={lockHash}
|
||||
options={{ left: 'Latest version', right: 'Lock current hash permanently' }}
|
||||
|
||||
@@ -103,7 +103,6 @@ export function getStepPropPicker(
|
||||
flow: Flow,
|
||||
args: any,
|
||||
include_node: boolean,
|
||||
approvers: boolean = false
|
||||
): StepPropPicker {
|
||||
const flowInput = getFlowInput(dfs(parentModule?.id, flow), flowState, args, flow.schema)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Writable } from 'svelte/store'
|
||||
import type ScriptEditorDrawer from './content/ScriptEditorDrawer.svelte'
|
||||
import type { Schedule } from './scheduleUtils'
|
||||
|
||||
export type FlowEditorContext = {
|
||||
@@ -6,4 +7,5 @@ export type FlowEditorContext = {
|
||||
select: (id: string) => void
|
||||
schedule: Writable<Schedule>,
|
||||
previewArgs: Writable<Record<string, any>>,
|
||||
scriptEditorDrawer: Writable<ScriptEditorDrawer | undefined>
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ export async function loadSchemaFromModule(module: FlowModule): Promise<{
|
||||
schema = emptySchema()
|
||||
await inferArgs(mod.language!, mod.content ?? '', schema)
|
||||
} else if (mod.type == 'script' && mod.path && mod.path != '') {
|
||||
schema = await loadSchema(mod.path!)
|
||||
schema = await loadSchema(mod.path!, mod.hash)
|
||||
} else if (mod.type == 'flow' && mod.path && mod.path != '') {
|
||||
schema = await loadSchemaFlow(mod.path!)
|
||||
} else {
|
||||
|
||||
@@ -51,6 +51,7 @@ export type GraphItem = Node | Loop | Branch
|
||||
|
||||
export type GraphModuleState = {
|
||||
type: FlowStatusModule.type
|
||||
args: any
|
||||
logs?: string
|
||||
result?: any
|
||||
scheduled_for?: string
|
||||
|
||||
@@ -5,7 +5,7 @@ import { inferArgs } from './infer'
|
||||
import { workspaceStore } from './stores'
|
||||
import { emptySchema } from './utils'
|
||||
|
||||
export async function loadSchema(path: string): Promise<Schema> {
|
||||
export async function loadSchema(path: string, hash?: string): Promise<Schema> {
|
||||
if (path.startsWith('hub/')) {
|
||||
const { content, language, schema } = await ScriptService.getHubScriptByPath({ path })
|
||||
if (language == 'deno') {
|
||||
@@ -15,6 +15,12 @@ export async function loadSchema(path: string): Promise<Schema> {
|
||||
} else {
|
||||
return schema ?? emptySchema()
|
||||
}
|
||||
} else if (hash) {
|
||||
const script = await ScriptService.getScriptByHash({
|
||||
workspace: get(workspaceStore)!,
|
||||
hash
|
||||
})
|
||||
return script.schema
|
||||
} else {
|
||||
const script = await ScriptService.getScriptByPath({
|
||||
workspace: get(workspaceStore)!,
|
||||
|
||||
@@ -511,6 +511,14 @@ export async function getScriptByPath(path: string): Promise<{
|
||||
}
|
||||
}
|
||||
|
||||
export async function getLatestHashForScript(path: string): Promise<string> {
|
||||
const script = await ScriptService.getScriptByPath({
|
||||
workspace: get(workspaceStore)!,
|
||||
path: path ?? ''
|
||||
})
|
||||
return script.hash
|
||||
}
|
||||
|
||||
export async function loadHubScripts() {
|
||||
try {
|
||||
const scripts = (await ScriptService.listHubScripts()).asks ?? []
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('Caught unhandled promise rejection without message', event)
|
||||
console.error('Caught unhandled promise rejection without message', event.reason, event)
|
||||
}
|
||||
}
|
||||
loadUser()
|
||||
|
||||
Reference in New Issue
Block a user