mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
feat: save inline scripts of apps to workspace + search by path
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
-- Add down migration script here
|
||||
ALTER TABLE script DROP COLUMN timeout;
|
||||
ALTER TABLE script DROP COLUMN delete_after_use;
|
||||
ALTER TABLE script DROP COLUMN restart_unless_cancelled;
|
||||
ALTER TABLE flow DROP COLUMN timeout;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE script ADD COLUMN timeout INTEGER;
|
||||
ALTER TABLE flow ADD COLUMN timeout INTEGER;
|
||||
ALTER TABLE script ADD COLUMN delete_after_use BOOLEAN;
|
||||
ALTER TABLE script ADD COLUMN restart_unless_cancelled SMALLINT;
|
||||
@@ -2579,6 +2579,7 @@ async fn test_flow_lock_all(db: Pool<Postgres>) {
|
||||
ws_error_handler_muted: None,
|
||||
priority: None,
|
||||
dedicated_worker: None,
|
||||
timeout: None,
|
||||
},
|
||||
draft_only: None,
|
||||
},
|
||||
@@ -3129,6 +3130,9 @@ async fn run_deployed_relative_imports(db: &Pool<Postgres>, script_content: Stri
|
||||
schema: std::collections::HashMap::new(),
|
||||
ws_error_handler_muted: Some(false),
|
||||
priority: None,
|
||||
delete_after_use: None,
|
||||
timeout: None,
|
||||
restart_unless_cancelled: None,
|
||||
},
|
||||
).await.unwrap();
|
||||
|
||||
|
||||
@@ -6912,6 +6912,12 @@ components:
|
||||
type: boolean
|
||||
priority:
|
||||
type: integer
|
||||
restart_unless_cancelled:
|
||||
type: boolean
|
||||
timeout:
|
||||
type: integer
|
||||
delete_after_use:
|
||||
type: boolean
|
||||
required:
|
||||
- hash
|
||||
- path
|
||||
@@ -6990,6 +6996,12 @@ components:
|
||||
type: boolean
|
||||
priority:
|
||||
type: integer
|
||||
restart_unless_cancelled:
|
||||
type: boolean
|
||||
timeout:
|
||||
type: integer
|
||||
delete_after_use:
|
||||
type: boolean
|
||||
required:
|
||||
- path
|
||||
- summary
|
||||
@@ -8257,6 +8269,8 @@ components:
|
||||
type: integer
|
||||
dedicated_worker:
|
||||
type: boolean
|
||||
timeout:
|
||||
type: number
|
||||
required:
|
||||
- path
|
||||
- edited_by
|
||||
@@ -8279,6 +8293,8 @@ components:
|
||||
type: integer
|
||||
dedicated_worker:
|
||||
type: boolean
|
||||
timeout:
|
||||
type: number
|
||||
required:
|
||||
- path
|
||||
|
||||
|
||||
@@ -1681,6 +1681,12 @@ struct ScriptMetadata {
|
||||
priority: Option<i16>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
tag: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub timeout: Option<i32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub delete_after_use: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub restart_unless_cancelled: Option<bool>,
|
||||
}
|
||||
|
||||
pub fn is_none_or_false(val: &Option<bool>) -> bool {
|
||||
@@ -1886,6 +1892,9 @@ async fn tarball_workspace(
|
||||
ws_error_handler_muted: script.ws_error_handler_muted,
|
||||
priority: script.priority,
|
||||
tag: script.tag,
|
||||
timeout: script.timeout,
|
||||
delete_after_use: script.delete_after_use,
|
||||
restart_unless_cancelled: script.restart_unless_cancelled,
|
||||
|
||||
};
|
||||
let metadata_str = serde_json::to_string_pretty(&metadata).unwrap();
|
||||
|
||||
@@ -41,6 +41,8 @@ pub struct Flow {
|
||||
pub tag: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ws_error_handler_muted: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub timeout: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -74,6 +76,7 @@ pub struct NewFlow {
|
||||
pub tag: Option<String>,
|
||||
pub ws_error_handler_muted: Option<bool>,
|
||||
pub dedicated_worker: Option<bool>,
|
||||
pub timeout: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
|
||||
|
||||
@@ -176,6 +176,12 @@ pub struct Script {
|
||||
pub priority: Option<i16>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cache_ttl: Option<i32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub timeout: Option<i32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub delete_after_use: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub restart_unless_cancelled: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -234,6 +240,9 @@ pub struct NewScript {
|
||||
pub dedicated_worker: Option<bool>,
|
||||
pub ws_error_handler_muted: Option<bool>,
|
||||
pub priority: Option<i16>,
|
||||
pub timeout: Option<i32>,
|
||||
pub delete_after_use: Option<bool>,
|
||||
pub restart_unless_cancelled: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
||||
@@ -11,15 +11,16 @@
|
||||
import FlowIcon from './home/FlowIcon.svelte'
|
||||
import { Alert, Button } from './common'
|
||||
import { goto } from '$app/navigation'
|
||||
import YAML from 'yaml'
|
||||
|
||||
let search: string = ''
|
||||
|
||||
export function open() {
|
||||
export async function open(nsearch?: string) {
|
||||
isOpen = true
|
||||
loadScripts()
|
||||
loadResources()
|
||||
loadApps()
|
||||
loadFlows()
|
||||
await Promise.all([loadScripts(), loadResources(), loadApps(), loadFlows()])
|
||||
if (nsearch) {
|
||||
search = nsearch
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadScripts() {
|
||||
@@ -38,6 +39,8 @@
|
||||
flows = await FlowService.listSearchFlow({ workspace: $workspaceStore ?? '' })
|
||||
}
|
||||
|
||||
let searchKind: 'all' | 'scripts' | 'flows' | 'apps' | 'resources' = 'all'
|
||||
|
||||
let isOpen = false
|
||||
|
||||
let scripts: undefined | { path: string; content: string }[] = undefined
|
||||
@@ -52,13 +55,19 @@
|
||||
let apps: undefined | { path: string; value: any }[] = undefined
|
||||
let filteredAppItems: { path: string; value: any; marked: any }[] = []
|
||||
|
||||
let searchKind: 'all' | 'scripts' | 'flows' | 'apps' | 'resources' = 'all'
|
||||
|
||||
function getCounts(n: number) {
|
||||
return ` (${n})`
|
||||
}
|
||||
$: counts =
|
||||
search == '' || !scripts || !resources || !flows || !apps
|
||||
search == '' ||
|
||||
!scripts ||
|
||||
!resources ||
|
||||
!flows ||
|
||||
!apps ||
|
||||
!filteredAppItems ||
|
||||
!filteredFlowItems ||
|
||||
!filteredResourceItems ||
|
||||
!filteredScriptItems
|
||||
? {
|
||||
all: '',
|
||||
apps: '',
|
||||
@@ -93,7 +102,7 @@
|
||||
filter={search}
|
||||
items={resources}
|
||||
f={(s) => {
|
||||
return JSON.stringify(s.value, null, 4)
|
||||
return YAML.stringify(s.value)
|
||||
}}
|
||||
bind:filteredItems={filteredResourceItems}
|
||||
/>
|
||||
@@ -102,7 +111,7 @@
|
||||
filter={search}
|
||||
items={flows}
|
||||
f={(s) => {
|
||||
return JSON.stringify(s.value, null, 4)
|
||||
return YAML.stringify(s.value, null, 4)
|
||||
}}
|
||||
bind:filteredItems={filteredFlowItems}
|
||||
/>
|
||||
@@ -111,7 +120,7 @@
|
||||
filter={search}
|
||||
items={apps}
|
||||
f={(s) => {
|
||||
return JSON.stringify(s.value, null, 4)
|
||||
return YAML.stringify(s.value, null, 4)
|
||||
}}
|
||||
bind:filteredItems={filteredAppItems}
|
||||
/>
|
||||
@@ -243,6 +252,8 @@
|
||||
|
||||
<div class="mt-1 overflow-auto max-h-[80vh]">
|
||||
{#if !$enterpriseLicense}
|
||||
<div class="py-1" />
|
||||
|
||||
<Alert title="Content Search is an EE feature" type="warning">
|
||||
Without EE, content search will only search among 10 scripts, 3 flows, 3 apps and 3
|
||||
resources.
|
||||
@@ -252,8 +263,8 @@
|
||||
|
||||
{#if search.length > 0}
|
||||
<div class="flex flex-col gap-4">
|
||||
{#if (searchKind == 'all' || searchKind == 'scripts') && filteredScriptItems.length > 0}
|
||||
{#each filteredScriptItems as item}
|
||||
{#if (searchKind == 'all' || searchKind == 'scripts') && filteredScriptItems?.length > 0}
|
||||
{#each filteredScriptItems ?? [] as item}
|
||||
<div>
|
||||
<div class="text-sm font-semibold"
|
||||
><a href="/scripts/get/{item.path}">Script: {item.path}</a></div
|
||||
@@ -280,8 +291,8 @@
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if (searchKind == 'all' || searchKind == 'resources') && filteredResourceItems.length > 0}
|
||||
{#each filteredResourceItems as item}
|
||||
{#if (searchKind == 'all' || searchKind == 'resources') && filteredResourceItems?.length > 0}
|
||||
{#each filteredResourceItems ?? [] as item}
|
||||
<div>
|
||||
<div class="text-sm font-semibold">Resource: {item.path}</div>
|
||||
<div class="flex gap-2 justify-between">
|
||||
@@ -292,8 +303,8 @@
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if (searchKind == 'all' || searchKind == 'flows') && filteredFlowItems.length > 0}
|
||||
{#each filteredFlowItems as item}
|
||||
{#if (searchKind == 'all' || searchKind == 'flows') && filteredFlowItems?.length > 0}
|
||||
{#each filteredFlowItems ?? [] as item}
|
||||
<div>
|
||||
<div class="text-sm font-semibold"
|
||||
><a href="/flows/get/{item.path}">Flow: {item.path}</a></div
|
||||
@@ -318,8 +329,8 @@
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if (searchKind == 'all' || searchKind == 'apps') && filteredAppItems.length > 0}
|
||||
{#each filteredAppItems as item}
|
||||
{#if (searchKind == 'all' || searchKind == 'apps') && filteredAppItems?.length > 0}
|
||||
{#each filteredAppItems ?? [] as item}
|
||||
<div>
|
||||
<div class="text-sm font-semibold"
|
||||
><a href="/apps/get/{item.path}">App: {item.path}</a></div
|
||||
|
||||
@@ -23,7 +23,17 @@
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { getScriptByPath, scriptLangToEditorLang } from '$lib/scripts'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import { DollarSign, History, Library, Link, Package, Plus, RotateCw, Users } from 'lucide-svelte'
|
||||
import {
|
||||
DollarSign,
|
||||
History,
|
||||
Library,
|
||||
Link,
|
||||
Package,
|
||||
Plus,
|
||||
RotateCw,
|
||||
Save,
|
||||
Users
|
||||
} from 'lucide-svelte'
|
||||
import { capitalize, toCamel } from '$lib/utils'
|
||||
import type { Schema, SchemaProperty, SupportedLanguage } from '$lib/common'
|
||||
import ScriptVersionHistory from './ScriptVersionHistory.svelte'
|
||||
@@ -54,6 +64,8 @@
|
||||
export let scriptPath: string | undefined = undefined
|
||||
export let diffEditor: DiffEditor | undefined = undefined
|
||||
export let args: Record<string, any>
|
||||
export let noHistory = false
|
||||
export let saveToWorkspace = false
|
||||
|
||||
let contextualVariablePicker: ItemPicker
|
||||
let variablePicker: ItemPicker
|
||||
@@ -557,7 +569,7 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
{#if scriptPath}
|
||||
{#if scriptPath && !noHistory}
|
||||
<Button
|
||||
btnClasses="!font-medium text-tertiary"
|
||||
size="xs"
|
||||
@@ -585,6 +597,17 @@
|
||||
Library
|
||||
</Button>
|
||||
{/if}
|
||||
{#if saveToWorkspace}
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
startIcon={{ icon: Save }}
|
||||
on:click={() => dispatch('createScriptFromInlineScript')}
|
||||
iconOnly={false}
|
||||
>
|
||||
Save to workspace
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { superadmin, userStore, workspaceStore } from '$lib/stores'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { writable } from 'svelte/store'
|
||||
import { Button, Drawer, DrawerContent } from './common'
|
||||
import { Alert, Button, Drawer, DrawerContent } from './common'
|
||||
import Badge from './common/badge/Badge.svelte'
|
||||
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
|
||||
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
@@ -25,7 +25,8 @@
|
||||
import { random_adj } from './random_positive_adjetive'
|
||||
import Required from './Required.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import { Eye, Folder, Plus, User } from 'lucide-svelte'
|
||||
import { Eye, Folder, Plus, SearchCode, User } from 'lucide-svelte'
|
||||
import ContentSearch from './ContentSearch.svelte'
|
||||
|
||||
type PathKind = 'resource' | 'script' | 'variable' | 'flow' | 'schedule' | 'app' | 'raw_app'
|
||||
let meta: Meta | undefined = undefined
|
||||
@@ -261,8 +262,14 @@
|
||||
function setDirty() {
|
||||
!dirty && (dirty = true)
|
||||
}
|
||||
|
||||
let contentSearch: ContentSearch
|
||||
</script>
|
||||
|
||||
{#if kind != 'app' && kind != 'schedule' && initialPath != '' && initialPath != undefined}
|
||||
<ContentSearch bind:this={contentSearch} />
|
||||
{/if}
|
||||
|
||||
<Drawer bind:this={newFolder}>
|
||||
<DrawerContent
|
||||
title="New Folder"
|
||||
@@ -441,6 +448,26 @@
|
||||
</div>
|
||||
<div class="text-red-600 dark:text-red-400 text-2xs">{error}</div>
|
||||
</div>
|
||||
|
||||
{#if kind != 'app' && kind != 'schedule' && initialPath != '' && initialPath != undefined && initialPath != path}
|
||||
<Alert type="warning" class="mt-4" title="Moving may break other items relying on it">
|
||||
You are renaming an item that may be depended upon by other items. This may break apps, flows
|
||||
or resources. Find if it used elsewhere using the content search. Note that linked variables
|
||||
and resources (having the same path) are automatically moved together.
|
||||
<div class="flex pt-2">
|
||||
<Button
|
||||
variant="border"
|
||||
color="dark"
|
||||
on:click={() => {
|
||||
contentSearch?.open(initialPath)
|
||||
}}
|
||||
startIcon={{ icon: SearchCode }}
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
</div>
|
||||
</Alert>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
export let diffEditor: DiffEditor | undefined = undefined
|
||||
export let collabMode = false
|
||||
export let edit = true
|
||||
export let noHistory = false
|
||||
export let saveToWorkspace = false
|
||||
|
||||
let websocketAlive = {
|
||||
pyright: false,
|
||||
@@ -246,12 +248,15 @@
|
||||
on:collabPopup={() => (showCollabPopup = true)}
|
||||
{editor}
|
||||
{lang}
|
||||
on:createScriptFromInlineScript
|
||||
{websocketAlive}
|
||||
collabUsers={peers}
|
||||
kind={asKind(kind)}
|
||||
{template}
|
||||
{diffEditor}
|
||||
{args}
|
||||
{noHistory}
|
||||
{saveToWorkspace}
|
||||
/>
|
||||
{#if !noSyncFromGithub}
|
||||
<div class="py-1">
|
||||
|
||||
@@ -159,6 +159,10 @@
|
||||
{editor}
|
||||
bind:this={inlineScriptEditorDrawer}
|
||||
bind:inlineScript
|
||||
on:createScriptFromInlineScript={() => {
|
||||
dispatch('createScriptFromInlineScript')
|
||||
drawerIsOpen = false
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
<div class="h-full flex flex-col gap-1">
|
||||
|
||||
+4
-1
@@ -27,17 +27,20 @@
|
||||
>
|
||||
{#if inlineScript && inlineScript.language != 'frontend'}
|
||||
<ScriptEditor
|
||||
noHistory
|
||||
noSyncFromGithub
|
||||
lang={inlineScript.language}
|
||||
path={inlineScript.path ? inlineScript.path + '_fullscreen' : undefined}
|
||||
fixedOverflowWidgets={false}
|
||||
bind:code={inlineScript.content}
|
||||
bind:schema={inlineScript.schema}
|
||||
on:createScriptFromInlineScript
|
||||
tag={undefined}
|
||||
saveToWorkspace
|
||||
/>
|
||||
{/if}
|
||||
<svelte:fragment slot="actions">
|
||||
<Button startIcon={{ icon: Save }} disabled>Automatically Saved</Button>
|
||||
<Button size="xs" startIcon={{ icon: Save }} disabled>Automatically Synced</Button>
|
||||
</svelte:fragment>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
+12
-2
@@ -4,7 +4,7 @@
|
||||
import EmptyInlineScript from './EmptyInlineScript.svelte'
|
||||
import InlineScriptEditor from './InlineScriptEditor.svelte'
|
||||
|
||||
import { getContext } from 'svelte'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import type { AppViewerContext } from '../../types'
|
||||
import InlineScriptRunnableByPath from './InlineScriptRunnableByPath.svelte'
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
componentInput.runnable = runnable
|
||||
}
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
{#if transformer}
|
||||
@@ -51,10 +53,18 @@
|
||||
Selected editor component is a transformer but component has no transformer
|
||||
</span>
|
||||
{/if}
|
||||
{:else if componentInput && componentInput.type == 'runnable'}
|
||||
{:else if componentInput?.type == 'runnable'}
|
||||
{#if componentInput?.runnable?.type === 'runnableByName' && componentInput?.runnable?.name !== undefined}
|
||||
{#if componentInput.runnable.inlineScript}
|
||||
<InlineScriptEditor
|
||||
on:createScriptFromInlineScript={() => {
|
||||
if (
|
||||
componentInput?.type == 'runnable' &&
|
||||
componentInput?.runnable?.type === 'runnableByName'
|
||||
) {
|
||||
dispatch('createScriptFromInlineScript', componentInput?.runnable)
|
||||
}
|
||||
}}
|
||||
{defaultUserInput}
|
||||
{id}
|
||||
{componentType}
|
||||
|
||||
+3
-1
@@ -4,7 +4,7 @@
|
||||
import EmptyInlineScript from './EmptyInlineScript.svelte'
|
||||
import InlineScriptRunnableByPath from './InlineScriptRunnableByPath.svelte'
|
||||
import type { Runnable, StaticAppInput } from '../../inputType'
|
||||
import { getContext } from 'svelte'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
|
||||
export let runnable: HiddenRunnable
|
||||
export let id: string
|
||||
@@ -24,6 +24,7 @@
|
||||
recomputeOnInputChanged: true
|
||||
}
|
||||
}
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
{#if transformer}
|
||||
@@ -48,6 +49,7 @@
|
||||
{/if}
|
||||
{:else if runnable?.type === 'runnableByName' && runnable.inlineScript}
|
||||
<InlineScriptEditor
|
||||
on:createScriptFromInlineScript={() => dispatch('createScriptFromInlineScript', runnable)}
|
||||
{id}
|
||||
bind:inlineScript={runnable.inlineScript}
|
||||
bind:name={runnable.name}
|
||||
|
||||
+61
-2
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppEditorContext, AppViewerContext } from '../../types'
|
||||
import type { AppEditorContext, AppViewerContext, HiddenRunnable } from '../../types'
|
||||
import SplitPanesWrapper from '$lib/components/splitPanes/SplitPanesWrapper.svelte'
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||
import InlineScriptsPanelList from './InlineScriptsPanelList.svelte'
|
||||
@@ -9,6 +9,11 @@
|
||||
import { findGridItem } from '../appUtils'
|
||||
import InlineScriptHiddenRunnable from './InlineScriptHiddenRunnable.svelte'
|
||||
import { BG_PREFIX } from '../../utils'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import type { RunnableByName } from '../../inputType'
|
||||
import { ScriptService } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { findNextAvailablePath } from '$lib/path'
|
||||
|
||||
const { app, runnableComponents } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { selectedComponentInEditor } = getContext<AppEditorContext>('AppEditorContext')
|
||||
@@ -51,6 +56,50 @@
|
||||
$: unusedInlineScript = $app?.unusedInlineScripts?.findIndex(
|
||||
(k_, index) => `unused-${index}` === $selectedComponentInEditor
|
||||
)
|
||||
|
||||
async function createScriptFromInlineScript(
|
||||
id: string,
|
||||
runnable: HiddenRunnable | RunnableByName
|
||||
) {
|
||||
if (runnable.type != 'runnableByName') {
|
||||
sendUserToast('Only inline scripts can be saved to workspace', true)
|
||||
return
|
||||
}
|
||||
if (!runnable.inlineScript) {
|
||||
sendUserToast('No inline script found', true)
|
||||
return
|
||||
}
|
||||
let path = `${runnable.inlineScript.path}/inline_${id}`
|
||||
path = await findNextAvailablePath(path)
|
||||
let language = runnable.inlineScript.language
|
||||
if (language == 'frontend') {
|
||||
sendUserToast('Frontend scripts can not be saved to workspace', true)
|
||||
return
|
||||
}
|
||||
await ScriptService.createScript({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
path: path,
|
||||
summary: runnable.name ?? '',
|
||||
description: '',
|
||||
content: runnable.inlineScript.content,
|
||||
parent_hash: undefined,
|
||||
schema: runnable.inlineScript.schema,
|
||||
is_template: false,
|
||||
language
|
||||
}
|
||||
})
|
||||
|
||||
Object.assign(runnable, {
|
||||
type: 'runnableByPath',
|
||||
schema: runnable.inlineScript.schema,
|
||||
runType: 'script',
|
||||
recomputeIds: undefined,
|
||||
path
|
||||
})
|
||||
|
||||
$app = $app
|
||||
}
|
||||
</script>
|
||||
|
||||
<SplitPanesWrapper>
|
||||
@@ -65,11 +114,18 @@
|
||||
</div>
|
||||
{:else if gridItem}
|
||||
{#key gridItem?.id}
|
||||
<InlineScriptsPanelWithTable bind:gridItem />
|
||||
<InlineScriptsPanelWithTable
|
||||
on:createScriptFromInlineScript={(e) => {
|
||||
createScriptFromInlineScript(gridItem?.id ?? 'unknown', e.detail)
|
||||
}}
|
||||
bind:gridItem
|
||||
/>
|
||||
{/key}
|
||||
{:else if unusedInlineScript > -1 && $app.unusedInlineScripts?.[unusedInlineScript]}
|
||||
{#key unusedInlineScript}
|
||||
<InlineScriptEditor
|
||||
on:createScriptFromInlineScript={() =>
|
||||
sendUserToast('Cannot save to workspace unused scripts', true)}
|
||||
id={`unused-${unusedInlineScript}`}
|
||||
bind:name={$app.unusedInlineScripts[unusedInlineScript].name}
|
||||
bind:inlineScript={$app.unusedInlineScripts[unusedInlineScript].inlineScript}
|
||||
@@ -84,6 +140,9 @@
|
||||
{#key hiddenInlineScript}
|
||||
{#if $app.hiddenInlineScripts?.[hiddenInlineScript]}
|
||||
<InlineScriptHiddenRunnable
|
||||
on:createScriptFromInlineScript={(e) => {
|
||||
createScriptFromInlineScript(BG_PREFIX + hiddenInlineScript, e.detail)
|
||||
}}
|
||||
transformer={$selectedComponentInEditor?.endsWith('_transformer')}
|
||||
on:delete={() => deleteBackgroundScript(hiddenInlineScript)}
|
||||
id={BG_PREFIX + hiddenInlineScript}
|
||||
|
||||
+3
@@ -10,6 +10,7 @@
|
||||
|
||||
{#if gridItem?.data?.id === $selectedComponentInEditor || gridItem?.data?.id + '_transformer' === $selectedComponentInEditor}
|
||||
<InlineScriptEditorPanel
|
||||
on:createScriptFromInlineScript
|
||||
defaultUserInput={gridItem.data?.type == 'formcomponent' ||
|
||||
gridItem?.data?.type == 'formbuttoncomponent'}
|
||||
id={gridItem.data.id}
|
||||
@@ -23,6 +24,7 @@
|
||||
{#each gridItem.data.actionButtons as actionButton, index (index)}
|
||||
{#if actionButton?.id === $selectedComponentInEditor || actionButton?.id + '_transformer' === $selectedComponentInEditor}
|
||||
<InlineScriptEditorPanel
|
||||
on:createScriptFromInlineScript
|
||||
componentType={actionButton.type}
|
||||
id={actionButton.id}
|
||||
transformer={$selectedComponentInEditor?.endsWith('_transformer')}
|
||||
@@ -36,6 +38,7 @@
|
||||
{#each gridItem.data.menuItems as actionButton, index (index)}
|
||||
{#if actionButton?.id === $selectedComponentInEditor || actionButton?.id + '_transformer' === $selectedComponentInEditor}
|
||||
<InlineScriptEditorPanel
|
||||
on:createScriptFromInlineScript
|
||||
componentType={actionButton.type}
|
||||
id={actionButton.id}
|
||||
transformer={$selectedComponentInEditor?.endsWith('_transformer')}
|
||||
|
||||
@@ -13,10 +13,11 @@ import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { getScriptByPath } from '$lib/scripts'
|
||||
import { get, type Writable } from 'svelte/store'
|
||||
import type { FlowModuleState, FlowState } from './flowState'
|
||||
import { emptyFlowModuleState, findNextAvailablePath } from './utils'
|
||||
import { emptyFlowModuleState } from './utils'
|
||||
import { NEVER_TESTED_THIS_FAR } from './models'
|
||||
import { loadSchemaFromModule } from './flowInfers'
|
||||
import { nextId } from './flowModuleNextId'
|
||||
import { findNextAvailablePath } from '$lib/path'
|
||||
|
||||
export async function loadFlowModuleState(flowModule: FlowModule): Promise<FlowModuleState> {
|
||||
try {
|
||||
|
||||
@@ -152,25 +152,3 @@ export function emptyFlowModuleState(): FlowModuleState {
|
||||
previewResult: NEVER_TESTED_THIS_FAR
|
||||
}
|
||||
}
|
||||
|
||||
export async function findNextAvailablePath(path: string): Promise<string> {
|
||||
try {
|
||||
await ScriptService.getScriptByPath({
|
||||
workspace: get(workspaceStore)!,
|
||||
path
|
||||
})
|
||||
|
||||
const [_, version] = path.split(/.*_([0-9]*)/)
|
||||
|
||||
if (version.length > 0) {
|
||||
path = path.slice(0, -(version.length + 1))
|
||||
}
|
||||
|
||||
path = `${path}_${Number(version) + 1}`
|
||||
|
||||
return findNextAvailablePath(path)
|
||||
} catch (e) {
|
||||
// Catching an error means the path is available
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,7 +356,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<Button
|
||||
on:click={contentSearch?.open}
|
||||
on:click={() => contentSearch?.open()}
|
||||
variant="border"
|
||||
btnClasses="py-2.5"
|
||||
size="xs"
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { get } from 'svelte/store'
|
||||
import { ScriptService } from './gen'
|
||||
import { workspaceStore } from './stores'
|
||||
|
||||
export async function findNextAvailablePath(path: string): Promise<string> {
|
||||
try {
|
||||
await ScriptService.getScriptByPath({
|
||||
workspace: get(workspaceStore)!,
|
||||
path
|
||||
})
|
||||
|
||||
const [_, version] = path.split(/.*_([0-9]*)/)
|
||||
|
||||
if (version.length > 0) {
|
||||
path = path.slice(0, -(version.length + 1))
|
||||
}
|
||||
|
||||
path = `${path}_${Number(version) + 1}`
|
||||
|
||||
return findNextAvailablePath(path)
|
||||
} catch (e) {
|
||||
// Catching an error means the path is available
|
||||
return path
|
||||
}
|
||||
}
|
||||
@@ -103,6 +103,8 @@ components:
|
||||
type: number
|
||||
timeout:
|
||||
type: number
|
||||
delete_after_use:
|
||||
type: boolean
|
||||
summary:
|
||||
type: string
|
||||
mock:
|
||||
|
||||
Reference in New Issue
Block a user