mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-05 08:02:18 +00:00
feat(drafts): clone per-user drafts when forking a workspace
`clone_workspace_data` clones every other workspace-scoped table on fork creation (resources, variables, scripts, flows, apps, raw apps, triggers, schedules) but quietly dropped the `draft` table. With per-user drafts that meant any open editor in the parent lost its pending edits the moment a fork was created — surprising and inconsistent with how forks treat the deployed surface. New `clone_drafts` mirrors the existing clone helpers: a single INSERT...SELECT into the target workspace, preserving `path`, `typ`, `value`, `created_at`, and `email`. The `email` FK targets `password.email` which is instance-scoped so it carries across workspaces without remap. `created_at` is preserved on purpose so the per-tab `last_sync` baseline lines up with the parent's timeline — otherwise the fork's next autosave would race a stale `last_sync` and trip the conflict modal on every cloned draft. Plain INSERT (not UPSERT) is safe because the fork target is empty at create time; no conflict against the partial unique indexes (`draft_pkey_with_user` / `draft_pkey_legacy`). The synthetic BIGSERIAL `id` PK is regenerated by the default so it stays out of the column list.
This commit is contained in:
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO draft (workspace_id, path, typ, value, created_at, email)\n SELECT $2, path, typ, value, created_at, email\n FROM draft\n WHERE workspace_id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a8c3f6e53ae6bf257c610031ba8e21eb719f2cf78748b0b35fe742a941839158"
|
||||
}
|
||||
@@ -3867,6 +3867,10 @@ async fn clone_workspace_data(
|
||||
// Clone raw apps
|
||||
clone_raw_apps(tx, source_workspace_id, target_workspace_id).await?;
|
||||
|
||||
// Clone per-user drafts (and the legacy NULL-email workspace draft,
|
||||
// if any) so users picking up the fork keep their pending edits.
|
||||
clone_drafts(tx, source_workspace_id, target_workspace_id).await?;
|
||||
|
||||
// Clone workspace runnable dependencies and dependency map
|
||||
clone_workspace_runnable_dependencies(tx, source_workspace_id, target_workspace_id).await?;
|
||||
|
||||
@@ -4708,6 +4712,35 @@ async fn clone_raw_apps(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Clone every per-user draft (and the legacy NULL-email workspace draft,
|
||||
/// if present) from the parent. The fork target is empty at create time so
|
||||
/// a plain INSERT is safe — no need to UPSERT against the partial unique
|
||||
/// indexes (`draft_pkey_with_user` / `draft_pkey_legacy`). `id` is the
|
||||
/// BIGSERIAL synthetic PK and is regenerated by the default; we don't list
|
||||
/// it in the column set. `created_at` is preserved so the per-tab
|
||||
/// `last_sync` baseline the editor reads (`?get_draft=true` → overlay's
|
||||
/// `draft_saved_at`) lines up with the parent's timeline — otherwise the
|
||||
/// fork's first POST from any open editor would race a stale `last_sync`
|
||||
/// and trip the conflict modal on every cloned draft.
|
||||
async fn clone_drafts(
|
||||
tx: &mut Transaction<'_, Postgres>,
|
||||
source_workspace_id: &str,
|
||||
target_workspace_id: &str,
|
||||
) -> Result<()> {
|
||||
sqlx::query!(
|
||||
"INSERT INTO draft (workspace_id, path, typ, value, created_at, email)
|
||||
SELECT $2, path, typ, value, created_at, email
|
||||
FROM draft
|
||||
WHERE workspace_id = $1",
|
||||
source_workspace_id,
|
||||
target_workspace_id,
|
||||
)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn clone_workspace_runnable_dependencies(
|
||||
tx: &mut Transaction<'_, Postgres>,
|
||||
source_workspace_id: &str,
|
||||
|
||||
Reference in New Issue
Block a user