From 9e36ae066c5b8fab98907b15894e028b2bb63089 Mon Sep 17 00:00:00 2001 From: HugoCasa Date: Mon, 22 Apr 2024 15:59:13 +0200 Subject: [PATCH] feat: hide scripts with on function main from operators + badge/filter for devs (#3584) * feat: save in DB whether script has not main function * fix: if cannot parse script args, clear schema * feat: no main function badge and filter * fix: sqlx * fix: no main --- ...9597deabd7ca93653ad72d2cdc37efefc9d4.json} | 5 +- ...419170502_add_script_no_main_func.down.sql | 1 + ...40419170502_add_script_no_main_func.up.sql | 2 + backend/tests/worker.rs | 1 + backend/windmill-api/openapi.yaml | 12 ++++ backend/windmill-api/src/scripts.rs | 16 +++-- backend/windmill-api/src/workspaces.rs | 3 + backend/windmill-common/src/scripts.rs | 6 ++ cli/script.ts | 3 + frontend/src/lib/components/Dev.svelte | 1 + .../src/lib/components/NoMainFuncBadge.svelte | 9 +++ .../src/lib/components/ScriptBuilder.svelte | 10 +++- .../src/lib/components/ScriptEditor.svelte | 1 + .../components/common/table/ScriptRow.svelte | 4 ++ .../flows/content/ScriptEditorDrawer.svelte | 4 ++ .../src/lib/components/home/ItemsList.svelte | 60 ++++++++++++++++--- 16 files changed, 121 insertions(+), 17 deletions(-) rename backend/.sqlx/{query-020d33ed5d47350b456783fd548422ea8dcf2d786d0e9fa849754db82c9fa378.json => query-92d00c6a1f4c40f2a23c9ba758a59597deabd7ca93653ad72d2cdc37efefc9d4.json} (86%) create mode 100644 backend/migrations/20240419170502_add_script_no_main_func.down.sql create mode 100644 backend/migrations/20240419170502_add_script_no_main_func.up.sql create mode 100644 frontend/src/lib/components/NoMainFuncBadge.svelte diff --git a/backend/.sqlx/query-020d33ed5d47350b456783fd548422ea8dcf2d786d0e9fa849754db82c9fa378.json b/backend/.sqlx/query-92d00c6a1f4c40f2a23c9ba758a59597deabd7ca93653ad72d2cdc37efefc9d4.json similarity index 86% rename from backend/.sqlx/query-020d33ed5d47350b456783fd548422ea8dcf2d786d0e9fa849754db82c9fa378.json rename to backend/.sqlx/query-92d00c6a1f4c40f2a23c9ba758a59597deabd7ca93653ad72d2cdc37efefc9d4.json index 78bfc65e20..a49e26981a 100644 --- a/backend/.sqlx/query-020d33ed5d47350b456783fd548422ea8dcf2d786d0e9fa849754db82c9fa378.json +++ b/backend/.sqlx/query-92d00c6a1f4c40f2a23c9ba758a59597deabd7ca93653ad72d2cdc37efefc9d4.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28)", + "query": "INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29)", "describe": { "columns": [], "parameters": { @@ -66,10 +66,11 @@ "Bool", "Int4", "Varchar", + "Bool", "Bool" ] }, "nullable": [] }, - "hash": "020d33ed5d47350b456783fd548422ea8dcf2d786d0e9fa849754db82c9fa378" + "hash": "92d00c6a1f4c40f2a23c9ba758a59597deabd7ca93653ad72d2cdc37efefc9d4" } diff --git a/backend/migrations/20240419170502_add_script_no_main_func.down.sql b/backend/migrations/20240419170502_add_script_no_main_func.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20240419170502_add_script_no_main_func.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20240419170502_add_script_no_main_func.up.sql b/backend/migrations/20240419170502_add_script_no_main_func.up.sql new file mode 100644 index 0000000000..10fc6ee77f --- /dev/null +++ b/backend/migrations/20240419170502_add_script_no_main_func.up.sql @@ -0,0 +1,2 @@ +-- Add up migration script here +ALTER TABLE script ADD COLUMN no_main_func BOOLEAN; \ No newline at end of file diff --git a/backend/tests/worker.rs b/backend/tests/worker.rs index 3f640c40ea..36af6c8d93 100644 --- a/backend/tests/worker.rs +++ b/backend/tests/worker.rs @@ -3164,6 +3164,7 @@ async fn run_deployed_relative_imports(db: &Pool, script_content: Stri deployment_message: None, concurrency_key: None, visible_to_runner_only: None, + no_main_func: None, }, ).await.unwrap(); diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 13d3dbeae6..7cdf2f7582 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -3401,6 +3401,13 @@ paths: in: query schema: type: boolean + - name: hide_without_main + description: | + (default false) + hide the scripts without an exported main function + in: query + schema: + type: boolean - name: is_template description: | (default regardless) @@ -8356,6 +8363,8 @@ components: type: boolean visible_to_runner_only: type: boolean + no_main_func: + type: boolean required: - hash - path @@ -8371,6 +8380,7 @@ components: - language - kind - starred + - no_main_func NewScript: type: object @@ -8444,6 +8454,8 @@ components: type: string visible_to_runner_only: type: boolean + no_main_func: + type: boolean required: - path - summary diff --git a/backend/windmill-api/src/scripts.rs b/backend/windmill-api/src/scripts.rs index a86116647b..4cc923a732 100644 --- a/backend/windmill-api/src/scripts.rs +++ b/backend/windmill-api/src/scripts.rs @@ -91,6 +91,8 @@ pub struct ScriptWDraft { pub concurrency_key: Option, #[serde(skip_serializing_if = "Option::is_none")] pub visible_to_runner_only: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub no_main_func: Option, } pub fn global_service() -> Router { @@ -192,7 +194,8 @@ async fn list_scripts( "tag", "draft.path IS NOT NULL as has_draft", "draft_only", - "ws_error_handler_muted" + "ws_error_handler_muted", + "no_main_func", ]) .left() .join("favorite") @@ -212,6 +215,10 @@ async fn list_scripts( .limit(per_page) .clone(); + if authed.is_operator || lq.hide_without_main.unwrap_or(false) { + sqlb.and_where("o.no_main_func IS NOT TRUE"); + } + if lq.show_archived.unwrap_or(false) { sqlb.and_where_eq( "o.created_at", @@ -475,8 +482,8 @@ async fn create_script( content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, \ draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, \ dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, \ - delete_after_use, timeout, concurrency_key, visible_to_runner_only) \ - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28)", + delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func) \ + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29)", &w_id, &hash.0, ns.path, @@ -505,6 +512,7 @@ async fn create_script( ns.timeout, ns.concurrency_key, ns.visible_to_runner_only, + ns.no_main_func ) .execute(&mut tx) .await?; @@ -724,7 +732,7 @@ async fn get_script_by_path_w_draft( let mut tx = user_db.begin(&authed).await?; let script_o = sqlx::query_as::<_, ScriptWDraft>( - "SELECT hash, script.path, summary, description, content, language, kind, tag, schema, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, ws_error_handler_muted, draft.value as draft, dedicated_worker, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only FROM script LEFT JOIN draft ON + "SELECT hash, script.path, summary, description, content, language, kind, tag, schema, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, ws_error_handler_muted, draft.value as draft, dedicated_worker, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func FROM script LEFT JOIN draft ON script.path = draft.path AND script.workspace_id = draft.workspace_id AND draft.typ = 'script' WHERE script.path = $1 AND script.workspace_id = $2 \ AND script.created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND \ diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index 6208462146..058af770df 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -2183,6 +2183,8 @@ struct ScriptMetadata { pub restart_unless_cancelled: Option, #[serde(skip_serializing_if = "Option::is_none")] pub visible_to_runner_only: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub no_main_func: Option, } pub fn is_none_or_false(val: &Option) -> bool { @@ -2459,6 +2461,7 @@ async fn tarball_workspace( delete_after_use: script.delete_after_use, restart_unless_cancelled: script.restart_unless_cancelled, visible_to_runner_only: script.visible_to_runner_only, + no_main_func: script.no_main_func, }; let metadata_str = serde_json::to_string_pretty(&metadata).unwrap(); archive diff --git a/backend/windmill-common/src/scripts.rs b/backend/windmill-common/src/scripts.rs index 61c7f3e8cc..3543c08f78 100644 --- a/backend/windmill-common/src/scripts.rs +++ b/backend/windmill-common/src/scripts.rs @@ -175,6 +175,8 @@ pub struct Script { pub concurrency_key: Option, #[serde(skip_serializing_if = "Option::is_none")] pub visible_to_runner_only: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub no_main_func: Option, } #[derive(Serialize, sqlx::FromRow)] @@ -194,6 +196,8 @@ pub struct ListableScript { pub draft_only: Option, pub has_deploy_errors: bool, pub ws_error_handler_muted: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub no_main_func: Option, } #[derive(Serialize)] @@ -251,6 +255,7 @@ pub struct NewScript { #[serde(skip_serializing_if = "Option::is_none")] pub concurrency_key: Option, pub visible_to_runner_only: Option, + pub no_main_func: Option, } fn lock_deserialize<'de, D>(deserializer: D) -> Result, D::Error> @@ -320,6 +325,7 @@ pub struct ListScriptQuery { pub is_template: Option, pub kinds: Option, pub starred_only: Option, + pub hide_without_main: Option, } pub fn to_i64(s: &str) -> crate::error::Result { diff --git a/cli/script.ts b/cli/script.ts index a91dfff202..2a8ef863bc 100644 --- a/cli/script.ts +++ b/cli/script.ts @@ -167,6 +167,7 @@ export async function handleFile( Boolean(remote.restart_unless_cancelled) && Boolean(typed.visible_to_runner_only) == Boolean(remote.visible_to_runner_only) && + Boolean(typed.no_main_func) == Boolean(remote.no_main_func) && typed.priority == Boolean(remote.priority)) ) { log.info(colors.green(`Script ${remotePath} is up to date`)); @@ -198,6 +199,7 @@ export async function handleFile( deployment_message: message, restart_unless_cancelled: typed?.restart_unless_cancelled, visible_to_runner_only: typed?.visible_to_runner_only, + no_main_func: typed?.no_main_func, priority: typed?.priority, }, }); @@ -227,6 +229,7 @@ export async function handleFile( deployment_message: message, restart_unless_cancelled: typed?.restart_unless_cancelled, visible_to_runner_only: typed?.visible_to_runner_only, + no_main_func: typed?.no_main_func, priority: typed?.priority, }, }); diff --git a/frontend/src/lib/components/Dev.svelte b/frontend/src/lib/components/Dev.svelte index cc80709daf..b568dd37a1 100644 --- a/frontend/src/lib/components/Dev.svelte +++ b/frontend/src/lib/components/Dev.svelte @@ -309,6 +309,7 @@ } catch (e) { console.error(e) validCode = false + schema = emptySchema() } } diff --git a/frontend/src/lib/components/NoMainFuncBadge.svelte b/frontend/src/lib/components/NoMainFuncBadge.svelte new file mode 100644 index 0000000000..54309e2839 --- /dev/null +++ b/frontend/src/lib/components/NoMainFuncBadge.svelte @@ -0,0 +1,9 @@ + + + + The script has no main function exported + No main + diff --git a/frontend/src/lib/components/ScriptBuilder.svelte b/frontend/src/lib/components/ScriptBuilder.svelte index 0bf45f33e6..0f717f7f8a 100644 --- a/frontend/src/lib/components/ScriptBuilder.svelte +++ b/frontend/src/lib/components/ScriptBuilder.svelte @@ -215,7 +215,9 @@ script.schema = script.schema ?? emptySchema() try { await inferArgs(script.language, script.content, script.schema as any) + script.no_main_func = undefined } catch (error) { + script.no_main_func = true sendUserToast( `The main signature was not parsable. This script is considered to be without main function` ) @@ -245,7 +247,8 @@ delete_after_use: script.delete_after_use, timeout: script.timeout, concurrency_key: emptyString(script.concurrency_key) ? undefined : script.concurrency_key, - visible_to_runner_only: script.visible_to_runner_only + visible_to_runner_only: script.visible_to_runner_only, + no_main_func: script.no_main_func } }) @@ -333,7 +336,9 @@ script.schema = script.schema ?? emptySchema() try { await inferArgs(script.language, script.content, script.schema as any) + script.no_main_func = undefined } catch (error) { + script.no_main_func = true sendUserToast( `The main signature was not parsable. This script is considered to be without main function` ) @@ -372,7 +377,8 @@ concurrency_key: emptyString(script.concurrency_key) ? undefined : script.concurrency_key, - visible_to_runner_only: script.visible_to_runner_only + visible_to_runner_only: script.visible_to_runner_only, + no_main_func: script.no_main_func } }) } diff --git a/frontend/src/lib/components/ScriptEditor.svelte b/frontend/src/lib/components/ScriptEditor.svelte index 8f52101165..3f210da17e 100644 --- a/frontend/src/lib/components/ScriptEditor.svelte +++ b/frontend/src/lib/components/ScriptEditor.svelte @@ -112,6 +112,7 @@ schema = nschema } catch (e) { validCode = false + schema = emptySchema() } } diff --git a/frontend/src/lib/components/common/table/ScriptRow.svelte b/frontend/src/lib/components/common/table/ScriptRow.svelte index 31919452fa..7aa5df73d4 100644 --- a/frontend/src/lib/components/common/table/ScriptRow.svelte +++ b/frontend/src/lib/components/common/table/ScriptRow.svelte @@ -34,6 +34,7 @@ } from 'lucide-svelte' import ScriptVersionHistory from '$lib/components/ScriptVersionHistory.svelte' import { Drawer, DrawerContent } from '..' + import NoMainFuncBadge from '$lib/components/NoMainFuncBadge.svelte' export let script: Script & { canWrite: boolean } export let marked: string | undefined @@ -108,6 +109,9 @@ archived {/if} + {#if script.no_main_func} + + {/if}
diff --git a/frontend/src/lib/components/flows/content/ScriptEditorDrawer.svelte b/frontend/src/lib/components/flows/content/ScriptEditorDrawer.svelte index 33328ee4f2..e87ce01462 100644 --- a/frontend/src/lib/components/flows/content/ScriptEditorDrawer.svelte +++ b/frontend/src/lib/components/flows/content/ScriptEditorDrawer.svelte @@ -47,6 +47,7 @@ ws_error_handler_muted?: boolean dedicated_worker?: boolean visible_to_runner_only?: boolean + no_main_func?: boolean } | undefined = undefined @@ -64,6 +65,7 @@ ws_error_handler_muted?: boolean dedicated_worker?: boolean visible_to_runner_only?: boolean + no_main_func?: boolean } | undefined = undefined @@ -73,7 +75,9 @@ script.schema = script.schema ?? emptySchema() try { await inferArgs(script.language, script.content, script.schema) + script.no_main_func = undefined } catch (error) { + script.no_main_func = true sendUserToast( `Impossible to infer the schema. Assuming this is a script without main function`, true diff --git a/frontend/src/lib/components/home/ItemsList.svelte b/frontend/src/lib/components/home/ItemsList.svelte index a7aca1c021..39876d44b9 100644 --- a/frontend/src/lib/components/home/ItemsList.svelte +++ b/frontend/src/lib/components/home/ItemsList.svelte @@ -14,7 +14,14 @@ } from '$lib/gen' import { userStore, workspaceStore } from '$lib/stores' import type uFuzzy from '@leeoniya/ufuzzy' - import { Code2, FoldVertical, LayoutDashboard, SearchCode, UnfoldVertical } from 'lucide-svelte' + import { + Code2, + FoldVertical, + LayoutDashboard, + SearchCode, + SlidersHorizontal, + UnfoldVertical + } from 'lucide-svelte' export let filter = '' export let subtab: 'flow' | 'script' | 'app' = 'script' @@ -36,6 +43,7 @@ import DrawerContent from '../common/drawer/DrawerContent.svelte' import Item from './Item.svelte' import TreeViewRoot from './TreeViewRoot.svelte' + import { Popup } from '../common' type TableItem = T & { canWrite: boolean @@ -64,10 +72,11 @@ let nbDisplayed = 15 - async function loadScripts(): Promise { + async function loadScripts(hideWithoutMain: boolean): Promise { const loadedScripts = await ScriptService.listScripts({ workspace: $workspaceStore!, - showArchived: archived ? true : undefined + showArchived: archived ? true : undefined, + hideWithoutMain: hideWithoutMain ? true : undefined }) scripts = loadedScripts.map((script: Script) => { @@ -192,7 +201,7 @@ $: { if ($userStore && $workspaceStore) { - loadScripts() + loadScripts(hideWithoutMain) loadFlows() if (!archived) { loadApps() @@ -265,11 +274,14 @@ const TREE_VIEW_SETTING_NAME = 'treeView' const FILTER_USER_FOLDER_SETTING_NAME = 'filterUserFolders' + const HIDE_WITHOUT_MAIN_SETTING = 'hideWithoutMain' let treeView = getLocalSetting(TREE_VIEW_SETTING_NAME) == 'true' let filterUserFolders = getLocalSetting(FILTER_USER_FOLDER_SETTING_NAME) == 'true' + let hideWithoutMain = getLocalSetting(HIDE_WITHOUT_MAIN_SETTING) == 'true' $: storeLocalSetting(TREE_VIEW_SETTING_NAME, treeView ? 'true' : undefined) $: storeLocalSetting(FILTER_USER_FOLDER_SETTING_NAME, filterUserFolders ? 'true' : undefined) + $: storeLocalSetting(HIDE_WITHOUT_MAIN_SETTING, hideWithoutMain ? 'true' : undefined) let contentSearch: ContentSearch @@ -404,7 +416,37 @@ {/if} {#if !loading}
- + + +