diff --git a/backend/.sqlx/query-381d6ead880f0f0c32e6d87d053bf16fad254b0b070317d9bed4fd97541a0272.json b/backend/.sqlx/query-381d6ead880f0f0c32e6d87d053bf16fad254b0b070317d9bed4fd97541a0272.json new file mode 100644 index 0000000000..d5781ac152 --- /dev/null +++ b/backend/.sqlx/query-381d6ead880f0f0c32e6d87d053bf16fad254b0b070317d9bed4fd97541a0272.json @@ -0,0 +1,26 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE draft AS d\n SET path = $3,\n value = to_json(\n CASE WHEN to_jsonb(d.value) -> $4::text = to_jsonb($2::text)\n THEN jsonb_set(to_jsonb(d.value), ARRAY[$4::text], to_jsonb($3::text), false)\n ELSE to_jsonb(d.value)\n END\n )\n WHERE d.workspace_id = $1\n AND d.path = $2\n AND d.typ::text = ANY($5::text[])\n AND NOT EXISTS (\n SELECT 1 FROM draft o\n WHERE o.workspace_id = d.workspace_id\n AND o.path = $3\n AND o.typ = d.typ\n AND o.email IS NOT DISTINCT FROM d.email\n )\n RETURNING d.id", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Text", + "Text", + "TextArray" + ] + }, + "nullable": [ + false + ] + }, + "hash": "381d6ead880f0f0c32e6d87d053bf16fad254b0b070317d9bed4fd97541a0272" +} diff --git a/backend/windmill-api/src/drafts.rs b/backend/windmill-api/src/drafts.rs index 405345ed5a..a6030c1f3d 100644 --- a/backend/windmill-api/src/drafts.rs +++ b/backend/windmill-api/src/drafts.rs @@ -502,12 +502,27 @@ async fn resolve_moved_to( // The client re-points its in-flight draft with this patch rather than // reproducing `typed_path_field` / `base_version_field` on its own side. - // Both keys have to be rewritten together: the path alone would leave the - // draft claiming the pre-move version, and the editor would greet it with a - // stale-draft prompt the moment it landed. + // The version is always restamped — it is lineage, not intent, and without + // it the draft lands at the new path still claiming the pre-move version and + // the editor greets it with a stale-draft prompt. The typed path follows the + // same rule as `move_drafts_for_path`: only when it still names the old path, + // so a rename the user staged in this very editor survives the relocation. + // Present and naming the old path ⇒ repoint. Absent ⇒ omit, so the patch + // never manufactures a target the draft did not have. Naming anywhere else + // ⇒ omit, so a staged rename is preserved. + let repoint_path = serde_json::from_str::(value) + .ok() + .and_then(|v| { + v.get(kind.typed_path_field()) + .and_then(|p| p.as_str()) + .map(|p| p == path) + }) + .unwrap_or(false); Ok(moved.map(|(new_path, new_by, head)| { let mut patch = serde_json::Map::new(); - patch.insert(kind.typed_path_field().to_string(), json!(&new_path)); + if repoint_path { + patch.insert(kind.typed_path_field().to_string(), json!(&new_path)); + } if let Some(field) = kind.base_version_field() { patch.insert(field.to_string(), head); } @@ -667,7 +682,7 @@ async fn update_draft( current_timestamp: now, moved_to: None, moved_by: None, - moved_patch: None, + moved_patch: None, })) } } diff --git a/backend/windmill-common/src/user_drafts.rs b/backend/windmill-common/src/user_drafts.rs index b23b25017b..0e0f2f61ac 100644 --- a/backend/windmill-common/src/user_drafts.rs +++ b/backend/windmill-common/src/user_drafts.rs @@ -603,13 +603,21 @@ pub async fn move_drafts_for_path( base_version: Option<(&str, String)>, ) -> Result { let typs = kinds.iter().map(|k| k.as_str()).collect::>(); - // `create_missing = false` on the typed path: absent means "same as the - // row's path", which `SET path` already points at `new_path`. + // The typed path is only followed along when it still names the OLD path. + // - absent (`create_missing = false`): means "wherever my row sits", which + // `SET path` already points at `new_path`. + // - equal to `old_path`: no rename staged, so it has to follow or deploying + // this draft would send the item back where it came from. + // - anything else: a rename the user staged in their editor. Deploying + // should still land there, so the move leaves it alone. let moved = sqlx::query_scalar!( r#"UPDATE draft AS d SET path = $3, value = to_json( - jsonb_set(to_jsonb(d.value), ARRAY[$4::text], to_jsonb($3::text), false) + CASE WHEN to_jsonb(d.value) -> $4::text = to_jsonb($2::text) + THEN jsonb_set(to_jsonb(d.value), ARRAY[$4::text], to_jsonb($3::text), false) + ELSE to_jsonb(d.value) + END ) WHERE d.workspace_id = $1 AND d.path = $2