Save script asset usages + fixes + save fallback access types

This commit is contained in:
Diego Imbert
2025-07-04 17:13:22 +02:00
parent 8ac00f5442
commit 34a92c652b
9 changed files with 136 additions and 42 deletions
+15
View File
@@ -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
+2 -1
View File
@@ -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
+19 -1
View File
@@ -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,
+60 -2
View File
@@ -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<windmill_parser::asset_parser::AssetUsageAccessType> for AssetUsageAcc
}
}
}
pub async fn insert_asset_usage<'e>(
executor: impl PgExecutor<'e>,
workspace_id: &str,
parsed_asset: &ParseAssetsResult<String>,
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<AssetUsageAccessType> = 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<AssetUsageAccessType>,
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(())
}
+2
View File
@@ -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<String>,
pub has_preprocessor: Option<bool>,
pub on_behalf_of_email: Option<String>,
pub fallback_access_types: Option<Vec<AssetWithAccessType>>,
}
fn lock_deserialize<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
+15 -31
View File
@@ -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<AssetUsageAccessType> = 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<AssetUsageAccessType>,
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 {
@@ -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
@@ -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 @@
<div class="h-full !overflow-visible bg-gray-50 dark:bg-[#272D38] relative">
<div class="absolute top-2 right-4 z-10 flex flex-row gap-2">
{#if assets.status === 'ok' && assets.value.length > 0}
<AssetsDropdownButton assets={assets.value} />
<AssetsDropdownButton assets={assets.value} bind:fallbackAccessTypes />
{/if}
{#if testPanelSize === 0}
<HideButton
@@ -15,6 +15,10 @@
import { pluralize, truncate } from '$lib/utils'
import { untrack } from 'svelte'
import ExploreAssetButton from './ExploreAssetButton.svelte'
import {
assetDisplaysAsInputInFlowGraph,
assetDisplaysAsOutputInFlowGraph
} from '$lib/components/flows/utils'
let assets = usePromise(() => AssetService.listAssets({ workspace: $workspaceStore ?? '' }))
@@ -110,9 +114,12 @@
<span class="text-xs text-tertiary">{u.kind}</span>
</div>
<div class="flex gap-2">
<div class="text-xs border text-tertiary max-w-fit p-1 rounded-md">Read</div>
<div class="text-xs border text-tertiary max-w-fit p-1 rounded-md">Write</div>
<div class="text-xs border text-tertiary max-w-fit p-1 rounded-md">Todo</div>
{#if assetDisplaysAsInputInFlowGraph(u)}
<div class="text-xs border text-tertiary max-w-fit p-1 rounded-md">Read</div>
{/if}
{#if assetDisplaysAsOutputInFlowGraph(u)}
<div class="text-xs border text-tertiary max-w-fit p-1 rounded-md">Write</div>
{/if}
</div>
</a>
</li>