mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 00:02:30 +00:00
fix: a deploy is not blocked by the draft a move carried to its destination
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a51ead0e21
commit
4b348bb0bf
+5
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT d.email IS NULL as \"legacy!\", COALESCE(u.username, p.username) as username\n FROM draft d\n LEFT JOIN usr u ON u.workspace_id = d.workspace_id AND u.email = d.email\n LEFT JOIN password p ON p.email = d.email AND p.super_admin = true\n WHERE d.workspace_id = $1 AND d.path = $2 AND d.typ::text = ANY($3::text[])\n ORDER BY 2",
|
||||
"query": "SELECT d.email IS NULL as \"legacy!\", COALESCE(u.username, p.username) as username\n FROM draft d\n LEFT JOIN usr u ON u.workspace_id = d.workspace_id AND u.email = d.email\n LEFT JOIN password p ON p.email = d.email AND p.super_admin = true\n WHERE d.workspace_id = $1 AND d.path = $2 AND d.typ::text = ANY($3::text[])\n -- A row of the consuming owner's that a move brought here from the very path\n -- being renamed is the draft this deploy carries, not an item in its way. Any\n -- other row of theirs is a second item and still collides.\n AND ($4::text IS NULL OR d.email IS DISTINCT FROM $4 OR NOT EXISTS (\n SELECT 1 FROM draft_move m\n WHERE m.workspace_id = $1 AND m.typ::text = ANY($3::text[])\n AND m.old_path = $5 AND m.new_path = $2\n AND (m.email IS NULL OR m.email = $4)\n ))\n ORDER BY 2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -18,7 +18,9 @@
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"TextArray"
|
||||
"TextArray",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
@@ -26,5 +28,5 @@
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "07a759bde2efada8e71a29d059b5ef5575a908d8410418ef7fff1795bc0ec1f8"
|
||||
"hash": "e34f4d6a151cc791960c61197a4b00ea9db2f6ee47dc154faefe4c06c41cf1cf"
|
||||
}
|
||||
@@ -480,3 +480,53 @@ async fn test_a_legacy_discard_follows_a_rename(db: Pool<Postgres>) -> anyhow::R
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deploying a draft that a move carried off an archived script: its parent is still the
|
||||
/// version at the old path, so the deploy renames from there and carries what is left over
|
||||
/// — onto the very draft being deployed. That row is this deploy's own, not an item in its
|
||||
/// way, or the deploy is refused and every retry refuses again.
|
||||
#[sqlx::test(fixtures("base", "drafts_save_follows_move"))]
|
||||
async fn test_a_moved_draft_deploys_at_its_new_path(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
|
||||
sqlx::query("UPDATE script SET archived = true WHERE path = 'u/test-user/follow_a'")
|
||||
.execute(&db)
|
||||
.await?;
|
||||
let resp = reqwest::Client::new()
|
||||
.post(format!(
|
||||
"http://localhost:{port}/api/w/test-workspace/drafts/move/script/u/test-user/follow_a"
|
||||
))
|
||||
.header("Authorization", "Bearer SECRET_TOKEN")
|
||||
.json(&json!({ "new_path": "u/test-user/follow_b" }))
|
||||
.send()
|
||||
.await?;
|
||||
assert!(
|
||||
resp.status().is_success(),
|
||||
"move failed: {}",
|
||||
resp.text().await?
|
||||
);
|
||||
|
||||
// Deploy it where it now lives, still parented on the archived version it forked from.
|
||||
let resp = reqwest::Client::new()
|
||||
.post(format!(
|
||||
"http://localhost:{port}/api/w/test-workspace/scripts/create"
|
||||
))
|
||||
.header("Authorization", "Bearer SECRET_TOKEN")
|
||||
.json(&json!({
|
||||
"path": "u/test-user/follow_b",
|
||||
"parent_hash": HEAD_HASH,
|
||||
"summary": "A",
|
||||
"description": "",
|
||||
"content": "export function main() { return 2 }",
|
||||
"language": "deno",
|
||||
"schema": {}
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
let status = resp.status();
|
||||
let body = resp.text().await?;
|
||||
assert_eq!(status, 201, "the moved draft could not be deployed: {body}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1426,6 +1426,7 @@ async fn update_flow(
|
||||
&[UserDraftItemKind::Flow],
|
||||
flow_path,
|
||||
&nf.path,
|
||||
(!nf.skip_draft_deletion.unwrap_or(false)).then_some(authed.email.as_str()),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
@@ -2322,6 +2322,7 @@ async fn create_script_internal<'c>(
|
||||
&[UserDraftItemKind::Script],
|
||||
p_path,
|
||||
&ns.path,
|
||||
(!skip_draft_deletion).then_some(authed.email.as_str()),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
@@ -3770,6 +3770,7 @@ async fn update_app_internal<'a>(
|
||||
&[UserDraftItemKind::App, UserDraftItemKind::RawApp],
|
||||
path,
|
||||
&npath,
|
||||
(!ns.skip_draft_deletion.unwrap_or(false)).then_some(authed.email.as_str()),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
@@ -662,13 +662,16 @@ pub async fn delete_own_draft_for_path(
|
||||
///
|
||||
/// A draft already at `new_path` occupies it the way a deployed item does, so the move is
|
||||
/// refused with `BadRequest` inside the deploy's transaction, refusing the rename itself:
|
||||
/// moving onto it would merge two items or strand the row that lost.
|
||||
/// moving onto it would merge two items or strand the row that lost. `consumed_by` names
|
||||
/// the owner whose row there this deploy consumes, which is the draft being deployed after
|
||||
/// an earlier move carried it, and so cannot be in its own way.
|
||||
pub async fn move_drafts_for_path(
|
||||
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
w_id: &str,
|
||||
kinds: &[UserDraftItemKind],
|
||||
old_path: &str,
|
||||
new_path: &str,
|
||||
consumed_by: Option<&str>,
|
||||
) -> Result<()> {
|
||||
let typs = kinds.iter().map(|k| k.as_str()).collect::<Vec<_>>();
|
||||
// Named by workspace username, as the editors name other users' drafts: the
|
||||
@@ -679,10 +682,21 @@ pub async fn move_drafts_for_path(
|
||||
LEFT JOIN usr u ON u.workspace_id = d.workspace_id AND u.email = d.email
|
||||
LEFT JOIN password p ON p.email = d.email AND p.super_admin = true
|
||||
WHERE d.workspace_id = $1 AND d.path = $2 AND d.typ::text = ANY($3::text[])
|
||||
-- A row of the consuming owner's that a move brought here from the very path
|
||||
-- being renamed is the draft this deploy carries, not an item in its way. Any
|
||||
-- other row of theirs is a second item and still collides.
|
||||
AND ($4::text IS NULL OR d.email IS DISTINCT FROM $4 OR NOT EXISTS (
|
||||
SELECT 1 FROM draft_move m
|
||||
WHERE m.workspace_id = $1 AND m.typ::text = ANY($3::text[])
|
||||
AND m.old_path = $5 AND m.new_path = $2
|
||||
AND (m.email IS NULL OR m.email = $4)
|
||||
))
|
||||
ORDER BY 2"#,
|
||||
w_id,
|
||||
new_path,
|
||||
&typs as &[&str],
|
||||
consumed_by,
|
||||
old_path,
|
||||
)
|
||||
.fetch_all(&mut **tx)
|
||||
.await?;
|
||||
|
||||
Reference in New Issue
Block a user