diff --git a/backend/.sqlx/query-00f68ea1ad2d5ab045b4a20ce3f4dd7850041e396e14890e37e1d0db276d3694.json b/backend/.sqlx/query-00f68ea1ad2d5ab045b4a20ce3f4dd7850041e396e14890e37e1d0db276d3694.json deleted file mode 100644 index 2f208d73fb978..0000000000000 --- a/backend/.sqlx/query-00f68ea1ad2d5ab045b4a20ce3f4dd7850041e396e14890e37e1d0db276d3694.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE resource SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "00f68ea1ad2d5ab045b4a20ce3f4dd7850041e396e14890e37e1d0db276d3694" -} diff --git a/backend/.sqlx/query-029b81eb00250eacded407b12bcfbab2b3f35354bdb9ef6e30281a4ff6235060.json b/backend/.sqlx/query-029b81eb00250eacded407b12bcfbab2b3f35354bdb9ef6e30281a4ff6235060.json deleted file mode 100644 index 7174024dfe3cd..0000000000000 --- a/backend/.sqlx/query-029b81eb00250eacded407b12bcfbab2b3f35354bdb9ef6e30281a4ff6235060.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE asset SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "029b81eb00250eacded407b12bcfbab2b3f35354bdb9ef6e30281a4ff6235060" -} diff --git a/backend/.sqlx/query-0659bab15d4cccdb04c7a57e0e3bbb6bfebb8896601a27ddf5618d4eae678bc1.json b/backend/.sqlx/query-0659bab15d4cccdb04c7a57e0e3bbb6bfebb8896601a27ddf5618d4eae678bc1.json deleted file mode 100644 index 39826b865bea4..0000000000000 --- a/backend/.sqlx/query-0659bab15d4cccdb04c7a57e0e3bbb6bfebb8896601a27ddf5618d4eae678bc1.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE usr SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "0659bab15d4cccdb04c7a57e0e3bbb6bfebb8896601a27ddf5618d4eae678bc1" -} diff --git a/backend/.sqlx/query-0ba594244a366a31d9bed97a2d7b031d42c23463599d267d1712d1af1d26b321.json b/backend/.sqlx/query-0ba594244a366a31d9bed97a2d7b031d42c23463599d267d1712d1af1d26b321.json deleted file mode 100644 index bde380baec016..0000000000000 --- a/backend/.sqlx/query-0ba594244a366a31d9bed97a2d7b031d42c23463599d267d1712d1af1d26b321.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE audit SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "0ba594244a366a31d9bed97a2d7b031d42c23463599d267d1712d1af1d26b321" -} diff --git a/backend/.sqlx/query-0ca4365e7144584ef5723db7e133bb42525ff91a734caa87be8b802c6607e6be.json b/backend/.sqlx/query-0ca4365e7144584ef5723db7e133bb42525ff91a734caa87be8b802c6607e6be.json deleted file mode 100644 index b33796f955a79..0000000000000 --- a/backend/.sqlx/query-0ca4365e7144584ef5723db7e133bb42525ff91a734caa87be8b802c6607e6be.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE kafka_trigger SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "0ca4365e7144584ef5723db7e133bb42525ff91a734caa87be8b802c6607e6be" -} diff --git a/backend/.sqlx/query-0d0c379b1cd2eec15869dd0b1a31886a95d53096fdcb1cdb1e0eb282b54105dc.json b/backend/.sqlx/query-0d0c379b1cd2eec15869dd0b1a31886a95d53096fdcb1cdb1e0eb282b54105dc.json deleted file mode 100644 index a3ab0fa0a1557..0000000000000 --- a/backend/.sqlx/query-0d0c379b1cd2eec15869dd0b1a31886a95d53096fdcb1cdb1e0eb282b54105dc.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE workspace_key SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "0d0c379b1cd2eec15869dd0b1a31886a95d53096fdcb1cdb1e0eb282b54105dc" -} diff --git a/backend/.sqlx/query-0ea5ba568ec0f62b808fe938a41174646b6bdd658b8461db1bb90a871d076718.json b/backend/.sqlx/query-0ea5ba568ec0f62b808fe938a41174646b6bdd658b8461db1bb90a871d076718.json deleted file mode 100644 index 19fe39406796e..0000000000000 --- a/backend/.sqlx/query-0ea5ba568ec0f62b808fe938a41174646b6bdd658b8461db1bb90a871d076718.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_queue SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "0ea5ba568ec0f62b808fe938a41174646b6bdd658b8461db1bb90a871d076718" -} diff --git a/backend/.sqlx/query-15c29ab03fdeda346adc4db3fd0b9d9aee1a99f0c795afe203efe42b967569a5.json b/backend/.sqlx/query-15c29ab03fdeda346adc4db3fd0b9d9aee1a99f0c795afe203efe42b967569a5.json deleted file mode 100644 index 73ad9fc2eba41..0000000000000 --- a/backend/.sqlx/query-15c29ab03fdeda346adc4db3fd0b9d9aee1a99f0c795afe203efe42b967569a5.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE variable SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "15c29ab03fdeda346adc4db3fd0b9d9aee1a99f0c795afe203efe42b967569a5" -} diff --git a/backend/.sqlx/query-16577af504ba1bade4c92f023dac246591354e28e4c0a61095a2d16a2753fa3a.json b/backend/.sqlx/query-16577af504ba1bade4c92f023dac246591354e28e4c0a61095a2d16a2753fa3a.json new file mode 100644 index 0000000000000..a712d558d4a6a --- /dev/null +++ b/backend/.sqlx/query-16577af504ba1bade4c92f023dac246591354e28e4c0a61095a2d16a2753fa3a.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)\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": "16577af504ba1bade4c92f023dac246591354e28e4c0a61095a2d16a2753fa3a" +} diff --git a/backend/.sqlx/query-1c94d4f3b90a47b40263c254f85d14cc8b551e9ab0208e3a10ca717a4e85888b.json b/backend/.sqlx/query-1c94d4f3b90a47b40263c254f85d14cc8b551e9ab0208e3a10ca717a4e85888b.json deleted file mode 100644 index 650bf4bb28cd3..0000000000000 --- a/backend/.sqlx/query-1c94d4f3b90a47b40263c254f85d14cc8b551e9ab0208e3a10ca717a4e85888b.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE http_trigger SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "1c94d4f3b90a47b40263c254f85d14cc8b551e9ab0208e3a10ca717a4e85888b" -} diff --git a/backend/.sqlx/query-2133e06ac9f7de884dbd36d51bca9a427bde64a9e278658bc0777e23abde4cd5.json b/backend/.sqlx/query-2133e06ac9f7de884dbd36d51bca9a427bde64a9e278658bc0777e23abde4cd5.json deleted file mode 100644 index e2bad68513d1a..0000000000000 --- a/backend/.sqlx/query-2133e06ac9f7de884dbd36d51bca9a427bde64a9e278658bc0777e23abde4cd5.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE resource_type SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "2133e06ac9f7de884dbd36d51bca9a427bde64a9e278658bc0777e23abde4cd5" -} diff --git a/backend/.sqlx/query-4244640e62fffb0f6978f8f7d78291b3294a6a7d1549d752f14acf5972552ba5.json b/backend/.sqlx/query-4244640e62fffb0f6978f8f7d78291b3294a6a7d1549d752f14acf5972552ba5.json deleted file mode 100644 index 841d549e20564..0000000000000 --- a/backend/.sqlx/query-4244640e62fffb0f6978f8f7d78291b3294a6a7d1549d752f14acf5972552ba5.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "4244640e62fffb0f6978f8f7d78291b3294a6a7d1549d752f14acf5972552ba5" -} diff --git a/backend/.sqlx/query-5395887680e4ff458a962d99614085e45a723964b7937cfe9943019e5c49df90.json b/backend/.sqlx/query-5395887680e4ff458a962d99614085e45a723964b7937cfe9943019e5c49df90.json deleted file mode 100644 index be281c2cdc761..0000000000000 --- a/backend/.sqlx/query-5395887680e4ff458a962d99614085e45a723964b7937cfe9943019e5c49df90.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE app SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "5395887680e4ff458a962d99614085e45a723964b7937cfe9943019e5c49df90" -} diff --git a/backend/.sqlx/query-5422a53e642c491b02be5d17fe9d7389a42a84c21fea9876c84a5ba719b6ede5.json b/backend/.sqlx/query-5422a53e642c491b02be5d17fe9d7389a42a84c21fea9876c84a5ba719b6ede5.json deleted file mode 100644 index 7217822bfac51..0000000000000 --- a/backend/.sqlx/query-5422a53e642c491b02be5d17fe9d7389a42a84c21fea9876c84a5ba719b6ede5.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE draft SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "5422a53e642c491b02be5d17fe9d7389a42a84c21fea9876c84a5ba719b6ede5" -} diff --git a/backend/.sqlx/query-543b8f6e4850c15439e9bed8432db071c361867f9603d962b6edec6c18160491.json b/backend/.sqlx/query-543b8f6e4850c15439e9bed8432db071c361867f9603d962b6edec6c18160491.json deleted file mode 100644 index eb59d3ccdd342..0000000000000 --- a/backend/.sqlx/query-543b8f6e4850c15439e9bed8432db071c361867f9603d962b6edec6c18160491.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE token SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "543b8f6e4850c15439e9bed8432db071c361867f9603d962b6edec6c18160491" -} diff --git a/backend/.sqlx/query-5876d8271adcb75752d518fc52ac192e162469a504338b4a0a37e8bcec114385.json b/backend/.sqlx/query-5876d8271adcb75752d518fc52ac192e162469a504338b4a0a37e8bcec114385.json deleted file mode 100644 index 0d3b712e9ecf9..0000000000000 --- a/backend/.sqlx/query-5876d8271adcb75752d518fc52ac192e162469a504338b4a0a37e8bcec114385.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE folder SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "5876d8271adcb75752d518fc52ac192e162469a504338b4a0a37e8bcec114385" -} diff --git a/backend/.sqlx/query-5b24b4731623b25bf3e65ade4d71a507669c0718363d89e9115824a93d288969.json b/backend/.sqlx/query-5b24b4731623b25bf3e65ade4d71a507669c0718363d89e9115824a93d288969.json new file mode 100644 index 0000000000000..2877af01b7982 --- /dev/null +++ b/backend/.sqlx/query-5b24b4731623b25bf3e65ade4d71a507669c0718363d89e9115824a93d288969.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM v2_job_completed WHERE workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "5b24b4731623b25bf3e65ade4d71a507669c0718363d89e9115824a93d288969" +} diff --git a/backend/.sqlx/query-65121a4bfba70c2d7055a2b58c8520fddaef1bd9a3f041e851f9c136c73d34e7.json b/backend/.sqlx/query-65121a4bfba70c2d7055a2b58c8520fddaef1bd9a3f041e851f9c136c73d34e7.json deleted file mode 100644 index 84d19d1d0f6c1..0000000000000 --- a/backend/.sqlx/query-65121a4bfba70c2d7055a2b58c8520fddaef1bd9a3f041e851f9c136c73d34e7.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE job_stats SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "65121a4bfba70c2d7055a2b58c8520fddaef1bd9a3f041e851f9c136c73d34e7" -} diff --git a/backend/.sqlx/query-6bdb3fcfe16fc40222dc7010a11026d4d4e0d381b31fe02da7d2667c0cdc1a85.json b/backend/.sqlx/query-6bdb3fcfe16fc40222dc7010a11026d4d4e0d381b31fe02da7d2667c0cdc1a85.json deleted file mode 100644 index c498b1790a2fd..0000000000000 --- 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-724538cc1ad304a6533911be473738376aecad7444939a620f47a95f78360989.json b/backend/.sqlx/query-724538cc1ad304a6533911be473738376aecad7444939a620f47a95f78360989.json new file mode 100644 index 0000000000000..c5ff177e9183c --- /dev/null +++ b/backend/.sqlx/query-724538cc1ad304a6533911be473738376aecad7444939a620f47a95f78360989.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT \n w.id \n FROM \n workspace w \n WHERE \n w.parent_workspace_id = $1 \n AND NOT EXISTS (\n SELECT 1 FROM usr u WHERE u.workspace_id = w.id\n )\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "724538cc1ad304a6533911be473738376aecad7444939a620f47a95f78360989" +} diff --git a/backend/.sqlx/query-73706be0610149682fa6131494212095e9494ade5e01b138c7fbbd16f42be791.json b/backend/.sqlx/query-73706be0610149682fa6131494212095e9494ade5e01b138c7fbbd16f42be791.json deleted file mode 100644 index c7ecbf1b56094..0000000000000 --- a/backend/.sqlx/query-73706be0610149682fa6131494212095e9494ade5e01b138c7fbbd16f42be791.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE workspace_env SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "73706be0610149682fa6131494212095e9494ade5e01b138c7fbbd16f42be791" -} diff --git a/backend/.sqlx/query-73a2dbe03a7debbcb236765a9f9d73f3aeb11469f773797ece590f9327878085.json b/backend/.sqlx/query-73a2dbe03a7debbcb236765a9f9d73f3aeb11469f773797ece590f9327878085.json deleted file mode 100644 index 0a7febe815883..0000000000000 --- a/backend/.sqlx/query-73a2dbe03a7debbcb236765a9f9d73f3aeb11469f773797ece590f9327878085.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE account SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "73a2dbe03a7debbcb236765a9f9d73f3aeb11469f773797ece590f9327878085" -} diff --git a/backend/.sqlx/query-7e90578bb37b2923cd94c201f1818c8c5b0bd62c9555949dda2e6aba3e09a4f0.json b/backend/.sqlx/query-7e90578bb37b2923cd94c201f1818c8c5b0bd62c9555949dda2e6aba3e09a4f0.json deleted file mode 100644 index 7158ad6a0e778..0000000000000 --- a/backend/.sqlx/query-7e90578bb37b2923cd94c201f1818c8c5b0bd62c9555949dda2e6aba3e09a4f0.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE job_logs SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "7e90578bb37b2923cd94c201f1818c8c5b0bd62c9555949dda2e6aba3e09a4f0" -} diff --git a/backend/.sqlx/query-7f9da38a52ecee61e6ecfe272207b1572b7974b3f77400aa9d4a80cf1a225500.json b/backend/.sqlx/query-7f9da38a52ecee61e6ecfe272207b1572b7974b3f77400aa9d4a80cf1a225500.json new file mode 100644 index 0000000000000..3ed75b2b9cd19 --- /dev/null +++ b/backend/.sqlx/query-7f9da38a52ecee61e6ecfe272207b1572b7974b3f77400aa9d4a80cf1a225500.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO\n workspace (id, name, owner, deleted, premium, parent_workspace_id)\n SELECT $1, $2, owner, deleted, premium, $3\n FROM \n workspace \n WHERE \n id = $4\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "7f9da38a52ecee61e6ecfe272207b1572b7974b3f77400aa9d4a80cf1a225500" +} diff --git a/backend/.sqlx/query-7fff1cc19a4b2a7ee99b1e4bd74986f4c9253052272145f37c4da1dfe5be0754.json b/backend/.sqlx/query-7fff1cc19a4b2a7ee99b1e4bd74986f4c9253052272145f37c4da1dfe5be0754.json deleted file mode 100644 index b350debd426cf..0000000000000 --- a/backend/.sqlx/query-7fff1cc19a4b2a7ee99b1e4bd74986f4c9253052272145f37c4da1dfe5be0754.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE schedule SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "7fff1cc19a4b2a7ee99b1e4bd74986f4c9253052272145f37c4da1dfe5be0754" -} diff --git a/backend/.sqlx/query-81226fe1d728ffba3629b6bd61414a79327699f8718d010a71aca1dcf281420b.json b/backend/.sqlx/query-81226fe1d728ffba3629b6bd61414a79327699f8718d010a71aca1dcf281420b.json new file mode 100644 index 0000000000000..35443df17df43 --- /dev/null +++ b/backend/.sqlx/query-81226fe1d728ffba3629b6bd61414a79327699f8718d010a71aca1dcf281420b.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT \n 1 \n FROM \n workspace \n WHERE \n id = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "?column?", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "81226fe1d728ffba3629b6bd61414a79327699f8718d010a71aca1dcf281420b" +} diff --git a/backend/.sqlx/query-81a9d976ac5c1a78c83b95a1164995e78878a4a4ff6894a04e6626cdd98c24e4.json b/backend/.sqlx/query-81a9d976ac5c1a78c83b95a1164995e78878a4a4ff6894a04e6626cdd98c24e4.json deleted file mode 100644 index 4295debb89141..0000000000000 --- 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-8f0031533f1bf407bd5d8af4d364eaf00d4c38ee7ba75141b40fc9fcd2ffc0b8.json b/backend/.sqlx/query-8f0031533f1bf407bd5d8af4d364eaf00d4c38ee7ba75141b40fc9fcd2ffc0b8.json deleted file mode 100644 index 86f46e27e58f7..0000000000000 --- a/backend/.sqlx/query-8f0031533f1bf407bd5d8af4d364eaf00d4c38ee7ba75141b40fc9fcd2ffc0b8.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO workspace SELECT $1, $2, owner, deleted, premium FROM workspace WHERE id = $3", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "8f0031533f1bf407bd5d8af4d364eaf00d4c38ee7ba75141b40fc9fcd2ffc0b8" -} diff --git a/backend/.sqlx/query-93e6cdf58803f63d7d465f9d778c052c40dcdacade0f9b9921860160604f3763.json b/backend/.sqlx/query-93e6cdf58803f63d7d465f9d778c052c40dcdacade0f9b9921860160604f3763.json deleted file mode 100644 index 53679a94302c6..0000000000000 --- 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-940b6f4869e545cbeb796ef379dd718cf03e3752607b6bf7c85a176f8a44cbc2.json b/backend/.sqlx/query-940b6f4869e545cbeb796ef379dd718cf03e3752607b6bf7c85a176f8a44cbc2.json deleted file mode 100644 index d52dec49fcc6c..0000000000000 --- a/backend/.sqlx/query-940b6f4869e545cbeb796ef379dd718cf03e3752607b6bf7c85a176f8a44cbc2.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE dependency_map SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "940b6f4869e545cbeb796ef379dd718cf03e3752607b6bf7c85a176f8a44cbc2" -} diff --git a/backend/.sqlx/query-96bc4b0f9909bd555fa58547fa87e4b311c4b515b9e3d14d922d7aec53a9505e.json b/backend/.sqlx/query-96bc4b0f9909bd555fa58547fa87e4b311c4b515b9e3d14d922d7aec53a9505e.json deleted file mode 100644 index e344f1a1214ff..0000000000000 --- a/backend/.sqlx/query-96bc4b0f9909bd555fa58547fa87e4b311c4b515b9e3d14d922d7aec53a9505e.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE script SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "96bc4b0f9909bd555fa58547fa87e4b311c4b515b9e3d14d922d7aec53a9505e" -} diff --git a/backend/.sqlx/query-98880172e7d163e0db06eea2b9008c5082a3e34d8d294b341f01f549be6c7333.json b/backend/.sqlx/query-98880172e7d163e0db06eea2b9008c5082a3e34d8d294b341f01f549be6c7333.json deleted file mode 100644 index 90102ef566089..0000000000000 --- a/backend/.sqlx/query-98880172e7d163e0db06eea2b9008c5082a3e34d8d294b341f01f549be6c7333.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE websocket_trigger SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "98880172e7d163e0db06eea2b9008c5082a3e34d8d294b341f01f549be6c7333" -} diff --git a/backend/.sqlx/query-a0d7dbff9ef98c3cc25281cb5dc9e29023766ba04c57d07c3f89f07640833619.json b/backend/.sqlx/query-a0d7dbff9ef98c3cc25281cb5dc9e29023766ba04c57d07c3f89f07640833619.json deleted file mode 100644 index 11b099d79160d..0000000000000 --- a/backend/.sqlx/query-a0d7dbff9ef98c3cc25281cb5dc9e29023766ba04c57d07c3f89f07640833619.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE raw_app SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "a0d7dbff9ef98c3cc25281cb5dc9e29023766ba04c57d07c3f89f07640833619" -} diff --git a/backend/.sqlx/query-a17b1b8d4f58c58c253e63ec4c2fbb1df4bef54003b01fa901bfa782b5f83342.json b/backend/.sqlx/query-a17b1b8d4f58c58c253e63ec4c2fbb1df4bef54003b01fa901bfa782b5f83342.json deleted file mode 100644 index a4d3cdb8290bf..0000000000000 --- a/backend/.sqlx/query-a17b1b8d4f58c58c253e63ec4c2fbb1df4bef54003b01fa901bfa782b5f83342.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE capture_config SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "a17b1b8d4f58c58c253e63ec4c2fbb1df4bef54003b01fa901bfa782b5f83342" -} diff --git a/backend/.sqlx/query-b101ce2acb77e2f3861d9e50ea241f55bb3ffc8afd26f377c8f0c9f894321806.json b/backend/.sqlx/query-b101ce2acb77e2f3861d9e50ea241f55bb3ffc8afd26f377c8f0c9f894321806.json deleted file mode 100644 index 6e9978bf2901f..0000000000000 --- a/backend/.sqlx/query-b101ce2acb77e2f3861d9e50ea241f55bb3ffc8afd26f377c8f0c9f894321806.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE deployment_metadata SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "b101ce2acb77e2f3861d9e50ea241f55bb3ffc8afd26f377c8f0c9f894321806" -} diff --git a/backend/.sqlx/query-b686da843d5df74e4a58b528b42e726f76c1da907d590589d9b1e8fed930d9b5.json b/backend/.sqlx/query-b686da843d5df74e4a58b528b42e726f76c1da907d590589d9b1e8fed930d9b5.json new file mode 100644 index 0000000000000..897c95a7b90eb --- /dev/null +++ b/backend/.sqlx/query-b686da843d5df74e4a58b528b42e726f76c1da907d590589d9b1e8fed930d9b5.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT \n parent_workspace_id \n FROM \n workspace \n WHERE \n id = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "parent_workspace_id", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + true + ] + }, + "hash": "b686da843d5df74e4a58b528b42e726f76c1da907d590589d9b1e8fed930d9b5" +} diff --git a/backend/.sqlx/query-bb0259204a4d05cbe52d781de9116f273ae1e72d178aaab1ba1ed6af3eff83de.json b/backend/.sqlx/query-bb0259204a4d05cbe52d781de9116f273ae1e72d178aaab1ba1ed6af3eff83de.json new file mode 100644 index 0000000000000..8284a6a51030b --- /dev/null +++ b/backend/.sqlx/query-bb0259204a4d05cbe52d781de9116f273ae1e72d178aaab1ba1ed6af3eff83de.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace SET parent_workspace_id = NULL WHERE id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "bb0259204a4d05cbe52d781de9116f273ae1e72d178aaab1ba1ed6af3eff83de" +} diff --git a/backend/.sqlx/query-bf0ae8e884556a87f8a495aa2c8245e69b8f68b7c2748b2b1c29a3f013123a09.json b/backend/.sqlx/query-bf0ae8e884556a87f8a495aa2c8245e69b8f68b7c2748b2b1c29a3f013123a09.json deleted file mode 100644 index c6939fb1ea7ab..0000000000000 --- a/backend/.sqlx/query-bf0ae8e884556a87f8a495aa2c8245e69b8f68b7c2748b2b1c29a3f013123a09.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE capture SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "bf0ae8e884556a87f8a495aa2c8245e69b8f68b7c2748b2b1c29a3f013123a09" -} diff --git a/backend/.sqlx/query-c721347a78ada56730f92038820e4b7e9e1f541f76bb37a0906a3c96dfe8169e.json b/backend/.sqlx/query-c721347a78ada56730f92038820e4b7e9e1f541f76bb37a0906a3c96dfe8169e.json deleted file mode 100644 index f09296e07416c..0000000000000 --- a/backend/.sqlx/query-c721347a78ada56730f92038820e4b7e9e1f541f76bb37a0906a3c96dfe8169e.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE favorite SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "c721347a78ada56730f92038820e4b7e9e1f541f76bb37a0906a3c96dfe8169e" -} diff --git a/backend/.sqlx/query-cfa0daf8f342aedd2d9b0bbb00965187e3838bbfe6a14be5b7a308e246123f71.json b/backend/.sqlx/query-cfa0daf8f342aedd2d9b0bbb00965187e3838bbfe6a14be5b7a308e246123f71.json deleted file mode 100644 index 55d1ef464d2d3..0000000000000 --- a/backend/.sqlx/query-cfa0daf8f342aedd2d9b0bbb00965187e3838bbfe6a14be5b7a308e246123f71.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE input SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "cfa0daf8f342aedd2d9b0bbb00965187e3838bbfe6a14be5b7a308e246123f71" -} diff --git a/backend/.sqlx/query-d96cc71bbfecd92af3742648bbbdf751eb8dd44b700e82539b359a926cacc011.json b/backend/.sqlx/query-d96cc71bbfecd92af3742648bbbdf751eb8dd44b700e82539b359a926cacc011.json new file mode 100644 index 0000000000000..76f4fc887cb47 --- /dev/null +++ b/backend/.sqlx/query-d96cc71bbfecd92af3742648bbbdf751eb8dd44b700e82539b359a926cacc011.json @@ -0,0 +1,24 @@ +{ + "db_name": "PostgreSQL", + "query": "WITH batch AS (\n SELECT id FROM v2_job_completed\n WHERE workspace_id = $1\n LIMIT $2\n )\n UPDATE v2_job_completed\n SET workspace_id = $3\n WHERE id IN (SELECT id FROM batch)\n RETURNING 1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "?column?", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text", + "Int8", + "Varchar" + ] + }, + "nullable": [ + null + ] + }, + "hash": "d96cc71bbfecd92af3742648bbbdf751eb8dd44b700e82539b359a926cacc011" +} diff --git a/backend/.sqlx/query-e2b17a69501978f2617ff4d20198111e3dab821c25d3164db7983b3192d149a0.json b/backend/.sqlx/query-e2b17a69501978f2617ff4d20198111e3dab821c25d3164db7983b3192d149a0.json deleted file mode 100644 index 83f516cae2890..0000000000000 --- a/backend/.sqlx/query-e2b17a69501978f2617ff4d20198111e3dab821c25d3164db7983b3192d149a0.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE nats_trigger SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "e2b17a69501978f2617ff4d20198111e3dab821c25d3164db7983b3192d149a0" -} diff --git a/backend/.sqlx/query-fc384ad2e85f3b21887d65ae68e8c408acbe4ad5c5c63a9ca9dac35dbb2eec2e.json b/backend/.sqlx/query-fc384ad2e85f3b21887d65ae68e8c408acbe4ad5c5c63a9ca9dac35dbb2eec2e.json deleted file mode 100644 index 8a460c09fd9df..0000000000000 --- a/backend/.sqlx/query-fc384ad2e85f3b21887d65ae68e8c408acbe4ad5c5c63a9ca9dac35dbb2eec2e.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE workspace_invite SET workspace_id = $1 WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "fc384ad2e85f3b21887d65ae68e8c408acbe4ad5c5c63a9ca9dac35dbb2eec2e" -} diff --git a/backend/Cargo.lock b/backend/Cargo.lock index cd4732b4e6d35..f1c399ff1cfeb 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -4514,7 +4514,16 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" dependencies = [ - "derive_builder_macro", + "derive_builder_macro 0.12.0", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro 0.20.2", ] [[package]] @@ -4529,16 +4538,38 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.107", +] + [[package]] name = "derive_builder_macro" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" dependencies = [ - "derive_builder_core", + "derive_builder_core 0.12.0", "syn 1.0.109", ] +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core 0.20.2", + "syn 2.0.107", +] + [[package]] name = "derive_more" version = "0.99.20" @@ -10195,9 +10226,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" dependencies = [ "memchr", "serde", @@ -11374,14 +11405,13 @@ dependencies = [ [[package]] name = "samael" version = "0.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75583aad4a51c50fc0af69c230d18078c9d5a69a98d0f6013d01053acf744f4" +source = "git+https://github.com/njaremko/samael?rev=464d015e3ae393e4b5dd00b4d6baa1b617de0dd6#464d015e3ae393e4b5dd00b4d6baa1b617de0dd6" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "bindgen 0.69.5", "chrono", "data-encoding", - "derive_builder", + "derive_builder 0.20.2", "flate2", "lazy_static", "libc", @@ -11390,7 +11420,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "pkg-config", - "quick-xml 0.30.0", + "quick-xml 0.31.0", "rand 0.8.5", "serde", "thiserror 1.0.69", @@ -13569,7 +13599,7 @@ checksum = "d9be88c795d8b9f9c4002b3a8f26a6d0876103a6f523b32ea3bac52d8560c17c" dependencies = [ "aho-corasick", "clap", - "derive_builder", + "derive_builder 0.12.0", "esaxx-rs", "getrandom 0.2.16", "indicatif", diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 6375018798043..668f65dd208b9 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -770,6 +770,130 @@ paths: schema: type: boolean + /workspaces/migrate/tables: + post: + summary: migrate workspace data from source to target + operationId: migrateWorkspaceTables + tags: + - workspace + requestBody: + description: migration request + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/MigrateWorkspaceRequest" + responses: + "200": + description: migration completed + content: + text/plain: + schema: + type: string + + /workspaces/migrate/jobs: + post: + summary: migrate workspace jobs from source to target + operationId: migrateWorkspaceJobs + tags: + - workspace + requestBody: + description: migration jobs + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/MigrateJobsRequest" + responses: + "200": + description: migration completed + content: + application/json: + schema: + $ref: "#/components/schemas/MigrateJobsStatus" + + + /workspaces/migrate/status: + get: + summary: get migration status + operationId: getMigrationStatus + tags: + - workspace + parameters: + - name: source_workspace + in: query + required: true + description: source workspace id to check migration status + schema: + type: string + responses: + "200": + description: migration status + content: + application/json: + schema: + $ref: "#/components/schemas/MigrationStatus" + + /workspaces/migrate/complete: + post: + operationId: completeWorkspaceMigration + tags: + - workspace + requestBody: + description: complete migration request + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CompleteWorkspaceMigrationRequest" + responses: + "200": + description: migration completed + content: + text/plain: + schema: + type: string + + /workspaces/migrate/{workspace}/revert: + post: + summary: revert incomplete workspace migration + operationId: revertWorkspaceMigration + tags: + - workspace + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + description: revert migration request + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/RevertWorkspaceMigrationRequest" + responses: + "200": + description: migration reverted + content: + text/plain: + schema: + type: string + + /workspaces/migrate/{workspace}/incomplete: + get: + summary: get incomplete migration for source workspace + operationId: getIncompleteMigration + tags: + - workspace + parameters: + - $ref: "#/components/parameters/WorkspaceId" + responses: + "200": + description: target workspace id if incomplete migration exists + content: + application/json: + schema: + type: string + nullable: true + /settings/get_ducklake_instance_catalog_db_status: post: summary: Returns the set-up statuses of ducklake instance catalog dbs @@ -17650,6 +17774,83 @@ components: - SKIP - FAIL + MigrateWorkspaceRequest: + type: object + properties: + source_workspace_id: + type: string + description: source workspace id + target_workspace_id: + type: string + description: target workspace id + target_workspace_name: + type: string + description: target workspace name + required: + - source_workspace_id + - target_workspace_name + - target_workspace_id + + CompleteWorkspaceMigrationRequest: + type: object + properties: + source_workspace_id: + type: string + description: source workspace id + target_workspace_id: + type: string + description: target workspace id + required: + - source_workspace_id + - target_workspace_id + + RevertWorkspaceMigrationRequest: + type: object + properties: + target_workspace_id: + type: string + description: target workspace id to revert + required: + - target_workspace_id + + MigrateJobsRequest: + type: object + properties: + source_workspace_id: + type: string + description: source workspace id + target_workspace_id: + type: string + description: target workspace id + batch_size: + type: integer + format: int64 + default: 10000 + description: number of jobs to migrate per batch + required: + - source_workspace_id + - target_workspace_id + + MigrateJobsStatus: + type: object + properties: + migrated_count: + type: integer + format: int64 + description: number of jobs migrated in this batch + required: + - migrated_count + + MigrationStatus: + type: object + properties: + processed_jobs: + type: integer + format: int64 + description: number of jobs processed in source workspace + required: + - processed_jobs + DucklakeInstanceCatalogDbStatusLogs: type: object properties: diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index 2b7fbfc93a74d..0b30a0ed6b76b 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -67,6 +67,8 @@ use crate::teams_oss::{ workspaces_list_available_teams_channels, workspaces_list_available_teams_ids, }; +use crate::workspaces_extra::{get_migration_status, migrate_jobs, migrate_workspace, complete_workspace_migration, revert_workspace_migration, get_incomplete_migration}; + lazy_static::lazy_static! { static ref WORKSPACE_KEY_REGEXP: Regex = Regex::new("^[a-zA-Z0-9]{64}$").unwrap(); } @@ -143,10 +145,6 @@ pub fn workspaced_service() -> Router { .route("/get_workspace_name", get(get_workspace_name)) .route("/change_workspace_name", post(change_workspace_name)) .route("/change_workspace_color", post(change_workspace_color)) - .route( - "/change_workspace_id", - post(crate::workspaces_extra::change_workspace_id), - ) .route("/usage", get(get_usage)) .route("/used_triggers", get(get_used_triggers)) .route("/critical_alerts", get(get_critical_alerts)) @@ -169,6 +167,19 @@ pub fn workspaced_service() -> Router { #[cfg(not(feature = "stripe"))] router } + +pub fn migrate_service() -> Router { + Router::new().nest( + "/migrate", + Router::new() + .route("/tables", post(migrate_workspace)) + .route("/jobs", post(migrate_jobs)) + .route("/status", get(get_migration_status)) + .route("/complete", post(complete_workspace_migration)) + .route("/:workspace/revert", post(revert_workspace_migration)) + .route("/:workspace/incomplete", get(get_incomplete_migration)), + ) +} pub fn global_service() -> Router { Router::new() .route("/list_as_superadmin", get(list_workspaces_as_super_admin)) @@ -188,6 +199,7 @@ pub fn global_service() -> Router { "/create_workspace_require_superadmin", get(create_workspace_require_superadmin), ) + .merge(migrate_service()) } #[derive(FromRow, Serialize)] @@ -2185,6 +2197,13 @@ lazy_static::lazy_static! { } }; + pub static ref MIGRATE_JOBS_WORKSPACE_REQUIRE_SUPERADMIN: bool = { + match std::env::var("MIGRATE_JOBS_WORKSPACE_REQUIRE_SUPERADMIN") { + Ok(val) => val == "true", + Err(_) => true, + } + }; + } async fn create_workspace_require_superadmin() -> String { diff --git a/backend/windmill-api/src/workspaces_extra.rs b/backend/windmill-api/src/workspaces_extra.rs index 2df23f3ce95c9..d33a6ff3c90d2 100644 --- a/backend/windmill-api/src/workspaces_extra.rs +++ b/backend/windmill-api/src/workspaces_extra.rs @@ -1,6 +1,9 @@ use crate::db::ApiAuthed; -use crate::workspaces::{check_w_id_conflict, CREATE_WORKSPACE_REQUIRE_SUPERADMIN, WM_FORK_PREFIX}; +use crate::workspaces::{ + check_w_id_conflict, CREATE_WORKSPACE_REQUIRE_SUPERADMIN, + MIGRATE_JOBS_WORKSPACE_REQUIRE_SUPERADMIN, WM_FORK_PREFIX, +}; use crate::{db::DB, utils::require_super_admin}; use axum::extract::Query; @@ -13,6 +16,8 @@ use sqlx::{Postgres, Transaction}; use windmill_audit::audit_oss::audit_log; use windmill_audit::ActionKind; +use windmill_common::db::UserDB; +use windmill_common::error::JsonResult; use windmill_common::worker::CLOUD_HOSTED; use windmill_common::{ @@ -21,588 +26,709 @@ use windmill_common::{ utils::require_admin, }; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; #[derive(Deserialize)] -pub(crate) struct ChangeWorkspaceId { - new_id: String, - new_name: String, +pub struct MigrateJobRequest { + source_workspace_id: String, + target_workspace_id: String, + #[serde(default = "default_batch_size")] + batch_size: i64, } -pub(crate) async fn change_workspace_id( - authed: ApiAuthed, - Path(old_id): Path, - Extension(db): Extension, - Json(rw): Json, -) -> Result { - if *CLOUD_HOSTED && !is_super_admin_email(&db, &authed.email).await? { - return Err(Error::BadRequest( - "This feature is not available on the cloud".to_string(), - )); - } +#[derive(Deserialize)] +pub struct MigrateWorkspaceRequest { + source_workspace_id: String, + target_workspace_id: String, + target_workspace_name: String, +} - if *CREATE_WORKSPACE_REQUIRE_SUPERADMIN { - require_super_admin(&db, &authed.email).await?; - } else { - require_admin(authed.is_admin, &authed.username)?; - } +fn default_batch_size() -> i64 { + DEFAULT_BATCH_SIZE +} - let mut tx = db.begin().await?; +#[derive(Deserialize)] +pub(crate) struct DeleteWorkspaceQuery { + pub(crate) only_delete_forks: Option, +} - check_w_id_conflict(&mut tx, &rw.new_id).await?; +async fn delete_workspace_tables(tx: &mut Transaction<'_, Postgres>, w_id: &str) -> Result<()> { + sqlx::query!("DELETE FROM workspace_env WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; - // duplicate workspace with new id name - sqlx::query!( - "INSERT INTO workspace SELECT $1, $2, owner, deleted, premium FROM workspace WHERE id = $3", - &rw.new_id, - &rw.new_name, - &old_id - ) - .execute(&mut *tx) - .await?; + sqlx::query!("DELETE FROM dependency_map WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; + sqlx::query!("DELETE FROM v2_job_queue WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; + sqlx::query!("DELETE FROM v2_job WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; + sqlx::query!("DELETE FROM capture WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; - sqlx::query!( - "UPDATE account SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + // capture_config has on delete cascade - sqlx::query!( - "UPDATE app SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + sqlx::query!("DELETE FROM draft WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; + sqlx::query!("DELETE FROM script WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; + sqlx::query!("DELETE FROM flow WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; + sqlx::query!("DELETE FROM app WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; + sqlx::query!("DELETE FROM raw_app WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; + sqlx::query!("DELETE FROM input WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; + sqlx::query!("DELETE FROM variable WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; + sqlx::query!("DELETE FROM resource WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; - sqlx::query!( - "UPDATE audit SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + sqlx::query!("DELETE FROM schedule WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; - sqlx::query!( - "UPDATE capture SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + sqlx::query!("DELETE FROM v2_job_completed WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; - sqlx::query!( - "UPDATE capture_config SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + sqlx::query!("DELETE FROM job_stats WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; sqlx::query!( - "UPDATE http_trigger SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id + "DELETE FROM deployment_metadata WHERE workspace_id = $1", + w_id ) - .execute(&mut *tx) + .execute(&mut **tx) .await?; - sqlx::query!( - "UPDATE websocket_trigger SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + sqlx::query!("DELETE FROM usr WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; - sqlx::query!( - "UPDATE kafka_trigger SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + sqlx::query!("DELETE FROM resource_type WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; - sqlx::query!( - "UPDATE nats_trigger SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + sqlx::query!("DELETE FROM workspace_invite WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; - sqlx::query!( - "UPDATE v2_job_completed SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + sqlx::query!("DELETE FROM usr_to_group WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; - sqlx::query!( - "UPDATE dependency_map SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + sqlx::query!("DELETE FROM group_ WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; - sqlx::query!( - "UPDATE deployment_metadata SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + sqlx::query!("DELETE FROM folder WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; - sqlx::query!( - "UPDATE draft SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + sqlx::query!("DELETE FROM account WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; - sqlx::query!( - "UPDATE favorite SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + sqlx::query!("DELETE FROM workspace_key WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; 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 - FROM flow WHERE workspace_id = $2", - &rw.new_id, - &old_id + "DELETE FROM workspace_settings WHERE workspace_id = $1", + w_id ) - .execute(&mut *tx) + .execute(&mut **tx) .await?; - sqlx::query!( - "UPDATE flow_version SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + sqlx::query!("DELETE FROM token WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; - sqlx::query!( - "UPDATE workspace_runnable_dependencies SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + sqlx::query!("DELETE FROM http_trigger WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; sqlx::query!( - "UPDATE asset SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id + "DELETE FROM websocket_trigger WHERE workspace_id = $1", + w_id ) - .execute(&mut *tx) + .execute(&mut **tx) .await?; - sqlx::query!( - "UPDATE flow_node SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + sqlx::query!("DELETE FROM kafka_trigger WHERE workspace_id = $1", w_id) + .execute(&mut **tx) + .await?; - sqlx::query!("DELETE FROM flow WHERE workspace_id = $1", &old_id) - .execute(&mut *tx) + // NATS triggers have on delete cascade + + sqlx::query!("DELETE FROM workspace WHERE id = $1", w_id) + .execute(&mut **tx) .await?; - // have to duplicate group_ with new workspace id because of foreign key constraint - sqlx::query!( - "INSERT INTO group_ SELECT $1, name, summary, extra_perms FROM group_ WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + Ok(()) +} - sqlx::query!( - "UPDATE usr_to_group SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id +pub(crate) async fn delete_workspace( + Extension(db): Extension, + Path(w_id): Path, + authed: ApiAuthed, + Query(dwq): Query, +) -> Result { + let w_id = match w_id.as_str() { + "starter" => Err(Error::BadRequest( + "starter workspace cannot be deleted".to_string(), + )), + "admins" => Err(Error::BadRequest( + "admins workspace cannot be deleted".to_string(), + )), + _ => Ok(w_id), + }?; + + if dwq.only_delete_forks.unwrap_or(false) && !w_id.starts_with(WM_FORK_PREFIX) { + return Err(Error::BadRequest( + "Cannot delete this workspace because it is not a workspace fork.".to_string(), + )); + } + + let mut tx = db.begin().await?; + if !(w_id.starts_with(WM_FORK_PREFIX) && is_workspace_owner(&authed, &w_id, &mut tx).await?) { + require_super_admin(&db, &authed.email).await?; + } + + delete_workspace_tables(&mut tx, &w_id).await?; + + audit_log( + &mut *tx, + &authed, + "workspaces.delete", + ActionKind::Delete, + &w_id, + Some(&authed.email), + None, ) - .execute(&mut *tx) .await?; + tx.commit().await?; - // then delete old group_ - sqlx::query!("DELETE FROM group_ WHERE workspace_id = $1", &old_id) - .execute(&mut *tx) + Ok(format!("Deleted workspace {}", &w_id)) +} + +async fn is_workspace_owner( + authed: &ApiAuthed, + w_id: &str, + tx: &mut Transaction<'_, Postgres>, +) -> Result { + let owner = sqlx::query_scalar!("SELECT owner FROM workspace WHERE id = $1", w_id) + .fetch_optional(&mut **tx) .await?; + Ok(owner.map(|o| o == authed.email).unwrap_or(false)) +} - sqlx::query!( - "UPDATE folder SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; +#[inline] +pub async fn is_allowed_to_migrate(db: &DB, authed: &ApiAuthed, predicate: bool) -> Result<()> { + if *CLOUD_HOSTED && !is_super_admin_email(db, &authed.email).await? { + return Err(Error::BadRequest( + "This feature is not available on the cloud".to_string(), + )); + } - sqlx::query!( - "UPDATE input SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + if predicate { + require_super_admin(db, &authed.email).await?; + } else { + require_admin(authed.is_admin, &authed.username)?; + } - sqlx::query!( - "UPDATE job_logs SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + Ok(()) +} - sqlx::query!( - "UPDATE job_stats SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; +pub async fn migrate_workspace( + authed: ApiAuthed, + Extension(db): Extension, + Json(req): Json, +) -> Result { + is_allowed_to_migrate(&db, &authed, *CREATE_WORKSPACE_REQUIRE_SUPERADMIN).await?; - sqlx::query!( - "UPDATE v2_job_queue SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + let mut tx = db.begin().await?; - sqlx::query!( - "UPDATE v2_job SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + check_w_id_conflict(&mut tx, &req.target_workspace_id).await?; sqlx::query!( - "UPDATE raw_app SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id + r#" + INSERT INTO + workspace (id, name, owner, deleted, premium, parent_workspace_id) + SELECT $1, $2, owner, deleted, premium, $3 + FROM + workspace + WHERE + id = $4 + "#, + &req.target_workspace_id, + &req.target_workspace_name, + &req.source_workspace_id, + &req.source_workspace_id, ) .execute(&mut *tx) .await?; - sqlx::query!( - "UPDATE resource SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + migrate_workspace_data_tables(&mut tx, &req.source_workspace_id, &req.target_workspace_id) + .await?; - sqlx::query!( - "UPDATE resource_type SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id + audit_log( + &mut *tx, + &authed, + "workspace.migrate", + ActionKind::Update, + &req.target_workspace_id, + Some(&authed.email), + Some( + [ + ("source", req.source_workspace_id.as_str()), + ("target", req.target_workspace_id.as_str()), + ] + .into(), + ), ) - .execute(&mut *tx) .await?; - sqlx::query!( - "UPDATE schedule SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + tx.commit().await?; - sqlx::query!( - "UPDATE script SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; + Ok(format!( + "Migrated from {} to {}", + &req.source_workspace_id, &req.target_workspace_id + )) +} - sqlx::query!( - "UPDATE token SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id - ) - .execute(&mut *tx) - .await?; +async fn migrate_workspace_data_tables( + tx: &mut Transaction<'_, Postgres>, + source: &str, + target: &str, +) -> Result<()> { + let non_auth_tables = [ + "account", + "app", + "audit", + "capture", + "capture_config", + "http_trigger", + "websocket_trigger", + "kafka_trigger", + "nats_trigger", + "dependency_map", + "deployment_metadata", + "draft", + "favorite", + "asset", + "folder", + "input", + "raw_app", + "resource", + "resource_type", + "schedule", + "script", + "variable", + "job_logs", + "job_stats", + "v2_job_queue", + ]; + + for table in non_auth_tables { + sqlx::query(&format!( + r#" + UPDATE + {} + SET + workspace_id = $1 + WHERE + workspace_id = $2 + "#, + table + )) + .bind(target) + .bind(source) + .execute(&mut **tx) + .await?; + } sqlx::query!( - "UPDATE usage SET id = $1 WHERE id = $2 AND is_workspace = true", - &rw.new_id, - &old_id + "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 + FROM flow WHERE workspace_id = $2", + target, + source ) - .execute(&mut *tx) + .execute(&mut **tx) .await?; + sqlx::query!("DELETE FROM flow WHERE workspace_id = $1", source) + .execute(&mut **tx) + .await?; + sqlx::query!( - "UPDATE usr SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id + "UPDATE flow_version SET workspace_id = $1 WHERE workspace_id = $2", + target, + source ) - .execute(&mut *tx) + .execute(&mut **tx) .await?; sqlx::query!( - "UPDATE variable SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id + "UPDATE flow_node SET workspace_id = $1 WHERE workspace_id = $2", + target, + source ) - .execute(&mut *tx) + .execute(&mut **tx) .await?; sqlx::query!( - "UPDATE workspace_env SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id + "UPDATE workspace_runnable_dependencies SET workspace_id = $1 WHERE workspace_id = $2", + target, + source ) - .execute(&mut *tx) + .execute(&mut **tx) .await?; sqlx::query!( - "UPDATE workspace_invite SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id + "INSERT INTO group_ SELECT $1, name, summary, extra_perms FROM group_ WHERE workspace_id = $2", + target, + source ) - .execute(&mut *tx) + .execute(&mut **tx) .await?; sqlx::query!( - "UPDATE workspace_key SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id + "UPDATE usr_to_group SET workspace_id = $1 WHERE workspace_id = $2", + target, + source ) - .execute(&mut *tx) + .execute(&mut **tx) .await?; + sqlx::query!("DELETE FROM group_ WHERE workspace_id = $1", source) + .execute(&mut **tx) + .await?; + sqlx::query!( - "UPDATE workspace_settings SET workspace_id = $1 WHERE workspace_id = $2", - &rw.new_id, - &old_id + "UPDATE usage SET id = $1 WHERE id = $2 AND is_workspace = true", + target, + source ) - .execute(&mut *tx) + .execute(&mut **tx) .await?; - // delete old workspace - sqlx::query!("DELETE FROM workspace WHERE id = $1", &old_id) - .execute(&mut *tx) + Ok(()) +} + +async fn migrate_auth_tables( + tx: &mut Transaction<'_, Postgres>, + source: &str, + target: &str, +) -> Result<()> { + let auth_tables = [ + "token", + "usr", + "workspace_env", + "workspace_invite", + "workspace_key", + "workspace_settings", + ]; + + for table in auth_tables { + sqlx::query(&format!( + "UPDATE {} SET workspace_id = $1 WHERE workspace_id = $2", + table + )) + .bind(target) + .bind(source) + .execute(&mut **tx) .await?; + } - audit_log( - &mut *tx, - &authed, - "workspace.change_workspace_id", - ActionKind::Update, - &rw.new_id, - Some(&authed.email), - None, - ) - .await?; - tx.commit().await?; - Ok(format!( - "updated workspace from {} to {}", - &old_id, &rw.new_id - )) + Ok(()) } #[derive(Deserialize)] -pub(crate) struct DeleteWorkspaceQuery { - pub(crate) only_delete_forks: Option, +pub struct CompleteWorkspaceMigrationRequest { + source_workspace_id: String, + target_workspace_id: String, } -pub(crate) async fn delete_workspace( - Extension(db): Extension, - Path(w_id): Path, +pub async fn complete_workspace_migration( authed: ApiAuthed, - Query(dwq): Query, + Extension(db): Extension, + Json(req): Json, ) -> Result { - let w_id = match w_id.as_str() { - "starter" => Err(Error::BadRequest( - "starter workspace cannot be deleted".to_string(), - )), - "admins" => Err(Error::BadRequest( - "admins workspace cannot be deleted".to_string(), - )), - _ => Ok(w_id), - }?; - - if dwq.only_delete_forks.unwrap_or(false) && !w_id.starts_with(WM_FORK_PREFIX) { - return Err(Error::BadRequest( - "Cannot delete this workspace because it is not a workspace fork.".to_string(), - )); - } + is_allowed_to_migrate(&db, &authed, *CREATE_WORKSPACE_REQUIRE_SUPERADMIN).await?; let mut tx = db.begin().await?; - if !(w_id.starts_with(WM_FORK_PREFIX) && is_workspace_owner(&authed, &w_id, &mut tx).await?) { - require_super_admin(&db, &authed.email).await?; - } - - sqlx::query!("DELETE FROM workspace_env WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; - - sqlx::query!("DELETE FROM dependency_map WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; - sqlx::query!("DELETE FROM v2_job_queue WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; - sqlx::query!("DELETE FROM v2_job WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; - sqlx::query!("DELETE FROM capture WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; - - // capture_config has on delete cascade - - sqlx::query!("DELETE FROM draft WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; - sqlx::query!("DELETE FROM script WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; - sqlx::query!("DELETE FROM flow WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; - sqlx::query!("DELETE FROM app WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; - sqlx::query!("DELETE FROM raw_app WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; - sqlx::query!("DELETE FROM input WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; - sqlx::query!("DELETE FROM variable WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; - sqlx::query!("DELETE FROM resource WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; - sqlx::query!("DELETE FROM schedule WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; - - sqlx::query!( - "DELETE FROM v2_job_completed WHERE workspace_id = $1", - &w_id - ) - .execute(&mut *tx) - .await?; + let source_workspace_id = sqlx::query_scalar!( + "SELECT parent_workspace_id FROM workspace WHERE id = $1", + &req.target_workspace_id + ) + .fetch_optional(&mut *tx) + .await? + .flatten(); + + match source_workspace_id { + Some(workspace) => { + if workspace != req.source_workspace_id { + return Err(Error::BadRequest( + "Invalid migration: target workspace was not created from source workspace" + .to_string(), + )); + } + } + None => { + return Err(Error::BadRequest( + "Target workspace does not exist".to_string(), + )); + } + } - sqlx::query!("DELETE FROM job_stats WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; + migrate_auth_tables(&mut tx, &req.source_workspace_id, &req.target_workspace_id).await?; sqlx::query!( - "DELETE FROM deployment_metadata WHERE workspace_id = $1", - &w_id + "UPDATE workspace SET parent_workspace_id = NULL WHERE id = $1", + &req.target_workspace_id ) .execute(&mut *tx) .await?; - sqlx::query!("DELETE FROM usr WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; + delete_workspace_tables(&mut tx, &req.source_workspace_id).await?; - sqlx::query!("DELETE FROM resource_type WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; - - sqlx::query!( - "DELETE FROM workspace_invite WHERE workspace_id = $1", - &w_id + audit_log( + &mut *tx, + &authed, + "workspace.complete_migration", + ActionKind::Update, + &req.target_workspace_id, + Some(&authed.email), + Some( + [ + ("source", req.source_workspace_id.as_str()), + ("target", req.target_workspace_id.as_str()), + ] + .into(), + ), ) - .execute(&mut *tx) .await?; - sqlx::query!("DELETE FROM usr_to_group WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; + tx.commit().await?; - sqlx::query!("DELETE FROM group_ WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; + Ok(format!( + "Completed migration from {} to {}", + &req.source_workspace_id, &req.target_workspace_id + )) +} - sqlx::query!("DELETE FROM folder WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; +#[derive(Deserialize)] +pub struct RevertWorkspaceMigrationRequest { + target_workspace_id: String, +} - sqlx::query!("DELETE FROM account WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; +pub async fn revert_workspace_migration( + authed: ApiAuthed, + Extension(db): Extension, + Path(w_id): Path, + Json(req): Json, +) -> Result { + is_allowed_to_migrate(&db, &authed, *CREATE_WORKSPACE_REQUIRE_SUPERADMIN).await?; - sqlx::query!("DELETE FROM workspace_key WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; + let mut tx = db.begin().await?; - sqlx::query!( - "DELETE FROM workspace_settings WHERE workspace_id = $1", - &w_id + let from_workspace_id = sqlx::query_scalar!( + r#" + SELECT + parent_workspace_id + FROM + workspace + WHERE + id = $1 + "#, + &req.target_workspace_id, ) - .execute(&mut *tx) - .await?; + .fetch_optional(&mut *tx) + .await? + .flatten(); - sqlx::query!("DELETE FROM token WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; + let source_workspace_id = match from_workspace_id { + Some(source_workspace_id) => { + if source_workspace_id != w_id { + return Err(Error::BadRequest( + "No incomplete migration found for this workspace".to_string(), + )); + } + source_workspace_id + } + None => { + return Err(Error::BadRequest( + "Target workspace does not exist".to_string(), + )); + } + }; - sqlx::query!("DELETE FROM http_trigger WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; + migrate_workspace_data_tables(&mut tx, &req.target_workspace_id, &source_workspace_id).await?; sqlx::query!( - "DELETE FROM websocket_trigger WHERE workspace_id = $1", - &w_id + r#"DELETE FROM workspace WHERE id = $1"#, + req.target_workspace_id ) .execute(&mut *tx) .await?; - sqlx::query!("DELETE FROM kafka_trigger WHERE workspace_id = $1", &w_id) - .execute(&mut *tx) - .await?; - - // NATS triggers have on delete cascade - - sqlx::query!("DELETE FROM workspace WHERE id = $1", &w_id) - .execute(&mut *tx) - .await?; - audit_log( &mut *tx, &authed, - "workspaces.delete", + "workspace.revert_migration", ActionKind::Delete, - &w_id, + &req.target_workspace_id, Some(&authed.email), - None, + Some( + [ + ("source", source_workspace_id.as_str()), + ("target", req.target_workspace_id.as_str()), + ] + .into(), + ), ) .await?; + tx.commit().await?; - Ok(format!("Deleted workspace {}", &w_id)) + Ok(format!( + "Reverted migration for workspace {}", + &req.target_workspace_id + )) } -async fn is_workspace_owner( - authed: &ApiAuthed, - w_id: &str, - tx: &mut Transaction<'_, Postgres>, -) -> Result { - let owner = sqlx::query_scalar!("SELECT owner FROM workspace WHERE id = $1", w_id) - .fetch_optional(&mut **tx) - .await?; - Ok(owner.map(|o| o == authed.email).unwrap_or(false)) +pub async fn get_incomplete_migration( + authed: ApiAuthed, + Extension(db): Extension, + Path(w_id): Path, +) -> JsonResult> { + require_admin(authed.is_admin, &authed.username)?; + + let target_workspace = sqlx::query_scalar!( + r#" + SELECT + w.id + FROM + workspace w + WHERE + w.parent_workspace_id = $1 + AND NOT EXISTS ( + SELECT 1 FROM usr u WHERE u.workspace_id = w.id + ) + "#, + w_id + ) + .fetch_optional(&db) + .await?; + + Ok(Json(target_workspace)) +} + +const DEFAULT_BATCH_SIZE: i64 = 10000; + +#[derive(Serialize)] +pub struct MigrateJobsBatchResponse { + migrated_count: i64, +} + +#[derive(Serialize)] +pub struct MigrationStatus { + processed_jobs: i64, +} + +pub async fn get_migration_status( + authed: ApiAuthed, + Query(params): Query>, + Extension(db): Extension, +) -> JsonResult { + require_admin(authed.is_admin, &authed.username)?; + + let source_workspace = params + .get("source_workspace") + .ok_or_else(|| Error::BadRequest("source_workspace parameter required".to_string()))?; + + let source_jobs = sqlx::query_scalar!( + "SELECT + COUNT(*) + FROM ( + SELECT id FROM v2_job WHERE workspace_id = $1 + UNION ALL + SELECT id FROM v2_job_completed WHERE workspace_id = $1 + ) as total_jobs", + source_workspace + ) + .fetch_one(&db) + .await? + .unwrap_or(0); + + Ok(Json(MigrationStatus { processed_jobs: source_jobs })) +} + +pub async fn migrate_jobs( + authed: ApiAuthed, + Extension(db): Extension, + Extension(user_db): Extension, + Json(req): Json, +) -> JsonResult { + is_allowed_to_migrate(&db, &authed, *MIGRATE_JOBS_WORKSPACE_REQUIRE_SUPERADMIN).await?; + + let mut tx = user_db.begin(&authed).await?; + + let migrated_count = sqlx::query_scalar!( + "WITH + batch_completed_jobs AS ( + SELECT id FROM v2_job_completed + WHERE workspace_id = $1 + LIMIT $2 + ), + batch_jobs AS ( + SELECT id FROM v2_job + WHERE workspace_id = $1 + LIMIT $2 + ), + updated_completed AS ( + UPDATE v2_job_completed + SET workspace_id = $3 + WHERE id IN (SELECT id FROM batch_completed_jobs) + RETURNING 1 + ), + updated_jobs AS ( + UPDATE v2_job + SET workspace_id = $3 + WHERE id IN (SELECT id FROM batch_jobs) + RETURNING 1 + ) + SELECT + (SELECT COUNT(*) FROM updated_completed) + + (SELECT COUNT(*) FROM updated_jobs) as total_count", + req.source_workspace_id, + req.batch_size, + req.target_workspace_id + ) + .fetch_one(&mut *tx) + .await? + .unwrap_or(0); + + tx.commit().await?; + + Ok(Json(MigrateJobsBatchResponse { migrated_count })) } diff --git a/cli/src/commands/workspace/migrate.ts b/cli/src/commands/workspace/migrate.ts new file mode 100644 index 0000000000000..59deda60ed24a --- /dev/null +++ b/cli/src/commands/workspace/migrate.ts @@ -0,0 +1,128 @@ +// deno-lint-ignore-file no-explicit-any +import { Command, colors, log } from "../../../deps.ts"; +import { GlobalOptions } from "../../types.ts"; +import { requireLogin } from "../../core/auth.ts"; +import { setClient } from "../../../deps.ts"; +import * as wmill from "../../../gen/services.gen.ts"; +import { getActiveWorkspace } from "./workspace.ts"; + +async function migrate( + opts: GlobalOptions & { + token?: string; + remote?: string; + sourceWorkspace?: string; + }, + targetWorkspaceId: string +) { + let token: string; + let remote: string; + let sourceWorkspaceId: string; + + if (opts.token && opts.remote && opts.sourceWorkspace) { + token = opts.token; + remote = opts.remote.endsWith("/") + ? opts.remote.substring(0, opts.remote.length - 1) + : opts.remote; + sourceWorkspaceId = opts.sourceWorkspace; + log.info( + colors.blue("Running in worker job mode with provided credentials") + ); + } else { + await requireLogin(opts); + + const workspace = await getActiveWorkspace(opts); + if (!workspace) { + throw new Error( + "No active workspace. Please run 'wmill workspace add' first." + ); + } + + token = workspace.token; + remote = workspace.remote.endsWith("/") + ? workspace.remote.substring(0, workspace.remote.length - 1) + : workspace.remote; + sourceWorkspaceId = workspace.workspaceId; + + log.info(colors.blue("Running in CLI mode with active workspace")); + } + + setClient(token, remote); + + log.info(colors.blue("Starting workspace migration:")); + log.info(` Source: ${colors.bold(sourceWorkspaceId)}`); + log.info(""); + + try { + log.info(colors.blue("=".repeat(60))); + log.info(colors.blue("Migrating jobs")); + log.info(colors.blue("=".repeat(60))); + log.info(""); + + const initialStatus = await wmill.getMigrationStatus({ + sourceWorkspace: sourceWorkspaceId, + }); + + const totalJobs = initialStatus.processed_jobs || 0; + log.info(`Total jobs to migrate: ${colors.bold(totalJobs.toString())}`); + + if (totalJobs === 0) { + log.info(colors.yellow("No jobs to migrate")); + return; + } + + let totalMigrated = 0; + const batchSize = 10000; + + while (true) { + log.info(`Processing batch (size: ${batchSize})...`); + + const batchResult = await wmill.migrateWorkspaceJobs({ + requestBody: { + source_workspace_id: sourceWorkspaceId, + target_workspace_id: targetWorkspaceId, + batch_size: batchSize, + }, + }); + + const migratedInBatch = batchResult.migrated_count || 0; + totalMigrated += migratedInBatch; + + const progress = Math.round((totalMigrated / totalJobs) * 100); + log.info(`${colors.green(migratedInBatch.toString())} jobs migrated`); + log.info( + `Progress: ${colors.cyan( + `${totalMigrated}/${totalJobs}` + )} (${colors.yellow(`${progress}%`)})` + ); + + if (migratedInBatch < batchSize) { + break; + } + } + + const jobsResult = `Successfully migrated ${totalMigrated} jobs`; + + log.info(colors.green(`✅ ${jobsResult}`)); + log.info(""); + log.info(colors.green("=".repeat(60))); + log.info(colors.green("✅ Complete migration finished successfully!")); + log.info(colors.green("=".repeat(60))); + } catch (error) { + log.error(colors.red(`❌ Migration failed: ${error}`)); + throw error; + } +} + +const command = new Command() + .name("migrate") + .description("Migrate workspace data from source to target workspace") + .arguments("") + .option("-t --token ", "API token for worker job mode") + .option("-r --remote ", "Remote URL for worker job mode") + .option( + "-s --source-workspace ", + "Source workspace ID (defaults to active workspace in CLI mode, required in worker job mode)" + ) + .action(migrate as any); + +export default command; diff --git a/cli/src/commands/workspace/workspace.ts b/cli/src/commands/workspace/workspace.ts index e67617a562d2c..9a34d492fd407 100644 --- a/cli/src/commands/workspace/workspace.ts +++ b/cli/src/commands/workspace/workspace.ts @@ -5,6 +5,7 @@ import { loginInteractive, tryGetLoginInfo } from "../../core/login.ts"; import { colors, Command, Confirm, Input, log, setClient, Table } from "../../../deps.ts"; import { requireLogin } from "../../core/auth.ts"; import { createWorkspaceFork, deleteWorkspaceFork } from "./fork.ts"; +import migrate from "./migrate.ts"; import * as wmill from "../../../gen/services.gen.ts"; @@ -477,6 +478,7 @@ const command = new Command() .command("delete-fork") .description("Delete a forked workspace and git branch") .arguments("") - .action(deleteWorkspaceFork as any); + .action(deleteWorkspaceFork as any) + .command("migrate", migrate); export default command; diff --git a/frontend/src/lib/components/settings/ChangeWorkspaceId.svelte b/frontend/src/lib/components/settings/ChangeWorkspaceId.svelte index 52333e703609e..4b9f1f1b346f3 100644 --- a/frontend/src/lib/components/settings/ChangeWorkspaceId.svelte +++ b/frontend/src/lib/components/settings/ChangeWorkspaceId.svelte @@ -3,19 +3,29 @@ import Alert from '../common/alert/Alert.svelte' import Button from '../common/button/Button.svelte' import { sendUserToast } from '$lib/toast' - import { WorkspaceService } from '$lib/gen' + import { WorkspaceService, JobService } from '$lib/gen' import Modal from '../common/modal/Modal.svelte' - import { Pen } from 'lucide-svelte' + import { Pen, Loader2 } from 'lucide-svelte' import { isCloudHosted } from '$lib/cloud' + import { onDestroy } from 'svelte' + import { hubPaths } from '$lib/hub' - let newName = '' - let newId = '' - let checking = false - let errorId = '' + let { open = $bindable(false) } = $props() - $: newId = newName.toLowerCase().replace(/\s/gi, '-') + let newName = $state('') + let newId = $state('') - $: validateName(newId) + $effect(() => { + if (!incompleteMigration && newName) { + newId = newName.toLowerCase().replace(/\s/gi, '-') + } + }) + let checking = $state(false) + let errorId = $state('') + + $effect(() => { + validateName(newId) + }) async function validateName(id: string): Promise { checking = true @@ -30,30 +40,216 @@ checking = false } - let loading = false - async function renameWorkspace() { + let loading = $state(false) + let workspaceDataMigrated = $state(false) + let oldWorkspaceId = $state('') + + let jobMigrationJobId = $state(undefined) + let migratingJobs = $state(false) + let jobMigrationComplete = $state(false) + let pollInterval: number | null = null + + let migrationInProgress = $state(false) + let migrationError = $state(null) + + const isWorkspaceDataStep = $derived(migrationInProgress && !workspaceDataMigrated) + const isJobsStep = $derived(migratingJobs) + const isComplete = $derived(jobMigrationComplete) + const isError = $derived(!!migrationError) + + let incompleteMigration = $state<{ + targetWorkspaceId: string + sourceWorkspaceId: string + } | null>(null) + let checkingIncomplete = $state(false) + + async function checkForIncompleteMigration() { + if (!$workspaceStore) return + + console.log('Checking for incomplete migration for workspace:', $workspaceStore) + try { + checkingIncomplete = true + const targetWorkspaceId = await WorkspaceService.getIncompleteMigration({ + workspace: $workspaceStore + }) + + console.log('Incomplete migration check result:', targetWorkspaceId) + if (targetWorkspaceId) { + incompleteMigration = { + targetWorkspaceId, + sourceWorkspaceId: $workspaceStore + } + oldWorkspaceId = $workspaceStore + newId = targetWorkspaceId + newName = targetWorkspaceId.replace(/-/g, ' ') + workspaceDataMigrated = true + } + } catch (err: any) { + console.error('Error checking for incomplete migration:', err) + } finally { + checkingIncomplete = false + } + } + + async function checkJobStatus() { + if (!jobMigrationJobId) return + try { + const jobResult = await JobService.getCompletedJobResultMaybe({ + workspace: $workspaceStore!, + id: jobMigrationJobId + }) + if (jobResult.completed) { + migratingJobs = false + stopPolling() + if (jobResult.success) { + jobMigrationComplete = true + sendUserToast('Job migration completed!') + try { + await WorkspaceService.completeWorkspaceMigration({ + requestBody: { + source_workspace_id: $workspaceStore!, + target_workspace_id: newId + } + }) + sendUserToast(`Migration completed and old workspace deleted`) + window.location.href = `/workspace_settings?tab=general&workspace=${newId}` + } catch (error) { + throw error + } + } else { + throw Error(JSON.stringify(jobResult.result)) + } + } + } catch (err: any) { + stopPolling() + migratingJobs = false + migrationInProgress = false + migrationError = err.body || err.message + sendUserToast(`Migration error: ${err.body || err.message}`, true) + } + } + + async function cancelJobMigration() { + if (!jobMigrationJobId) return + + try { + await JobService.cancelQueuedJob({ + workspace: $workspaceStore!, + id: jobMigrationJobId, + requestBody: { + reason: 'User cancelled migration' + } + }) + + sendUserToast('Migration cancelled') + stopPolling() + migratingJobs = false + migrationInProgress = false + incompleteMigration = { + sourceWorkspaceId: $workspaceStore!, + targetWorkspaceId: newId + } + } catch (err: any) { + sendUserToast(`Failed to cancel: ${err.body || err.message}`, true) + } + } + + function startPolling() { + if (!pollInterval) { + pollInterval = setInterval(checkJobStatus, 1000) as any + } + } + + function stopPolling() { + if (pollInterval) { + clearInterval(pollInterval) + pollInterval = null + } + } + + async function performFullMigration(ignore_workspace_data?: boolean) { + try { + migrationInProgress = true + migrationError = null loading = true - await WorkspaceService.changeWorkspaceId({ + oldWorkspaceId = $workspaceStore! + + if (!ignore_workspace_data) { + await WorkspaceService.migrateWorkspaceTables({ + requestBody: { + source_workspace_id: oldWorkspaceId, + target_workspace_id: newId, + target_workspace_name: newName + } + }) + workspaceDataMigrated = true + sendUserToast(`Workspace data migrated to ${newId}`) + } + + migratingJobs = true + + jobMigrationJobId = await JobService.runScriptByPath({ workspace: $workspaceStore!, + path: hubPaths.workspaceMigrator, + requestBody: { + source_workspace_id: oldWorkspaceId, + target_workspace_id: newId + }, + skipPreprocessor: true + }) + + startPolling() + loading = false + } catch (err: any) { + migrationError = err.body || err.message + sendUserToast(`Migration failed: ${migrationError}`, true) + } finally { + if (!migratingJobs) { + loading = false + migrationInProgress = false + } + } + } + + async function revertMigration() { + const sourceWorkspaceId = incompleteMigration?.sourceWorkspaceId || oldWorkspaceId + const targetWorkspaceId = incompleteMigration?.targetWorkspaceId || newId + + if (!sourceWorkspaceId || !targetWorkspaceId) return + + try { + loading = true + + await WorkspaceService.revertWorkspaceMigration({ + workspace: sourceWorkspaceId, requestBody: { - new_name: newName, - new_id: newId + target_workspace_id: targetWorkspaceId } }) - open = false - sendUserToast(`Renamed workspace to ${newName}. Reloading...`) - await new Promise((resolve) => setTimeout(resolve, 1000)) - window.location.href = '/workspace_settings?tab=general&workspace=' + newId - } catch (err) { - sendUserToast(`Error renaming workspace: ${err}`, true) + sendUserToast('Migration reverted successfully!') + + incompleteMigration = null + workspaceDataMigrated = false + migratingJobs = false + jobMigrationComplete = false + migrationInProgress = false + migrationError = null + newName = '' + newId = '' + oldWorkspaceId = '' + errorId = '' + } catch (err: any) { + sendUserToast(`Failed to revert migration: ${err.body || err.message}`, true) } finally { loading = false } } - export let open = false + onDestroy(() => { + stopPolling() + })
@@ -63,6 +259,7 @@ {#if !isCloudHosted() || $superadmin}
- -
- - Renaming the workspace may take a few minutes to complete. Once finished, please update your - webhook calls and adjust your CLI sync configuration accordingly. - -

Current ID
{$workspaceStore ?? ''}

- -