mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-26 16:02:36 +00:00
Save script asset usages + fixes + save fallback access types
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user