workspace specific nit fixes (#9072)

* fix: capture linked variables in trash on bulk resource delete

delete_resources_bulk grew linked-variable cascade deletion in an
earlier commit on this branch but only mirrored the deletion side of
delete_resource — not the trashbin capture side. Linked variables
deleted via bulk were permanently lost while their single-delete
counterparts could be recovered from trash.

Fetch each resource's linked variable rows as JSON before bulk delete
and stash them under `trash_data['linked_variables']` of that
resource's trash entry, matching the shape produced by single-resource
delete.

* fix: ws_specific cleanup gaps in variable rename + bulk delete; tooltip

Four spots:

1. update_variable rename block: when a variable is renamed and a
   linked resource at the same path is renamed alongside, also move
   any explicit ws_specific 'resource' marker from the old path to
   the new one. Symmetric with what update_resource already does for
   ws_specific 'variable'.

2. delete_variables_bulk: clean ws_specific 'resource' rows for any
   linked resource paths before the resource DELETE. Without this,
   bulk-delete leaves orphaned markers that would cause a freshly
   recreated resource at the same path to be falsely treated as
   workspace-specific. (linked_resource trash capture is already
   present in the bulk path — the reviewer note about that was
   inaccurate against the current code.)

3. list_ws_specific: ORDER BY item_kind, path so the CLI sees a
   stable list across pulls/pushes — cheap on a small per-workspace
   row set and avoids spurious diffs.

4. VariableForm tooltip: mirror the resource form so users who find a
   variable already toggled know it may have been auto-marked by a
   workspace-specific resource referencing it, and that disabling
   doesn't retroactively un-mark the referencing resource.

* sqlx prepare
This commit is contained in:
Diego Imbert
2026-05-11 07:56:52 +00:00
committed by GitHub
parent b95f0e2379
commit da9e416b8e
7 changed files with 94 additions and 9 deletions
@@ -46,11 +46,11 @@
]
},
"nullable": [
false,
false,
false,
false,
false,
true,
true,
true,
true,
true,
true,
true
]
@@ -0,0 +1,28 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT s.item_kind, s.path\n FROM ws_specific s\n WHERE s.workspace_id = $1\n AND (\n (s.item_kind = 'resource' AND EXISTS (\n SELECT 1 FROM resource r\n WHERE r.workspace_id = s.workspace_id AND r.path = s.path\n ))\n OR (s.item_kind = 'variable' AND EXISTS (\n SELECT 1 FROM variable v\n WHERE v.workspace_id = s.workspace_id AND v.path = s.path\n ))\n )\n ORDER BY s.item_kind, s.path\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "item_kind",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
false
]
},
"hash": "290599fc173947acb518344d6fb631af9f524389309f17ef04f70c773b1d5e75"
}
@@ -15,7 +15,7 @@
]
},
"nullable": [
true
null
]
},
"hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55"
@@ -7351,6 +7351,7 @@ async fn list_ws_specific(
WHERE v.workspace_id = s.workspace_id AND v.path = s.path
))
)
ORDER BY s.item_kind, s.path
"#,
&w_id
)
+33 -2
View File
@@ -1238,10 +1238,39 @@ async fn delete_resources_bulk(
.await?;
if let Some(res_data) = trash_resource {
// Per-resource linked vars so each resource's trash entry carries
// exactly the variables that vanished with it (matching the
// single-delete shape: trash_data["linked_variables"]).
let mut this_linked: Vec<String> = Vec::new();
if let Some(value) = res_data.get("value") {
collect_var_refs(value, &mut linked_var_paths);
collect_var_refs(value, &mut this_linked);
}
this_linked.sort();
this_linked.dedup();
let trash_linked_vars: Vec<serde_json::Value> = if this_linked.is_empty() {
Vec::new()
} else {
let placeholders: Vec<String> = this_linked
.iter()
.enumerate()
.map(|(i, _)| format!("${}", i + 2))
.collect();
let query = format!(
"SELECT to_jsonb(t) FROM variable t WHERE workspace_id = $1 AND path IN ({})",
placeholders.join(", ")
);
let mut q = sqlx::query_scalar::<_, serde_json::Value>(&query).bind(&w_id);
for var_path in &this_linked {
q = q.bind(var_path);
}
q.fetch_all(&mut *tx).await?
};
let mut trash_data = serde_json::json!({"row": res_data});
if !trash_linked_vars.is_empty() {
trash_data["linked_variables"] = serde_json::Value::Array(trash_linked_vars);
}
let trash_data = serde_json::json!({"row": res_data});
windmill_common::trashbin::move_to_trash(
&mut *tx,
&w_id,
@@ -1251,6 +1280,8 @@ async fn delete_resources_bulk(
&authed.username,
)
.await?;
linked_var_paths.extend(this_linked);
}
}
linked_var_paths.sort();
+25
View File
@@ -755,6 +755,17 @@ async fn delete_variables_bulk(
)
.fetch_all(&mut *tx)
.await?;
// Mirror single delete_variable: clean the linked-resource ws_specific
// markers BEFORE deleting the resource rows so they don't survive as
// orphans. A resource later created at the same path would otherwise
// inherit a stale ws_specific flag.
sqlx::query!(
"DELETE FROM ws_specific WHERE workspace_id = $1 AND item_kind = 'resource' AND path = ANY($2)",
w_id,
&deleted_paths
)
.execute(&mut *tx)
.await?;
sqlx::query!(
"DELETE FROM resource WHERE path = ANY($1) AND workspace_id = $2",
&deleted_paths,
@@ -1019,6 +1030,20 @@ async fn update_variable(
)
.execute(&mut *tx)
.await?;
// The linked resource at the same path is renamed above; move
// its ws_specific 'resource' marker too so an explicitly-flagged
// resource doesn't lose its ws_specific status on rename and
// doesn't leave a stale marker at the old path. Symmetric with
// update_resource's rename block.
sqlx::query!(
"UPDATE ws_specific SET path = $1 WHERE workspace_id = $2 AND item_kind = 'resource' AND path = $3",
npath,
w_id,
path
)
.execute(&mut *tx)
.await?;
}
}
@@ -86,7 +86,7 @@
{#if deployTo}
<Label
label="Workspace specific"
tooltip="Prevents this variable from being deployed to prod/staging"
tooltip="Prevents this variable from being deployed to prod/staging. May have been enabled automatically because a workspace-specific resource references this variable via $var:. Disabling this toggle does not retroactively un-mark the resource that referenced it."
>
<Toggle bind:checked={wsSpecific} />
</Label>