listAssetsByUsage and asset nodes on transitive usages

This commit is contained in:
Diego Imbert
2025-07-02 16:17:01 +02:00
parent f18b051ce0
commit 9283bfae30
4 changed files with 106 additions and 39 deletions
+33 -14
View File
@@ -13005,31 +13005,50 @@ paths:
usage_kind:
$ref: '#/components/schemas/AssetUsageKind'
/w/{workspace}/assets/list_for_usage:
get:
summary: List all assets used by a given usage path
operationId: listAssetsForUsage
/w/{workspace}/assets/list_by_usages:
post:
summary: List all assets used by given usages paths
operationId: listAssetsByUsage
tags:
- asset
parameters:
- $ref: '#/components/parameters/WorkspaceId'
- $ref: '#/components/parameters/AssetUsageKind'
- $ref: '#/components/parameters/AssetUsagePath'
requestBody:
description: list assets by usages
required: true
content:
application/json:
schema:
type: object
required: [usages]
properties:
usages:
type: array
items:
type: object
required: [usage_path, usage_kind]
properties:
usage_path:
type: string
usage_kind:
$ref: '#/components/schemas/AssetUsageKind'
responses:
'200':
description: all assets used by the given usage path
description: all assets used by the given usage paths, in the same order
content:
application/json:
schema:
type: array
items:
type: object
required: [path, kind]
properties:
path:
type: string
kind:
$ref: '#/components/schemas/AssetKind'
type: array
items:
type: object
required: [path, kind]
properties:
path:
type: string
kind:
$ref: '#/components/schemas/AssetKind'
components:
securitySchemes:
+35 -23
View File
@@ -1,5 +1,5 @@
use axum::{
extract::{Path, Query},
extract::Path,
routing::{get, post},
Extension, Json, Router,
};
@@ -17,7 +17,7 @@ pub fn workspaced_service() -> Router {
Router::new()
.route("/link", post(link_assets))
.route("/list", get(list_assets))
.route("/list_for_usage", get(list_assets_for_usage))
.route("/list_by_usages", post(list_assets_by_usages))
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone, Hash, Eq, sqlx::Type)]
@@ -42,6 +42,12 @@ pub struct Asset {
pub kind: AssetKind,
}
#[derive(Deserialize)]
struct Usage {
usage_kind: AssetUsageKind,
usage_path: String,
}
#[derive(Deserialize)]
pub struct LinkAssetsBody {
pub assets: Vec<Asset>,
@@ -117,31 +123,37 @@ async fn list_assets(
}
#[derive(Deserialize)]
struct ListAssetForUsageQuery {
usage_kind: AssetUsageKind,
usage_path: String,
struct ListAssetsByUsagesBody {
usages: Vec<Usage>,
}
async fn list_assets_for_usage(
async fn list_assets_by_usages(
authed: ApiAuthed,
Path(w_id): Path<String>,
Query(ListAssetForUsageQuery { usage_kind, usage_path }): Query<ListAssetForUsageQuery>,
Extension(user_db): Extension<UserDB>,
) -> JsonResult<Vec<Value>> {
let assets = sqlx::query_scalar!(
r#"SELECT
jsonb_build_object(
'path', path,
'kind', kind
) as "list!: _"
FROM asset
WHERE workspace_id = $1 AND usage_path = $2 AND usage_kind = $3"#,
w_id,
usage_path,
usage_kind as AssetUsageKind
)
.fetch_all(&mut *user_db.begin(&authed).await?)
.await?;
Json(body): Json<ListAssetsByUsagesBody>,
) -> JsonResult<Vec<Vec<Value>>> {
let mut assets_vec = vec![];
Ok(Json(assets))
let mut tx = user_db.begin(&authed).await?;
for usage in body.usages {
let assets = sqlx::query_scalar!(
r#"SELECT
jsonb_build_object(
'path', path,
'kind', kind
) as "list!: _"
FROM asset
WHERE workspace_id = $1 AND usage_path = $2 AND usage_kind = $3"#,
w_id,
usage.usage_path,
usage.usage_kind as AssetUsageKind
)
.fetch_all(&mut *tx)
.await?;
assets_vec.push(assets);
}
Ok(Json(assets_vec))
}
+1 -1
View File
@@ -86,7 +86,7 @@ export type FlowEditorContext = {
export type FlowGraphAssetContext = StateStore<{
selectedAsset: Asset | undefined
assetsMap?: Record<string, { asset: Asset; accessType: 'read' | 'write' }[]> // Maps module ids to their assets
assetsMap: Record<string, { asset: Asset; accessType: 'read' | 'write' }[]> // Maps module ids to their assets
s3FilePicker: S3FilePicker | undefined
dbManagerDrawer: DbManagerDrawer | undefined
resourceEditorDrawer: ResourceEditorDrawer | undefined
@@ -1,5 +1,11 @@
<script lang="ts">
import { FlowService, ResourceService, type FlowModule } from '../../gen'
import {
AssetService,
FlowService,
ResourceService,
type AssetUsageKind,
type FlowModule
} from '../../gen'
import { NODE, type GraphModuleState } from '.'
import { getContext, onDestroy, setContext, tick, untrack } from 'svelte'
@@ -191,6 +197,8 @@
})
setContext<FlowGraphAssetContext>('FlowGraphAssetContext', flowGraphAssetsCtx)
const assetsMap = $derived(flowGraphAssetsCtx.val.assetsMap)
// Fetch resource metadata for the ExploreAssetButton
const resMetadataCache = $derived(flowGraphAssetsCtx.val.resourceMetadataCache)
$effect(() => {
for (const { asset } of Object.values(assetsMap ?? []).flatMap((x) => x)) {
@@ -201,6 +209,34 @@
}
})
// Fetch transitive assets (path scripts and flows)
$effect(() => {
if (!$workspaceStore) return
let usages: { usage_path: string; usage_kind: AssetUsageKind }[] = []
let modIds: string[] = []
for (const mod of getAllModules(modules)) {
if (mod.id in assetsMap) continue
if (mod.value.type === 'flow' || mod.value.type === 'script') {
usages.push({ usage_path: mod.value.path, usage_kind: mod.value.type })
modIds.push(mod.id)
}
}
if (usages.length) {
AssetService.listAssetsByUsage({
workspace: $workspaceStore,
requestBody: { usages }
}).then((result) => {
result.map((assets, idx) => {
const [usage, modId] = [usages[idx], modIds[idx]]
assetsMap[modId] = assets.map((asset) => ({
asset,
accessType: usage.usage_kind === 'flow' ? 'read' : 'write'
}))
})
})
}
})
function computeSimplifiableFlow(modules: FlowModule[], simplifiedFlow: boolean) {
const isSimplif = isSimplifiable(modules)
simplifiableFlow = isSimplif ? { simplifiedFlow } : undefined