From d760ea5eaf4dc33007f1fd3e5e07b86925a0aa11 Mon Sep 17 00:00:00 2001 From: wendrul <53628737+wendrul@users.noreply.github.com> Date: Thu, 26 Mar 2026 19:28:18 +0100 Subject: [PATCH] fix: add relative imports to the dependency list in deploymentUI (#8548) * prepare sqlx * Add relative imports to getDependencies of deployUI * nit * fix: correct get_imports doc comment, add tracing, use Set for dedup - Fix copy-pasted doc comment on get_imports (said "get dependents") - Add tracing::debug to get_imports handler to match get_dependents - Use Set for O(1) duplicate detection in deploy dependency traversal Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Ruben Fiszel Co-authored-by: Claude Opus 4.6 (1M context) --- ...20e383a998a54c95355bb85fe7e762a0d9765.json | 23 +++++++++++++++++ .../windmill-api-workspaces/src/workspaces.rs | 25 +++++++++++++++++++ backend/windmill-api/openapi.yaml | 24 ++++++++++++++++++ .../src/scoped_dependency_map.rs | 23 ++++++++++++++++- .../src/lib/components/DeployWorkspace.svelte | 12 +++++++++ 5 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 backend/.sqlx/query-52d765c87cb8da0ca71fb53156820e383a998a54c95355bb85fe7e762a0d9765.json diff --git a/backend/.sqlx/query-52d765c87cb8da0ca71fb53156820e383a998a54c95355bb85fe7e762a0d9765.json b/backend/.sqlx/query-52d765c87cb8da0ca71fb53156820e383a998a54c95355bb85fe7e762a0d9765.json new file mode 100644 index 0000000000..16ae512f37 --- /dev/null +++ b/backend/.sqlx/query-52d765c87cb8da0ca71fb53156820e383a998a54c95355bb85fe7e762a0d9765.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT DISTINCT imported_path as \"imported_path!\"\n FROM dependency_map\n WHERE workspace_id = $1\n AND importer_path = $2\n AND imported_path NOT LIKE 'dependencies/%'\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "imported_path!", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "52d765c87cb8da0ca71fb53156820e383a998a54c95355bb85fe7e762a0d9765" +} diff --git a/backend/windmill-api-workspaces/src/workspaces.rs b/backend/windmill-api-workspaces/src/workspaces.rs index 9f12ab4ee2..646a2369b9 100644 --- a/backend/windmill-api-workspaces/src/workspaces.rs +++ b/backend/windmill-api-workspaces/src/workspaces.rs @@ -79,6 +79,7 @@ pub fn workspaced_service() -> Router { .route("/rebuild_dependency_map", post(rebuild_dependency_map)) .route("/get_dependency_map", get(get_dependency_map)) .route("/get_dependents/*imported_path", get(get_dependents)) + .route("/get_imports/*importer_path", get(get_imports)) .route("/get_dependents_amounts", post(get_dependents_amounts)) .route("/get_settings", get(get_settings)) .route( @@ -4358,6 +4359,30 @@ async fn get_dependents( Ok(Json(dependents)) } +async fn get_imports( + Extension(db): Extension, + Path((w_id, importer_path)): Path<(String, String)>, + _authed: ApiAuthed, +) -> JsonResult> { + tracing::debug!( + workspace_id = %w_id, + importer_path = %importer_path, + "API: Getting imports for importer path" + ); + + let imports = ScopedDependencyMap::get_imports(&importer_path, &w_id, &db).await?; + + tracing::debug!( + workspace_id = %w_id, + importer_path = %importer_path, + imports_count = imports.len(), + "API: Found imports: {:?}", + imports + ); + + Ok(Json(imports)) +} + #[derive(Serialize, Debug)] struct DependentsAmount { imported_path: String, diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 2b83550ab2..1275d648c2 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -2714,6 +2714,30 @@ paths: items: $ref: "#/components/schemas/DependencyDependent" + /w/{workspace}/workspaces/get_imports/{importer_path}: + get: + summary: get script imports for an importer path + operationId: getImports + tags: + - workspace + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - name: importer_path + in: path + required: true + schema: + type: string + description: The script path to get imports for + responses: + "200": + description: list of imported script paths + content: + application/json: + schema: + type: array + items: + type: string + /w/{workspace}/workspaces/get_dependents_amounts: post: summary: get dependents amounts for multiple imported paths diff --git a/backend/windmill-dep-map/src/scoped_dependency_map.rs b/backend/windmill-dep-map/src/scoped_dependency_map.rs index 9821b8e610..fcee6fe5f1 100644 --- a/backend/windmill-dep-map/src/scoped_dependency_map.rs +++ b/backend/windmill-dep-map/src/scoped_dependency_map.rs @@ -445,7 +445,28 @@ SELECT importer_node_id, imported_path, imported_lockfile_hash } } - /// Get dependents of any imported path - returns scripts/flows/apps that depend on it + /// Get imports of a given importer path - returns paths that the importer depends on + pub async fn get_imports<'c>( + importer_path: &str, + workspace_id: &str, + e: impl PgExecutor<'c>, + ) -> Result> { + sqlx::query_scalar!( + r#" + SELECT DISTINCT imported_path as "imported_path!" + FROM dependency_map + WHERE workspace_id = $1 + AND importer_path = $2 + AND imported_path NOT LIKE 'dependencies/%' + "#, + workspace_id, + importer_path + ) + .fetch_all(e) + .await + .map_err(Error::from) + } + pub async fn get_dependents<'c>( imported_path: &str, workspace_id: &str, diff --git a/frontend/src/lib/components/DeployWorkspace.svelte b/frontend/src/lib/components/DeployWorkspace.svelte index 9f1fd71146..3645bd587b 100644 --- a/frontend/src/lib/components/DeployWorkspace.svelte +++ b/frontend/src/lib/components/DeployWorkspace.svelte @@ -262,13 +262,25 @@ return getTriggerDependency(additionalInformation.triggers.kind, path, $workspaceStore!) } throw new Error('Missing trigger information') + } else if (kind == 'script') { + const imports = await WorkspaceService.getImports({ + workspace: $workspaceStore!, + importerPath: path + }) + return imports.map((importedPath) => ({ kind: 'script' as Kind, path: importedPath })) } return [] } let toProcess = [{ kind, path }] + let processedSet = new Set() let processed: { kind: Kind; path: string }[] = [] while (toProcess.length > 0) { const { kind, path } = toProcess.pop()! + const key = `${kind}:${path}` + if (processedSet.has(key)) { + continue + } + processedSet.add(key) toProcess.push(...(await rec(kind, path))) processed.push({ kind, path }) }