Compare commits

...

22 Commits

Author SHA1 Message Date
HugoCasa
58d9052a8b nit 2025-11-28 17:03:28 +01:00
HugoCasa
e912042c08 Merge remote-tracking branch 'origin/main' into dieri/change-workspace-id-async 2025-11-28 16:59:28 +01:00
dieriba
039ecf0f53 update sqllx 2025-11-15 09:33:54 +01:00
dieriba
5097b4c97f better query 2025-11-15 09:32:42 +01:00
dieriba
7139e414d5 update sqlx 2025-11-15 01:21:05 +01:00
dieriba
61628ff2d9 Merge branch 'main' into dieri/change-workspace-id-async 2025-11-15 00:24:06 +01:00
dieriba
5ae489ee4b migrate v2_job 2025-10-27 16:50:31 +01:00
dieriba
03252d82bf better ux 2025-10-27 16:13:03 +01:00
dieriba
3e81551c99 Merge branch 'main' into dieri/change-workspace-id-async 2025-10-27 11:13:42 +01:00
dieriba
04afbf4624 nits 2025-10-24 16:58:31 +02:00
dieriba
3c1449b39f update sqlx 2025-10-24 16:41:48 +02:00
dieriba
c0a4c48ec1 Merge branch 'main' into dieri/change-workspace-id-async 2025-10-24 16:39:13 +02:00
dieriba
7b2aa2c7d8 fix 2025-10-24 16:38:42 +02:00
dieriba
3aee427068 update 2025-10-24 05:36:12 +02:00
dieriba
b1b4ac9821 fix 2025-10-24 04:57:45 +02:00
dieriba
48af67d2a5 wip 2025-10-24 03:43:36 +02:00
dieriba
fca9b19542 Merge branch 'main' into dieri/change-workspace-id-async 2025-10-24 00:46:08 +02:00
dieriba
64325bc9b1 update 2025-10-23 23:13:23 +02:00
dieriba
00e28d4749 Merge branch 'main' into dieri/change-workspace-id-async 2025-10-23 16:20:19 +02:00
dieriba
4934d113d4 update 2025-10-23 16:19:51 +02:00
Dieriba toure
b6ae5d4f57 ok 2025-10-22 19:49:03 +02:00
Dieriba toure
e38dd2f1a5 ok 2025-10-21 18:27:23 +02:00
51 changed files with 1617 additions and 1113 deletions

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT \n COUNT(*) \n FROM (\n SELECT id FROM v2_job WHERE workspace_id = $1\n UNION ALL\n SELECT id FROM v2_job_completed WHERE workspace_id = $1\n ) as total_jobs",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "14cb27031d383462b35e6b71c35242af1ac0276d3143e3af00e9a99f058fe5bb"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -0,0 +1,24 @@
{
"db_name": "PostgreSQL",
"query": "WITH \n batch_completed_jobs AS (\n SELECT id FROM v2_job_completed\n WHERE workspace_id = $1\n LIMIT $2\n ),\n batch_jobs AS (\n SELECT id FROM v2_job\n WHERE workspace_id = $1\n LIMIT $2\n ),\n updated_completed AS (\n UPDATE v2_job_completed\n SET workspace_id = $3\n WHERE id IN (SELECT id FROM batch_completed_jobs)\n RETURNING 1\n ),\n updated_jobs AS (\n UPDATE v2_job\n SET workspace_id = $3\n WHERE id IN (SELECT id FROM batch_jobs)\n RETURNING 1\n )\n SELECT \n (SELECT COUNT(*) FROM updated_completed) + \n (SELECT COUNT(*) FROM updated_jobs) as total_count",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "total_count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Int8",
"Varchar"
]
},
"nullable": [
null
]
},
"hash": "706a45bebb2321d6bb2330d225ecdb0573b5dc8221b33b93a7fd5244980d99b7"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT \n w.id \n FROM \n workspace w \n LEFT JOIN usr u ON u.workspace_id = w.id\n WHERE \n w.parent_workspace_id = $1 \n AND u.workspace_id IS NULL\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false
]
},
"hash": "7a88553e784004530f829085d7bd61a5493d9e292396f14c149eeb016376574c"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -847,6 +847,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
@@ -18152,6 +18276,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:

View File

@@ -67,6 +67,11 @@ use crate::teams_oss::{
workspaces_list_available_teams_channels, workspaces_list_available_teams_ids,
};
use crate::workspaces_extra::{
complete_workspace_migration, get_incomplete_migration, get_migration_status, migrate_jobs,
migrate_workspace, revert_workspace_migration,
};
lazy_static::lazy_static! {
static ref WORKSPACE_KEY_REGEXP: Regex = Regex::new("^[a-zA-Z0-9]{64}$").unwrap();
}
@@ -148,10 +153,6 @@ pub fn workspaced_service() -> Router {
.route("/create_fork", post(create_workspace_fork))
.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))
@@ -178,6 +179,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))
@@ -197,6 +211,7 @@ pub fn global_service() -> Router {
"/create_workspace_require_superadmin",
get(create_workspace_require_superadmin),
)
.merge(migrate_service())
}
#[derive(FromRow, Serialize)]
@@ -2352,6 +2367,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 {

File diff suppressed because it is too large Load Diff

View File

@@ -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("<target_workspace_id:string>")
.option("-t --token <token:string>", "API token for worker job mode")
.option("-r --remote <url:string>", "Remote URL for worker job mode")
.option(
"-s --source-workspace <workspace:string>",
"Source workspace ID (defaults to active workspace in CLI mode, required in worker job mode)"
)
.action(migrate as any);
export default command;

View File

@@ -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";

View File

@@ -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<void> {
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<string | undefined>(undefined)
let migratingJobs = $state(false)
let jobMigrationComplete = $state(false)
let pollInterval: number | null = null
let migrationInProgress = $state(false)
let migrationError = $state<string | null>(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 {
loading = true
await WorkspaceService.changeWorkspaceId({
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: {
new_name: newName,
new_id: newId
reason: 'User cancelled migration'
}
})
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 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
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: {
target_workspace_id: targetWorkspaceId
}
})
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()
})
</script>
<div class="flex flex-col gap-1">
@@ -63,6 +259,7 @@
{#if !isCloudHosted() || $superadmin}
<Button
on:click={() => {
checkForIncompleteMigration()
open = true
}}
size="xs"
@@ -78,38 +275,255 @@
<p class="text-xs text-secondary font-normal">Slug to uniquely identify your workspace</p>
</div>
<Modal bind:open title="Change workspace ID">
<div class="flex flex-col gap-4">
<Alert type="warning" title="Warning">
Renaming the workspace may take a few minutes to complete. Once finished, please update your
webhook calls and adjust your CLI sync configuration accordingly.
</Alert>
<p class="text-secondary text-sm"
>Current ID <br /> <span class="font-bold">{$workspaceStore ?? ''}</span></p
>
<label class="block">
<span class="text-secondary text-sm">New name</span>
<input type="text" bind:value={newName} />
</label>
<label class="block">
<span class="text-secondary text-sm">New ID</span>
<input type="text" bind:value={newId} />
{#if errorId}
<div class="text-red-500 text-xs mt-1">{errorId}</div>
<Modal
bind:open
title={isComplete
? 'Migration Complete'
: migrationInProgress
? isWorkspaceDataStep
? 'Step 1: Migrating workspace data'
: 'Step 2: Migrating Jobs'
: 'Change Workspace ID'}
>
<div class="flex flex-col gap-6">
{#if checkingIncomplete}
<Alert type="info" title="Checking for incomplete migrations...">
<p class="text-sm">Please wait while we check for any incomplete workspace migrations.</p>
</Alert>
{:else if migrationInProgress || migratingJobs}
<div
class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 p-6 rounded-lg"
>
<div class="flex items-center gap-4 mb-4">
<Loader2 class="w-12 h-12 animate-spin text-blue-500" />
<div>
<h3 class="font-semibold text-blue-900 dark:text-blue-100">Migration in Progress</h3>
<p class="text-sm text-blue-700 dark:text-blue-300">
{#if isWorkspaceDataStep}
Migrating workspace data (scripts, flows, apps, settings)...
{:else if isJobsStep}
Migrating job history - this may take several minutes
{:else}
This process may take several minutes depending on job history size
{/if}
</p>
</div>
</div>
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
<div
class="bg-blue-500 h-2 rounded-full transition-all duration-500"
style="width: {isWorkspaceDataStep ? '30%' : isJobsStep ? '70%' : '100%'}"
></div>
</div>
</div>
<div class="bg-surface-secondary p-4 rounded-lg border">
<p class="text-sm font-medium mb-3">Migration Progress</p>
<div class="space-y-2">
<div class="flex items-center gap-3">
<div
class="w-4 h-4 rounded-full {!isWorkspaceDataStep && workspaceDataMigrated
? 'bg-green-500'
: 'bg-blue-500 animate-pulse'} flex items-center justify-center"
>
{#if !isWorkspaceDataStep && workspaceDataMigrated}
<span class="text-white text-xs"></span>
{/if}
</div>
<span class="text-sm">Step 1: Migrate workspace data</span>
</div>
<div class="flex items-center gap-3">
<div
class="w-4 h-4 rounded-full {isComplete
? 'bg-green-500'
: isJobsStep
? 'bg-blue-500 animate-pulse'
: 'bg-gray-300 dark:bg-gray-600'} flex items-center justify-center"
>
{#if isComplete}
<span class="text-white text-xs"></span>
{/if}
</div>
<span class="text-sm {isJobsStep || isComplete ? '' : 'text-gray-500'}"
>Step 2: Migrate job</span
>
</div>
</div>
<div
class="space-y-1 text-sm text-secondary mt-3 pt-3 border-t border-gray-200 dark:border-gray-700"
>
<p><span class="font-medium">From:</span> {oldWorkspaceId}</p>
<p><span class="font-medium">To:</span> {newId}</p>
</div>
</div>
<Alert type="warning" title="Important">
<p class="text-sm">
Please do not close this window or navigate away during the migration process. This
ensures data integrity and allows proper completion tracking.
</p>
</Alert>
{:else if isError && migrationError}
<Alert type="error" title="Migration Failed">
<div class="space-y-3">
<p class="text-sm"> The change workspace ID process failed. </p>
<details class="text-sm">
<summary class="cursor-pointer font-medium">Error Details</summary>
<pre class="mt-2 text-xs bg-gray-100 dark:bg-gray-800 p-2 rounded overflow-auto"
>{migrationError}</pre
>
</details>
</div>
</Alert>
{:else}
{#if incompleteMigration}
<Alert type="warning" title="Incomplete Migration Detected">
<div class="space-y-3">
<p class="text-sm">
We found an incomplete migration from workspace <strong
>{incompleteMigration.sourceWorkspaceId}</strong
>
to <strong>{incompleteMigration.targetWorkspaceId}</strong>.
</p>
<p class="text-sm"> You can either complete the migration or revert to start over. </p>
</div>
</Alert>
{/if}
</label>
<div
class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 p-4 rounded-lg"
>
<div class="flex items-center gap-2 mb-2">
<div class="w-2 h-2 bg-blue-500 rounded-full"></div>
<span class="text-blue-700 dark:text-blue-300 text-sm font-medium">Current Workspace</span
>
</div>
<p class="font-mono text-lg font-bold text-blue-900 dark:text-blue-100">
{$workspaceStore ?? ''}
</p>
</div>
<div class="space-y-4">
<label class="block">
<span class="text-secondary text-sm font-medium">New Workspace Name</span>
<input
type="text"
bind:value={newName}
placeholder="Enter new workspace name"
class="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 dark:disabled:bg-gray-800 disabled:cursor-not-allowed"
disabled={incompleteMigration !== null || migrationInProgress || workspaceDataMigrated}
/>
</label>
<label class="block">
<span class="text-secondary text-sm font-medium">New Workspace ID</span>
<input
type="text"
bind:value={newId}
placeholder="auto-generated from name"
class="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 dark:disabled:bg-gray-800 disabled:cursor-not-allowed"
disabled={incompleteMigration !== null || migrationInProgress || workspaceDataMigrated}
/>
{#if errorId && incompleteMigration == null}
<div class="text-red-500 text-xs mt-1 flex items-center gap-1">
<span class="w-3 h-3 text-red-500"></span>
{errorId}
</div>
{/if}
</label>
</div>
<Alert type="warning" title="Important Notice">
<p class="text-sm">
Once finished, please update your webhook calls and adjust your CLI sync configuration
accordingly.
</p>
</Alert>
{/if}
</div>
<svelte:fragment slot="actions">
<Button
size="sm"
disabled={checking || errorId.length > 0 || !newName || !newId}
{loading}
on:click={() => {
renameWorkspace()
}}
>
Save
</Button>
{#if migrationInProgress && migratingJobs}
<Button
onclick={(e) => {
e.preventDefault()
cancelJobMigration()
}}
color="red"
size="sm"
>
Cancel Migration
</Button>
{:else if incompleteMigration}
<div class="flex gap-3 w-full">
<Button
size="sm"
color="red"
onclick={(e) => {
e.preventDefault()
revertMigration()
}}
{loading}
>
Revert Migration
</Button>
<Button
size="sm"
disabled={loading}
onclick={async (e) => {
e.preventDefault()
incompleteMigration = null
workspaceDataMigrated = true
await performFullMigration(true)
}}
color="blue"
>
Complete Migration
</Button>
</div>
{:else if isError}
<div class="flex gap-3">
<Button
size="sm"
color="red"
onclick={(e) => {
e.preventDefault()
revertMigration()
}}
{loading}
>
Revert Migration
</Button>
<Button
size="sm"
color="blue"
onclick={async (e) => {
e.preventDefault()
migrationError = null
migrationInProgress = false
await performFullMigration(!isWorkspaceDataStep)
}}
>
Try Again
</Button>
</div>
{:else}
<Button
size="sm"
disabled={checking ||
errorId.length > 0 ||
!newName ||
!newId ||
loading ||
migrationInProgress}
{loading}
onclick={async (e) => {
e.preventDefault()
await performFullMigration()
}}
color="blue"
>
Change Workspace
</Button>
{/if}
</svelte:fragment>
</Modal>

View File

@@ -99,6 +99,7 @@ type HubPaths = {
emailErrorHandler: string
cloneRepoToS3forGitRepoViewer: string
appReport: string
workspaceMigrator: string
}
export const hubPaths = JSON.parse(rawHubPaths) as HubPaths

View File

@@ -43,5 +43,6 @@
"smtpReport": "hub/9086/smtp",
"appReport": "hub/28076/app-report",
"cloneRepoToS3forGitRepoViewer_0": "hub/19825/clone_repo_and_upload_to_instance_storage",
"cloneRepoToS3forGitRepoViewer": "hub/19827/clone_repo_and_upload_to_instance_storage"
"cloneRepoToS3forGitRepoViewer": "hub/19827/clone_repo_and_upload_to_instance_storage",
"workspaceMigrator": "hub/28067/workspace_job_migrator"
}

Submodule intra-uuid-e4eed185-2f29-415f-947e-b2bdc697c8a9-4888488-dtoure added at 6668698e0b