Compare commits

...

5 Commits

Author SHA1 Message Date
Ruben Fiszel
1ea08f1e8f inline script drawer 2023-02-28 15:46:06 +01:00
Ruben Fiszel
d302c92766 Merge branch 'main' into rf/flowFix 2023-02-28 11:18:22 +01:00
Ruben Fiszel
128622117d progress 2023-02-28 11:15:56 +01:00
Ruben Fiszel
150ce7f731 Merge branch 'main' into rf/flowFix 2023-02-27 18:54:26 +01:00
Ruben Fiszel
fae406ee8f update docs 2023-02-27 18:26:59 +01:00
21 changed files with 240 additions and 29 deletions

View File

@@ -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&nbsp;<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>

View File

@@ -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%">

View File

@@ -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

View File

@@ -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

View File

@@ -110,6 +110,10 @@
}
}
export function focus() {
editor?.focus()
}
let width = 0
async function loadMonaco() {
model = meditor.createModel(code, lang, mUri.parse(uri))

View File

@@ -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'

View File

@@ -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">

View File

@@ -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

View File

@@ -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}

View File

@@ -57,6 +57,7 @@
pickableProperties={undefined}
on:select={({ detail }) => {
editor?.insertAtCursor(detail)
editor?.focus()
}}
>
<SimpleEditor

View File

@@ -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"

View File

@@ -64,6 +64,7 @@
pickableProperties={undefined}
on:select={({ detail }) => {
editor?.insertAtCursor(detail)
editor?.focus()
}}
>
<InputTransformForm

View File

@@ -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>

View File

@@ -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' }}

View File

@@ -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)

View File

@@ -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>
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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)!,

View File

@@ -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 ?? []

View File

@@ -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()