From d3d35d4cd86dc73a4e2e007f457bc945ccef8263 Mon Sep 17 00:00:00 2001 From: hugocasa Date: Wed, 28 Jan 2026 19:18:54 +0100 Subject: [PATCH] fix(backend): leave job and audit history and archive workspace when changing workspace id (#7724) --- ...1f7f387f5055c47f493271d26731336257384.json | 10 +- ...40862ed795f96870dd9ec999ea7e9373ec51.json} | 4 +- ...c6fb21c7ef440d1a7ee57e7efd6a079db16d5.json | 14 - ...c1607757eecba9d2d0326b09d6fcd4dc45c12.json | 15 + ...830ed343a5c6a1fef780f477dfbed80300f3.json} | 4 +- ...153c43903f929ae5d62fbba12610f89c36d55.json | 2 +- ...dbeec6327a8d87d6a45d6c0823ec42c65639.json} | 4 +- ...7ea87026bb4e56c0ccfb12f2feaf1a6124c86.json | 15 + ...3f0c4e8ba950bf79e9bd1cf2618a322be7858.json | 15 + ...026d4d4e0d381b31fe02da7d2667c0cdc1a85.json | 15 - ...30aa94729cfdb307c7f56584db8edcfd48e42.json | 22 ++ ...37e6d0491a2e8d04fe87eddb5a573de00d125.json | 15 + ...995e78878a4a4ff6894a04e6626cdd98c24e4.json | 15 - ...93f31e941e71b9addd1e915fe779f81a6ae43.json | 15 + ...3b60169989703ee33d14ab348301885c9d745.json | 16 + ...94d939a9e3ab3921605e5b0f4f7586e431c2.json} | 4 +- ...0280a8c1d6a7f1dc99f14c894899031534bea.json | 15 + ...6dc1adfdaeed3f8dfc9d44887347f82f2d520.json | 14 + ...c052c40dcdacade0f9b9921860160604f3763.json | 15 - ...bbe728f4eabcd6a6e7ad7c8445efb6966e0c9.json | 15 + ...dbe467e17e15d4a6512889009d850760502d9.json | 15 + ...7440bcb4733ff706669abc787cd414c11f82e.json | 15 + ...ae00cfdcc3ef056625a5a5bef76377953278.json} | 5 +- ...66cd5a6ca2ab25926f70095328d5ea2d2bc7c.json | 15 - ...dfde77f0387d1ed8f5defa34359147e9c0907.json | 15 + ...e4ee91c14823639cbcdb77d92409fe5218c3c.json | 15 + ...5ec974b035405da38bf3e7764f7e22232b760.json | 22 -- backend/windmill-api/src/workspaces.rs | 52 +-- backend/windmill-api/src/workspaces_extra.rs | 300 ++++++++++++------ .../settings/ChangeWorkspaceId.svelte | 15 +- 30 files changed, 475 insertions(+), 238 deletions(-) rename backend/.sqlx/{query-65121a4bfba70c2d7055a2b58c8520fddaef1bd9a3f041e851f9c136c73d34e7.json => query-0cfb1528c3636dd1f43c41b91aa340862ed795f96870dd9ec999ea7e9373ec51.json} (51%) delete mode 100644 backend/.sqlx/query-2925aee1a56827a1ca839673ea4c6fb21c7ef440d1a7ee57e7efd6a079db16d5.json create mode 100644 backend/.sqlx/query-452fe403cef5a62cb34b9996794c1607757eecba9d2d0326b09d6fcd4dc45c12.json rename backend/.sqlx/{query-0659bab15d4cccdb04c7a57e0e3bbb6bfebb8896601a27ddf5618d4eae678bc1.json => query-4c8d3693059ce1e2bbc84d76b543830ed343a5c6a1fef780f477dfbed80300f3.json} (52%) rename backend/.sqlx/{query-0ba594244a366a31d9bed97a2d7b031d42c23463599d267d1712d1af1d26b321.json => query-642b6c2c55c19f554a0c4e8dcc9ddbeec6327a8d87d6a45d6c0823ec42c65639.json} (51%) create mode 100644 backend/.sqlx/query-65c2ecb52cc777f17ffeb77be597ea87026bb4e56c0ccfb12f2feaf1a6124c86.json create mode 100644 backend/.sqlx/query-681bab96dce7249eda1d0d207513f0c4e8ba950bf79e9bd1cf2618a322be7858.json delete mode 100644 backend/.sqlx/query-6bdb3fcfe16fc40222dc7010a11026d4d4e0d381b31fe02da7d2667c0cdc1a85.json create mode 100644 backend/.sqlx/query-739501f637dac4e5f4843e3ebfc30aa94729cfdb307c7f56584db8edcfd48e42.json create mode 100644 backend/.sqlx/query-7c45f8d05a10ccf538c1b63aa1337e6d0491a2e8d04fe87eddb5a573de00d125.json delete mode 100644 backend/.sqlx/query-81a9d976ac5c1a78c83b95a1164995e78878a4a4ff6894a04e6626cdd98c24e4.json create mode 100644 backend/.sqlx/query-81e87e212075159270ff6c2811e93f31e941e71b9addd1e915fe779f81a6ae43.json create mode 100644 backend/.sqlx/query-82185eb02e03e3dd1a4b5a3f22c3b60169989703ee33d14ab348301885c9d745.json rename backend/.sqlx/{query-0d0c379b1cd2eec15869dd0b1a31886a95d53096fdcb1cdb1e0eb282b54105dc.json => query-863581331dc7abd9ffa47c6977ee94d939a9e3ab3921605e5b0f4f7586e431c2.json} (57%) create mode 100644 backend/.sqlx/query-893c2dea38bfff42ad8cbd7afaf0280a8c1d6a7f1dc99f14c894899031534bea.json create mode 100644 backend/.sqlx/query-8b8d29f3133228dbc5bdf1927e06dc1adfdaeed3f8dfc9d44887347f82f2d520.json delete mode 100644 backend/.sqlx/query-93e6cdf58803f63d7d465f9d778c052c40dcdacade0f9b9921860160604f3763.json create mode 100644 backend/.sqlx/query-98034dc06e7478f4a828ea58cf9bbe728f4eabcd6a6e7ad7c8445efb6966e0c9.json create mode 100644 backend/.sqlx/query-b3f791c8ef04f0aefd9b510d751dbe467e17e15d4a6512889009d850760502d9.json create mode 100644 backend/.sqlx/query-b928974804d710f37c4703a23c67440bcb4733ff706669abc787cd414c11f82e.json rename backend/.sqlx/{query-8f0031533f1bf407bd5d8af4d364eaf00d4c38ee7ba75141b40fc9fcd2ffc0b8.json => query-e1e55ce8c28ac4d253c289e4c972ae00cfdcc3ef056625a5a5bef76377953278.json} (63%) delete mode 100644 backend/.sqlx/query-e2f7a1a48bb026df5c7155b76e166cd5a6ca2ab25926f70095328d5ea2d2bc7c.json create mode 100644 backend/.sqlx/query-e3491d953cb9f85a5e3a9b1a386dfde77f0387d1ed8f5defa34359147e9c0907.json create mode 100644 backend/.sqlx/query-f23a296f5e04dcf6f2cda808193e4ee91c14823639cbcdb77d92409fe5218c3c.json delete mode 100644 backend/.sqlx/query-f863cb7ec416d774dc4c8170b2f5ec974b035405da38bf3e7764f7e22232b760.json diff --git a/backend/.sqlx/query-07168aaf14cb6beff0ad4274b441f7f387f5055c47f493271d26731336257384.json b/backend/.sqlx/query-07168aaf14cb6beff0ad4274b441f7f387f5055c47f493271d26731336257384.json index e7ed0aee65..d29a18c691 100644 --- a/backend/.sqlx/query-07168aaf14cb6beff0ad4274b441f7f387f5055c47f493271d26731336257384.json +++ b/backend/.sqlx/query-07168aaf14cb6beff0ad4274b441f7f387f5055c47f493271d26731336257384.json @@ -46,11 +46,11 @@ ] }, "nullable": [ - false, - false, - false, - false, - false, + true, + true, + true, + true, + true, true, true ] diff --git a/backend/.sqlx/query-65121a4bfba70c2d7055a2b58c8520fddaef1bd9a3f041e851f9c136c73d34e7.json b/backend/.sqlx/query-0cfb1528c3636dd1f43c41b91aa340862ed795f96870dd9ec999ea7e9373ec51.json similarity index 51% rename from backend/.sqlx/query-65121a4bfba70c2d7055a2b58c8520fddaef1bd9a3f041e851f9c136c73d34e7.json rename to backend/.sqlx/query-0cfb1528c3636dd1f43c41b91aa340862ed795f96870dd9ec999ea7e9373ec51.json index 84d19d1d0f..e47040278e 100644 --- a/backend/.sqlx/query-65121a4bfba70c2d7055a2b58c8520fddaef1bd9a3f041e851f9c136c73d34e7.json +++ b/backend/.sqlx/query-0cfb1528c3636dd1f43c41b91aa340862ed795f96870dd9ec999ea7e9373ec51.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE job_stats SET workspace_id = $1 WHERE workspace_id = $2", + "query": "UPDATE mcp_oauth_server_code SET workspace_id = $1 WHERE workspace_id = $2", "describe": { "columns": [], "parameters": { @@ -11,5 +11,5 @@ }, "nullable": [] }, - "hash": "65121a4bfba70c2d7055a2b58c8520fddaef1bd9a3f041e851f9c136c73d34e7" + "hash": "0cfb1528c3636dd1f43c41b91aa340862ed795f96870dd9ec999ea7e9373ec51" } diff --git a/backend/.sqlx/query-2925aee1a56827a1ca839673ea4c6fb21c7ef440d1a7ee57e7efd6a079db16d5.json b/backend/.sqlx/query-2925aee1a56827a1ca839673ea4c6fb21c7ef440d1a7ee57e7efd6a079db16d5.json deleted file mode 100644 index a14f0bfd0d..0000000000 --- a/backend/.sqlx/query-2925aee1a56827a1ca839673ea4c6fb21c7ef440d1a7ee57e7efd6a079db16d5.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE job_logs SET workspace_id = $1\n WHERE job_id IN (SELECT id FROM v2_job WHERE workspace_id = $1)", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [] - }, - "hash": "2925aee1a56827a1ca839673ea4c6fb21c7ef440d1a7ee57e7efd6a079db16d5" -} diff --git a/backend/.sqlx/query-452fe403cef5a62cb34b9996794c1607757eecba9d2d0326b09d6fcd4dc45c12.json b/backend/.sqlx/query-452fe403cef5a62cb34b9996794c1607757eecba9d2d0326b09d6fcd4dc45c12.json new file mode 100644 index 0000000000..6cfd918a7d --- /dev/null +++ b/backend/.sqlx/query-452fe403cef5a62cb34b9996794c1607757eecba9d2d0326b09d6fcd4dc45c12.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE mcp_oauth_refresh_token SET workspace_id = $1 WHERE workspace_id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "452fe403cef5a62cb34b9996794c1607757eecba9d2d0326b09d6fcd4dc45c12" +} diff --git a/backend/.sqlx/query-0659bab15d4cccdb04c7a57e0e3bbb6bfebb8896601a27ddf5618d4eae678bc1.json b/backend/.sqlx/query-4c8d3693059ce1e2bbc84d76b543830ed343a5c6a1fef780f477dfbed80300f3.json similarity index 52% rename from backend/.sqlx/query-0659bab15d4cccdb04c7a57e0e3bbb6bfebb8896601a27ddf5618d4eae678bc1.json rename to backend/.sqlx/query-4c8d3693059ce1e2bbc84d76b543830ed343a5c6a1fef780f477dfbed80300f3.json index 39826b865b..c9f804589f 100644 --- a/backend/.sqlx/query-0659bab15d4cccdb04c7a57e0e3bbb6bfebb8896601a27ddf5618d4eae678bc1.json +++ b/backend/.sqlx/query-4c8d3693059ce1e2bbc84d76b543830ed343a5c6a1fef780f477dfbed80300f3.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE usr SET workspace_id = $1 WHERE workspace_id = $2", + "query": "UPDATE ai_agent_memory SET workspace_id = $1 WHERE workspace_id = $2", "describe": { "columns": [], "parameters": { @@ -11,5 +11,5 @@ }, "nullable": [] }, - "hash": "0659bab15d4cccdb04c7a57e0e3bbb6bfebb8896601a27ddf5618d4eae678bc1" + "hash": "4c8d3693059ce1e2bbc84d76b543830ed343a5c6a1fef780f477dfbed80300f3" } diff --git a/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json index 36ddb8ab9f..713ccb9dd3 100644 --- a/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json +++ b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json @@ -15,7 +15,7 @@ ] }, "nullable": [ - true + null ] }, "hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55" diff --git a/backend/.sqlx/query-0ba594244a366a31d9bed97a2d7b031d42c23463599d267d1712d1af1d26b321.json b/backend/.sqlx/query-642b6c2c55c19f554a0c4e8dcc9ddbeec6327a8d87d6a45d6c0823ec42c65639.json similarity index 51% rename from backend/.sqlx/query-0ba594244a366a31d9bed97a2d7b031d42c23463599d267d1712d1af1d26b321.json rename to backend/.sqlx/query-642b6c2c55c19f554a0c4e8dcc9ddbeec6327a8d87d6a45d6c0823ec42c65639.json index bde380baec..7e467a4207 100644 --- a/backend/.sqlx/query-0ba594244a366a31d9bed97a2d7b031d42c23463599d267d1712d1af1d26b321.json +++ b/backend/.sqlx/query-642b6c2c55c19f554a0c4e8dcc9ddbeec6327a8d87d6a45d6c0823ec42c65639.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE audit SET workspace_id = $1 WHERE workspace_id = $2", + "query": "UPDATE flow_conversation SET workspace_id = $1 WHERE workspace_id = $2", "describe": { "columns": [], "parameters": { @@ -11,5 +11,5 @@ }, "nullable": [] }, - "hash": "0ba594244a366a31d9bed97a2d7b031d42c23463599d267d1712d1af1d26b321" + "hash": "642b6c2c55c19f554a0c4e8dcc9ddbeec6327a8d87d6a45d6c0823ec42c65639" } diff --git a/backend/.sqlx/query-65c2ecb52cc777f17ffeb77be597ea87026bb4e56c0ccfb12f2feaf1a6124c86.json b/backend/.sqlx/query-65c2ecb52cc777f17ffeb77be597ea87026bb4e56c0ccfb12f2feaf1a6124c86.json new file mode 100644 index 0000000000..2492cbc015 --- /dev/null +++ b/backend/.sqlx/query-65c2ecb52cc777f17ffeb77be597ea87026bb4e56c0ccfb12f2feaf1a6124c86.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO flow\n (workspace_id, path, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at, lock_error_logs)\n SELECT $1, path, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at, lock_error_logs\n FROM flow WHERE workspace_id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "65c2ecb52cc777f17ffeb77be597ea87026bb4e56c0ccfb12f2feaf1a6124c86" +} diff --git a/backend/.sqlx/query-681bab96dce7249eda1d0d207513f0c4e8ba950bf79e9bd1cf2618a322be7858.json b/backend/.sqlx/query-681bab96dce7249eda1d0d207513f0c4e8ba950bf79e9bd1cf2618a322be7858.json new file mode 100644 index 0000000000..208025236a --- /dev/null +++ b/backend/.sqlx/query-681bab96dce7249eda1d0d207513f0c4e8ba950bf79e9bd1cf2618a322be7858.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job SET workspace_id = $1\n WHERE workspace_id = $2\n AND id IN (SELECT id FROM v2_job_queue WHERE workspace_id = $1)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "681bab96dce7249eda1d0d207513f0c4e8ba950bf79e9bd1cf2618a322be7858" +} diff --git a/backend/.sqlx/query-6bdb3fcfe16fc40222dc7010a11026d4d4e0d381b31fe02da7d2667c0cdc1a85.json b/backend/.sqlx/query-6bdb3fcfe16fc40222dc7010a11026d4d4e0d381b31fe02da7d2667c0cdc1a85.json deleted file mode 100644 index c498b1790a..0000000000 --- a/backend/.sqlx/query-6bdb3fcfe16fc40222dc7010a11026d4d4e0d381b31fe02da7d2667c0cdc1a85.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_completed SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "6bdb3fcfe16fc40222dc7010a11026d4d4e0d381b31fe02da7d2667c0cdc1a85" -} diff --git a/backend/.sqlx/query-739501f637dac4e5f4843e3ebfc30aa94729cfdb307c7f56584db8edcfd48e42.json b/backend/.sqlx/query-739501f637dac4e5f4843e3ebfc30aa94729cfdb307c7f56584db8edcfd48e42.json new file mode 100644 index 0000000000..35fe16babb --- /dev/null +++ b/backend/.sqlx/query-739501f637dac4e5f4843e3ebfc30aa94729cfdb307c7f56584db8edcfd48e42.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT path FROM schedule WHERE workspace_id = $1 AND enabled = true", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "path", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "739501f637dac4e5f4843e3ebfc30aa94729cfdb307c7f56584db8edcfd48e42" +} diff --git a/backend/.sqlx/query-7c45f8d05a10ccf538c1b63aa1337e6d0491a2e8d04fe87eddb5a573de00d125.json b/backend/.sqlx/query-7c45f8d05a10ccf538c1b63aa1337e6d0491a2e8d04fe87eddb5a573de00d125.json new file mode 100644 index 0000000000..f2bc7955da --- /dev/null +++ b/backend/.sqlx/query-7c45f8d05a10ccf538c1b63aa1337e6d0491a2e8d04fe87eddb5a573de00d125.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO workspace_settings SELECT $1, slack_team_id, slack_name, slack_command_script, slack_email, customer_id, plan, webhook, deploy_to, ai_config, large_file_storage, git_sync, default_app, default_scripts, deploy_ui, mute_critical_alerts, color, operator_settings, teams_command_script, teams_team_id, teams_team_name, git_app_installations, ducklake, slack_oauth_client_id, slack_oauth_client_secret, datatable, teams_team_guid, auto_invite, error_handler, success_handler FROM workspace_settings WHERE workspace_id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "7c45f8d05a10ccf538c1b63aa1337e6d0491a2e8d04fe87eddb5a573de00d125" +} diff --git a/backend/.sqlx/query-81a9d976ac5c1a78c83b95a1164995e78878a4a4ff6894a04e6626cdd98c24e4.json b/backend/.sqlx/query-81a9d976ac5c1a78c83b95a1164995e78878a4a4ff6894a04e6626cdd98c24e4.json deleted file mode 100644 index 4295debb89..0000000000 --- a/backend/.sqlx/query-81a9d976ac5c1a78c83b95a1164995e78878a4a4ff6894a04e6626cdd98c24e4.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO flow \n (workspace_id, path, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at) \n SELECT $1, path, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at\n FROM flow WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "81a9d976ac5c1a78c83b95a1164995e78878a4a4ff6894a04e6626cdd98c24e4" -} diff --git a/backend/.sqlx/query-81e87e212075159270ff6c2811e93f31e941e71b9addd1e915fe779f81a6ae43.json b/backend/.sqlx/query-81e87e212075159270ff6c2811e93f31e941e71b9addd1e915fe779f81a6ae43.json new file mode 100644 index 0000000000..f702d50d79 --- /dev/null +++ b/backend/.sqlx/query-81e87e212075159270ff6c2811e93f31e941e71b9addd1e915fe779f81a6ae43.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_diff SET source_workspace_id = $1 WHERE source_workspace_id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "81e87e212075159270ff6c2811e93f31e941e71b9addd1e915fe779f81a6ae43" +} diff --git a/backend/.sqlx/query-82185eb02e03e3dd1a4b5a3f22c3b60169989703ee33d14ab348301885c9d745.json b/backend/.sqlx/query-82185eb02e03e3dd1a4b5a3f22c3b60169989703ee33d14ab348301885c9d745.json new file mode 100644 index 0000000000..c0573a8fab --- /dev/null +++ b/backend/.sqlx/query-82185eb02e03e3dd1a4b5a3f22c3b60169989703ee33d14ab348301885c9d745.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO workspace SELECT $1, $2, owner, false, premium FROM workspace WHERE id = $3", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "82185eb02e03e3dd1a4b5a3f22c3b60169989703ee33d14ab348301885c9d745" +} diff --git a/backend/.sqlx/query-0d0c379b1cd2eec15869dd0b1a31886a95d53096fdcb1cdb1e0eb282b54105dc.json b/backend/.sqlx/query-863581331dc7abd9ffa47c6977ee94d939a9e3ab3921605e5b0f4f7586e431c2.json similarity index 57% rename from backend/.sqlx/query-0d0c379b1cd2eec15869dd0b1a31886a95d53096fdcb1cdb1e0eb282b54105dc.json rename to backend/.sqlx/query-863581331dc7abd9ffa47c6977ee94d939a9e3ab3921605e5b0f4f7586e431c2.json index a3ab0fa0a1..ead3c39e8f 100644 --- a/backend/.sqlx/query-0d0c379b1cd2eec15869dd0b1a31886a95d53096fdcb1cdb1e0eb282b54105dc.json +++ b/backend/.sqlx/query-863581331dc7abd9ffa47c6977ee94d939a9e3ab3921605e5b0f4f7586e431c2.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE workspace_key SET workspace_id = $1 WHERE workspace_id = $2", + "query": "UPDATE email_trigger SET workspace_id = $1 WHERE workspace_id = $2", "describe": { "columns": [], "parameters": { @@ -11,5 +11,5 @@ }, "nullable": [] }, - "hash": "0d0c379b1cd2eec15869dd0b1a31886a95d53096fdcb1cdb1e0eb282b54105dc" + "hash": "863581331dc7abd9ffa47c6977ee94d939a9e3ab3921605e5b0f4f7586e431c2" } diff --git a/backend/.sqlx/query-893c2dea38bfff42ad8cbd7afaf0280a8c1d6a7f1dc99f14c894899031534bea.json b/backend/.sqlx/query-893c2dea38bfff42ad8cbd7afaf0280a8c1d6a7f1dc99f14c894899031534bea.json new file mode 100644 index 0000000000..24c2932627 --- /dev/null +++ b/backend/.sqlx/query-893c2dea38bfff42ad8cbd7afaf0280a8c1d6a7f1dc99f14c894899031534bea.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO skip_workspace_diff_tally SELECT $1, added_at FROM skip_workspace_diff_tally WHERE workspace_id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "893c2dea38bfff42ad8cbd7afaf0280a8c1d6a7f1dc99f14c894899031534bea" +} diff --git a/backend/.sqlx/query-8b8d29f3133228dbc5bdf1927e06dc1adfdaeed3f8dfc9d44887347f82f2d520.json b/backend/.sqlx/query-8b8d29f3133228dbc5bdf1927e06dc1adfdaeed3f8dfc9d44887347f82f2d520.json new file mode 100644 index 0000000000..75d82cfd43 --- /dev/null +++ b/backend/.sqlx/query-8b8d29f3133228dbc5bdf1927e06dc1adfdaeed3f8dfc9d44887347f82f2d520.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE job_perms SET workspace_id = $1\n WHERE job_id IN (SELECT id FROM v2_job WHERE workspace_id = $1)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "8b8d29f3133228dbc5bdf1927e06dc1adfdaeed3f8dfc9d44887347f82f2d520" +} diff --git a/backend/.sqlx/query-93e6cdf58803f63d7d465f9d778c052c40dcdacade0f9b9921860160604f3763.json b/backend/.sqlx/query-93e6cdf58803f63d7d465f9d778c052c40dcdacade0f9b9921860160604f3763.json deleted file mode 100644 index 53679a9430..0000000000 --- a/backend/.sqlx/query-93e6cdf58803f63d7d465f9d778c052c40dcdacade0f9b9921860160604f3763.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE workspace_settings SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "93e6cdf58803f63d7d465f9d778c052c40dcdacade0f9b9921860160604f3763" -} diff --git a/backend/.sqlx/query-98034dc06e7478f4a828ea58cf9bbe728f4eabcd6a6e7ad7c8445efb6966e0c9.json b/backend/.sqlx/query-98034dc06e7478f4a828ea58cf9bbe728f4eabcd6a6e7ad7c8445efb6966e0c9.json new file mode 100644 index 0000000000..5bc2d903dc --- /dev/null +++ b/backend/.sqlx/query-98034dc06e7478f4a828ea58cf9bbe728f4eabcd6a6e7ad7c8445efb6966e0c9.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_integrations SET workspace_id = $1 WHERE workspace_id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "98034dc06e7478f4a828ea58cf9bbe728f4eabcd6a6e7ad7c8445efb6966e0c9" +} diff --git a/backend/.sqlx/query-b3f791c8ef04f0aefd9b510d751dbe467e17e15d4a6512889009d850760502d9.json b/backend/.sqlx/query-b3f791c8ef04f0aefd9b510d751dbe467e17e15d4a6512889009d850760502d9.json new file mode 100644 index 0000000000..72be1f1b6f --- /dev/null +++ b/backend/.sqlx/query-b3f791c8ef04f0aefd9b510d751dbe467e17e15d4a6512889009d850760502d9.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO usr SELECT $1, username, email, is_admin, created_at, operator, disabled, role FROM usr WHERE workspace_id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "b3f791c8ef04f0aefd9b510d751dbe467e17e15d4a6512889009d850760502d9" +} diff --git a/backend/.sqlx/query-b928974804d710f37c4703a23c67440bcb4733ff706669abc787cd414c11f82e.json b/backend/.sqlx/query-b928974804d710f37c4703a23c67440bcb4733ff706669abc787cd414c11f82e.json new file mode 100644 index 0000000000..f57e112008 --- /dev/null +++ b/backend/.sqlx/query-b928974804d710f37c4703a23c67440bcb4733ff706669abc787cd414c11f82e.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO workspace_key SELECT $1, kind, key FROM workspace_key WHERE workspace_id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "b928974804d710f37c4703a23c67440bcb4733ff706669abc787cd414c11f82e" +} diff --git a/backend/.sqlx/query-8f0031533f1bf407bd5d8af4d364eaf00d4c38ee7ba75141b40fc9fcd2ffc0b8.json b/backend/.sqlx/query-e1e55ce8c28ac4d253c289e4c972ae00cfdcc3ef056625a5a5bef76377953278.json similarity index 63% rename from backend/.sqlx/query-8f0031533f1bf407bd5d8af4d364eaf00d4c38ee7ba75141b40fc9fcd2ffc0b8.json rename to backend/.sqlx/query-e1e55ce8c28ac4d253c289e4c972ae00cfdcc3ef056625a5a5bef76377953278.json index 86f46e27e5..92fba4894f 100644 --- a/backend/.sqlx/query-8f0031533f1bf407bd5d8af4d364eaf00d4c38ee7ba75141b40fc9fcd2ffc0b8.json +++ b/backend/.sqlx/query-e1e55ce8c28ac4d253c289e4c972ae00cfdcc3ef056625a5a5bef76377953278.json @@ -1,16 +1,15 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO workspace SELECT $1, $2, owner, deleted, premium FROM workspace WHERE id = $3", + "query": "UPDATE native_trigger SET workspace_id = $1 WHERE workspace_id = $2", "describe": { "columns": [], "parameters": { "Left": [ - "Varchar", "Varchar", "Text" ] }, "nullable": [] }, - "hash": "8f0031533f1bf407bd5d8af4d364eaf00d4c38ee7ba75141b40fc9fcd2ffc0b8" + "hash": "e1e55ce8c28ac4d253c289e4c972ae00cfdcc3ef056625a5a5bef76377953278" } diff --git a/backend/.sqlx/query-e2f7a1a48bb026df5c7155b76e166cd5a6ca2ab25926f70095328d5ea2d2bc7c.json b/backend/.sqlx/query-e2f7a1a48bb026df5c7155b76e166cd5a6ca2ab25926f70095328d5ea2d2bc7c.json deleted file mode 100644 index 17b075d382..0000000000 --- a/backend/.sqlx/query-e2f7a1a48bb026df5c7155b76e166cd5a6ca2ab25926f70095328d5ea2d2bc7c.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job SET workspace_id = $1\n WHERE workspace_id = $2\n AND (id IN (SELECT id FROM v2_job_queue WHERE workspace_id = $1)\n OR id IN (SELECT id FROM v2_job_completed WHERE workspace_id = $1))", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [] - }, - "hash": "e2f7a1a48bb026df5c7155b76e166cd5a6ca2ab25926f70095328d5ea2d2bc7c" -} diff --git a/backend/.sqlx/query-e3491d953cb9f85a5e3a9b1a386dfde77f0387d1ed8f5defa34359147e9c0907.json b/backend/.sqlx/query-e3491d953cb9f85a5e3a9b1a386dfde77f0387d1ed8f5defa34359147e9c0907.json new file mode 100644 index 0000000000..ae9426a38a --- /dev/null +++ b/backend/.sqlx/query-e3491d953cb9f85a5e3a9b1a386dfde77f0387d1ed8f5defa34359147e9c0907.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_dependencies SET workspace_id = $1 WHERE workspace_id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "e3491d953cb9f85a5e3a9b1a386dfde77f0387d1ed8f5defa34359147e9c0907" +} diff --git a/backend/.sqlx/query-f23a296f5e04dcf6f2cda808193e4ee91c14823639cbcdb77d92409fe5218c3c.json b/backend/.sqlx/query-f23a296f5e04dcf6f2cda808193e4ee91c14823639cbcdb77d92409fe5218c3c.json new file mode 100644 index 0000000000..9145a3ca59 --- /dev/null +++ b/backend/.sqlx/query-f23a296f5e04dcf6f2cda808193e4ee91c14823639cbcdb77d92409fe5218c3c.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_diff SET fork_workspace_id = $1 WHERE fork_workspace_id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "f23a296f5e04dcf6f2cda808193e4ee91c14823639cbcdb77d92409fe5218c3c" +} diff --git a/backend/.sqlx/query-f863cb7ec416d774dc4c8170b2f5ec974b035405da38bf3e7764f7e22232b760.json b/backend/.sqlx/query-f863cb7ec416d774dc4c8170b2f5ec974b035405da38bf3e7764f7e22232b760.json deleted file mode 100644 index 110b4035db..0000000000 --- a/backend/.sqlx/query-f863cb7ec416d774dc4c8170b2f5ec974b035405da38bf3e7764f7e22232b760.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT COUNT(*) FROM v2_job WHERE workspace_id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - null - ] - }, - "hash": "f863cb7ec416d774dc4c8170b2f5ec974b035405da38bf3e7764f7e22232b760" -} diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index 9c384e8e11..636b838c31 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -3617,18 +3617,18 @@ async fn edit_workspace( Ok(format!("Updated workspace {}", &w_id)) } -async fn archive_workspace( - Extension(db): Extension, - Path(w_id): Path, - authed: ApiAuthed, -) -> Result { - require_admin(authed.is_admin, &authed.username)?; - +/// Archive a workspace: disable schedules, cancel jobs, and mark as deleted. +/// Returns (schedules_disabled_count, jobs_canceled_count). +pub(crate) async fn archive_workspace_impl( + db: &DB, + w_id: &str, + username: &str, +) -> Result<(usize, usize)> { // Step 1: Disable all schedules and clear their queued jobs let mut tx = db.begin().await?; let disabled_schedules = sqlx::query_scalar!( "UPDATE schedule SET enabled = false WHERE workspace_id = $1 AND enabled = true RETURNING path", - &w_id + w_id ) .fetch_all(&mut *tx) .await?; @@ -3642,15 +3642,20 @@ async fn archive_workspace( // Clear all schedule-related jobs using the existing clear_schedule function for schedule_path in &disabled_schedules { - crate::schedule::clear_schedule(&mut tx, schedule_path, &w_id).await?; + crate::schedule::clear_schedule(&mut tx, schedule_path, w_id).await?; } + // Mark workspace as archived + sqlx::query!("UPDATE workspace SET deleted = true WHERE id = $1", w_id) + .execute(&mut *tx) + .await?; + tx.commit().await?; // Step 2: Get all remaining queued jobs for this workspace (non-schedule jobs) let jobs_to_cancel = - sqlx::query_scalar!("SELECT id FROM v2_job_queue WHERE workspace_id = $1", &w_id) - .fetch_all(&db) + sqlx::query_scalar!("SELECT id FROM v2_job_queue WHERE workspace_id = $1", w_id) + .fetch_all(db) .await?; let jobs_count = jobs_to_cancel.len(); @@ -3664,9 +3669,9 @@ async fn archive_workspace( let canceled_count = if !jobs_to_cancel.is_empty() { let axum::Json(canceled_jobs) = crate::jobs::cancel_jobs( jobs_to_cancel, - &db, - &authed.username, - &w_id, + db, + username, + w_id, false, // force_cancel ) .await?; @@ -3678,12 +3683,21 @@ async fn archive_workspace( 0 }; - // Step 4: Archive the workspace - let mut tx = db.begin().await?; - sqlx::query!("UPDATE workspace SET deleted = true WHERE id = $1", &w_id) - .execute(&mut *tx) - .await?; + Ok((schedules_count, canceled_count)) +} +async fn archive_workspace( + Extension(db): Extension, + Path(w_id): Path, + authed: ApiAuthed, +) -> Result { + require_admin(authed.is_admin, &authed.username)?; + + let (schedules_count, canceled_count) = + archive_workspace_impl(&db, &w_id, &authed.username).await?; + + // Audit log + let mut tx = db.begin().await?; let mut audit_params = HashMap::new(); audit_params.insert("disabled_schedules", schedules_count.to_string()); audit_params.insert("canceled_jobs", canceled_count.to_string()); diff --git a/backend/windmill-api/src/workspaces_extra.rs b/backend/windmill-api/src/workspaces_extra.rs index cfec0e5143..79b9554ebf 100644 --- a/backend/windmill-api/src/workspaces_extra.rs +++ b/backend/windmill-api/src/workspaces_extra.rs @@ -1,6 +1,11 @@ +use std::collections::HashMap; + use crate::db::ApiAuthed; -use crate::workspaces::{check_w_id_conflict, CREATE_WORKSPACE_REQUIRE_SUPERADMIN, WM_FORK_PREFIX}; +use crate::workspaces::{ + archive_workspace_impl, check_w_id_conflict, CREATE_WORKSPACE_REQUIRE_SUPERADMIN, + WM_FORK_PREFIX, +}; use crate::{db::DB, utils::require_super_admin}; use axum::extract::Query; @@ -21,6 +26,7 @@ use windmill_common::{ error::{Error, Result}, utils::require_admin, }; +use windmill_queue::schedule::{get_schedule_opt, push_scheduled_job}; use serde::Deserialize; @@ -48,34 +54,19 @@ pub(crate) async fn change_workspace_id( require_admin(authed.is_admin, &authed.username)?; } - // Check total job count before attempting migration - let job_count = sqlx::query_scalar!( - "SELECT COUNT(*) FROM v2_job WHERE workspace_id = $1", - &old_id - ) - .fetch_one(&db) - .await? - .unwrap_or(0); - - if job_count > 100_000 { - return Err(Error::BadRequest( - format!( - "Workspace has {} jobs which exceeds the 100k limit for direct migration. Please use the Windmill CLI to migrate jobs instead: `wmill jobs pull/push`.", - job_count - ) - )); - } - let mut tx = db.begin().await?; check_w_id_conflict(&mut tx, &rw.new_id).await?; - info!("Changing workspace id from {} to {}", old_id, rw.new_id); + info!( + "Changing workspace id from {} to {} (move and archive approach)", + old_id, rw.new_id + ); - // duplicate workspace with new id name - info!("Duplicating workspace row"); + // Create new workspace with new id and name + info!("Creating new workspace row"); sqlx::query!( - "INSERT INTO workspace SELECT $1, $2, owner, deleted, premium FROM workspace WHERE id = $3", + "INSERT INTO workspace SELECT $1, $2, owner, false, premium FROM workspace WHERE id = $3", &rw.new_id, &rw.new_name, &old_id @@ -83,6 +74,43 @@ pub(crate) async fn change_workspace_id( .execute(&mut *tx) .await?; + // Duplicate workspace settings (keep copy in old workspace for reference) + info!("Duplicating workspace_settings table"); + sqlx::query!( + "INSERT INTO workspace_settings SELECT $1, slack_team_id, slack_name, slack_command_script, slack_email, customer_id, plan, webhook, deploy_to, ai_config, large_file_storage, git_sync, default_app, default_scripts, deploy_ui, mute_critical_alerts, color, operator_settings, teams_command_script, teams_team_id, teams_team_name, git_app_installations, ducklake, slack_oauth_client_id, slack_oauth_client_secret, datatable, teams_team_guid, auto_invite, error_handler, success_handler FROM workspace_settings WHERE workspace_id = $2", + &rw.new_id, + &old_id + ) + .execute(&mut *tx) + .await?; + + info!("Duplicating workspace_key table"); + sqlx::query!( + "INSERT INTO workspace_key SELECT $1, kind, key FROM workspace_key WHERE workspace_id = $2", + &rw.new_id, + &old_id + ) + .execute(&mut *tx) + .await?; + + info!("Updating workspace_env table"); + sqlx::query!( + "UPDATE workspace_env SET workspace_id = $1 WHERE workspace_id = $2", + &rw.new_id, + &old_id + ) + .execute(&mut *tx) + .await?; + + info!("Updating workspace_invite table"); + sqlx::query!( + "UPDATE workspace_invite SET workspace_id = $1 WHERE workspace_id = $2", + &rw.new_id, + &old_id + ) + .execute(&mut *tx) + .await?; + info!("Updating account table"); sqlx::query!( "UPDATE account SET workspace_id = $1 WHERE workspace_id = $2", @@ -101,15 +129,6 @@ pub(crate) async fn change_workspace_id( .execute(&mut *tx) .await?; - info!("Updating audit table"); - sqlx::query!( - "UPDATE audit SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; - info!("Updating capture table"); sqlx::query!( "UPDATE capture SET workspace_id = $1 WHERE workspace_id = $2", @@ -200,9 +219,18 @@ pub(crate) async fn change_workspace_id( .execute(&mut *tx) .await?; - info!("Updating v2_job_completed table"); + info!("Updating email_trigger table"); sqlx::query!( - "UPDATE v2_job_completed SET workspace_id = $1 WHERE workspace_id = $2", + "UPDATE email_trigger SET workspace_id = $1 WHERE workspace_id = $2", + &rw.new_id, + &old_id + ) + .execute(&mut *tx) + .await?; + + info!("Updating native_trigger table"); + sqlx::query!( + "UPDATE native_trigger SET workspace_id = $1 WHERE workspace_id = $2", &rw.new_id, &old_id ) @@ -245,11 +273,12 @@ pub(crate) async fn change_workspace_id( .execute(&mut *tx) .await?; + // Duplicate flow table rows (FK constraint requires insert then delete) info!("Duplicating flow table rows"); sqlx::query!( - "INSERT INTO flow - (workspace_id, path, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at) - SELECT $1, path, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at + "INSERT INTO flow + (workspace_id, path, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at, lock_error_logs) + SELECT $1, path, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at, lock_error_logs FROM flow WHERE workspace_id = $2", &rw.new_id, &old_id @@ -275,6 +304,85 @@ pub(crate) async fn change_workspace_id( .execute(&mut *tx) .await?; + info!("Updating workspace_dependencies table"); + sqlx::query!( + "UPDATE workspace_dependencies SET workspace_id = $1 WHERE workspace_id = $2", + &rw.new_id, + &old_id + ) + .execute(&mut *tx) + .await?; + + info!("Updating workspace_diff table"); + sqlx::query!( + "UPDATE workspace_diff SET source_workspace_id = $1 WHERE source_workspace_id = $2", + &rw.new_id, + &old_id + ) + .execute(&mut *tx) + .await?; + sqlx::query!( + "UPDATE workspace_diff SET fork_workspace_id = $1 WHERE fork_workspace_id = $2", + &rw.new_id, + &old_id + ) + .execute(&mut *tx) + .await?; + + info!("Updating workspace_integrations table"); + sqlx::query!( + "UPDATE workspace_integrations SET workspace_id = $1 WHERE workspace_id = $2", + &rw.new_id, + &old_id + ) + .execute(&mut *tx) + .await?; + + info!("Updating ai_agent_memory table"); + sqlx::query!( + "UPDATE ai_agent_memory SET workspace_id = $1 WHERE workspace_id = $2", + &rw.new_id, + &old_id + ) + .execute(&mut *tx) + .await?; + + info!("Updating flow_conversation table"); + sqlx::query!( + "UPDATE flow_conversation SET workspace_id = $1 WHERE workspace_id = $2", + &rw.new_id, + &old_id + ) + .execute(&mut *tx) + .await?; + + info!("Updating mcp_oauth_refresh_token table"); + sqlx::query!( + "UPDATE mcp_oauth_refresh_token SET workspace_id = $1 WHERE workspace_id = $2", + &rw.new_id, + &old_id + ) + .execute(&mut *tx) + .await?; + + info!("Updating mcp_oauth_server_code table"); + sqlx::query!( + "UPDATE mcp_oauth_server_code SET workspace_id = $1 WHERE workspace_id = $2", + &rw.new_id, + &old_id + ) + .execute(&mut *tx) + .await?; + + info!("Duplicating skip_workspace_diff_tally table"); + sqlx::query!( + "INSERT INTO skip_workspace_diff_tally SELECT $1, added_at FROM skip_workspace_diff_tally WHERE workspace_id = $2", + &rw.new_id, + &old_id + ) + .execute(&mut *tx) + .await?; + info!("Updating asset table"); sqlx::query!( "UPDATE asset SET workspace_id = $1 WHERE workspace_id = $2", @@ -298,7 +406,7 @@ pub(crate) async fn change_workspace_id( .execute(&mut *tx) .await?; - // have to duplicate group_ with new workspace id because of foreign key constraint + // Duplicate group_ with new workspace id (FK constraint) info!("Duplicating group_ table rows"); sqlx::query!( "INSERT INTO group_ SELECT $1, name, summary, extra_perms FROM group_ WHERE workspace_id = $2", @@ -326,13 +434,12 @@ pub(crate) async fn change_workspace_id( .execute(&mut *tx) .await?; - // then delete old group_ info!("Deleting old group_ rows"); sqlx::query!("DELETE FROM group_ WHERE workspace_id = $1", &old_id) .execute(&mut *tx) .await?; - // duplicate folders with new workspace id because of foreign key constraint + // Duplicate folders with new workspace id (FK constraint) info!("Duplicating folder table rows"); sqlx::query!( "INSERT INTO folder SELECT name, $1, display_name, owners, extra_perms, summary, edited_at, created_by FROM folder WHERE workspace_id = $2", @@ -351,7 +458,6 @@ pub(crate) async fn change_workspace_id( .execute(&mut *tx) .await?; - // then delete old folders info!("Deleting old folder rows"); sqlx::query!("DELETE FROM folder WHERE workspace_id = $1", &old_id) .execute(&mut *tx) @@ -366,16 +472,27 @@ pub(crate) async fn change_workspace_id( .execute(&mut *tx) .await?; - info!("Updating job_stats table"); - sqlx::query!( - "UPDATE job_stats SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, + // Get enabled schedules and clear their queued jobs BEFORE moving jobs + // This way we don't need to filter out scheduled jobs - they're already removed + let enabled_schedule_paths: Vec = sqlx::query_scalar!( + "SELECT path FROM schedule WHERE workspace_id = $1 AND enabled = true", &old_id ) - .execute(&mut *tx) + .fetch_all(&mut *tx) .await?; - info!("Updating v2_job_queue table"); + info!( + "Found {} enabled schedules, clearing their queued jobs", + enabled_schedule_paths.len() + ); + + for schedule_path in &enabled_schedule_paths { + crate::schedule::clear_schedule(&mut tx, schedule_path, &old_id).await?; + } + + // Move queued jobs (not running) to new workspace using skip lock + // Scheduled jobs were already cleared above, so no need to filter them + info!("Moving v2_job_queue entries to new workspace"); sqlx::query!( "UPDATE v2_job_queue SET workspace_id = $1 WHERE id IN ( @@ -391,21 +508,20 @@ pub(crate) async fn change_workspace_id( .execute(&mut *tx) .await?; - info!("Updating v2_job table"); + info!("Updating v2_job table for moved queue entries"); sqlx::query!( "UPDATE v2_job SET workspace_id = $1 WHERE workspace_id = $2 - AND (id IN (SELECT id FROM v2_job_queue WHERE workspace_id = $1) - OR id IN (SELECT id FROM v2_job_completed WHERE workspace_id = $1))", + AND id IN (SELECT id FROM v2_job_queue WHERE workspace_id = $1)", &rw.new_id, &old_id ) .execute(&mut *tx) .await?; - info!("Updating job_logs table for migrated jobs"); + info!("Updating job_perms table for migrated jobs"); sqlx::query!( - "UPDATE job_logs SET workspace_id = $1 + "UPDATE job_perms SET workspace_id = $1 WHERE job_id IN (SELECT id FROM v2_job WHERE workspace_id = $1)", &rw.new_id ) @@ -475,9 +591,9 @@ pub(crate) async fn change_workspace_id( .execute(&mut *tx) .await?; - info!("Updating usr table"); + info!("Duplicating usr table"); sqlx::query!( - "UPDATE usr SET workspace_id = $1 WHERE workspace_id = $2", + "INSERT INTO usr SELECT $1, username, email, is_admin, created_at, operator, disabled, role FROM usr WHERE workspace_id = $2", &rw.new_id, &old_id ) @@ -493,49 +609,18 @@ pub(crate) async fn change_workspace_id( .execute(&mut *tx) .await?; - info!("Updating workspace_env table"); - sqlx::query!( - "UPDATE workspace_env SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + // Re-push jobs for enabled schedules in the new workspace (same transaction) + info!( + "Re-pushing jobs for {} enabled schedules", + enabled_schedule_paths.len() + ); + for schedule_path in &enabled_schedule_paths { + if let Some(schedule) = get_schedule_opt(&mut *tx, &rw.new_id, schedule_path).await? { + tx = push_scheduled_job(&db, tx, &schedule, None, None).await?; + } + } - info!("Updating workspace_invite table"); - sqlx::query!( - "UPDATE workspace_invite SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; - - info!("Updating workspace_key table"); - sqlx::query!( - "UPDATE workspace_key SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; - - info!("Updating workspace_settings table"); - sqlx::query!( - "UPDATE workspace_settings SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; - - // delete old workspace - info!("Deleting old workspace row"); - sqlx::query!("DELETE FROM workspace WHERE id = $1", &old_id) - .execute(&mut *tx) - .await?; - - info!("Workspace id change completed successfully"); + // Audit log in the same transaction as the workspace changes audit_log( &mut *tx, &authed, @@ -543,13 +628,30 @@ pub(crate) async fn change_workspace_id( ActionKind::Update, &rw.new_id, Some(&authed.email), - None, + Some( + [("old_workspace_id", old_id.as_str())] + .into_iter() + .collect::>(), + ), ) .await?; + tx.commit().await?; + + // Archive old workspace: disable schedules, cancel remaining jobs, set deleted=true + // Note: schedules were already moved to new workspace, so this will find 0 schedules + info!("Archiving old workspace"); + let (_schedules_count, canceled_count) = + archive_workspace_impl(&db, &old_id, &authed.username).await?; + + info!( + "Workspace id change completed: moved {} to {}, archived old workspace", + old_id, rw.new_id + ); + Ok(format!( - "updated workspace from {} to {}", - &old_id, &rw.new_id + "Moved workspace from {} to {}, archived old workspace (canceled {} remaining jobs)", + &old_id, &rw.new_id, canceled_count )) } diff --git a/frontend/src/lib/components/settings/ChangeWorkspaceId.svelte b/frontend/src/lib/components/settings/ChangeWorkspaceId.svelte index 17af87fd8d..a83e7d067f 100644 --- a/frontend/src/lib/components/settings/ChangeWorkspaceId.svelte +++ b/frontend/src/lib/components/settings/ChangeWorkspaceId.svelte @@ -43,7 +43,7 @@ }) open = false - sendUserToast(`Renamed workspace to ${newName}. Reloading...`) + sendUserToast(`Moved workspace to ${newName}. Old workspace archived. Reloading...`) await new Promise((resolve) => setTimeout(resolve, 1000)) window.location.href = '/workspace_settings?tab=general&workspace=' + newId } catch (err) { @@ -80,12 +80,13 @@
- - Once finished, please update your webhook calls and adjust your CLI sync configuration - accordingly. - - - Make sure that all your jobs are completed before changing the workspace ID. + +
    +
  • All content (scripts, flows, apps, resources, etc.) moves to the new ID
  • +
  • Old workspace is archived with completed jobs, logs, and audit history
  • +
  • Running jobs will be canceled
  • +
+

Remember to update webhook URLs and CLI sync config afterward.

Current ID
{$workspaceStore ?? ''}