feat: list references upon renaming a script or a flow (#5487)

* Refactored flow_workspace_runnables to more generic workspace_runnable_dependencies

* list flows referencing an item upon renaming it

* Refactor with two exclusive columns to avoid breaking FK constraints

* Show apps depending on item upon renaming

* sqlx prepare

* list-disc instead of •

* on delete and on update cascade

* displayPathChangedWarning oneOf check instead noneOf

* combine migrations + add "on update cascade" to flow fk

* unique index on app dependencies to avoid duplicates

* create new workspace_runnable_dependencies instead of renaming old table

* Add "looking for references" loading msg

* Revert "create new workspace_runnable_dependencies instead of renaming old table"

This reverts commit 015c38ca8f.

* flow_workspace_runnables view for backwards compatibility

* Add warning for script imports on rename

* support import dependency tracking in deno

* number of using scripts / flows / apps tooltip

* forgot sqlx prepare

* delete app-related rows in down migration
This commit is contained in:
Diego Imbert
2025-03-24 22:08:19 +01:00
committed by GitHub
parent ec11d577c6
commit e868fe2bf5
31 changed files with 648 additions and 17 deletions

View File

@@ -0,0 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO workspace_runnable_dependencies (app_path, runnable_path, runnable_is_flow, workspace_id) VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Bool",
"Varchar"
]
},
"nullable": []
},
"hash": "01755585cd3f6e100a66da331720286cbc09d4abf2926146b24a8c95cf21e5c8"
}

View File

@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO workspace_runnable_dependencies (flow_path, runnable_path, runnable_is_flow, workspace_id) VALUES ($1, $2, TRUE, $3) ON CONFLICT DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "09efbd7177c6172569dc29b7d9ede70315eeb4e0ef9ed3165365f257e27f5e68"
}

View File

@@ -0,0 +1,24 @@
{
"db_name": "PostgreSQL",
"query": "SELECT a.path\n FROM workspace_runnable_dependencies wru \n JOIN app a\n ON wru.app_path = a.path AND wru.workspace_id = a.workspace_id\n WHERE wru.runnable_path = $1 AND wru.runnable_is_flow = $2 AND wru.workspace_id = $3",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Bool",
"Text"
]
},
"nullable": [
false
]
},
"hash": "171b11d66b9ec6cb7b0dd74929e233389683f8d510850487453052a317391f0f"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM workspace_runnable_usage WHERE resource_path = $1 AND workspace_id = $2 and resource_kind = 'flow'",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "1ea8c2ce29da8d1be19dfd3ecc7f84fef7557f3e3046e7188ee06f6c3a2b2add"
}

View File

@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE workspace_runnable_dependencies SET flow_path = REGEXP_REPLACE(flow_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1') WHERE flow_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "44ce12725e09c1e32d82dfe9539ee42065315ac8df5fc8bd8004c0992e6f1bdb"
}

View File

@@ -0,0 +1,24 @@
{
"db_name": "PostgreSQL",
"query": "SELECT f.path\n FROM workspace_runnable_usage wru \n JOIN flow f\n ON wru.resource_path = f.path AND wru.workspace_id = f.workspace_id\n WHERE wru.runnable_path = $1 AND wru.runnable_is_flow = $2 AND wru.workspace_id = $3",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Bool",
"Text"
]
},
"nullable": [
false
]
},
"hash": "56a2fe44b73728c9aba10f3d1c7707bd1c48c58aaa5b2ae7560cbed3c8d46329"
}

View File

@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT importer_path FROM dependency_map \n WHERE workspace_id = $1 AND imported_path = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "importer_path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false
]
},
"hash": "6aa04cc83e746ebca45959294b6184d17c5100a3f3dc9fda02b1a7acc0b73fa5"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM workspace_runnable_dependencies WHERE flow_path = $1 AND workspace_id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "7023184bfeece73252aac9e3e89c5954f40cf46905b40b40ba97a21558a1ac01"
}

View File

@@ -0,0 +1,24 @@
{
"db_name": "PostgreSQL",
"query": "SELECT f.path\n FROM workspace_runnable_dependencies wru \n JOIN flow f\n ON wru.flow_path = f.path AND wru.workspace_id = f.workspace_id\n WHERE wru.runnable_path = $1 AND wru.runnable_is_flow = $2 AND wru.workspace_id = $3",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Bool",
"Text"
]
},
"nullable": [
false
]
},
"hash": "808790cc01ec68be41dfeb80dc560d447fd719f2a56ea954c8cce49ffabd4245"
}

View File

@@ -0,0 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO workspace_runnable_usage (resource_path, runnable_path, script_hash, runnable_is_flow, workspace_id, resource_kind) VALUES ($1, $2, $3, FALSE, $4, 'flow') ON CONFLICT DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Int8",
"Varchar"
]
},
"nullable": []
},
"hash": "894e5080e83b098839a050bb472359d6d9fdfdbffb40a2464a45056660a3a2d4"
}

View File

@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE workspace_runnable_usage SET resource_path = REGEXP_REPLACE(resource_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1') WHERE resource_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "9c956541e068154193243795d96cc62a0fc9d4ba56014f3e554cc2b50ce1da06"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM workspace_runnable_usage WHERE resource_path = $1 AND workspace_id = $2 and resource_kind = 'app'",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "a28f8697da75d7ac2c6e13dda394ee12e8cffe925bd7cc4e9d02dc90ffe200f3"
}

View File

@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO workspace_runnable_usage (resource_path, runnable_path, runnable_is_flow, workspace_id, resource_kind) VALUES ($1, $2, TRUE, $3, 'flow') ON CONFLICT DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "ab9783a48e0f6cacd5dd86cfa21ba8354d4cd733335db3d76155b663f668b9d7"
}

View File

@@ -0,0 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO workspace_runnable_dependencies (flow_path, runnable_path, script_hash, runnable_is_flow, workspace_id) VALUES ($1, $2, $3, FALSE, $4) ON CONFLICT DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Int8",
"Varchar"
]
},
"nullable": []
},
"hash": "aeca239a2997efc514ee56a6b3766fa5f32b5a56e28100a2487b478e5dc3eaec"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE workspace_runnable_usage SET workspace_id = $1 WHERE workspace_id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Text"
]
},
"nullable": []
},
"hash": "af2f780da994befd09959d3b1b077eb9b71337a9aa2a64cd2db031e64e929251"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM workspace_runnable_dependencies WHERE app_path = $1 AND workspace_id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "b190ad25e22367f71c1e16e34bfbbe4303249c8d8664962d479056971f0409a2"
}

View File

@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE workspace_runnable_usage SET runnable_path = REGEXP_REPLACE(runnable_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1') WHERE runnable_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "baf63525ca210c22d3ad8c0b197dc6b39dabab77ae7810f553f1773995db51f1"
}

View File

@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE workspace_runnable_dependencies SET runnable_path = REGEXP_REPLACE(runnable_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1') WHERE runnable_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "c14cc34a5350865b8a5b57205b6099e8f0ea697e279f6a72484ab031b7e1e952"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE workspace_runnable_dependencies SET workspace_id = $1 WHERE workspace_id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Text"
]
},
"nullable": []
},
"hash": "e19fab3d594c8b9d38f9caa8f71b16ffcb2bc8d94a8240143a913ad125ad6eb8"
}

View File

@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE workspace_runnable_dependencies SET app_path = REGEXP_REPLACE(app_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1') WHERE app_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "f699cc3644aeb35a0588bbb3a6bf2dc0746d9f3a1bea104123188fe2921bc886"
}

View File

@@ -0,0 +1,24 @@
DROP VIEW flow_workspace_runnables;
DELETE FROM workspace_runnable_dependencies WHERE flow_path IS NULL;
ALTER TABLE workspace_runnable_dependencies
DROP CONSTRAINT flow_workspace_runnables_workspace_id_flow_path_fkey;
ALTER TABLE workspace_runnable_dependencies
ADD CONSTRAINT flow_workspace_runnables_workspace_id_flow_path_fkey
FOREIGN KEY (flow_path, workspace_id) REFERENCES flow (path, workspace_id)
ON DELETE CASCADE;
ALTER TABLE workspace_runnable_dependencies
DROP CONSTRAINT workspace_runnable_dependencies_path_exclusive;
ALTER TABLE workspace_runnable_dependencies
DROP CONSTRAINT fk_workspace_runnable_dependencies_app_path;
ALTER TABLE workspace_runnable_dependencies DROP COLUMN app_path;
ALTER TABLE workspace_runnable_dependencies ALTER flow_path SET NOT NULL;
ALTER TABLE workspace_runnable_dependencies
RENAME TO flow_workspace_runnables;

View File

@@ -0,0 +1,42 @@
-- flow_workspace_runnables only stored runnable usages by
-- flows although apps can also use runnables
ALTER TABLE flow_workspace_runnables
RENAME TO workspace_runnable_dependencies;
ALTER TABLE workspace_runnable_dependencies ALTER flow_path DROP NOT NULL;
ALTER TABLE workspace_runnable_dependencies
ADD COLUMN app_path VARCHAR(255);
ALTER TABLE workspace_runnable_dependencies
ADD CONSTRAINT workspace_runnable_dependencies_path_exclusive CHECK (
(flow_path IS NOT NULL AND app_path IS NULL) OR
(flow_path IS NULL AND app_path IS NOT NULL)
);
ALTER TABLE workspace_runnable_dependencies
ADD CONSTRAINT fk_workspace_runnable_dependencies_app_path
FOREIGN KEY (app_path, workspace_id) REFERENCES app (path, workspace_id)
ON DELETE CASCADE
ON UPDATE CASCADE;
ALTER TABLE workspace_runnable_dependencies
DROP CONSTRAINT flow_workspace_runnables_workspace_id_flow_path_fkey;
ALTER TABLE workspace_runnable_dependencies
ADD CONSTRAINT flow_workspace_runnables_workspace_id_flow_path_fkey
FOREIGN KEY (flow_path, workspace_id) REFERENCES flow (path, workspace_id)
ON DELETE CASCADE
ON UPDATE CASCADE;
CREATE UNIQUE INDEX app_workspace_without_hash_unique_idx ON workspace_runnable_dependencies (app_path, runnable_path, runnable_is_flow, workspace_id) WHERE script_hash IS NULL;
CREATE UNIQUE INDEX app_workspace_with_hash_unique_idx ON workspace_runnable_dependencies (app_path, runnable_path, script_hash, runnable_is_flow, workspace_id) WHERE script_hash IS NOT NULL;
-- This is to maintain compatibility with old workers
CREATE VIEW flow_workspace_runnables AS
SELECT flow_path, runnable_path, script_hash, runnable_is_flow, workspace_id
FROM workspace_runnable_dependencies;

View File

@@ -4774,6 +4774,25 @@ paths:
items:
$ref: "#/components/schemas/ScriptHistory"
/w/{workspace}/scripts/list_paths_from_workspace_runnable/{path}:
get:
summary: list script paths using provided script as a relative import
operationId: listScriptPathsFromWorkspaceRunnable
tags:
- script
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/ScriptPath"
responses:
"200":
description: list of script paths
content:
application/json:
schema:
type: array
items:
type: string
/w/{workspace}/scripts/get_latest_version/{path}:
get:
summary: get scripts's latest version (hash)
@@ -5934,6 +5953,26 @@ paths:
schema:
$ref: "#/components/schemas/AppHistory"
/w/{workspace}/apps/list_paths_from_workspace_runnable/{runnable_kind}/{path}:
get:
summary: list app paths from workspace runnable
operationId: listAppPathsFromWorkspaceRunnable
tags:
- app
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/RunnableKind"
- $ref: "#/components/parameters/ScriptPath"
responses:
"200":
description: list of app paths
content:
application/json:
schema:
type: array
items:
type: string
/w/{workspace}/apps/history_update/a/{id}/v/{version}:
post:
summary: update app history

View File

@@ -12,7 +12,7 @@ use crate::{
db::{ApiAuthed, DB},
resources::get_resource_value_interpolated_internal,
users::{require_owner_of_path, OptAuthed},
utils::WithStarredInfoQuery,
utils::{RunnableKind, WithStarredInfoQuery},
webhook_util::{WebhookMessage, WebhookShared},
HTTP_CLIENT,
};
@@ -89,6 +89,10 @@ pub fn workspaced_service() -> Router {
.route("/history/p/*path", get(get_app_history))
.route("/get_latest_version/*path", get(get_latest_version))
.route("/history_update/a/:id/v/:version", post(update_app_history))
.route(
"/list_paths_from_workspace_runnable/:runnable_kind/*path",
get(list_paths_from_workspace_runnable),
)
.route("/custom_path_exists/*custom_path", get(custom_path_exists))
}
@@ -719,6 +723,28 @@ async fn get_secret_id(
Ok(hx)
}
async fn list_paths_from_workspace_runnable(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Path((w_id, runnable_kind, path)): Path<(String, RunnableKind, StripPath)>,
) -> JsonResult<Vec<String>> {
let mut tx = user_db.begin(&authed).await?;
let runnables = sqlx::query_scalar!(
r#"SELECT a.path
FROM workspace_runnable_dependencies wru
JOIN app a
ON wru.app_path = a.path AND wru.workspace_id = a.workspace_id
WHERE wru.runnable_path = $1 AND wru.runnable_is_flow = $2 AND wru.workspace_id = $3"#,
path.to_path(),
matches!(runnable_kind, RunnableKind::Flow),
w_id
)
.fetch_all(&mut *tx)
.await?;
tx.commit().await?;
Ok(Json(runnables))
}
async fn create_app(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,

View File

@@ -336,10 +336,10 @@ async fn list_paths_from_workspace_runnable(
let mut tx = user_db.begin(&authed).await?;
let runnables = sqlx::query_scalar!(
r#"SELECT f.path
FROM flow_workspace_runnables fwr
JOIN flow f
ON fwr.flow_path = f.path AND fwr.workspace_id = f.workspace_id
WHERE fwr.runnable_path = $1 AND fwr.runnable_is_flow = $2 AND fwr.workspace_id = $3"#,
FROM workspace_runnable_dependencies wru
JOIN flow f
ON wru.flow_path = f.path AND wru.workspace_id = f.workspace_id
WHERE wru.runnable_path = $1 AND wru.runnable_is_flow = $2 AND wru.workspace_id = $3"#,
path.to_path(),
matches!(runnable_kind, RunnableKind::Flow),
w_id

View File

@@ -147,6 +147,10 @@ pub fn workspaced_service() -> Router {
)
.route("/history/p/*path", get(get_script_history))
.route("/get_latest_version/*path", get(get_latest_version))
.route(
"/list_paths_from_workspace_runnable/*path",
get(list_paths_from_workspace_runnable),
)
.route(
"/history_update/h/:hash/p/*path",
post(update_script_history),
@@ -447,6 +451,24 @@ async fn create_snapshot_script(
return Ok((StatusCode::CREATED, format!("{}", script_hash.unwrap())));
}
async fn list_paths_from_workspace_runnable(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Path((w_id, path)): Path<(String, StripPath)>,
) -> JsonResult<Vec<String>> {
let mut tx = user_db.begin(&authed).await?;
let runnables = sqlx::query_scalar!(
r#"SELECT importer_path FROM dependency_map
WHERE workspace_id = $1 AND imported_path = $2"#,
w_id,
path.to_path(),
)
.fetch_all(&mut *tx)
.await?;
tx.commit().await?;
Ok(Json(runnables))
}
async fn create_script(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,

View File

@@ -2538,7 +2538,7 @@ async fn update_username_in_workpsace<'c>(
.await?;
sqlx::query!(
r#"UPDATE flow_workspace_runnables SET flow_path = REGEXP_REPLACE(flow_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\1') WHERE flow_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3"#,
r#"UPDATE workspace_runnable_dependencies SET flow_path = REGEXP_REPLACE(flow_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\1') WHERE flow_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3"#,
new_username,
old_username,
w_id
@@ -2546,7 +2546,15 @@ async fn update_username_in_workpsace<'c>(
.await?;
sqlx::query!(
r#"UPDATE flow_workspace_runnables SET runnable_path = REGEXP_REPLACE(runnable_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\1') WHERE runnable_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3"#,
r#"UPDATE workspace_runnable_dependencies SET app_path = REGEXP_REPLACE(app_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\1') WHERE app_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3"#,
new_username,
old_username,
w_id
).execute(&mut **tx)
.await?;
sqlx::query!(
r#"UPDATE workspace_runnable_dependencies SET runnable_path = REGEXP_REPLACE(runnable_path,'u/' || $2 || '/(.*)','u/' || $1 || '/\1') WHERE runnable_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3"#,
new_username,
old_username,
w_id

View File

@@ -190,7 +190,7 @@ pub(crate) async fn change_workspace_id(
.await?;
sqlx::query!(
"UPDATE flow_workspace_runnables SET workspace_id = $1 WHERE workspace_id = $2",
"UPDATE workspace_runnable_dependencies SET workspace_id = $1 WHERE workspace_id = $2",
&rw.new_id,
&old_id
)

View File

@@ -177,7 +177,7 @@ fn try_normalize(path: &Path) -> Option<PathBuf> {
Some(ret)
}
fn parse_bun_relative_imports(raw_code: &str, script_path: &str) -> error::Result<Vec<String>> {
fn parse_ts_relative_imports(raw_code: &str, script_path: &str) -> error::Result<Vec<String>> {
let mut relative_imports = vec![];
let r = parse_expr_for_imports(raw_code)?;
for import in r {
@@ -209,8 +209,8 @@ pub fn extract_relative_imports(
match language {
#[cfg(feature = "python")]
Some(ScriptLang::Python3) => parse_relative_imports(&raw_code, script_path).ok(),
Some(ScriptLang::Bun) | Some(ScriptLang::Bunnative) => {
parse_bun_relative_imports(&raw_code, script_path).ok()
Some(ScriptLang::Bun) | Some(ScriptLang::Bunnative) | Some(ScriptLang::Deno) => {
parse_ts_relative_imports(&raw_code, script_path).ok()
}
_ => None,
}
@@ -644,7 +644,7 @@ pub async fn handle_flow_dependency_job(
tx = clear_dependency_parent_path(&parent_path, &job_path, &job.workspace_id, "flow", tx)
.await?;
sqlx::query!(
"DELETE FROM flow_workspace_runnables WHERE flow_path = $1 AND workspace_id = $2",
"DELETE FROM workspace_runnable_dependencies WHERE flow_path = $1 AND workspace_id = $2",
job_path,
job.workspace_id
)
@@ -1013,7 +1013,7 @@ async fn lock_modules<'c>(
}
FlowModuleValue::Script { path, hash, .. } if !path.starts_with("hub/") => {
sqlx::query!(
"INSERT INTO flow_workspace_runnables (flow_path, runnable_path, script_hash, runnable_is_flow, workspace_id) VALUES ($1, $2, $3, FALSE, $4) ON CONFLICT DO NOTHING",
"INSERT INTO workspace_runnable_dependencies (flow_path, runnable_path, script_hash, runnable_is_flow, workspace_id) VALUES ($1, $2, $3, FALSE, $4) ON CONFLICT DO NOTHING",
job_path,
path,
hash.map(|h| h.0),
@@ -1024,10 +1024,10 @@ async fn lock_modules<'c>(
}
FlowModuleValue::Flow { path, .. } => {
sqlx::query!(
"INSERT INTO flow_workspace_runnables (flow_path, runnable_path, runnable_is_flow, workspace_id) VALUES ($1, $2, TRUE, $3) ON CONFLICT DO NOTHING",
"INSERT INTO workspace_runnable_dependencies (flow_path, runnable_path, runnable_is_flow, workspace_id) VALUES ($1, $2, TRUE, $3) ON CONFLICT DO NOTHING",
job_path,
path,
job.workspace_id
job.workspace_id,
)
.execute(&mut *tx)
.await?;
@@ -1439,6 +1439,22 @@ async fn lock_modules_app(
) -> Result<Value> {
match value {
Value::Object(mut m) => {
if let (Some(Value::String(ref run_type)), Some(path), Some("runnableByPath")) = (
m.get("runType"),
m.get("path").and_then(|s| s.as_str()),
m.get("type").and_then(|s| s.as_str()),
) {
// No script_hash because apps don't supports script version locks yet
sqlx::query!(
"INSERT INTO workspace_runnable_dependencies (app_path, runnable_path, runnable_is_flow, workspace_id) VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING",
job_path,
path,
run_type == "flow",
job.workspace_id
)
.execute(db)
.await?;
}
if m.contains_key("inlineScript") {
let v = m.get_mut("inlineScript").unwrap();
if let Some(v) = v.as_object_mut() {
@@ -1595,6 +1611,15 @@ pub async fn handle_app_dependency_job(
.clone()
.ok_or_else(|| Error::internal_err("App Dependency requires script hash".to_owned()))?
.0;
sqlx::query!(
"DELETE FROM workspace_runnable_dependencies WHERE app_path = $1 AND workspace_id = $2",
job_path,
job.workspace_id
)
.execute(db)
.await?;
let record = sqlx::query!("SELECT app_id, value FROM app_version WHERE id = $1", id)
.fetch_optional(db)
.await?

View File

@@ -5,6 +5,7 @@
<script lang="ts">
import { pathToMeta, type Meta } from '$lib/common'
import { localeConcatAnd, pluralize } from '$lib/utils'
import {
AppService,
FlowService,
@@ -30,7 +31,8 @@
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
import FolderEditor from './FolderEditor.svelte'
import { random_adj } from './random_positive_adjetive'
import { Eye, Folder, Plus, SearchCode, User } from 'lucide-svelte'
import { Eye, Folder, Loader2, Plus, SearchCode, User } from 'lucide-svelte'
import Tooltip from './Tooltip.svelte'
type PathKind =
| 'resource'
@@ -330,6 +332,37 @@
const openSearchWithPrefilledText: (t?: string) => void = getContext(
'openSearchWithPrefilledText'
)
$: displayPathChangedWarning =
(['flow', 'script', 'resource', 'variable'] as PathKind[]).includes(kind) &&
initialPath &&
initialPath !== path
$: pathUsageInFlowsPromise =
(kind == 'script' || kind == 'flow') &&
!!$workspaceStore &&
FlowService.listFlowPathsFromWorkspaceRunnable({
workspace: $workspaceStore,
path: initialPath,
runnableKind: kind
})
$: pathUsageInAppsPromise =
(kind == 'script' || kind == 'flow') &&
!!$workspaceStore &&
AppService.listAppPathsFromWorkspaceRunnable({
workspace: $workspaceStore,
path: initialPath,
runnableKind: kind
})
$: pathUsageInScriptsPromise =
kind == 'script' &&
!!$workspaceStore &&
ScriptService.listScriptPathsFromWorkspaceRunnable({
workspace: $workspaceStore,
path: initialPath
})
</script>
<Drawer bind:this={newFolder}>
@@ -503,7 +536,65 @@
<div class="text-red-600 dark:text-red-400 text-2xs mt-1.5">{error}</div>
</div>
{#if kind != 'app' && kind != 'schedule' && kind != 'http_trigger' && kind != 'websocket_trigger' && initialPath != '' && initialPath != undefined && initialPath != path}
{#if pathUsageInFlowsPromise || pathUsageInAppsPromise || pathUsageInScriptsPromise}
{#await Promise.all( [pathUsageInAppsPromise, pathUsageInFlowsPromise, pathUsageInScriptsPromise] )}
<Loader2 class="animate-spin" size={16} />
{:then [apps, flows, scripts]}
{#if (apps && apps.length) || (flows && flows.length) || (scripts && scripts.length)}
<p class="text-xs">
Used by {localeConcatAnd([
...(scripts && scripts.length ? [pluralize(scripts.length, 'script')] : []),
...(flows && flows.length ? [pluralize(flows.length, 'flow')] : []),
...(apps && apps.length ? [pluralize(apps.length, 'app')] : [])
])}
<Tooltip>
<ul>
{#each scripts || [] as path}
<li><a target="_blank" href="/scripts/edit/{path}">{path}</a></li>
{/each}
{#each flows || [] as path}
<li><a target="_blank" href="/flows/edit/{path}">{path}</a></li>
{/each}
{#each apps || [] as path}
<li><a target="_blank" href="/apps/edit/{path}">{path}</a></li>
{/each}
</ul>
</Tooltip>
</p>
{#if displayPathChangedWarning}
<Alert
type="warning"
class="mt-4"
title="Moving this item will break the following referencing it:"
>
<ul class="list-disc">
{#each scripts || [] as scriptPath}
<li>
<a href={`/scripts/edit/${scriptPath}`} class="text-blue-400" target="_blank">
{scriptPath}
</a>
</li>
{/each}
{#each flows || [] as flowPath}
<li>
<a href={`/flows/edit/${flowPath}`} class="text-blue-400" target="_blank">
{flowPath}
</a>
</li>
{/each}
{#each apps || [] as appPath}
<li>
<a href={`/apps/edit/${appPath}`} class="text-blue-400" target="_blank">
{appPath}
</a>
</li>
{/each}
</ul>
</Alert>
{/if}
{/if}
{/await}
{:else if displayPathChangedWarning}
<Alert type="warning" class="mt-4" title="Moving may break other items relying on it">
You are renaming an item that may be depended upon by other items. This may break apps, flows
or resources. Find if it used elsewhere using the content search. Note that linked variables

View File

@@ -1163,3 +1163,9 @@ export function isObjectTooBig(obj: any): boolean {
const { totalItems, maxDepth } = analyze(obj)
return maxDepth > MAX_DEPTH || totalItems > MAX_ITEMS
}
export function localeConcatAnd(items: string[]) {
if (!items.length) return ''
if (items.length === 1) return items[0]
return items.slice(0, -1).join(', ') + ' and ' + items[items.length - 1]
}