diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index bf83bdfd1d..6d973d68dc 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -13722,6 +13722,21 @@ components: type: boolean on_behalf_of_email: type: string + fallback_access_types: + type: array + items: + type: object + required: [path, kind, access_type] + properties: + path: + type: string + kind: + type: string + enum: [s3object, resource] + access_type: + type: string + enum: [r, w, rw] + required: - path - summary diff --git a/backend/windmill-api/src/assets.rs b/backend/windmill-api/src/assets.rs index a28f04f073..6ea1756f46 100644 --- a/backend/windmill-api/src/assets.rs +++ b/backend/windmill-api/src/assets.rs @@ -27,7 +27,8 @@ async fn list_assets( 'kind', kind, 'usages', ARRAY_AGG(jsonb_build_object( 'path', usage_path, - 'kind', usage_kind + 'kind', usage_kind, + 'access_type', usage_access_type )) ) as "list!: _" FROM asset diff --git a/backend/windmill-api/src/scripts.rs b/backend/windmill-api/src/scripts.rs index 6b74c6381b..e8a70396ae 100644 --- a/backend/windmill-api/src/scripts.rs +++ b/backend/windmill-api/src/scripts.rs @@ -42,7 +42,11 @@ use windmill_audit::audit_oss::audit_log; use windmill_audit::ActionKind; use windmill_worker::process_relative_imports; -use windmill_common::{error::to_anyhow, worker::CLOUD_HOSTED}; +use windmill_common::{ + assets::{clear_asset_usage, insert_asset_usage, parse_assets, AssetUsageKind}, + error::to_anyhow, + worker::CLOUD_HOSTED, +}; use windmill_common::{ db::UserDB, @@ -993,6 +997,20 @@ async fn create_script_internal<'c>( } }); + clear_asset_usage(&db, &w_id, &script_path, AssetUsageKind::Script).await?; + + for asset in parse_assets(&ns.content, language)?.iter().flatten() { + insert_asset_usage( + &db, + &w_id, + asset, + ns.fallback_access_types.as_ref().map(Vec::as_slice), + &ns.path, + AssetUsageKind::Script, + ) + .await?; + } + handle_deployment_metadata( &authed.email, &authed.username, diff --git a/backend/windmill-common/src/assets.rs b/backend/windmill-common/src/assets.rs index 0835901b9f..a7b859e9d7 100644 --- a/backend/windmill-common/src/assets.rs +++ b/backend/windmill-common/src/assets.rs @@ -1,7 +1,8 @@ use serde::{Deserialize, Serialize}; +use sqlx::PgExecutor; use windmill_parser::asset_parser::ParseAssetsResult; -use crate::scripts::ScriptLang; +use crate::{error, scripts::ScriptLang}; #[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone, Hash, Eq, sqlx::Type)] #[sqlx(type_name = "ASSET_KIND", rename_all = "lowercase")] @@ -39,7 +40,7 @@ pub struct AssetUsage { pub access_type: AssetUsageAccessType, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, Hash)] pub struct AssetWithAccessType { pub path: String, pub kind: AssetKind, @@ -86,3 +87,60 @@ impl From for AssetUsageAcc } } } + +pub async fn insert_asset_usage<'e>( + executor: impl PgExecutor<'e>, + workspace_id: &str, + parsed_asset: &ParseAssetsResult, + fallback_access_types: Option<&[AssetWithAccessType]>, + usage_path: &str, + usage_kind: AssetUsageKind, +) -> error::Result<()> { + let kind: AssetKind = parsed_asset.kind.into(); + let asset_alternative_access_type = || { + fallback_access_types + .as_ref() + .and_then(|v| { + v.iter() + .find(|a| a.kind == kind && a.path == parsed_asset.path) + }) + .map(|a| a.access_type) + }; + let access_type: Option = parsed_asset + .access_type + .map(Into::into) + .or_else(asset_alternative_access_type); + + sqlx::query!( + r#"INSERT INTO asset (workspace_id, path, kind, usage_access_type, usage_path, usage_kind) + VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT DO NOTHING"#, + workspace_id, + parsed_asset.path, + kind as AssetKind, + access_type as Option, + usage_path, + usage_kind as AssetUsageKind + ) + .execute(executor) + .await?; + + Ok(()) +} + +pub async fn clear_asset_usage<'e>( + executor: impl PgExecutor<'e>, + workspace_id: &str, + usage_path: &str, + usage_kind: AssetUsageKind, +) -> error::Result<()> { + sqlx::query!( + r#"DELETE FROM asset WHERE workspace_id = $1 AND usage_path = $2 AND usage_kind = $3"#, + workspace_id, + usage_path, + usage_kind as AssetUsageKind + ) + .execute(executor) + .await?; + + Ok(()) +} diff --git a/backend/windmill-common/src/scripts.rs b/backend/windmill-common/src/scripts.rs index ffa7bd486a..57dd7d2498 100644 --- a/backend/windmill-common/src/scripts.rs +++ b/backend/windmill-common/src/scripts.rs @@ -13,6 +13,7 @@ use std::{ }; use crate::{ + assets::AssetWithAccessType, error::{to_anyhow, Error}, utils::http_get_from_hub, DB, DEFAULT_HUB_BASE_URL, HUB_BASE_URL, @@ -348,6 +349,7 @@ pub struct NewScript { pub codebase: Option, pub has_preprocessor: Option, pub on_behalf_of_email: Option, + pub fallback_access_types: Option>, } fn lock_deserialize<'de, D>(deserializer: D) -> Result, D::Error> diff --git a/backend/windmill-worker/src/worker_lockfiles.rs b/backend/windmill-worker/src/worker_lockfiles.rs index 4770aa81d5..a4c0363442 100644 --- a/backend/windmill-worker/src/worker_lockfiles.rs +++ b/backend/windmill-worker/src/worker_lockfiles.rs @@ -11,7 +11,9 @@ use serde_json::{json, Value}; use sha2::Digest; use sqlx::types::Json; use uuid::Uuid; -use windmill_common::assets::{parse_assets, AssetKind, AssetUsageAccessType, AssetUsageKind}; +use windmill_common::assets::{ + clear_asset_usage, insert_asset_usage, parse_assets, AssetUsageKind, +}; use windmill_common::error::Error; use windmill_common::error::Result; use windmill_common::flows::{FlowModule, FlowModuleValue, FlowNodeId}; @@ -718,6 +720,8 @@ pub async fn handle_flow_dependency_job( .execute(&mut *tx) .await?; } + clear_asset_usage(&mut *tx, &job.workspace_id, &job_path, AssetUsageKind::Flow).await?; + let modified_ids; let errors; (flow.modules, tx, modified_ids, errors) = lock_modules( @@ -1213,36 +1217,16 @@ async fn lock_modules<'c>( } }; - if let Some(assets) = parse_assets(&content, language)? { - for asset_result in assets { - let kind: AssetKind = asset_result.kind.into(); - let asset_alternative_access_type = || { - asset_fallback_access_types - .as_ref() - .and_then(|v| { - v.iter() - .find(|a| a.kind == kind && a.path == asset_result.path) - }) - .map(|a| a.access_type) - }; - let access_type: Option = asset_result - .access_type - .map(Into::into) - .or_else(asset_alternative_access_type); - - sqlx::query!( - r#"INSERT INTO asset (workspace_id, path, kind, usage_access_type, usage_path, usage_kind) - VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT DO NOTHING"#, - job.workspace_id, - asset_result.path, - kind as AssetKind, - access_type as Option, - job_path, - AssetUsageKind::Flow as AssetUsageKind - ) - .execute(&mut *tx) - .await?; - } + for asset in parse_assets(&content, language)?.iter().flatten() { + insert_asset_usage( + &mut *tx, + &job.workspace_id, + asset, + asset_fallback_access_types.as_ref().map(Vec::as_slice), + job_path, + AssetUsageKind::Flow, + ) + .await?; } e.value = windmill_common::worker::to_raw_value(&FlowModuleValue::RawScript { diff --git a/frontend/src/lib/components/ScriptBuilder.svelte b/frontend/src/lib/components/ScriptBuilder.svelte index 480cb03e18..28cf048daa 100644 --- a/frontend/src/lib/components/ScriptBuilder.svelte +++ b/frontend/src/lib/components/ScriptBuilder.svelte @@ -97,9 +97,13 @@ } from './triggers/utils' import DraftTriggersConfirmationModal from './common/confirmationModal/DraftTriggersConfirmationModal.svelte' import { Triggers } from './triggers/triggers.svelte' + import type { AssetWithAccessType } from './assets/lib' interface Props { - script: NewScript & { draft_triggers?: Trigger[] } + script: NewScript & { + draft_triggers?: Trigger[] + fallback_access_types?: AssetWithAccessType[] + } fullyLoaded?: boolean initialPath?: string template?: 'docker' | 'bunnative' | 'script' @@ -536,7 +540,8 @@ no_main_func: script.no_main_func, has_preprocessor: script.has_preprocessor, deployment_message: deploymentMsg || undefined, - on_behalf_of_email: script.on_behalf_of_email + on_behalf_of_email: script.on_behalf_of_email, + fallback_access_types: script.fallback_access_types } }) @@ -1756,6 +1761,7 @@ kind={script.kind} {template} tag={script.tag} + bind:fallbackAccessTypes={script.fallback_access_types} lastSavedCode={savedScript?.draft?.content} lastDeployedCode={savedScript?.draft_only ? undefined : savedScript?.content} bind:args diff --git a/frontend/src/lib/components/ScriptEditor.svelte b/frontend/src/lib/components/ScriptEditor.svelte index a089b4d1c5..a784ce23fc 100644 --- a/frontend/src/lib/components/ScriptEditor.svelte +++ b/frontend/src/lib/components/ScriptEditor.svelte @@ -48,6 +48,7 @@ import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte' import AssetsDropdownButton from './assets/AssetsDropdownButton.svelte' import { usePromise } from '$lib/svelte5Utils.svelte' + import type { AssetWithAccessType } from './assets/lib' interface Props { // Exported @@ -78,6 +79,7 @@ lastSavedCode?: string | undefined lastDeployedCode?: string | undefined editor_bar_right?: import('svelte').Snippet + fallbackAccessTypes?: AssetWithAccessType[] } let { @@ -106,7 +108,8 @@ stablePathForCaptures = '', lastSavedCode = undefined, lastDeployedCode = undefined, - editor_bar_right + editor_bar_right, + fallbackAccessTypes = $bindable() }: Props = $props() $effect.pre(() => { @@ -512,7 +515,7 @@
{#if assets.status === 'ok' && assets.value.length > 0} - + {/if} {#if testPanelSize === 0} AssetService.listAssets({ workspace: $workspaceStore ?? '' })) @@ -110,9 +114,12 @@ {u.kind}
-
Read
-
Write
-
Todo
+ {#if assetDisplaysAsInputInFlowGraph(u)} +
Read
+ {/if} + {#if assetDisplaysAsOutputInFlowGraph(u)} +
Write
+ {/if}