From a9e6ba392d7863f85a5d1fe00cb7287fb61eadc2 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 19:26:06 +0000 Subject: [PATCH] feat: add workspace setting to disable error handler for u/ scripts/flows (#7634) * feat: add workspace setting to disable error handler for u/ scripts/flows Add a new workspace setting `error_handler_muted_on_user_path` that allows disabling the error handler for jobs related to scripts/flows that start with the "u/" prefix (user scripts/flows). Changes: - Add migration to add `error_handler_muted_on_user_path` column to workspace_settings - Update WorkspaceSettings struct and get_settings query - Update EditErrorHandler struct and edit_error_handler API endpoint - Update error handler cache to include the new setting - Add skip logic in send_error_to_workspace_handler for u/ paths - Add toggle in workspace settings UI The implementation uses the existing cached settings to avoid additional database queries, making it efficient. Closes #7633 Co-authored-by: windmill-internal-app[bot] * all * update ee private * combine migrations * sqlx * fix: migration fixes for auto_invite JSONB column - Fix TEXT[] to JSONB conversion using to_jsonb() - Add GIN index on auto_invite for efficient ? operator queries - Add index cleanup to down migration Co-Authored-By: Claude Opus 4.5 * sqlx * all * all * all --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: windmill-internal-app[bot] Co-authored-by: Ruben Fiszel Co-authored-by: Claude Opus 4.5 --- ...829e58ae993932e9bbad5aebab4770cc44d61.json | 16 ++ ...722f3510548eaf525c499ad0e24cdf8332b22.json | 20 -- ...8234ca7d1efeee9661f3901f298da375e73f7.json | 106 +++----- ...700bb6b1a99f6abfb3390a54a8a42c6768903.json | 14 ++ ...b7a73d9492e07680732af9af09e223ade1f37.json | 14 -- ...b54752362c9cc7deb179f75c7f1795866d13a.json | 20 -- ...b25931f145f771b93828e8e6dfcc1f906443d.json | 22 -- ...319f769907025f9c7d1150d45e8ecb1bff4ab.json | 15 ++ ...795de710421bcd3e4c06588e56092677bd05.json} | 186 ++++++-------- ...68c027c645afb73dfc2360245b68ba593cab4.json | 12 + ...56dd4493d19611ed988d694d9f4289dc29c48.json | 14 -- ...d549d50af537c43b4b6ded63b6e83070243be.json | 22 ++ ...fd80928e6fade09e60a9a2d35121c81885cca.json | 16 -- ...50bc8849f7ed60a471d46f3be3d39664a48ca.json | 14 -- ...a2999cdcf938f817327e9168b3edb0fce7fb7.json | 15 -- ...153c43903f929ae5d62fbba12610f89c36d55.json | 2 +- ...329951ef19a1cb3e1b429fcd1abe0e2db45b5.json | 14 -- ...679b3d7c97f6c137b189d552182899782e99f.json | 35 +++ ...177e9d543b8e59a15f7e35c9b5d06dddd3142.json | 94 +++++++ ...522c6e652cfa86f10642ade6aaadd6174fb52.json | 14 ++ ...e21295673c45e4caae1bdbb47517d4608dc8d.json | 17 -- ...2082e8dfbaada01243bae07065c089bc42fa9.json | 12 + ...c3cef39361b0987a22b7d2b2346652f81177e.json | 12 - ...3612cfbdecb9fb9cf768f431d4dbd4cd56cfb.json | 22 ++ ...2827e2fda6711c0a1b104ca6435ddb14b47d.json} | 8 +- ...10cc79a1249f48de45ea1c15235d4b05576b.json} | 8 +- ...52836ae8496d75d7cf7fee497b14943b1b6f6.json | 22 ++ ...6dc29f3f930149fedc240d44c614dfc794ff7.json | 20 ++ ...43ecedc55b2e6319aefcf9797e7dfad0812c.json} | 4 +- ...2e67f3eee46d7dbccb09cadeaafa0534952d6.json | 16 -- ...714942358e2620f3bdfb080504ec1da332dbd.json | 15 ++ ...f9e75fd22924ad2dbe124bb361f3e7fd8bfe0.json | 17 -- ...29339e41cbc7a7ef6ad97b3f0eb93de04c359.json | 12 - ...45031f65f214bbf1c599c3ee1d1e7ac0b50f.json} | 8 +- ...320482375d0bb5e617a9dd236a631a7b9ff05.json | 14 ++ ...ffe2635785b53d31d0ceb3d72226a0c316e8a.json | 118 --------- ...559236fd875192b7b7f4824c63675fdc6a551.json | 12 + ...6b65990f5055c1bc25eedefd303be75f553f.json} | 4 +- ...7f74e5ad617830dd835a45e17fd378bcbeada.json | 238 ------------------ ...44ed5d63dd6023b1dab77d5f188b299f03b9d.json | 14 ++ ...e5041db783f1e6d545533a2d95f54e616b9fb.json | 22 -- ...67be6f33f8ca02ef4768bccd32a33a70a4300.json | 12 - ...c78f649db89c78b2b8cd7e778bf18a6b41e67.json | 15 ++ ...203ca16321a4550073258734a6208b83c00c2.json | 20 ++ ...38a890ee353fec64ee73c312a3352da7f7515.json | 22 -- ...3c13864e855219c7f366c86f0605ec25d1538.json | 35 --- backend/ee-repo-ref.txt | 2 +- ...172000_grouped_workspace_settings.down.sql | 46 ++++ ...24172000_grouped_workspace_settings.up.sql | 59 +++++ backend/tests/success_handler.rs | 19 +- backend/windmill-api/openapi.yaml | 157 +++++++++--- backend/windmill-api/src/flows.rs | 8 +- backend/windmill-api/src/groups.rs | 17 +- backend/windmill-api/src/scripts.rs | 2 +- backend/windmill-api/src/users.rs | 6 +- backend/windmill-api/src/workspaces.rs | 207 ++++++++------- backend/windmill-api/src/workspaces_export.rs | 159 ++++++++++-- backend/windmill-queue/src/jobs.rs | 52 ++-- cli/src/commands/sync/pull.ts | 2 +- cli/src/core/settings.ts | 214 +++++++++++----- .../settings/WorkspaceUserSettings.svelte | 18 +- .../(logged)/workspace_settings/+page.svelte | 43 ++-- 62 files changed, 1253 insertions(+), 1142 deletions(-) create mode 100644 backend/.sqlx/query-03caaec9f41be87eac32e163d7a829e58ae993932e9bbad5aebab4770cc44d61.json delete mode 100644 backend/.sqlx/query-085bbcfcebc9e56984295046c48722f3510548eaf525c499ad0e24cdf8332b22.json create mode 100644 backend/.sqlx/query-0984e469bbd5f97cf9a6cac4bf5700bb6b1a99f6abfb3390a54a8a42c6768903.json delete mode 100644 backend/.sqlx/query-0c9ad812013ff476a79ca8d6bb8b7a73d9492e07680732af9af09e223ade1f37.json delete mode 100644 backend/.sqlx/query-10a40902f5aba38aea23f2b0776b54752362c9cc7deb179f75c7f1795866d13a.json delete mode 100644 backend/.sqlx/query-18e550f4ec23d465632449b88c4b25931f145f771b93828e8e6dfcc1f906443d.json create mode 100644 backend/.sqlx/query-255ba68caa78d0e814ea817693a319f769907025f9c7d1150d45e8ecb1bff4ab.json rename backend/.sqlx/{query-1730f39fd1793d45fbb41b21389c61296a3ff7489ae12f52a19f9543173ac597.json => query-289919809e16aee33c81951b05a4795de710421bcd3e4c06588e56092677bd05.json} (75%) create mode 100644 backend/.sqlx/query-28f3dfb70aed943ad3d64da608168c027c645afb73dfc2360245b68ba593cab4.json delete mode 100644 backend/.sqlx/query-31db242523c534b2abbb5f4985f56dd4493d19611ed988d694d9f4289dc29c48.json create mode 100644 backend/.sqlx/query-397f5bef6454770910189db812ad549d50af537c43b4b6ded63b6e83070243be.json delete mode 100644 backend/.sqlx/query-404233e74aaafd987879c6c87d1fd80928e6fade09e60a9a2d35121c81885cca.json delete mode 100644 backend/.sqlx/query-4977b4027861ca234e95559a8b550bc8849f7ed60a471d46f3be3d39664a48ca.json delete mode 100644 backend/.sqlx/query-4a446b97cf813226d25ca40cefca2999cdcf938f817327e9168b3edb0fce7fb7.json delete mode 100644 backend/.sqlx/query-5ad2c883d26f39f3c141806428a329951ef19a1cb3e1b429fcd1abe0e2db45b5.json create mode 100644 backend/.sqlx/query-5dafddc81419883beda4cc9acb3679b3d7c97f6c137b189d552182899782e99f.json create mode 100644 backend/.sqlx/query-631ddef13d95a171b2807638485177e9d543b8e59a15f7e35c9b5d06dddd3142.json create mode 100644 backend/.sqlx/query-63ad0e3cca16987dba0be7b3efb522c6e652cfa86f10642ade6aaadd6174fb52.json delete mode 100644 backend/.sqlx/query-64c5ca0bd4a2c42d6f5415fae1de21295673c45e4caae1bdbb47517d4608dc8d.json create mode 100644 backend/.sqlx/query-65016ea993fb40d4ab6b0a6f15c2082e8dfbaada01243bae07065c089bc42fa9.json delete mode 100644 backend/.sqlx/query-6eaa0af9c5e0a8d64c3202753c9c3cef39361b0987a22b7d2b2346652f81177e.json create mode 100644 backend/.sqlx/query-75e740531bf794a8568350348253612cfbdecb9fb9cf768f431d4dbd4cd56cfb.json rename backend/.sqlx/{query-3ebc91867be0a4830ef4dbbe833aed348f78e3cb68fb1f3c855a491bdcda5017.json => query-7e01ef5799168c0fc2779d42ce352827e2fda6711c0a1b104ca6435ddb14b47d.json} (53%) rename backend/.sqlx/{query-1ff185d8b8b897a72180cd0002e0f7e9858eef249577ce23969522793c6b5608.json => query-7e5913aa98624836a9bd7b5cc5cb10cc79a1249f48de45ea1c15235d4b05576b.json} (50%) create mode 100644 backend/.sqlx/query-825ee06c5f88436ff7bb45e389552836ae8496d75d7cf7fee497b14943b1b6f6.json create mode 100644 backend/.sqlx/query-87044e1466cd114cbef336620e96dc29f3f930149fedc240d44c614dfc794ff7.json rename backend/.sqlx/{query-1e6c125c884002a1565b11a2c308ce89f9726d21bbb3cc712ad0b6450cbb44e6.json => query-87cb0e9503776dbaea70652612cf43ecedc55b2e6319aefcf9797e7dfad0812c.json} (60%) delete mode 100644 backend/.sqlx/query-9044c71dc8aab7f5d17bf27c4702e67f3eee46d7dbccb09cadeaafa0534952d6.json create mode 100644 backend/.sqlx/query-908ab9eda5ed0f3e76ea5a0e8a1714942358e2620f3bdfb080504ec1da332dbd.json delete mode 100644 backend/.sqlx/query-97b6e5779661343587c297b275df9e75fd22924ad2dbe124bb361f3e7fd8bfe0.json delete mode 100644 backend/.sqlx/query-a2bc410e1066ddc89ca4557ca0e29339e41cbc7a7ef6ad97b3f0eb93de04c359.json rename backend/.sqlx/{query-53210d19693ba46d97352b0ee321af59db453243c0cd17928ee942723d9cdc5c.json => query-acc9be5aa111985db14025ac0e0845031f65f214bbf1c599c3ee1d1e7ac0b50f.json} (50%) create mode 100644 backend/.sqlx/query-c0635f65d561c1a6b183b8fea45320482375d0bb5e617a9dd236a631a7b9ff05.json delete mode 100644 backend/.sqlx/query-c12a0b0d423577afbb2772f971bffe2635785b53d31d0ceb3d72226a0c316e8a.json create mode 100644 backend/.sqlx/query-c2f271bcea1f11b2979152cc743559236fd875192b7b7f4824c63675fdc6a551.json rename backend/.sqlx/{query-711c72a72298bb656882b35071e910dab721cda070f9c39bba3729bdc1467496.json => query-c7c0b7f760f9616ec4a18a8916226b65990f5055c1bc25eedefd303be75f553f.json} (68%) delete mode 100644 backend/.sqlx/query-d0dfd217a4f53aaee7b83d5b5267f74e5ad617830dd835a45e17fd378bcbeada.json create mode 100644 backend/.sqlx/query-dab323eda1fcaff0435e98d77e544ed5d63dd6023b1dab77d5f188b299f03b9d.json delete mode 100644 backend/.sqlx/query-e03cfb65fc9b19c7e68e43d14a6e5041db783f1e6d545533a2d95f54e616b9fb.json delete mode 100644 backend/.sqlx/query-e3f2459d9ac3534531d5724bb2d67be6f33f8ca02ef4768bccd32a33a70a4300.json create mode 100644 backend/.sqlx/query-e844b9ee75a1b5438ffe743185fc78f649db89c78b2b8cd7e778bf18a6b41e67.json create mode 100644 backend/.sqlx/query-e9808cc152e1ce4717260ebb42f203ca16321a4550073258734a6208b83c00c2.json delete mode 100644 backend/.sqlx/query-f8b34e09453d51d3df5be20652938a890ee353fec64ee73c312a3352da7f7515.json delete mode 100644 backend/.sqlx/query-fc8fa909ef7a15c7ad30dd938be3c13864e855219c7f366c86f0605ec25d1538.json create mode 100644 backend/migrations/20260124172000_grouped_workspace_settings.down.sql create mode 100644 backend/migrations/20260124172000_grouped_workspace_settings.up.sql diff --git a/backend/.sqlx/query-03caaec9f41be87eac32e163d7a829e58ae993932e9bbad5aebab4770cc44d61.json b/backend/.sqlx/query-03caaec9f41be87eac32e163d7a829e58ae993932e9bbad5aebab4770cc44d61.json new file mode 100644 index 0000000000..3b7836076b --- /dev/null +++ b/backend/.sqlx/query-03caaec9f41be87eac32e163d7a829e58ae993932e9bbad5aebab4770cc44d61.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE workspace_settings\n SET auto_invite = COALESCE(auto_invite, '{}'::jsonb)\n || jsonb_build_object('instance_groups', $2::jsonb, 'instance_groups_roles', $3::jsonb)\n WHERE workspace_id = $1\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Jsonb", + "Jsonb" + ] + }, + "nullable": [] + }, + "hash": "03caaec9f41be87eac32e163d7a829e58ae993932e9bbad5aebab4770cc44d61" +} diff --git a/backend/.sqlx/query-085bbcfcebc9e56984295046c48722f3510548eaf525c499ad0e24cdf8332b22.json b/backend/.sqlx/query-085bbcfcebc9e56984295046c48722f3510548eaf525c499ad0e24cdf8332b22.json deleted file mode 100644 index 0a474cb3a9..0000000000 --- a/backend/.sqlx/query-085bbcfcebc9e56984295046c48722f3510548eaf525c499ad0e24cdf8332b22.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT success_handler_extra_args FROM workspace_settings WHERE workspace_id = 'test-workspace'", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "success_handler_extra_args", - "type_info": "Json" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - true - ] - }, - "hash": "085bbcfcebc9e56984295046c48722f3510548eaf525c499ad0e24cdf8332b22" -} diff --git a/backend/.sqlx/query-08f288d2781d823e109a9e5b8848234ca7d1efeee9661f3901f298da375e73f7.json b/backend/.sqlx/query-08f288d2781d823e109a9e5b8848234ca7d1efeee9661f3901f298da375e73f7.json index b9956f4ee4..a16a99ac73 100644 --- a/backend/.sqlx/query-08f288d2781d823e109a9e5b8848234ca7d1efeee9661f3901f298da375e73f7.json +++ b/backend/.sqlx/query-08f288d2781d823e109a9e5b8848234ca7d1efeee9661f3901f298da375e73f7.json @@ -30,163 +30,128 @@ }, { "ordinal": 5, - "name": "auto_invite_domain", - "type_info": "Varchar" - }, - { - "ordinal": 6, - "name": "auto_invite_operator", - "type_info": "Bool" - }, - { - "ordinal": 7, "name": "customer_id", "type_info": "Varchar" }, { - "ordinal": 8, + "ordinal": 6, "name": "plan", "type_info": "Varchar" }, { - "ordinal": 9, + "ordinal": 7, "name": "webhook", "type_info": "Text" }, { - "ordinal": 10, + "ordinal": 8, "name": "deploy_to", "type_info": "Varchar" }, { - "ordinal": 11, - "name": "error_handler", - "type_info": "Varchar" - }, - { - "ordinal": 12, + "ordinal": 9, "name": "ai_config", "type_info": "Jsonb" }, { - "ordinal": 13, - "name": "error_handler_extra_args", - "type_info": "Json" - }, - { - "ordinal": 14, - "name": "error_handler_muted_on_cancel", - "type_info": "Bool" - }, - { - "ordinal": 15, + "ordinal": 10, "name": "large_file_storage", "type_info": "Jsonb" }, { - "ordinal": 16, + "ordinal": 11, "name": "git_sync", "type_info": "Jsonb" }, { - "ordinal": 17, + "ordinal": 12, "name": "default_app", "type_info": "Varchar" }, { - "ordinal": 18, - "name": "auto_add", - "type_info": "Bool" - }, - { - "ordinal": 19, + "ordinal": 13, "name": "default_scripts", "type_info": "Jsonb" }, { - "ordinal": 20, + "ordinal": 14, "name": "deploy_ui", "type_info": "Jsonb" }, { - "ordinal": 21, + "ordinal": 15, "name": "mute_critical_alerts", "type_info": "Bool" }, { - "ordinal": 22, + "ordinal": 16, "name": "color", "type_info": "Varchar" }, { - "ordinal": 23, + "ordinal": 17, "name": "operator_settings", "type_info": "Jsonb" }, { - "ordinal": 24, + "ordinal": 18, "name": "teams_command_script", "type_info": "Text" }, { - "ordinal": 25, + "ordinal": 19, "name": "teams_team_id", "type_info": "Text" }, { - "ordinal": 26, + "ordinal": 20, "name": "teams_team_name", "type_info": "Text" }, { - "ordinal": 27, + "ordinal": 21, "name": "git_app_installations", "type_info": "Jsonb" }, { - "ordinal": 28, + "ordinal": 22, "name": "ducklake", "type_info": "Jsonb" }, { - "ordinal": 29, - "name": "auto_add_instance_groups", - "type_info": "TextArray" - }, - { - "ordinal": 30, - "name": "auto_add_instance_groups_roles", - "type_info": "Jsonb" - }, - { - "ordinal": 31, + "ordinal": 23, "name": "slack_oauth_client_id", "type_info": "Varchar" }, { - "ordinal": 32, + "ordinal": 24, "name": "slack_oauth_client_secret", "type_info": "Varchar" }, { - "ordinal": 33, + "ordinal": 25, "name": "datatable", "type_info": "Jsonb" }, { - "ordinal": 34, + "ordinal": 26, "name": "teams_team_guid", "type_info": "Text" }, { - "ordinal": 35, - "name": "success_handler", - "type_info": "Text" + "ordinal": 27, + "name": "auto_invite", + "type_info": "Jsonb" }, { - "ordinal": 36, - "name": "success_handler_extra_args", - "type_info": "Json" + "ordinal": 28, + "name": "error_handler", + "type_info": "Jsonb" + }, + { + "ordinal": 29, + "name": "success_handler", + "type_info": "Jsonb" } ], "parameters": { @@ -209,12 +174,6 @@ true, true, true, - false, - true, - true, - true, - true, - true, true, true, true, @@ -230,7 +189,6 @@ true, true, true, - true, true ] }, diff --git a/backend/.sqlx/query-0984e469bbd5f97cf9a6cac4bf5700bb6b1a99f6abfb3390a54a8a42c6768903.json b/backend/.sqlx/query-0984e469bbd5f97cf9a6cac4bf5700bb6b1a99f6abfb3390a54a8a42c6768903.json new file mode 100644 index 0000000000..91cef887ad --- /dev/null +++ b/backend/.sqlx/query-0984e469bbd5f97cf9a6cac4bf5700bb6b1a99f6abfb3390a54a8a42c6768903.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE workspace_settings\n SET auto_invite = COALESCE(auto_invite, '{}'::jsonb) - 'instance_groups' - 'instance_groups_roles'\n WHERE workspace_id = $1\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "0984e469bbd5f97cf9a6cac4bf5700bb6b1a99f6abfb3390a54a8a42c6768903" +} diff --git a/backend/.sqlx/query-0c9ad812013ff476a79ca8d6bb8b7a73d9492e07680732af9af09e223ade1f37.json b/backend/.sqlx/query-0c9ad812013ff476a79ca8d6bb8b7a73d9492e07680732af9af09e223ade1f37.json deleted file mode 100644 index dd536593e3..0000000000 --- a/backend/.sqlx/query-0c9ad812013ff476a79ca8d6bb8b7a73d9492e07680732af9af09e223ade1f37.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE workspace_settings SET auto_invite_domain = NULL, auto_invite_operator = NULL, auto_add = NULL WHERE workspace_id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [] - }, - "hash": "0c9ad812013ff476a79ca8d6bb8b7a73d9492e07680732af9af09e223ade1f37" -} diff --git a/backend/.sqlx/query-10a40902f5aba38aea23f2b0776b54752362c9cc7deb179f75c7f1795866d13a.json b/backend/.sqlx/query-10a40902f5aba38aea23f2b0776b54752362c9cc7deb179f75c7f1795866d13a.json deleted file mode 100644 index 844b522215..0000000000 --- a/backend/.sqlx/query-10a40902f5aba38aea23f2b0776b54752362c9cc7deb179f75c7f1795866d13a.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT success_handler FROM workspace_settings WHERE workspace_id = 'test-workspace'", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "success_handler", - "type_info": "Text" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - true - ] - }, - "hash": "10a40902f5aba38aea23f2b0776b54752362c9cc7deb179f75c7f1795866d13a" -} diff --git a/backend/.sqlx/query-18e550f4ec23d465632449b88c4b25931f145f771b93828e8e6dfcc1f906443d.json b/backend/.sqlx/query-18e550f4ec23d465632449b88c4b25931f145f771b93828e8e6dfcc1f906443d.json deleted file mode 100644 index bfc9587a1e..0000000000 --- a/backend/.sqlx/query-18e550f4ec23d465632449b88c4b25931f145f771b93828e8e6dfcc1f906443d.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT COALESCE(auto_add_instance_groups, '{}') FROM workspace_settings WHERE workspace_id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "coalesce", - "type_info": "TextArray" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - null - ] - }, - "hash": "18e550f4ec23d465632449b88c4b25931f145f771b93828e8e6dfcc1f906443d" -} diff --git a/backend/.sqlx/query-255ba68caa78d0e814ea817693a319f769907025f9c7d1150d45e8ecb1bff4ab.json b/backend/.sqlx/query-255ba68caa78d0e814ea817693a319f769907025f9c7d1150d45e8ecb1bff4ab.json new file mode 100644 index 0000000000..245c65a7a8 --- /dev/null +++ b/backend/.sqlx/query-255ba68caa78d0e814ea817693a319f769907025f9c7d1150d45e8ecb1bff4ab.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_settings SET auto_invite = $1 WHERE workspace_id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Jsonb", + "Text" + ] + }, + "nullable": [] + }, + "hash": "255ba68caa78d0e814ea817693a319f769907025f9c7d1150d45e8ecb1bff4ab" +} diff --git a/backend/.sqlx/query-1730f39fd1793d45fbb41b21389c61296a3ff7489ae12f52a19f9543173ac597.json b/backend/.sqlx/query-289919809e16aee33c81951b05a4795de710421bcd3e4c06588e56092677bd05.json similarity index 75% rename from backend/.sqlx/query-1730f39fd1793d45fbb41b21389c61296a3ff7489ae12f52a19f9543173ac597.json rename to backend/.sqlx/query-289919809e16aee33c81951b05a4795de710421bcd3e4c06588e56092677bd05.json index 4661f7c12a..e7c6579d70 100644 --- a/backend/.sqlx/query-1730f39fd1793d45fbb41b21389c61296a3ff7489ae12f52a19f9543173ac597.json +++ b/backend/.sqlx/query-289919809e16aee33c81951b05a4795de710421bcd3e4c06588e56092677bd05.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT * FROM workspace_settings WHERE workspace_id = $1", + "query": "\n SELECT\n workspace_id,\n slack_team_id,\n teams_team_id,\n teams_team_name,\n teams_team_guid,\n slack_name,\n slack_command_script,\n teams_command_script,\n slack_email,\n slack_oauth_client_id,\n slack_oauth_client_secret,\n customer_id,\n plan,\n webhook,\n deploy_to,\n ai_config,\n large_file_storage,\n datatable,\n ducklake,\n git_sync,\n deploy_ui,\n default_app,\n default_scripts,\n mute_critical_alerts,\n color,\n operator_settings,\n git_app_installations,\n auto_invite,\n error_handler,\n success_handler\n FROM\n workspace_settings\n WHERE\n workspace_id = $1\n ", "describe": { "columns": [ { @@ -15,92 +15,92 @@ }, { "ordinal": 2, + "name": "teams_team_id", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "teams_team_name", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "teams_team_guid", + "type_info": "Text" + }, + { + "ordinal": 5, "name": "slack_name", "type_info": "Varchar" }, { - "ordinal": 3, + "ordinal": 6, "name": "slack_command_script", "type_info": "Varchar" }, { - "ordinal": 4, + "ordinal": 7, + "name": "teams_command_script", + "type_info": "Text" + }, + { + "ordinal": 8, "name": "slack_email", "type_info": "Varchar" }, - { - "ordinal": 5, - "name": "auto_invite_domain", - "type_info": "Varchar" - }, - { - "ordinal": 6, - "name": "auto_invite_operator", - "type_info": "Bool" - }, - { - "ordinal": 7, - "name": "customer_id", - "type_info": "Varchar" - }, - { - "ordinal": 8, - "name": "plan", - "type_info": "Varchar" - }, { "ordinal": 9, - "name": "webhook", - "type_info": "Text" + "name": "slack_oauth_client_id", + "type_info": "Varchar" }, { "ordinal": 10, - "name": "deploy_to", + "name": "slack_oauth_client_secret", "type_info": "Varchar" }, { "ordinal": 11, - "name": "error_handler", + "name": "customer_id", "type_info": "Varchar" }, { "ordinal": 12, + "name": "plan", + "type_info": "Varchar" + }, + { + "ordinal": 13, + "name": "webhook", + "type_info": "Text" + }, + { + "ordinal": 14, + "name": "deploy_to", + "type_info": "Varchar" + }, + { + "ordinal": 15, "name": "ai_config", "type_info": "Jsonb" }, { - "ordinal": 13, - "name": "error_handler_extra_args", - "type_info": "Json" - }, - { - "ordinal": 14, - "name": "error_handler_muted_on_cancel", - "type_info": "Bool" - }, - { - "ordinal": 15, + "ordinal": 16, "name": "large_file_storage", "type_info": "Jsonb" }, { - "ordinal": 16, - "name": "git_sync", + "ordinal": 17, + "name": "datatable", "type_info": "Jsonb" }, - { - "ordinal": 17, - "name": "default_app", - "type_info": "Varchar" - }, { "ordinal": 18, - "name": "auto_add", - "type_info": "Bool" + "name": "ducklake", + "type_info": "Jsonb" }, { "ordinal": 19, - "name": "default_scripts", + "name": "git_sync", "type_info": "Jsonb" }, { @@ -110,83 +110,48 @@ }, { "ordinal": 21, + "name": "default_app", + "type_info": "Varchar" + }, + { + "ordinal": 22, + "name": "default_scripts", + "type_info": "Jsonb" + }, + { + "ordinal": 23, "name": "mute_critical_alerts", "type_info": "Bool" }, { - "ordinal": 22, + "ordinal": 24, "name": "color", "type_info": "Varchar" }, { - "ordinal": 23, + "ordinal": 25, "name": "operator_settings", "type_info": "Jsonb" }, - { - "ordinal": 24, - "name": "teams_command_script", - "type_info": "Text" - }, - { - "ordinal": 25, - "name": "teams_team_id", - "type_info": "Text" - }, { "ordinal": 26, - "name": "teams_team_name", - "type_info": "Text" - }, - { - "ordinal": 27, "name": "git_app_installations", "type_info": "Jsonb" }, + { + "ordinal": 27, + "name": "auto_invite", + "type_info": "Jsonb" + }, { "ordinal": 28, - "name": "ducklake", + "name": "error_handler", "type_info": "Jsonb" }, { "ordinal": 29, - "name": "auto_add_instance_groups", - "type_info": "TextArray" - }, - { - "ordinal": 30, - "name": "auto_add_instance_groups_roles", - "type_info": "Jsonb" - }, - { - "ordinal": 31, - "name": "slack_oauth_client_id", - "type_info": "Varchar" - }, - { - "ordinal": 32, - "name": "slack_oauth_client_secret", - "type_info": "Varchar" - }, - { - "ordinal": 33, - "name": "datatable", - "type_info": "Jsonb" - }, - { - "ordinal": 34, - "name": "teams_team_guid", - "type_info": "Text" - }, - { - "ordinal": 35, "name": "success_handler", - "type_info": "Text" - }, - { - "ordinal": 36, - "name": "success_handler_extra_args", - "type_info": "Json" + "type_info": "Jsonb" } ], "parameters": { @@ -199,6 +164,10 @@ true, true, true, + true, + true, + true, + true, false, true, true, @@ -209,11 +178,6 @@ true, true, true, - false, - true, - true, - true, - true, true, true, true, @@ -225,14 +189,8 @@ false, true, true, - true, - true, - true, - true, - true, - true, true ] }, - "hash": "1730f39fd1793d45fbb41b21389c61296a3ff7489ae12f52a19f9543173ac597" + "hash": "289919809e16aee33c81951b05a4795de710421bcd3e4c06588e56092677bd05" } diff --git a/backend/.sqlx/query-28f3dfb70aed943ad3d64da608168c027c645afb73dfc2360245b68ba593cab4.json b/backend/.sqlx/query-28f3dfb70aed943ad3d64da608168c027c645afb73dfc2360245b68ba593cab4.json new file mode 100644 index 0000000000..3eb58e590f --- /dev/null +++ b/backend/.sqlx/query-28f3dfb70aed943ad3d64da608168c027c645afb73dfc2360245b68ba593cab4.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE workspace_settings\n SET success_handler = '{\"path\": \"script/f/test/success_handler\", \"extra_args\": {\"key\": \"value\"}}'::jsonb\n WHERE workspace_id = 'test-workspace'\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "28f3dfb70aed943ad3d64da608168c027c645afb73dfc2360245b68ba593cab4" +} diff --git a/backend/.sqlx/query-31db242523c534b2abbb5f4985f56dd4493d19611ed988d694d9f4289dc29c48.json b/backend/.sqlx/query-31db242523c534b2abbb5f4985f56dd4493d19611ed988d694d9f4289dc29c48.json deleted file mode 100644 index 6bb55d3c77..0000000000 --- a/backend/.sqlx/query-31db242523c534b2abbb5f4985f56dd4493d19611ed988d694d9f4289dc29c48.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE workspace_settings SET auto_add_instance_groups = NULL, auto_add_instance_groups_roles = NULL WHERE workspace_id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [] - }, - "hash": "31db242523c534b2abbb5f4985f56dd4493d19611ed988d694d9f4289dc29c48" -} diff --git a/backend/.sqlx/query-397f5bef6454770910189db812ad549d50af537c43b4b6ded63b6e83070243be.json b/backend/.sqlx/query-397f5bef6454770910189db812ad549d50af537c43b4b6ded63b6e83070243be.json new file mode 100644 index 0000000000..ff213bd9b5 --- /dev/null +++ b/backend/.sqlx/query-397f5bef6454770910189db812ad549d50af537c43b4b6ded63b6e83070243be.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT error_handler->>'path' FROM workspace_settings WHERE workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "?column?", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "397f5bef6454770910189db812ad549d50af537c43b4b6ded63b6e83070243be" +} diff --git a/backend/.sqlx/query-404233e74aaafd987879c6c87d1fd80928e6fade09e60a9a2d35121c81885cca.json b/backend/.sqlx/query-404233e74aaafd987879c6c87d1fd80928e6fade09e60a9a2d35121c81885cca.json deleted file mode 100644 index 2ed01e7a44..0000000000 --- a/backend/.sqlx/query-404233e74aaafd987879c6c87d1fd80928e6fade09e60a9a2d35121c81885cca.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE workspace_settings SET auto_add_instance_groups = $2, auto_add_instance_groups_roles = $3 WHERE workspace_id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "TextArray", - "Jsonb" - ] - }, - "nullable": [] - }, - "hash": "404233e74aaafd987879c6c87d1fd80928e6fade09e60a9a2d35121c81885cca" -} diff --git a/backend/.sqlx/query-4977b4027861ca234e95559a8b550bc8849f7ed60a471d46f3be3d39664a48ca.json b/backend/.sqlx/query-4977b4027861ca234e95559a8b550bc8849f7ed60a471d46f3be3d39664a48ca.json deleted file mode 100644 index 01184690ce..0000000000 --- a/backend/.sqlx/query-4977b4027861ca234e95559a8b550bc8849f7ed60a471d46f3be3d39664a48ca.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE\n workspace_settings\n SET\n success_handler = NULL,\n success_handler_extra_args = NULL\n WHERE\n workspace_id = $1\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [] - }, - "hash": "4977b4027861ca234e95559a8b550bc8849f7ed60a471d46f3be3d39664a48ca" -} diff --git a/backend/.sqlx/query-4a446b97cf813226d25ca40cefca2999cdcf938f817327e9168b3edb0fce7fb7.json b/backend/.sqlx/query-4a446b97cf813226d25ca40cefca2999cdcf938f817327e9168b3edb0fce7fb7.json deleted file mode 100644 index f984c967dd..0000000000 --- a/backend/.sqlx/query-4a446b97cf813226d25ca40cefca2999cdcf938f817327e9168b3edb0fce7fb7.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE workspace_settings SET\n auto_add_instance_groups = array_remove(auto_add_instance_groups, $1),\n auto_add_instance_groups_roles = auto_add_instance_groups_roles - $1\n WHERE workspace_id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [] - }, - "hash": "4a446b97cf813226d25ca40cefca2999cdcf938f817327e9168b3edb0fce7fb7" -} diff --git a/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json index 713ccb9dd3..36ddb8ab9f 100644 --- a/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json +++ b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json @@ -15,7 +15,7 @@ ] }, "nullable": [ - null + true ] }, "hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55" diff --git a/backend/.sqlx/query-5ad2c883d26f39f3c141806428a329951ef19a1cb3e1b429fcd1abe0e2db45b5.json b/backend/.sqlx/query-5ad2c883d26f39f3c141806428a329951ef19a1cb3e1b429fcd1abe0e2db45b5.json deleted file mode 100644 index 4b7a7ab231..0000000000 --- a/backend/.sqlx/query-5ad2c883d26f39f3c141806428a329951ef19a1cb3e1b429fcd1abe0e2db45b5.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE\n workspace_settings\n SET\n error_handler = NULL,\n error_handler_extra_args = NULL,\n error_handler_muted_on_cancel = false\n WHERE\n workspace_id = $1\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [] - }, - "hash": "5ad2c883d26f39f3c141806428a329951ef19a1cb3e1b429fcd1abe0e2db45b5" -} diff --git a/backend/.sqlx/query-5dafddc81419883beda4cc9acb3679b3d7c97f6c137b189d552182899782e99f.json b/backend/.sqlx/query-5dafddc81419883beda4cc9acb3679b3d7c97f6c137b189d552182899782e99f.json new file mode 100644 index 0000000000..6a218c247b --- /dev/null +++ b/backend/.sqlx/query-5dafddc81419883beda4cc9acb3679b3d7c97f6c137b189d552182899782e99f.json @@ -0,0 +1,35 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT workspace_id,\n (auto_invite->>'operator')::boolean as \"is_operator: bool\",\n (auto_invite->>'mode') = 'add' as \"is_auto_add: bool\"\n FROM workspace_settings ws\n WHERE (auto_invite->>'domain' = $1 OR auto_invite->>'domain' = '*')\n AND (auto_invite->>'enabled')::boolean = true\n AND NOT EXISTS (SELECT 1 FROM usr WHERE workspace_id = ws.workspace_id AND email = $2)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "is_operator: bool", + "type_info": "Bool" + }, + { + "ordinal": 2, + "name": "is_auto_add: bool", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + null, + null + ] + }, + "hash": "5dafddc81419883beda4cc9acb3679b3d7c97f6c137b189d552182899782e99f" +} diff --git a/backend/.sqlx/query-631ddef13d95a171b2807638485177e9d543b8e59a15f7e35c9b5d06dddd3142.json b/backend/.sqlx/query-631ddef13d95a171b2807638485177e9d543b8e59a15f7e35c9b5d06dddd3142.json new file mode 100644 index 0000000000..be4cd9aa85 --- /dev/null +++ b/backend/.sqlx/query-631ddef13d95a171b2807638485177e9d543b8e59a15f7e35c9b5d06dddd3142.json @@ -0,0 +1,94 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n auto_invite,\n webhook,\n deploy_to,\n error_handler,\n ai_config,\n large_file_storage,\n git_sync,\n default_app,\n default_scripts,\n workspace.name as \"name!\",\n mute_critical_alerts,\n color,\n operator_settings\n FROM workspace_settings\n LEFT JOIN workspace ON workspace.id = workspace_settings.workspace_id\n WHERE workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "auto_invite", + "type_info": "Jsonb" + }, + { + "ordinal": 1, + "name": "webhook", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "deploy_to", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "error_handler", + "type_info": "Jsonb" + }, + { + "ordinal": 4, + "name": "ai_config", + "type_info": "Jsonb" + }, + { + "ordinal": 5, + "name": "large_file_storage", + "type_info": "Jsonb" + }, + { + "ordinal": 6, + "name": "git_sync", + "type_info": "Jsonb" + }, + { + "ordinal": 7, + "name": "default_app", + "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "default_scripts", + "type_info": "Jsonb" + }, + { + "ordinal": 9, + "name": "name!", + "type_info": "Varchar" + }, + { + "ordinal": 10, + "name": "mute_critical_alerts", + "type_info": "Bool" + }, + { + "ordinal": 11, + "name": "color", + "type_info": "Varchar" + }, + { + "ordinal": 12, + "name": "operator_settings", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + true, + true, + true, + true, + true, + true, + true, + true, + true, + false, + true, + true, + true + ] + }, + "hash": "631ddef13d95a171b2807638485177e9d543b8e59a15f7e35c9b5d06dddd3142" +} diff --git a/backend/.sqlx/query-63ad0e3cca16987dba0be7b3efb522c6e652cfa86f10642ade6aaadd6174fb52.json b/backend/.sqlx/query-63ad0e3cca16987dba0be7b3efb522c6e652cfa86f10642ade6aaadd6174fb52.json new file mode 100644 index 0000000000..7b88a52d80 --- /dev/null +++ b/backend/.sqlx/query-63ad0e3cca16987dba0be7b3efb522c6e652cfa86f10642ade6aaadd6174fb52.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_settings SET success_handler = NULL WHERE workspace_id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "63ad0e3cca16987dba0be7b3efb522c6e652cfa86f10642ade6aaadd6174fb52" +} diff --git a/backend/.sqlx/query-64c5ca0bd4a2c42d6f5415fae1de21295673c45e4caae1bdbb47517d4608dc8d.json b/backend/.sqlx/query-64c5ca0bd4a2c42d6f5415fae1de21295673c45e4caae1bdbb47517d4608dc8d.json deleted file mode 100644 index d2460a340b..0000000000 --- a/backend/.sqlx/query-64c5ca0bd4a2c42d6f5415fae1de21295673c45e4caae1bdbb47517d4608dc8d.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE workspace_settings SET auto_invite_domain = $1, auto_invite_operator = $2, auto_add = $4 WHERE workspace_id = $3", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Bool", - "Text", - "Bool" - ] - }, - "nullable": [] - }, - "hash": "64c5ca0bd4a2c42d6f5415fae1de21295673c45e4caae1bdbb47517d4608dc8d" -} diff --git a/backend/.sqlx/query-65016ea993fb40d4ab6b0a6f15c2082e8dfbaada01243bae07065c089bc42fa9.json b/backend/.sqlx/query-65016ea993fb40d4ab6b0a6f15c2082e8dfbaada01243bae07065c089bc42fa9.json new file mode 100644 index 0000000000..c0e9198953 --- /dev/null +++ b/backend/.sqlx/query-65016ea993fb40d4ab6b0a6f15c2082e8dfbaada01243bae07065c089bc42fa9.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE workspace_settings\n SET success_handler = '{\"path\": \"script/f/test/success_handler\"}'::jsonb\n WHERE workspace_id = 'test-workspace'\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "65016ea993fb40d4ab6b0a6f15c2082e8dfbaada01243bae07065c089bc42fa9" +} diff --git a/backend/.sqlx/query-6eaa0af9c5e0a8d64c3202753c9c3cef39361b0987a22b7d2b2346652f81177e.json b/backend/.sqlx/query-6eaa0af9c5e0a8d64c3202753c9c3cef39361b0987a22b7d2b2346652f81177e.json deleted file mode 100644 index e5a4943731..0000000000 --- a/backend/.sqlx/query-6eaa0af9c5e0a8d64c3202753c9c3cef39361b0987a22b7d2b2346652f81177e.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE workspace_settings\n SET success_handler = 'script/f/test/success_handler',\n success_handler_extra_args = '{\"key\": \"value\"}'::json\n WHERE workspace_id = 'test-workspace'\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "6eaa0af9c5e0a8d64c3202753c9c3cef39361b0987a22b7d2b2346652f81177e" -} diff --git a/backend/.sqlx/query-75e740531bf794a8568350348253612cfbdecb9fb9cf768f431d4dbd4cd56cfb.json b/backend/.sqlx/query-75e740531bf794a8568350348253612cfbdecb9fb9cf768f431d4dbd4cd56cfb.json new file mode 100644 index 0000000000..3445985466 --- /dev/null +++ b/backend/.sqlx/query-75e740531bf794a8568350348253612cfbdecb9fb9cf768f431d4dbd4cd56cfb.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT auto_invite->'instance_groups' FROM workspace_settings WHERE workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "?column?", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "75e740531bf794a8568350348253612cfbdecb9fb9cf768f431d4dbd4cd56cfb" +} diff --git a/backend/.sqlx/query-3ebc91867be0a4830ef4dbbe833aed348f78e3cb68fb1f3c855a491bdcda5017.json b/backend/.sqlx/query-7e01ef5799168c0fc2779d42ce352827e2fda6711c0a1b104ca6435ddb14b47d.json similarity index 53% rename from backend/.sqlx/query-3ebc91867be0a4830ef4dbbe833aed348f78e3cb68fb1f3c855a491bdcda5017.json rename to backend/.sqlx/query-7e01ef5799168c0fc2779d42ce352827e2fda6711c0a1b104ca6435ddb14b47d.json index 73d8f1858c..7c8a5bc156 100644 --- a/backend/.sqlx/query-3ebc91867be0a4830ef4dbbe833aed348f78e3cb68fb1f3c855a491bdcda5017.json +++ b/backend/.sqlx/query-7e01ef5799168c0fc2779d42ce352827e2fda6711c0a1b104ca6435ddb14b47d.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n workspace_id,\n auto_add_instance_groups_roles\n FROM workspace_settings\n WHERE\n auto_add_instance_groups IS NOT NULL\n AND $1 = ANY(auto_add_instance_groups)\n ", + "query": "\n SELECT\n workspace_id,\n auto_invite->'instance_groups_roles' as instance_groups_roles\n FROM workspace_settings\n WHERE\n auto_invite->'instance_groups' IS NOT NULL\n AND auto_invite->'instance_groups' ? $1\n ", "describe": { "columns": [ { @@ -10,7 +10,7 @@ }, { "ordinal": 1, - "name": "auto_add_instance_groups_roles", + "name": "instance_groups_roles", "type_info": "Jsonb" } ], @@ -21,8 +21,8 @@ }, "nullable": [ false, - true + null ] }, - "hash": "3ebc91867be0a4830ef4dbbe833aed348f78e3cb68fb1f3c855a491bdcda5017" + "hash": "7e01ef5799168c0fc2779d42ce352827e2fda6711c0a1b104ca6435ddb14b47d" } diff --git a/backend/.sqlx/query-1ff185d8b8b897a72180cd0002e0f7e9858eef249577ce23969522793c6b5608.json b/backend/.sqlx/query-7e5913aa98624836a9bd7b5cc5cb10cc79a1249f48de45ea1c15235d4b05576b.json similarity index 50% rename from backend/.sqlx/query-1ff185d8b8b897a72180cd0002e0f7e9858eef249577ce23969522793c6b5608.json rename to backend/.sqlx/query-7e5913aa98624836a9bd7b5cc5cb10cc79a1249f48de45ea1c15235d4b05576b.json index 6b8589e8e6..0ddb481d3f 100644 --- a/backend/.sqlx/query-1ff185d8b8b897a72180cd0002e0f7e9858eef249577ce23969522793c6b5608.json +++ b/backend/.sqlx/query-7e5913aa98624836a9bd7b5cc5cb10cc79a1249f48de45ea1c15235d4b05576b.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n eig.igroup as group_name,\n ws.auto_add_instance_groups_roles\n FROM email_to_igroup eig\n INNER JOIN workspace_settings ws ON ws.workspace_id = $1\n WHERE eig.email = $2\n AND eig.igroup = ANY(ws.auto_add_instance_groups)\n ", + "query": "\n SELECT\n eig.igroup as group_name,\n ws.auto_invite->'instance_groups_roles' as instance_groups_roles\n FROM email_to_igroup eig\n INNER JOIN workspace_settings ws ON ws.workspace_id = $1\n WHERE eig.email = $2\n AND ws.auto_invite->'instance_groups' ? eig.igroup\n ", "describe": { "columns": [ { @@ -10,7 +10,7 @@ }, { "ordinal": 1, - "name": "auto_add_instance_groups_roles", + "name": "instance_groups_roles", "type_info": "Jsonb" } ], @@ -22,8 +22,8 @@ }, "nullable": [ false, - true + null ] }, - "hash": "1ff185d8b8b897a72180cd0002e0f7e9858eef249577ce23969522793c6b5608" + "hash": "7e5913aa98624836a9bd7b5cc5cb10cc79a1249f48de45ea1c15235d4b05576b" } diff --git a/backend/.sqlx/query-825ee06c5f88436ff7bb45e389552836ae8496d75d7cf7fee497b14943b1b6f6.json b/backend/.sqlx/query-825ee06c5f88436ff7bb45e389552836ae8496d75d7cf7fee497b14943b1b6f6.json new file mode 100644 index 0000000000..7063b2015c --- /dev/null +++ b/backend/.sqlx/query-825ee06c5f88436ff7bb45e389552836ae8496d75d7cf7fee497b14943b1b6f6.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n error_handler->>'path'\n FROM\n workspace_settings\n WHERE\n workspace_id = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "?column?", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "825ee06c5f88436ff7bb45e389552836ae8496d75d7cf7fee497b14943b1b6f6" +} diff --git a/backend/.sqlx/query-87044e1466cd114cbef336620e96dc29f3f930149fedc240d44c614dfc794ff7.json b/backend/.sqlx/query-87044e1466cd114cbef336620e96dc29f3f930149fedc240d44c614dfc794ff7.json new file mode 100644 index 0000000000..9915fbf6c8 --- /dev/null +++ b/backend/.sqlx/query-87044e1466cd114cbef336620e96dc29f3f930149fedc240d44c614dfc794ff7.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT success_handler->'extra_args' FROM workspace_settings WHERE workspace_id = 'test-workspace'", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "?column?", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "87044e1466cd114cbef336620e96dc29f3f930149fedc240d44c614dfc794ff7" +} diff --git a/backend/.sqlx/query-1e6c125c884002a1565b11a2c308ce89f9726d21bbb3cc712ad0b6450cbb44e6.json b/backend/.sqlx/query-87cb0e9503776dbaea70652612cf43ecedc55b2e6319aefcf9797e7dfad0812c.json similarity index 60% rename from backend/.sqlx/query-1e6c125c884002a1565b11a2c308ce89f9726d21bbb3cc712ad0b6450cbb44e6.json rename to backend/.sqlx/query-87cb0e9503776dbaea70652612cf43ecedc55b2e6319aefcf9797e7dfad0812c.json index 2a4c87da6a..3210220405 100644 --- a/backend/.sqlx/query-1e6c125c884002a1565b11a2c308ce89f9726d21bbb3cc712ad0b6450cbb44e6.json +++ b/backend/.sqlx/query-87cb0e9503776dbaea70652612cf43ecedc55b2e6319aefcf9797e7dfad0812c.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n ig.name as group_name,\n ws.workspace_id,\n w.name as workspace_name,\n ws.auto_add_instance_groups_roles->ig.name as role\n FROM instance_group ig\n INNER JOIN workspace_settings ws ON ws.auto_add_instance_groups IS NOT NULL\n AND ig.name = ANY(ws.auto_add_instance_groups)\n INNER JOIN workspace w ON w.id = ws.workspace_id AND w.deleted = false\n ORDER BY ig.name, ws.workspace_id\n ", + "query": "\n SELECT\n ig.name as group_name,\n ws.workspace_id,\n w.name as workspace_name,\n ws.auto_invite->'instance_groups_roles'->ig.name as role\n FROM instance_group ig\n INNER JOIN workspace_settings ws ON ws.auto_invite->'instance_groups' IS NOT NULL\n AND ws.auto_invite->'instance_groups' ? ig.name\n INNER JOIN workspace w ON w.id = ws.workspace_id AND w.deleted = false\n ORDER BY ig.name, ws.workspace_id\n ", "describe": { "columns": [ { @@ -34,5 +34,5 @@ null ] }, - "hash": "1e6c125c884002a1565b11a2c308ce89f9726d21bbb3cc712ad0b6450cbb44e6" + "hash": "87cb0e9503776dbaea70652612cf43ecedc55b2e6319aefcf9797e7dfad0812c" } diff --git a/backend/.sqlx/query-9044c71dc8aab7f5d17bf27c4702e67f3eee46d7dbccb09cadeaafa0534952d6.json b/backend/.sqlx/query-9044c71dc8aab7f5d17bf27c4702e67f3eee46d7dbccb09cadeaafa0534952d6.json deleted file mode 100644 index ed96f00f9c..0000000000 --- a/backend/.sqlx/query-9044c71dc8aab7f5d17bf27c4702e67f3eee46d7dbccb09cadeaafa0534952d6.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE\n workspace_settings\n SET\n success_handler = $1,\n success_handler_extra_args = $2\n WHERE\n workspace_id = $3\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Json", - "Text" - ] - }, - "nullable": [] - }, - "hash": "9044c71dc8aab7f5d17bf27c4702e67f3eee46d7dbccb09cadeaafa0534952d6" -} diff --git a/backend/.sqlx/query-908ab9eda5ed0f3e76ea5a0e8a1714942358e2620f3bdfb080504ec1da332dbd.json b/backend/.sqlx/query-908ab9eda5ed0f3e76ea5a0e8a1714942358e2620f3bdfb080504ec1da332dbd.json new file mode 100644 index 0000000000..ebadde2883 --- /dev/null +++ b/backend/.sqlx/query-908ab9eda5ed0f3e76ea5a0e8a1714942358e2620f3bdfb080504ec1da332dbd.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_settings SET success_handler = $1 WHERE workspace_id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Jsonb", + "Text" + ] + }, + "nullable": [] + }, + "hash": "908ab9eda5ed0f3e76ea5a0e8a1714942358e2620f3bdfb080504ec1da332dbd" +} diff --git a/backend/.sqlx/query-97b6e5779661343587c297b275df9e75fd22924ad2dbe124bb361f3e7fd8bfe0.json b/backend/.sqlx/query-97b6e5779661343587c297b275df9e75fd22924ad2dbe124bb361f3e7fd8bfe0.json deleted file mode 100644 index 69ed74c7ae..0000000000 --- a/backend/.sqlx/query-97b6e5779661343587c297b275df9e75fd22924ad2dbe124bb361f3e7fd8bfe0.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE\n workspace_settings\n SET\n error_handler = $1,\n error_handler_extra_args = $2,\n error_handler_muted_on_cancel = $3\n WHERE\n workspace_id = $4\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Json", - "Bool", - "Text" - ] - }, - "nullable": [] - }, - "hash": "97b6e5779661343587c297b275df9e75fd22924ad2dbe124bb361f3e7fd8bfe0" -} diff --git a/backend/.sqlx/query-a2bc410e1066ddc89ca4557ca0e29339e41cbc7a7ef6ad97b3f0eb93de04c359.json b/backend/.sqlx/query-a2bc410e1066ddc89ca4557ca0e29339e41cbc7a7ef6ad97b3f0eb93de04c359.json deleted file mode 100644 index 388a6900d3..0000000000 --- a/backend/.sqlx/query-a2bc410e1066ddc89ca4557ca0e29339e41cbc7a7ef6ad97b3f0eb93de04c359.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE workspace_settings\n SET success_handler = 'flow/f/test/success_flow'\n WHERE workspace_id = 'test-workspace'\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "a2bc410e1066ddc89ca4557ca0e29339e41cbc7a7ef6ad97b3f0eb93de04c359" -} diff --git a/backend/.sqlx/query-53210d19693ba46d97352b0ee321af59db453243c0cd17928ee942723d9cdc5c.json b/backend/.sqlx/query-acc9be5aa111985db14025ac0e0845031f65f214bbf1c599c3ee1d1e7ac0b50f.json similarity index 50% rename from backend/.sqlx/query-53210d19693ba46d97352b0ee321af59db453243c0cd17928ee942723d9cdc5c.json rename to backend/.sqlx/query-acc9be5aa111985db14025ac0e0845031f65f214bbf1c599c3ee1d1e7ac0b50f.json index dfb445ed36..5d054df550 100644 --- a/backend/.sqlx/query-53210d19693ba46d97352b0ee321af59db453243c0cd17928ee942723d9cdc5c.json +++ b/backend/.sqlx/query-acc9be5aa111985db14025ac0e0845031f65f214bbf1c599c3ee1d1e7ac0b50f.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT workspace_id, auto_add_instance_groups_roles FROM workspace_settings WHERE $1 = ANY(COALESCE(auto_add_instance_groups, '{}'))", + "query": "\n SELECT workspace_id, auto_invite->'instance_groups_roles' as instance_groups_roles\n FROM workspace_settings\n WHERE auto_invite->'instance_groups' ? $1\n ", "describe": { "columns": [ { @@ -10,7 +10,7 @@ }, { "ordinal": 1, - "name": "auto_add_instance_groups_roles", + "name": "instance_groups_roles", "type_info": "Jsonb" } ], @@ -21,8 +21,8 @@ }, "nullable": [ false, - true + null ] }, - "hash": "53210d19693ba46d97352b0ee321af59db453243c0cd17928ee942723d9cdc5c" + "hash": "acc9be5aa111985db14025ac0e0845031f65f214bbf1c599c3ee1d1e7ac0b50f" } diff --git a/backend/.sqlx/query-c0635f65d561c1a6b183b8fea45320482375d0bb5e617a9dd236a631a7b9ff05.json b/backend/.sqlx/query-c0635f65d561c1a6b183b8fea45320482375d0bb5e617a9dd236a631a7b9ff05.json new file mode 100644 index 0000000000..4305ef9e09 --- /dev/null +++ b/backend/.sqlx/query-c0635f65d561c1a6b183b8fea45320482375d0bb5e617a9dd236a631a7b9ff05.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_settings SET error_handler = NULL WHERE workspace_id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "c0635f65d561c1a6b183b8fea45320482375d0bb5e617a9dd236a631a7b9ff05" +} diff --git a/backend/.sqlx/query-c12a0b0d423577afbb2772f971bffe2635785b53d31d0ceb3d72226a0c316e8a.json b/backend/.sqlx/query-c12a0b0d423577afbb2772f971bffe2635785b53d31d0ceb3d72226a0c316e8a.json deleted file mode 100644 index ca8fd862d2..0000000000 --- a/backend/.sqlx/query-c12a0b0d423577afbb2772f971bffe2635785b53d31d0ceb3d72226a0c316e8a.json +++ /dev/null @@ -1,118 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT\n -- slack_team_id,\n -- slack_name,\n -- slack_command_script,\n -- CASE WHEN slack_email = 'missing@email.xyz' THEN NULL ELSE slack_email END AS slack_email,\n auto_invite_domain IS NOT NULL AS \"auto_invite_enabled!\",\n CASE WHEN auto_invite_operator IS TRUE THEN 'operator' ELSE 'developer' END AS \"auto_invite_as!\",\n CASE WHEN auto_add IS TRUE THEN 'add' ELSE 'invite' END AS \"auto_invite_mode!\",\n webhook,\n deploy_to,\n error_handler,\n ai_config,\n error_handler_extra_args,\n error_handler_muted_on_cancel,\n large_file_storage,\n git_sync,\n default_app,\n default_scripts,\n workspace.name,\n mute_critical_alerts,\n color,\n operator_settings\n FROM workspace_settings\n LEFT JOIN workspace ON workspace.id = workspace_settings.workspace_id\n WHERE workspace_id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "auto_invite_enabled!", - "type_info": "Bool" - }, - { - "ordinal": 1, - "name": "auto_invite_as!", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "auto_invite_mode!", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "webhook", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "deploy_to", - "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "error_handler", - "type_info": "Varchar" - }, - { - "ordinal": 6, - "name": "ai_config", - "type_info": "Jsonb" - }, - { - "ordinal": 7, - "name": "error_handler_extra_args", - "type_info": "Json" - }, - { - "ordinal": 8, - "name": "error_handler_muted_on_cancel", - "type_info": "Bool" - }, - { - "ordinal": 9, - "name": "large_file_storage", - "type_info": "Jsonb" - }, - { - "ordinal": 10, - "name": "git_sync", - "type_info": "Jsonb" - }, - { - "ordinal": 11, - "name": "default_app", - "type_info": "Varchar" - }, - { - "ordinal": 12, - "name": "default_scripts", - "type_info": "Jsonb" - }, - { - "ordinal": 13, - "name": "name", - "type_info": "Varchar" - }, - { - "ordinal": 14, - "name": "mute_critical_alerts", - "type_info": "Bool" - }, - { - "ordinal": 15, - "name": "color", - "type_info": "Varchar" - }, - { - "ordinal": 16, - "name": "operator_settings", - "type_info": "Jsonb" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - null, - null, - null, - true, - true, - true, - true, - true, - false, - true, - true, - true, - true, - false, - true, - true, - true - ] - }, - "hash": "c12a0b0d423577afbb2772f971bffe2635785b53d31d0ceb3d72226a0c316e8a" -} diff --git a/backend/.sqlx/query-c2f271bcea1f11b2979152cc743559236fd875192b7b7f4824c63675fdc6a551.json b/backend/.sqlx/query-c2f271bcea1f11b2979152cc743559236fd875192b7b7f4824c63675fdc6a551.json new file mode 100644 index 0000000000..c621e4b903 --- /dev/null +++ b/backend/.sqlx/query-c2f271bcea1f11b2979152cc743559236fd875192b7b7f4824c63675fdc6a551.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE workspace_settings\n SET success_handler = '{\"path\": \"flow/f/test/success_flow\"}'::jsonb\n WHERE workspace_id = 'test-workspace'\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "c2f271bcea1f11b2979152cc743559236fd875192b7b7f4824c63675fdc6a551" +} diff --git a/backend/.sqlx/query-711c72a72298bb656882b35071e910dab721cda070f9c39bba3729bdc1467496.json b/backend/.sqlx/query-c7c0b7f760f9616ec4a18a8916226b65990f5055c1bc25eedefd303be75f553f.json similarity index 68% rename from backend/.sqlx/query-711c72a72298bb656882b35071e910dab721cda070f9c39bba3729bdc1467496.json rename to backend/.sqlx/query-c7c0b7f760f9616ec4a18a8916226b65990f5055c1bc25eedefd303be75f553f.json index 908368edd5..3b175bb9b8 100644 --- a/backend/.sqlx/query-711c72a72298bb656882b35071e910dab721cda070f9c39bba3729bdc1467496.json +++ b/backend/.sqlx/query-c7c0b7f760f9616ec4a18a8916226b65990f5055c1bc25eedefd303be75f553f.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT workspace_id FROM workspace_settings WHERE auto_add_instance_groups IS NOT NULL AND $1 = ANY(auto_add_instance_groups)", + "query": "SELECT workspace_id FROM workspace_settings WHERE auto_invite->'instance_groups' IS NOT NULL AND auto_invite->'instance_groups' ? $1", "describe": { "columns": [ { @@ -18,5 +18,5 @@ false ] }, - "hash": "711c72a72298bb656882b35071e910dab721cda070f9c39bba3729bdc1467496" + "hash": "c7c0b7f760f9616ec4a18a8916226b65990f5055c1bc25eedefd303be75f553f" } diff --git a/backend/.sqlx/query-d0dfd217a4f53aaee7b83d5b5267f74e5ad617830dd835a45e17fd378bcbeada.json b/backend/.sqlx/query-d0dfd217a4f53aaee7b83d5b5267f74e5ad617830dd835a45e17fd378bcbeada.json deleted file mode 100644 index fc7f0e62a8..0000000000 --- a/backend/.sqlx/query-d0dfd217a4f53aaee7b83d5b5267f74e5ad617830dd835a45e17fd378bcbeada.json +++ /dev/null @@ -1,238 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT\n workspace_id,\n slack_team_id,\n teams_team_id,\n teams_team_name,\n teams_team_guid,\n slack_name,\n slack_command_script,\n teams_command_script,\n slack_email,\n slack_oauth_client_id,\n slack_oauth_client_secret,\n auto_invite_domain,\n auto_invite_operator,\n auto_add,\n customer_id,\n plan,\n webhook,\n deploy_to,\n ai_config,\n error_handler,\n error_handler_extra_args,\n error_handler_muted_on_cancel,\n success_handler,\n success_handler_extra_args,\n large_file_storage,\n datatable,\n ducklake,\n git_sync,\n deploy_ui,\n default_app,\n default_scripts,\n mute_critical_alerts,\n color,\n operator_settings,\n git_app_installations,\n auto_add_instance_groups,\n auto_add_instance_groups_roles\n FROM\n workspace_settings\n WHERE\n workspace_id = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "workspace_id", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "slack_team_id", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "teams_team_id", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "teams_team_name", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "teams_team_guid", - "type_info": "Text" - }, - { - "ordinal": 5, - "name": "slack_name", - "type_info": "Varchar" - }, - { - "ordinal": 6, - "name": "slack_command_script", - "type_info": "Varchar" - }, - { - "ordinal": 7, - "name": "teams_command_script", - "type_info": "Text" - }, - { - "ordinal": 8, - "name": "slack_email", - "type_info": "Varchar" - }, - { - "ordinal": 9, - "name": "slack_oauth_client_id", - "type_info": "Varchar" - }, - { - "ordinal": 10, - "name": "slack_oauth_client_secret", - "type_info": "Varchar" - }, - { - "ordinal": 11, - "name": "auto_invite_domain", - "type_info": "Varchar" - }, - { - "ordinal": 12, - "name": "auto_invite_operator", - "type_info": "Bool" - }, - { - "ordinal": 13, - "name": "auto_add", - "type_info": "Bool" - }, - { - "ordinal": 14, - "name": "customer_id", - "type_info": "Varchar" - }, - { - "ordinal": 15, - "name": "plan", - "type_info": "Varchar" - }, - { - "ordinal": 16, - "name": "webhook", - "type_info": "Text" - }, - { - "ordinal": 17, - "name": "deploy_to", - "type_info": "Varchar" - }, - { - "ordinal": 18, - "name": "ai_config", - "type_info": "Jsonb" - }, - { - "ordinal": 19, - "name": "error_handler", - "type_info": "Varchar" - }, - { - "ordinal": 20, - "name": "error_handler_extra_args", - "type_info": "Json" - }, - { - "ordinal": 21, - "name": "error_handler_muted_on_cancel", - "type_info": "Bool" - }, - { - "ordinal": 22, - "name": "success_handler", - "type_info": "Text" - }, - { - "ordinal": 23, - "name": "success_handler_extra_args", - "type_info": "Json" - }, - { - "ordinal": 24, - "name": "large_file_storage", - "type_info": "Jsonb" - }, - { - "ordinal": 25, - "name": "datatable", - "type_info": "Jsonb" - }, - { - "ordinal": 26, - "name": "ducklake", - "type_info": "Jsonb" - }, - { - "ordinal": 27, - "name": "git_sync", - "type_info": "Jsonb" - }, - { - "ordinal": 28, - "name": "deploy_ui", - "type_info": "Jsonb" - }, - { - "ordinal": 29, - "name": "default_app", - "type_info": "Varchar" - }, - { - "ordinal": 30, - "name": "default_scripts", - "type_info": "Jsonb" - }, - { - "ordinal": 31, - "name": "mute_critical_alerts", - "type_info": "Bool" - }, - { - "ordinal": 32, - "name": "color", - "type_info": "Varchar" - }, - { - "ordinal": 33, - "name": "operator_settings", - "type_info": "Jsonb" - }, - { - "ordinal": 34, - "name": "git_app_installations", - "type_info": "Jsonb" - }, - { - "ordinal": 35, - "name": "auto_add_instance_groups", - "type_info": "TextArray" - }, - { - "ordinal": 36, - "name": "auto_add_instance_groups_roles", - "type_info": "Jsonb" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - true, - true, - true, - true, - true, - true, - true, - false, - true, - true, - true, - true, - true, - true, - true, - true, - true, - true, - true, - true, - false, - true, - true, - true, - true, - true, - true, - true, - true, - true, - true, - true, - true, - false, - true, - true - ] - }, - "hash": "d0dfd217a4f53aaee7b83d5b5267f74e5ad617830dd835a45e17fd378bcbeada" -} diff --git a/backend/.sqlx/query-dab323eda1fcaff0435e98d77e544ed5d63dd6023b1dab77d5f188b299f03b9d.json b/backend/.sqlx/query-dab323eda1fcaff0435e98d77e544ed5d63dd6023b1dab77d5f188b299f03b9d.json new file mode 100644 index 0000000000..ee4c5ff1af --- /dev/null +++ b/backend/.sqlx/query-dab323eda1fcaff0435e98d77e544ed5d63dd6023b1dab77d5f188b299f03b9d.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_settings SET\n auto_invite = jsonb_set(\n jsonb_set(\n COALESCE(auto_invite, '{}'::jsonb),\n '{instance_groups}',\n (SELECT COALESCE(jsonb_agg(elem), '[]'::jsonb) FROM jsonb_array_elements(COALESCE(auto_invite->'instance_groups', '[]'::jsonb)) elem WHERE elem #>> '{}' != $1)\n ),\n '{instance_groups_roles}',\n COALESCE(auto_invite->'instance_groups_roles', '{}'::jsonb) - $1\n )\n WHERE auto_invite->'instance_groups' IS NOT NULL AND auto_invite->'instance_groups' ? $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "dab323eda1fcaff0435e98d77e544ed5d63dd6023b1dab77d5f188b299f03b9d" +} diff --git a/backend/.sqlx/query-e03cfb65fc9b19c7e68e43d14a6e5041db783f1e6d545533a2d95f54e616b9fb.json b/backend/.sqlx/query-e03cfb65fc9b19c7e68e43d14a6e5041db783f1e6d545533a2d95f54e616b9fb.json deleted file mode 100644 index c083a13f88..0000000000 --- a/backend/.sqlx/query-e03cfb65fc9b19c7e68e43d14a6e5041db783f1e6d545533a2d95f54e616b9fb.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT\n error_handler \n FROM \n workspace_settings \n WHERE \n workspace_id = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "error_handler", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - true - ] - }, - "hash": "e03cfb65fc9b19c7e68e43d14a6e5041db783f1e6d545533a2d95f54e616b9fb" -} diff --git a/backend/.sqlx/query-e3f2459d9ac3534531d5724bb2d67be6f33f8ca02ef4768bccd32a33a70a4300.json b/backend/.sqlx/query-e3f2459d9ac3534531d5724bb2d67be6f33f8ca02ef4768bccd32a33a70a4300.json deleted file mode 100644 index fd0ae9a5ab..0000000000 --- a/backend/.sqlx/query-e3f2459d9ac3534531d5724bb2d67be6f33f8ca02ef4768bccd32a33a70a4300.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE workspace_settings\n SET success_handler = 'script/f/test/success_handler'\n WHERE workspace_id = 'test-workspace'\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "e3f2459d9ac3534531d5724bb2d67be6f33f8ca02ef4768bccd32a33a70a4300" -} diff --git a/backend/.sqlx/query-e844b9ee75a1b5438ffe743185fc78f649db89c78b2b8cd7e778bf18a6b41e67.json b/backend/.sqlx/query-e844b9ee75a1b5438ffe743185fc78f649db89c78b2b8cd7e778bf18a6b41e67.json new file mode 100644 index 0000000000..01d0b92502 --- /dev/null +++ b/backend/.sqlx/query-e844b9ee75a1b5438ffe743185fc78f649db89c78b2b8cd7e778bf18a6b41e67.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_settings SET error_handler = $1 WHERE workspace_id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Jsonb", + "Text" + ] + }, + "nullable": [] + }, + "hash": "e844b9ee75a1b5438ffe743185fc78f649db89c78b2b8cd7e778bf18a6b41e67" +} diff --git a/backend/.sqlx/query-e9808cc152e1ce4717260ebb42f203ca16321a4550073258734a6208b83c00c2.json b/backend/.sqlx/query-e9808cc152e1ce4717260ebb42f203ca16321a4550073258734a6208b83c00c2.json new file mode 100644 index 0000000000..91479f0583 --- /dev/null +++ b/backend/.sqlx/query-e9808cc152e1ce4717260ebb42f203ca16321a4550073258734a6208b83c00c2.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT success_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "?column?", + "type_info": "Text" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "e9808cc152e1ce4717260ebb42f203ca16321a4550073258734a6208b83c00c2" +} diff --git a/backend/.sqlx/query-f8b34e09453d51d3df5be20652938a890ee353fec64ee73c312a3352da7f7515.json b/backend/.sqlx/query-f8b34e09453d51d3df5be20652938a890ee353fec64ee73c312a3352da7f7515.json deleted file mode 100644 index 13526abb60..0000000000 --- a/backend/.sqlx/query-f8b34e09453d51d3df5be20652938a890ee353fec64ee73c312a3352da7f7515.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT error_handler FROM workspace_settings WHERE workspace_id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "error_handler", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - true - ] - }, - "hash": "f8b34e09453d51d3df5be20652938a890ee353fec64ee73c312a3352da7f7515" -} diff --git a/backend/.sqlx/query-fc8fa909ef7a15c7ad30dd938be3c13864e855219c7f366c86f0605ec25d1538.json b/backend/.sqlx/query-fc8fa909ef7a15c7ad30dd938be3c13864e855219c7f366c86f0605ec25d1538.json deleted file mode 100644 index f5d5a46df5..0000000000 --- a/backend/.sqlx/query-fc8fa909ef7a15c7ad30dd938be3c13864e855219c7f366c86f0605ec25d1538.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT workspace_id, auto_invite_operator, auto_add FROM workspace_settings ws WHERE (auto_invite_domain = $1 OR auto_invite_domain = '*') AND NOT EXISTS (SELECT 1 FROM usr WHERE workspace_id = ws.workspace_id AND email = $2)", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "workspace_id", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "auto_invite_operator", - "type_info": "Bool" - }, - { - "ordinal": 2, - "name": "auto_add", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [ - false, - true, - true - ] - }, - "hash": "fc8fa909ef7a15c7ad30dd938be3c13864e855219c7f366c86f0605ec25d1538" -} diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 14c88d243a..a6d272f583 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -1549849fadc4e5634334a384bfe52343eb1e93f0 +13825341581c1b9eb7a415f0ae25dbc9ba84059f \ No newline at end of file diff --git a/backend/migrations/20260124172000_grouped_workspace_settings.down.sql b/backend/migrations/20260124172000_grouped_workspace_settings.down.sql new file mode 100644 index 0000000000..100c2f4746 --- /dev/null +++ b/backend/migrations/20260124172000_grouped_workspace_settings.down.sql @@ -0,0 +1,46 @@ +-- Step 0: Drop the GIN index on auto_invite +DROP INDEX IF EXISTS idx_workspace_settings_auto_invite; + +-- Step 1: Rename current JSONB columns to temporary names to avoid collision +ALTER TABLE workspace_settings + RENAME COLUMN error_handler TO error_handler_jsonb; +ALTER TABLE workspace_settings + RENAME COLUMN success_handler TO success_handler_jsonb; + +-- Step 2: Restore old columns with original names and types +ALTER TABLE workspace_settings + ADD COLUMN IF NOT EXISTS auto_invite_domain VARCHAR(255), + ADD COLUMN IF NOT EXISTS auto_invite_operator BOOLEAN, + ADD COLUMN IF NOT EXISTS auto_add BOOLEAN, + ADD COLUMN IF NOT EXISTS auto_add_instance_groups TEXT[], + ADD COLUMN IF NOT EXISTS auto_add_instance_groups_roles JSONB, + ADD COLUMN IF NOT EXISTS error_handler VARCHAR(255), + ADD COLUMN IF NOT EXISTS error_handler_extra_args JSON, + ADD COLUMN IF NOT EXISTS error_handler_muted_on_cancel BOOLEAN DEFAULT false, + ADD COLUMN IF NOT EXISTS error_handler_muted_on_user_path BOOLEAN DEFAULT false, + ADD COLUMN IF NOT EXISTS success_handler TEXT, + ADD COLUMN IF NOT EXISTS success_handler_extra_args JSON; + +-- Step 3: Migrate data back from JSONB columns to flat columns +UPDATE workspace_settings SET + auto_invite_domain = auto_invite->>'domain', + auto_invite_operator = COALESCE((auto_invite->>'operator')::boolean, false), + auto_add = (auto_invite->>'mode') = 'add', + auto_add_instance_groups = CASE + WHEN auto_invite->'instance_groups' IS NOT NULL AND auto_invite->'instance_groups' != 'null'::jsonb + THEN ARRAY(SELECT jsonb_array_elements_text(auto_invite->'instance_groups')) + ELSE NULL + END, + auto_add_instance_groups_roles = auto_invite->'instance_groups_roles', + error_handler = error_handler_jsonb->>'path', + error_handler_extra_args = (error_handler_jsonb->'extra_args')::json, + error_handler_muted_on_cancel = COALESCE((error_handler_jsonb->>'muted_on_cancel')::boolean, false), + error_handler_muted_on_user_path = COALESCE((error_handler_jsonb->>'muted_on_user_path')::boolean, false), + success_handler = success_handler_jsonb->>'path', + success_handler_extra_args = (success_handler_jsonb->'extra_args')::json; + +-- Step 4: Drop the JSONB columns +ALTER TABLE workspace_settings + DROP COLUMN IF EXISTS auto_invite, + DROP COLUMN IF EXISTS error_handler_jsonb, + DROP COLUMN IF EXISTS success_handler_jsonb; diff --git a/backend/migrations/20260124172000_grouped_workspace_settings.up.sql b/backend/migrations/20260124172000_grouped_workspace_settings.up.sql new file mode 100644 index 0000000000..f0b7373ec3 --- /dev/null +++ b/backend/migrations/20260124172000_grouped_workspace_settings.up.sql @@ -0,0 +1,59 @@ +-- Step 1: Add error_handler_muted_on_user_path column if it doesn't exist (was previously a separate migration) +ALTER TABLE workspace_settings ADD COLUMN IF NOT EXISTS error_handler_muted_on_user_path BOOL NOT NULL DEFAULT false; + +-- Step 2: Add new JSONB columns with temporary names (to avoid collision with existing TEXT columns) +ALTER TABLE workspace_settings + ADD COLUMN IF NOT EXISTS auto_invite JSONB, + ADD COLUMN IF NOT EXISTS error_handler_new JSONB, + ADD COLUMN IF NOT EXISTS success_handler_new JSONB; + +-- Step 3: Migrate existing data to new columns (use jsonb_strip_nulls to omit null values) +UPDATE workspace_settings SET + auto_invite = jsonb_strip_nulls(jsonb_build_object( + 'enabled', auto_invite_domain IS NOT NULL, + 'domain', auto_invite_domain, + 'operator', COALESCE(auto_invite_operator, false), + 'mode', CASE WHEN auto_add THEN 'add' ELSE 'invite' END, + 'instance_groups', CASE WHEN auto_add_instance_groups IS NOT NULL THEN to_jsonb(auto_add_instance_groups) ELSE NULL END, + 'instance_groups_roles', auto_add_instance_groups_roles + )), + error_handler_new = CASE + WHEN error_handler IS NOT NULL THEN jsonb_strip_nulls(jsonb_build_object( + 'path', error_handler, + 'extra_args', error_handler_extra_args::jsonb, + 'muted_on_cancel', error_handler_muted_on_cancel, + 'muted_on_user_path', error_handler_muted_on_user_path + )) + ELSE NULL + END, + success_handler_new = CASE + WHEN success_handler IS NOT NULL THEN jsonb_strip_nulls(jsonb_build_object( + 'path', success_handler, + 'extra_args', success_handler_extra_args::jsonb + )) + ELSE NULL + END; + +-- Step 4: Drop old columns that are now consolidated +ALTER TABLE workspace_settings + DROP COLUMN IF EXISTS auto_invite_domain, + DROP COLUMN IF EXISTS auto_invite_operator, + DROP COLUMN IF EXISTS auto_add, + DROP COLUMN IF EXISTS auto_add_instance_groups, + DROP COLUMN IF EXISTS auto_add_instance_groups_roles, + DROP COLUMN IF EXISTS error_handler, + DROP COLUMN IF EXISTS error_handler_extra_args, + DROP COLUMN IF EXISTS error_handler_muted_on_cancel, + DROP COLUMN IF EXISTS error_handler_muted_on_user_path, + DROP COLUMN IF EXISTS success_handler, + DROP COLUMN IF EXISTS success_handler_extra_args; + +-- Step 5: Rename new columns to their final names +ALTER TABLE workspace_settings + RENAME COLUMN error_handler_new TO error_handler; +ALTER TABLE workspace_settings + RENAME COLUMN success_handler_new TO success_handler; + +-- Step 6: Add GIN index on auto_invite for efficient JSONB queries (e.g., ? operator for instance_groups) +-- Using default GIN opclass (not jsonb_path_ops) to support the ? operator +CREATE INDEX IF NOT EXISTS idx_workspace_settings_auto_invite ON workspace_settings USING GIN (auto_invite); diff --git a/backend/tests/success_handler.rs b/backend/tests/success_handler.rs index 52416e5271..e3c733100e 100644 --- a/backend/tests/success_handler.rs +++ b/backend/tests/success_handler.rs @@ -12,12 +12,11 @@ async fn test_success_handler_cache(db: Pool) -> anyhow::Result<()> { // First, create a success handler script let _server = ApiServer::start(db.clone()).await?; - // Set up a success handler in workspace_settings + // Set up a success handler in workspace_settings using new JSONB column sqlx::query!( r#" UPDATE workspace_settings - SET success_handler = 'script/f/test/success_handler', - success_handler_extra_args = '{"key": "value"}'::json + SET success_handler = '{"path": "script/f/test/success_handler", "extra_args": {"key": "value"}}'::jsonb WHERE workspace_id = 'test-workspace' "# ) @@ -26,7 +25,7 @@ async fn test_success_handler_cache(db: Pool) -> anyhow::Result<()> { // Verify the success handler was set let result = sqlx::query_scalar!( - r#"SELECT success_handler FROM workspace_settings WHERE workspace_id = 'test-workspace'"# + r#"SELECT success_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'"# ) .fetch_one(&db) .await?; @@ -35,7 +34,7 @@ async fn test_success_handler_cache(db: Pool) -> anyhow::Result<()> { // Verify extra args were set let extra_args = sqlx::query_scalar!( - r#"SELECT success_handler_extra_args FROM workspace_settings WHERE workspace_id = 'test-workspace'"# + r#"SELECT success_handler->'extra_args' FROM workspace_settings WHERE workspace_id = 'test-workspace'"# ) .fetch_one(&db) .await?; @@ -55,7 +54,7 @@ async fn test_success_handler_settings(db: Pool) -> anyhow::Result<()> // Initially success_handler should be NULL let initial = sqlx::query_scalar!( - r#"SELECT success_handler FROM workspace_settings WHERE workspace_id = 'test-workspace'"# + r#"SELECT success_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'"# ) .fetch_one(&db) .await?; @@ -65,7 +64,7 @@ async fn test_success_handler_settings(db: Pool) -> anyhow::Result<()> sqlx::query!( r#" UPDATE workspace_settings - SET success_handler = 'flow/f/test/success_flow' + SET success_handler = '{"path": "flow/f/test/success_flow"}'::jsonb WHERE workspace_id = 'test-workspace' "# ) @@ -73,7 +72,7 @@ async fn test_success_handler_settings(db: Pool) -> anyhow::Result<()> .await?; let after_set = sqlx::query_scalar!( - r#"SELECT success_handler FROM workspace_settings WHERE workspace_id = 'test-workspace'"# + r#"SELECT success_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'"# ) .fetch_one(&db) .await?; @@ -91,7 +90,7 @@ async fn test_success_handler_settings(db: Pool) -> anyhow::Result<()> .await?; let after_remove = sqlx::query_scalar!( - r#"SELECT success_handler FROM workspace_settings WHERE workspace_id = 'test-workspace'"# + r#"SELECT success_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'"# ) .fetch_one(&db) .await?; @@ -152,7 +151,7 @@ export async function main(path: string, email: string, job_id: string, is_flow: sqlx::query!( r#" UPDATE workspace_settings - SET success_handler = 'script/f/test/success_handler' + SET success_handler = '{"path": "script/f/test/success_handler"}'::jsonb WHERE workspace_id = 'test-workspace' "# ) diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 006a9070c8..013280c7d3 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -2337,20 +2337,8 @@ paths: type: string teams_team_guid: type: string - auto_invite_domain: - type: string - auto_invite_operator: - type: boolean - auto_add: - type: boolean - auto_add_instance_groups: - type: array - items: - type: string - auto_add_instance_groups_roles: - type: object - additionalProperties: - type: string + auto_invite: + $ref: "#/components/schemas/AutoInviteConfig" plan: type: string customer_id: @@ -2362,15 +2350,9 @@ paths: ai_config: $ref: "#/components/schemas/AIConfig" error_handler: - type: string - error_handler_extra_args: - $ref: "#/components/schemas/ScriptArgs" - error_handler_muted_on_cancel: - type: boolean + $ref: "#/components/schemas/ErrorHandlerConfig" success_handler: - type: string - success_handler_extra_args: - $ref: "#/components/schemas/ScriptArgs" + $ref: "#/components/schemas/SuccessHandlerConfig" large_file_storage: $ref: "#/components/schemas/LargeFileStorage" ducklake: @@ -2391,8 +2373,6 @@ paths: type: string operator_settings: $ref: "#/components/schemas/OperatorSettings" - required: - - error_handler_muted_on_cancel /w/{workspace}/workspaces/get_deploy_to: get: @@ -3071,14 +3051,7 @@ paths: content: application/json: schema: - type: object - properties: - error_handler: - type: string - error_handler_extra_args: - $ref: "#/components/schemas/ScriptArgs" - error_handler_muted_on_cancel: - type: boolean + $ref: "#/components/schemas/EditErrorHandler" responses: "200": description: status @@ -3101,12 +3074,7 @@ paths: content: application/json: schema: - type: object - properties: - success_handler: - type: string - success_handler_extra_args: - $ref: "#/components/schemas/ScriptArgs" + $ref: "#/components/schemas/EditSuccessHandler" responses: "200": description: status @@ -16703,6 +16671,119 @@ components: # -- INLINE END -- # Do not change line above + AutoInviteConfig: + type: object + description: Configuration for auto-inviting users to the workspace + properties: + enabled: + type: boolean + default: false + domain: + type: string + operator: + type: boolean + default: false + description: If true, auto-invited users are added as operators. If false, they are added as developers. + mode: + type: string + enum: ["invite", "add"] + default: "invite" + instance_groups: + type: array + items: + type: string + instance_groups_roles: + type: object + additionalProperties: + type: string + + ErrorHandlerConfig: + type: object + description: Configuration for the workspace error handler + properties: + path: + type: string + description: Path to the error handler script or flow + extra_args: + $ref: "#/components/schemas/ScriptArgs" + muted_on_cancel: + type: boolean + default: false + muted_on_user_path: + type: boolean + default: false + + SuccessHandlerConfig: + type: object + description: Configuration for the workspace success handler + properties: + path: + type: string + description: Path to the success handler script or flow + extra_args: + $ref: "#/components/schemas/ScriptArgs" + + EditErrorHandler: + description: Request body for editing the workspace error handler. Accepts both new grouped format and legacy flat format for backward compatibility. + oneOf: + - $ref: "#/components/schemas/EditErrorHandlerNew" + - $ref: "#/components/schemas/EditErrorHandlerLegacy" + + EditErrorHandlerNew: + type: object + description: New grouped format for editing error handler + properties: + path: + type: string + description: Path to the error handler script or flow + extra_args: + $ref: "#/components/schemas/ScriptArgs" + muted_on_cancel: + type: boolean + default: false + muted_on_user_path: + type: boolean + default: false + + EditErrorHandlerLegacy: + type: object + description: Legacy flat format for editing error handler (deprecated, use new format) + properties: + error_handler: + type: string + description: Path to the error handler script or flow + error_handler_extra_args: + $ref: "#/components/schemas/ScriptArgs" + error_handler_muted_on_cancel: + type: boolean + default: false + + EditSuccessHandler: + description: Request body for editing the workspace success handler. Accepts both new grouped format and legacy flat format for backward compatibility. + oneOf: + - $ref: "#/components/schemas/EditSuccessHandlerNew" + - $ref: "#/components/schemas/EditSuccessHandlerLegacy" + + EditSuccessHandlerNew: + type: object + description: New grouped format for editing success handler + properties: + path: + type: string + description: Path to the success handler script or flow + extra_args: + $ref: "#/components/schemas/ScriptArgs" + + EditSuccessHandlerLegacy: + type: object + description: Legacy flat format for editing success handler (deprecated, use new format) + properties: + success_handler: + type: string + description: Path to the success handler script or flow + success_handler_extra_args: + $ref: "#/components/schemas/ScriptArgs" + VaultSettings: type: object required: diff --git a/backend/windmill-api/src/flows.rs b/backend/windmill-api/src/flows.rs index ba618c330a..e3168e4858 100644 --- a/backend/windmill-api/src/flows.rs +++ b/backend/windmill-api/src/flows.rs @@ -289,10 +289,10 @@ async fn toggle_workspace_error_handler( let error_handler_maybe: Option = sqlx::query_scalar!( r#" SELECT - error_handler - FROM - workspace_settings - WHERE + error_handler->>'path' + FROM + workspace_settings + WHERE workspace_id = $1 "#, w_id diff --git a/backend/windmill-api/src/groups.rs b/backend/windmill-api/src/groups.rs index b9681972b2..a13a6fbdba 100644 --- a/backend/windmill-api/src/groups.rs +++ b/backend/windmill-api/src/groups.rs @@ -682,9 +682,16 @@ async fn add_user_igroup( #[cfg(all(feature = "private", feature = "enterprise"))] { use crate::workspaces_ee::auto_add_user; - let workspaces = sqlx::query!("SELECT workspace_id, auto_add_instance_groups_roles FROM workspace_settings WHERE $1 = ANY(COALESCE(auto_add_instance_groups, '{}'))", &name).fetch_all(&mut *tx).await?; + let workspaces = sqlx::query!( + r#" + SELECT workspace_id, auto_invite->'instance_groups_roles' as instance_groups_roles + FROM workspace_settings + WHERE auto_invite->'instance_groups' ? $1 + "#, + &name + ).fetch_all(&mut *tx).await?; for ws in workspaces { - let role = ws.auto_add_instance_groups_roles.and_then(|r| r.get(&name).and_then(|v| v.as_str().map(String::from))).unwrap_or_else(|| "developer".to_string()); + let role = ws.instance_groups_roles.and_then(|r| r.get(&name).and_then(|v| v.as_str().map(String::from))).unwrap_or_else(|| "developer".to_string()); let (is_admin, is_operator) = match role.as_str() { "admin" => (true, false), "operator" => (false, true), _ => (false, false) }; auto_add_user(&email, &ws.workspace_id, &is_operator, &mut tx, &authed, Some(serde_json::json!({"source": "instance_group", "group": &name}))).await?; if is_admin { sqlx::query!("UPDATE usr SET is_admin = true WHERE workspace_id = $1 AND email = $2", &ws.workspace_id, &email).execute(&mut *tx).await?; } @@ -748,10 +755,10 @@ async fn list_igroups_with_workspaces(Extension(db): Extension) -> JsonResul ig.name as group_name, ws.workspace_id, w.name as workspace_name, - ws.auto_add_instance_groups_roles->ig.name as role + ws.auto_invite->'instance_groups_roles'->ig.name as role FROM instance_group ig - INNER JOIN workspace_settings ws ON ws.auto_add_instance_groups IS NOT NULL - AND ig.name = ANY(ws.auto_add_instance_groups) + INNER JOIN workspace_settings ws ON ws.auto_invite->'instance_groups' IS NOT NULL + AND ws.auto_invite->'instance_groups' ? ig.name INNER JOIN workspace w ON w.id = ws.workspace_id AND w.deleted = false ORDER BY ig.name, ws.workspace_id "# diff --git a/backend/windmill-api/src/scripts.rs b/backend/windmill-api/src/scripts.rs index 3135ba8d58..8b3c7ba5ea 100644 --- a/backend/windmill-api/src/scripts.rs +++ b/backend/windmill-api/src/scripts.rs @@ -1467,7 +1467,7 @@ async fn toggle_workspace_error_handler( let mut tx = user_db.begin(&authed).await?; let error_handler_maybe: Option = sqlx::query_scalar!( - "SELECT error_handler FROM workspace_settings WHERE workspace_id = $1", + "SELECT error_handler->>'path' FROM workspace_settings WHERE workspace_id = $1", w_id ) .fetch_optional(&mut *tx) diff --git a/backend/windmill-api/src/users.rs b/backend/windmill-api/src/users.rs index 10cf4cfddd..405a92de0b 100644 --- a/backend/windmill-api/src/users.rs +++ b/backend/windmill-api/src/users.rs @@ -1465,11 +1465,11 @@ async fn convert_user_to_group( r#" SELECT eig.igroup as group_name, - ws.auto_add_instance_groups_roles + ws.auto_invite->'instance_groups_roles' as instance_groups_roles FROM email_to_igroup eig INNER JOIN workspace_settings ws ON ws.workspace_id = $1 WHERE eig.email = $2 - AND eig.igroup = ANY(ws.auto_add_instance_groups) + AND ws.auto_invite->'instance_groups' ? eig.igroup "#, &w_id, &user_info.email @@ -1486,7 +1486,7 @@ async fn convert_user_to_group( // Determine the group with highest precedence (same logic as process_instance_group_auto_adds) let roles: std::collections::HashMap = - if let Some(roles_json) = &eligible_groups[0].auto_add_instance_groups_roles { + if let Some(roles_json) = &eligible_groups[0].instance_groups_roles { serde_json::from_value(roles_json.clone()).unwrap_or_default() } else { std::collections::HashMap::new() diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index 538e239982..27d10aaf0e 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -249,12 +249,6 @@ pub struct WorkspaceSettings { #[serde(skip_serializing_if = "Option::is_none")] pub slack_oauth_client_secret: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub auto_invite_domain: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub auto_invite_operator: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub auto_add: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub customer_id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub plan: Option, @@ -265,25 +259,15 @@ pub struct WorkspaceSettings { #[serde(skip_serializing_if = "Option::is_none")] pub ai_config: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub error_handler: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub error_handler_extra_args: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub error_handler_muted_on_cancel: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub success_handler: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub success_handler_extra_args: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub large_file_storage: Option, // effectively: DatasetsStorage + pub large_file_storage: Option, #[serde(skip_serializing_if = "Option::is_none")] pub ducklake: Option, #[serde(skip_serializing_if = "Option::is_none")] pub datatable: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub git_sync: Option, // effectively: WorkspaceGitSyncSettings + pub git_sync: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub deploy_ui: Option, // effectively: WorkspaceDeploymentUISettings + pub deploy_ui: Option, #[serde(skip_serializing_if = "Option::is_none")] pub default_app: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -296,13 +280,16 @@ pub struct WorkspaceSettings { pub operator_settings: Option, #[serde(skip_serializing_if = "Option::is_none")] pub git_app_installations: Option, + // Grouped config fields #[serde(skip_serializing_if = "Option::is_none")] - pub auto_add_instance_groups: Option>, + pub auto_invite: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub auto_add_instance_groups_roles: Option, + pub error_handler: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub success_handler: Option, } -// #[derive(sqlx::Type, Serialize, Deserialize, Debug)] +/// #[derive(sqlx::Type, Serialize, Deserialize, Debug)] // #[sqlx(type_name = "WORKSPACE_KEY_KIND", rename_all = "lowercase")] // pub enum WorkspaceKeyKind { // Cloud, @@ -440,19 +427,84 @@ pub struct NewWorkspaceUser { pub operator: bool, } +// New format for error handler (grouped) #[derive(Deserialize)] -pub struct EditErrorHandler { - pub error_handler: Option, - pub error_handler_extra_args: Option, - pub error_handler_muted_on_cancel: Option, +#[serde(deny_unknown_fields)] +pub struct EditErrorHandlerNew { + pub path: Option, + pub extra_args: Option, + #[serde(default)] + pub muted_on_cancel: bool, + #[serde(default)] + pub muted_on_user_path: bool, } +// Legacy format for error handler (flat fields from old CLI) #[derive(Deserialize)] -pub struct EditSuccessHandler { +pub struct EditErrorHandlerLegacy { + pub error_handler: Option, + pub error_handler_extra_args: Option, + #[serde(default)] + pub error_handler_muted_on_cancel: bool, +} + +// Accepts both old and new formats +#[derive(Deserialize)] +#[serde(untagged)] +pub enum EditErrorHandler { + New(EditErrorHandlerNew), + Legacy(EditErrorHandlerLegacy), +} + +impl EditErrorHandler { + pub fn into_normalized(self) -> EditErrorHandlerNew { + match self { + EditErrorHandler::New(new) => new, + EditErrorHandler::Legacy(legacy) => EditErrorHandlerNew { + path: legacy.error_handler, + extra_args: legacy.error_handler_extra_args, + muted_on_cancel: legacy.error_handler_muted_on_cancel, + muted_on_user_path: false, // Old format doesn't have this field + }, + } + } +} + +// New format for success handler (grouped) +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct EditSuccessHandlerNew { + pub path: Option, + pub extra_args: Option, +} + +// Legacy format for success handler (flat fields from old CLI) +#[derive(Deserialize)] +pub struct EditSuccessHandlerLegacy { pub success_handler: Option, pub success_handler_extra_args: Option, } +// Accepts both old and new formats +#[derive(Deserialize)] +#[serde(untagged)] +pub enum EditSuccessHandler { + New(EditSuccessHandlerNew), + Legacy(EditSuccessHandlerLegacy), +} + +impl EditSuccessHandler { + pub fn into_normalized(self) -> EditSuccessHandlerNew { + match self { + EditSuccessHandler::New(new) => new, + EditSuccessHandler::Legacy(legacy) => EditSuccessHandlerNew { + path: legacy.success_handler, + extra_args: legacy.success_handler_extra_args, + }, + } + } +} + lazy_static::lazy_static! { pub static ref EMAIL_REGEXP: Regex = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap(); } @@ -555,19 +607,11 @@ async fn get_settings( slack_email, slack_oauth_client_id, slack_oauth_client_secret, - auto_invite_domain, - auto_invite_operator, - auto_add, customer_id, plan, webhook, deploy_to, ai_config, - error_handler, - error_handler_extra_args, - error_handler_muted_on_cancel, - success_handler, - success_handler_extra_args, large_file_storage, datatable, ducklake, @@ -579,8 +623,9 @@ async fn get_settings( color, operator_settings, git_app_installations, - auto_add_instance_groups, - auto_add_instance_groups_roles + auto_invite, + error_handler, + success_handler FROM workspace_settings WHERE @@ -745,9 +790,8 @@ async fn get_slack_oauth_config( ) -> JsonResult { require_admin(authed.is_admin, &authed.username)?; - let settings = sqlx::query_as!( - WorkspaceSettings, - "SELECT * FROM workspace_settings WHERE workspace_id = $1", + let settings = sqlx::query!( + "SELECT slack_oauth_client_id, slack_oauth_client_secret FROM workspace_settings WHERE workspace_id = $1", &w_id ) .fetch_one(&db) @@ -2158,6 +2202,9 @@ async fn edit_error_handler( ) -> Result { require_admin(is_admin, &username)?; + // Normalize to new format (handles both old CLI and new CLI requests) + let ee = ee.into_normalized(); + let mut tx = db.begin().await?; sqlx::query_as!( @@ -2171,8 +2218,8 @@ async fn edit_error_handler( .execute(&mut *tx) .await?; - if let Some(error_handler) = &ee.error_handler { - match ee.error_handler_extra_args.as_ref() { + if let Some(path) = &ee.path { + match ee.extra_args.as_ref() { Some(extra_args) if extra_args.is_object() => { let Ok(email_recipients) = serde_json::from_value::>>( extra_args["email_recipients"].to_owned(), @@ -2196,41 +2243,34 @@ async fn edit_error_handler( None => {} _ => { return Err(Error::BadRequest( - "Field `error_handler_extra_args` expected to be JSON object".to_string(), + "Field `extra_args` expected to be JSON object".to_string(), )) } } + let mut error_handler = serde_json::json!({ + "path": path, + }); + if let Some(extra_args) = &ee.extra_args { + error_handler["extra_args"] = extra_args.clone(); + } + if ee.muted_on_cancel { + error_handler["muted_on_cancel"] = serde_json::json!(true); + } + if ee.muted_on_user_path { + error_handler["muted_on_user_path"] = serde_json::json!(true); + } + sqlx::query!( - r#" - UPDATE - workspace_settings - SET - error_handler = $1, - error_handler_extra_args = $2, - error_handler_muted_on_cancel = $3 - WHERE - workspace_id = $4 - "#, + "UPDATE workspace_settings SET error_handler = $1 WHERE workspace_id = $2", error_handler, - ee.error_handler_extra_args, - ee.error_handler_muted_on_cancel.unwrap_or(false), &w_id ) .execute(&mut *tx) .await?; } else { sqlx::query!( - r#" - UPDATE - workspace_settings - SET - error_handler = NULL, - error_handler_extra_args = NULL, - error_handler_muted_on_cancel = false - WHERE - workspace_id = $1 - "#, + "UPDATE workspace_settings SET error_handler = NULL WHERE workspace_id = $1", &w_id ) .execute(&mut *tx) @@ -2244,7 +2284,7 @@ async fn edit_error_handler( ActionKind::Update, &w_id, Some(&authed.email), - Some([("error_handler", &format!("{:?}", ee.error_handler)[..])].into()), + Some([("error_handler", &format!("{:?}", ee.path)[..])].into()), ) .await?; tx.commit().await?; @@ -2272,6 +2312,9 @@ async fn edit_success_handler( ) -> Result { require_admin(authed.is_admin, &authed.username)?; + // Normalize to new format (handles both old CLI and new CLI requests) + let es = es.into_normalized(); + let mut tx = db.begin().await?; sqlx::query_as!( @@ -2285,34 +2328,24 @@ async fn edit_success_handler( .execute(&mut *tx) .await?; - if let Some(success_handler) = &es.success_handler { + if let Some(path) = &es.path { + let mut success_handler = serde_json::json!({ + "path": path, + }); + if let Some(extra_args) = &es.extra_args { + success_handler["extra_args"] = extra_args.clone(); + } + sqlx::query!( - r#" - UPDATE - workspace_settings - SET - success_handler = $1, - success_handler_extra_args = $2 - WHERE - workspace_id = $3 - "#, + "UPDATE workspace_settings SET success_handler = $1 WHERE workspace_id = $2", success_handler, - es.success_handler_extra_args, &w_id ) .execute(&mut *tx) .await?; } else { sqlx::query!( - r#" - UPDATE - workspace_settings - SET - success_handler = NULL, - success_handler_extra_args = NULL - WHERE - workspace_id = $1 - "#, + "UPDATE workspace_settings SET success_handler = NULL WHERE workspace_id = $1", &w_id ) .execute(&mut *tx) @@ -2326,7 +2359,7 @@ async fn edit_success_handler( ActionKind::Update, &w_id, Some(&authed.email), - Some([("success_handler", &format!("{:?}", es.success_handler)[..])].into()), + Some([("success_handler", &format!("{:?}", es.path)[..])].into()), ) .await?; tx.commit().await?; diff --git a/backend/windmill-api/src/workspaces_export.rs b/backend/windmill-api/src/workspaces_export.rs index 84709466a3..9a730d1d44 100644 --- a/backend/windmill-api/src/workspaces_export.rs +++ b/backend/windmill-api/src/workspaces_export.rs @@ -177,6 +177,8 @@ pub(crate) struct ArchiveQueryParams { include_key: Option, include_workspace_dependencies: Option, default_ts: Option, + /// Settings format version: "v1" (default) returns legacy flat format, "v2" returns grouped format + settings_version: Option, } #[inline] @@ -253,12 +255,41 @@ struct SimplifiedGroup { admins: Vec, } +// V2 format: New grouped format #[derive(Serialize)] struct SimplifiedSettings { - // slack_team_id: Option, - // slack_name: Option, - // slack_command_script: Option, - // slack_email: Option, + #[serde(skip_serializing_if = "Option::is_none")] + auto_invite: Option, + #[serde(skip_serializing_if = "Option::is_none")] + webhook: Option, + #[serde(skip_serializing_if = "Option::is_none")] + deploy_to: Option, + #[serde(skip_serializing_if = "Option::is_none")] + error_handler: Option, + #[serde(skip_serializing_if = "Option::is_none")] + success_handler: Option, + #[serde(skip_serializing_if = "Option::is_none")] + ai_config: Option, + #[serde(skip_serializing_if = "Option::is_none")] + large_file_storage: Option, + #[serde(skip_serializing_if = "Option::is_none")] + git_sync: Option, + #[serde(skip_serializing_if = "Option::is_none")] + default_app: Option, + #[serde(skip_serializing_if = "Option::is_none")] + default_scripts: Option, + name: String, + #[serde(skip_serializing_if = "Option::is_none")] + mute_critical_alerts: Option, + #[serde(skip_serializing_if = "Option::is_none")] + color: Option, + #[serde(skip_serializing_if = "Option::is_none")] + operator_settings: Option, +} + +// V1 format: Legacy flat format for backward compatibility (matches main branch exactly) +#[derive(Serialize)] +struct SimplifiedSettingsLegacy { auto_invite_enabled: bool, auto_invite_as: String, auto_invite_mode: String, @@ -290,6 +321,25 @@ struct SimplifiedSettings { operator_settings: Option, } +// Internal struct for querying database +#[derive(sqlx::FromRow)] +struct SettingsRow { + auto_invite: Option, + webhook: Option, + deploy_to: Option, + error_handler: Option, + success_handler: Option, + ai_config: Option, + large_file_storage: Option, + git_sync: Option, + default_app: Option, + default_scripts: Option, + name: Option, + mute_critical_alerts: Option, + color: Option, + operator_settings: Option, +} + pub(crate) async fn tarball_workspace( authed: ApiAuthed, Extension(user_db): Extension, @@ -311,6 +361,7 @@ pub(crate) async fn tarball_workspace( include_key, include_workspace_dependencies, default_ts, + settings_version, }): Query, ) -> Result<([(HeaderName, String); 2], impl IntoResponse)> { // require_admin(authed.is_admin, &authed.username)?; @@ -843,41 +894,103 @@ pub(crate) async fn tarball_workspace( } if include_settings.unwrap_or(false) { - let settings = sqlx::query_as!( - SimplifiedSettings, + let row = sqlx::query_as::<_, SettingsRow>( r#"SELECT - -- slack_team_id, - -- slack_name, - -- slack_command_script, - -- CASE WHEN slack_email = 'missing@email.xyz' THEN NULL ELSE slack_email END AS slack_email, - auto_invite_domain IS NOT NULL AS "auto_invite_enabled!", - CASE WHEN auto_invite_operator IS TRUE THEN 'operator' ELSE 'developer' END AS "auto_invite_as!", - CASE WHEN auto_add IS TRUE THEN 'add' ELSE 'invite' END AS "auto_invite_mode!", + auto_invite, webhook, deploy_to, error_handler, + success_handler, ai_config, - error_handler_extra_args, - error_handler_muted_on_cancel, large_file_storage, git_sync, default_app, default_scripts, - workspace.name, + workspace.name as name, mute_critical_alerts, color, operator_settings FROM workspace_settings LEFT JOIN workspace ON workspace.id = workspace_settings.workspace_id WHERE workspace_id = $1"#, - &w_id - ).fetch_one(&mut *tx).await?; + ) + .bind(&w_id) + .fetch_one(&mut *tx) + .await?; - let settings_str = serde_json::to_value(settings) - .map(|v| serde_json::to_string_pretty(&v).ok()) - .ok() - .flatten() - .ok_or_else(|| Error::internal_err("Error serializing settings".to_string()))?; + // Use v2 format only if explicitly requested, otherwise use v1 (legacy) for backward compatibility + let settings_str = if settings_version.as_deref() == Some("v2") { + let settings = SimplifiedSettings { + auto_invite: row.auto_invite, + webhook: row.webhook, + deploy_to: row.deploy_to, + error_handler: row.error_handler, + success_handler: row.success_handler, + ai_config: row.ai_config, + large_file_storage: row.large_file_storage, + git_sync: row.git_sync, + default_app: row.default_app, + default_scripts: row.default_scripts, + name: row.name.clone().unwrap_or_default(), + mute_critical_alerts: row.mute_critical_alerts, + color: row.color.clone(), + operator_settings: row.operator_settings.clone(), + }; + serde_json::to_value(settings) + .map(|v| serde_json::to_string_pretty(&v).ok()) + .ok() + .flatten() + } else { + // V1 (legacy) format: convert JSONB to flat fields (matches main branch exactly) + let (auto_invite_enabled, auto_invite_as, auto_invite_mode) = + if let Some(ref ai) = row.auto_invite { + let enabled = ai.get("enabled").and_then(|v| v.as_bool()).unwrap_or(false); + let operator = ai.get("operator").and_then(|v| v.as_bool()).unwrap_or(false); + let mode = ai.get("mode").and_then(|v| v.as_str()).unwrap_or("invite"); + ( + enabled, + if operator { "operator".to_string() } else { "developer".to_string() }, + mode.to_string(), + ) + } else { + (false, "developer".to_string(), "invite".to_string()) + }; + + let (error_handler, error_handler_extra_args, error_handler_muted_on_cancel) = + if let Some(ref eh) = row.error_handler { + let path = eh.get("path").and_then(|v| v.as_str()).map(String::from); + let extra_args = eh.get("extra_args").cloned(); + let muted_on_cancel = eh.get("muted_on_cancel").and_then(|v| v.as_bool()).unwrap_or(false); + (path, extra_args, muted_on_cancel) + } else { + (None, None, false) + }; + + let settings = SimplifiedSettingsLegacy { + auto_invite_enabled, + auto_invite_as, + auto_invite_mode, + webhook: row.webhook, + deploy_to: row.deploy_to, + error_handler, + error_handler_extra_args, + error_handler_muted_on_cancel, + ai_config: row.ai_config, + large_file_storage: row.large_file_storage, + git_sync: row.git_sync, + default_app: row.default_app, + default_scripts: row.default_scripts, + name: row.name.unwrap_or_default(), + mute_critical_alerts: row.mute_critical_alerts, + color: row.color, + operator_settings: row.operator_settings, + }; + serde_json::to_value(settings) + .map(|v| serde_json::to_string_pretty(&v).ok()) + .ok() + .flatten() + } + .ok_or_else(|| Error::internal_err("Error serializing settings".to_string()))?; archive .write_to_archive(&settings_str, "settings.json") diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index 6806d3b76c..912c279254 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -777,8 +777,8 @@ lazy_static::lazy_static! { static ref RESTART_UNLESS_CANCELLED_CACHE: Cache<(i64, String), bool> = Cache::new(10000); // Cache for workspace error handler settings with 60s TTL - // Key: workspace_id, Value: (error_handler, error_handler_extra_args, error_handler_muted_on_cancel, expiry_timestamp) - static ref WORKSPACE_ERROR_HANDLER_CACHE: Cache, Option>>, bool, i64)> = Cache::new(1000); + // Key: workspace_id, Value: (error_handler, error_handler_extra_args, error_handler_muted_on_cancel, error_handler_muted_on_user_path, expiry_timestamp) + static ref WORKSPACE_ERROR_HANDLER_CACHE: Cache, Option>>, bool, bool, i64)> = Cache::new(1000); // Cache for workspace success handler settings with 60s TTL // Key: workspace_id, Value: (success_handler, success_handler_extra_args, expiry_timestamp) @@ -1521,12 +1521,12 @@ pub async fn report_error_to_workspace_handler_or_critical_side_channel( let w_id = &queued_job.workspace_id; let row_result = sqlx::query_as::<_, (Option, Option>>)>( r#" - SELECT - error_handler, - error_handler_extra_args - FROM - workspace_settings - WHERE + SELECT + error_handler->>'path', + (error_handler->'extra_args')::text::json + FROM + workspace_settings + WHERE workspace_id = $1 "#, ) @@ -1578,10 +1578,14 @@ pub async fn report_error_to_workspace_handler_or_critical_side_channel( async fn fetch_error_handler_from_db( db: &Pool, w_id: &str, -) -> Result<(Option, Option>>, bool), Error> { - sqlx::query_as::<_, (Option, Option>>, bool)>( +) -> Result<(Option, Option>>, bool, bool), Error> { + sqlx::query_as::<_, (Option, Option>>, Option, Option)>( r#" - SELECT error_handler, error_handler_extra_args, error_handler_muted_on_cancel + SELECT + error_handler->>'path', + (error_handler->'extra_args')::text::json, + (error_handler->>'muted_on_cancel')::boolean, + (error_handler->>'muted_on_user_path')::boolean FROM workspace_settings WHERE workspace_id = $1 "#, @@ -1590,6 +1594,9 @@ async fn fetch_error_handler_from_db( .fetch_optional(db) .await .context("fetching error handler info from workspace_settings")? + .map(|(path, extra_args, muted_on_cancel, muted_on_user_path)| { + (path, extra_args, muted_on_cancel.unwrap_or(false), muted_on_user_path.unwrap_or(false)) + }) .ok_or_else(|| Error::internal_err(format!("no workspace settings for id {w_id}"))) } @@ -1602,16 +1609,16 @@ pub async fn send_error_to_workspace_handler<'a, 'c, T: Serialize + Send + Sync> let w_id = &queued_job.workspace_id; let now = chrono::Utc::now().timestamp(); - let (error_handler, error_handler_extra_args, error_handler_muted_on_cancel) = + let (error_handler, error_handler_extra_args, error_handler_muted_on_cancel, error_handler_muted_on_user_path) = if let Some(cached) = WORKSPACE_ERROR_HANDLER_CACHE.get(w_id) { - if cached.3 > now { - (cached.0.clone(), cached.1.clone(), cached.2) + if cached.4 > now { + (cached.0.clone(), cached.1.clone(), cached.2, cached.3) } else { let row = fetch_error_handler_from_db(db, w_id).await?; let expiry = now + WORKSPACE_HANDLER_CACHE_TTL_SECONDS; WORKSPACE_ERROR_HANDLER_CACHE.insert( w_id.clone(), - (row.0.clone(), row.1.clone(), row.2, expiry), + (row.0.clone(), row.1.clone(), row.2, row.3, expiry), ); row } @@ -1620,7 +1627,7 @@ pub async fn send_error_to_workspace_handler<'a, 'c, T: Serialize + Send + Sync> let expiry = now + WORKSPACE_HANDLER_CACHE_TTL_SECONDS; WORKSPACE_ERROR_HANDLER_CACHE.insert( w_id.clone(), - (row.0.clone(), row.1.clone(), row.2, expiry), + (row.0.clone(), row.1.clone(), row.2, row.3, expiry), ); row }; @@ -1629,6 +1636,15 @@ pub async fn send_error_to_workspace_handler<'a, 'c, T: Serialize + Send + Sync> return Ok(()); } + // Skip error handler for scripts/flows starting with u/ + if error_handler_muted_on_user_path { + if let Some(ref path) = queued_job.runnable_path { + if path.starts_with("u/") { + return Ok(()); + } + } + } + if let Some(error_handler) = error_handler { let ws_error_handler_muted: Option = match queued_job.kind { JobKind::Script => { @@ -1685,7 +1701,9 @@ async fn fetch_success_handler_from_db( ) -> Result<(Option, Option>>), Error> { sqlx::query_as::<_, (Option, Option>>)>( r#" - SELECT success_handler, success_handler_extra_args + SELECT + success_handler->>'path', + (success_handler->'extra_args')::text::json FROM workspace_settings WHERE workspace_id = $1 "#, diff --git a/cli/src/commands/sync/pull.ts b/cli/src/commands/sync/pull.ts index 27bb04c3ad..607d789a6b 100644 --- a/cli/src/commands/sync/pull.ts +++ b/cli/src/commands/sync/pull.ts @@ -40,7 +40,7 @@ export async function downloadZip( }&skip_secrets=${skipSecrets ?? false}&include_schedules=${includeSchedules ?? false }&include_triggers=${includeTriggers ?? false}&include_users=${includeUsers ?? false }&include_groups=${includeGroups ?? false}&include_settings=${includeSettings ?? false - }&include_key=${includeKey ?? false}&include_workspace_dependencies=${includeWorkspaceDependenciesValue}&default_ts=${defaultTs ?? "bun"}&skip_resource_types=${skipResourceTypes ?? false}`; + }&include_key=${includeKey ?? false}&include_workspace_dependencies=${includeWorkspaceDependenciesValue}&default_ts=${defaultTs ?? "bun"}&skip_resource_types=${skipResourceTypes ?? false}&settings_version=v2`; const zipResponse = await fetch(url, { headers: requestHeaders, diff --git a/cli/src/core/settings.ts b/cli/src/core/settings.ts index d6fa528c9f..ed83f60658 100644 --- a/cli/src/core/settings.ts +++ b/cli/src/core/settings.ts @@ -8,19 +8,37 @@ import { deepEqual } from "../utils/utils.ts"; import { removeWorkerPrefix } from "../commands/worker-groups/worker-groups.ts"; import { decrypt, encrypt } from "../utils/local_encryption.ts"; +// New grouped config interfaces +export interface AutoInviteConfig { + enabled: boolean; + domain?: string; + operator: boolean; + mode: "invite" | "add"; + instance_groups?: string[]; + instance_groups_roles?: Record; +} + +export interface ErrorHandlerConfig { + path?: string; + extra_args?: any; + muted_on_cancel?: boolean; + muted_on_user_path?: boolean; +} + +export interface SuccessHandlerConfig { + path?: string; + extra_args?: any; +} + export interface SimplifiedSettings { - // slack_team_id?: string; - // slack_name?: string; - // slack_command_script?: string; - // slack_email?: string; - auto_invite_enabled: boolean; - auto_invite_as: string; - auto_invite_mode: string; + // Grouped format (current) + auto_invite?: AutoInviteConfig; + error_handler?: ErrorHandlerConfig; + success_handler?: SuccessHandlerConfig; + + // Other fields webhook?: string; deploy_to?: string; - error_handler?: string; - error_handler_extra_args?: any; - error_handler_muted_on_cancel?: boolean; ai_config?: AIConfig; large_file_storage?: any; git_sync?: any; @@ -32,6 +50,84 @@ export interface SimplifiedSettings { operator_settings?: any; } +// Legacy settings interface for reading old settings.yaml files +interface LegacySimplifiedSettings { + auto_invite_enabled?: boolean; + auto_invite_as?: string; + auto_invite_mode?: string; + error_handler?: string; + error_handler_extra_args?: any; + error_handler_muted_on_cancel?: boolean; + success_handler?: string; + success_handler_extra_args?: any; + // Other fields same as SimplifiedSettings + webhook?: string; + deploy_to?: string; + ai_config?: AIConfig; + large_file_storage?: any; + git_sync?: any; + default_app?: string; + default_scripts?: any; + name?: string; + mute_critical_alerts?: boolean; + color?: string; + operator_settings?: any; +} + +// Helper to convert legacy flat settings to new grouped format +export function migrateToGroupedFormat(settings: any): SimplifiedSettings { + const result: SimplifiedSettings = { name: settings.name ?? "" }; + + // Copy non-legacy fields + if (settings.webhook !== undefined) result.webhook = settings.webhook; + if (settings.deploy_to !== undefined) result.deploy_to = settings.deploy_to; + if (settings.ai_config !== undefined) result.ai_config = settings.ai_config; + if (settings.large_file_storage !== undefined) result.large_file_storage = settings.large_file_storage; + if (settings.git_sync !== undefined) result.git_sync = settings.git_sync; + if (settings.default_app !== undefined) result.default_app = settings.default_app; + if (settings.default_scripts !== undefined) result.default_scripts = settings.default_scripts; + if (settings.mute_critical_alerts !== undefined) result.mute_critical_alerts = settings.mute_critical_alerts; + if (settings.color !== undefined) result.color = settings.color; + if (settings.operator_settings !== undefined) result.operator_settings = settings.operator_settings; + + // Handle auto_invite: check if already grouped or needs migration + if (settings.auto_invite && typeof settings.auto_invite === "object") { + result.auto_invite = settings.auto_invite; + } else if (settings.auto_invite_enabled !== undefined) { + // Legacy format + result.auto_invite = { + enabled: settings.auto_invite_enabled, + operator: settings.auto_invite_as === "operator", + mode: (settings.auto_invite_mode as "invite" | "add") ?? "invite", + }; + } + + // Handle error_handler: check if already grouped or needs migration + if (settings.error_handler && typeof settings.error_handler === "object") { + result.error_handler = settings.error_handler; + } else if (typeof settings.error_handler === "string") { + // Legacy format (error_handler was a string path) + result.error_handler = { + path: settings.error_handler, + extra_args: settings.error_handler_extra_args, + muted_on_cancel: settings.error_handler_muted_on_cancel ?? false, + }; + } + + // Handle success_handler: check if already grouped or needs migration + if (settings.success_handler && typeof settings.success_handler === "object") { + result.success_handler = settings.success_handler; + } else if (typeof settings.success_handler === "string") { + // Legacy format (success_handler was a string path) + result.success_handler = { + path: settings.success_handler, + extra_args: settings.success_handler_extra_args, + }; + } + + return result; +} + const INSTANCE_SETTINGS_PATH = "instance_settings.yaml"; let instanceSettingsPath = INSTANCE_SETTINGS_PATH; async function checkInstanceSettingsPath(opts: InstanceSyncOptions) { @@ -52,8 +148,11 @@ export async function pushWorkspaceSettings( workspace: string, _path: string, settings: SimplifiedSettings | undefined, - localSettings: SimplifiedSettings + localSettings: SimplifiedSettings | any ) { + // Migrate local settings from legacy format if needed + localSettings = migrateToGroupedFormat(localSettings); + try { const remoteSettings = await wmill.getSettings({ workspace, @@ -63,22 +162,13 @@ export async function pushWorkspaceSettings( workspace, }); + // Build settings from remote (now using grouped format) settings = { - // slack_team_id: remoteSettings.slack_team_id, - // slack_name: remoteSettings.slack_name, - // slack_command_script: remoteSettings.slack_command_script, - // slack_email: remoteSettings.slack_email, - auto_invite_enabled: remoteSettings.auto_invite_domain !== null, - auto_invite_as: remoteSettings.auto_invite_operator - ? "operator" - : "developer", - auto_invite_mode: remoteSettings.auto_add ? "add" : "invite", + auto_invite: remoteSettings.auto_invite as AutoInviteConfig | undefined, + error_handler: remoteSettings.error_handler as ErrorHandlerConfig | undefined, + success_handler: remoteSettings.success_handler as SuccessHandlerConfig | undefined, webhook: remoteSettings.webhook, deploy_to: remoteSettings.deploy_to, - error_handler: remoteSettings.error_handler, - error_handler_extra_args: remoteSettings.error_handler_extra_args, - error_handler_muted_on_cancel: - remoteSettings.error_handler_muted_on_cancel ?? false, ai_config: remoteSettings.ai_config, large_file_storage: remoteSettings.large_file_storage, git_sync: remoteSettings.git_sync, @@ -98,8 +188,9 @@ export async function pushWorkspaceSettings( return; } log.debug(`Workspace settings are not up-to-date, updating...`); + if (localSettings.webhook !== settings.webhook) { - log.debug(`Updateing webhook...`); + log.debug(`Updating webhook...`); await wmill.editWebhook({ workspace, requestBody: { @@ -107,32 +198,25 @@ export async function pushWorkspaceSettings( }, }); } - if ( - localSettings.auto_invite_as !== settings.auto_invite_as || - localSettings.auto_invite_enabled !== settings.auto_invite_enabled || - localSettings.auto_invite_mode !== settings.auto_invite_mode - ) { + + // Handle auto_invite using grouped format + if (!deepEqual(localSettings.auto_invite, settings.auto_invite)) { log.debug(`Updating auto invite...`); - if (!["operator", "developer"].includes(settings.auto_invite_as)) { + const localAutoInvite = localSettings.auto_invite; + if (localAutoInvite?.mode && !["add", "invite"].includes(localAutoInvite.mode)) { throw new Error( - `Invalid value for auto_invite_as. Valid values are "operator" and "developer"` - ); - } - - if (!["add", "invite"].includes(settings.auto_invite_mode)) { - throw new Error( - `Invalid value for auto_invite_mode. Valid values are "invite" and "add"` + `Invalid value for auto_invite.mode. Valid values are "invite" and "add"` ); } try { await wmill.editAutoInvite({ workspace, - requestBody: localSettings.auto_invite_enabled + requestBody: localAutoInvite?.enabled ? { - operator: localSettings.auto_invite_as === "operator", + operator: localAutoInvite.operator ?? false, invite_all: true, - auto_add: localSettings.auto_invite_mode === "add", + auto_add: localAutoInvite.mode === "add", } : {}, }); @@ -143,16 +227,17 @@ export async function pushWorkspaceSettings( ); await wmill.editAutoInvite({ workspace, - requestBody: localSettings.auto_invite_enabled + requestBody: localAutoInvite?.enabled ? { - operator: localSettings.auto_invite_as === "operator", + operator: localAutoInvite.operator ?? false, invite_all: false, - auto_add: localSettings.auto_invite_mode === "add", + auto_add: localAutoInvite.mode === "add", } : {}, }); } } + if (!deepEqual(localSettings.ai_config, settings.ai_config)) { log.debug(`Updating copilot settings...`); await wmill.editCopilotConfig({ @@ -160,26 +245,35 @@ export async function pushWorkspaceSettings( requestBody: localSettings.ai_config ?? {}, }); } - if ( - localSettings.error_handler != settings.error_handler || - !deepEqual( - localSettings.error_handler_extra_args, - settings.error_handler_extra_args - ) || - (localSettings.error_handler_muted_on_cancel ?? false) != - (settings.error_handler_muted_on_cancel ?? false) - ) { + + // Handle error_handler using grouped format + if (!deepEqual(localSettings.error_handler, settings.error_handler)) { log.debug(`Updating error handler...`); + const localErrorHandler = localSettings.error_handler; await wmill.editErrorHandler({ workspace, requestBody: { - error_handler: localSettings.error_handler, - error_handler_extra_args: localSettings.error_handler_extra_args, - error_handler_muted_on_cancel: - localSettings.error_handler_muted_on_cancel ?? false, + path: localErrorHandler?.path, + extra_args: localErrorHandler?.extra_args, + muted_on_cancel: localErrorHandler?.muted_on_cancel ?? false, + muted_on_user_path: localErrorHandler?.muted_on_user_path ?? false, }, }); } + + // Handle success_handler using grouped format + if (!deepEqual(localSettings.success_handler, settings.success_handler)) { + log.debug(`Updating success handler...`); + const localSuccessHandler = localSettings.success_handler; + await wmill.editSuccessHandler({ + workspace, + requestBody: { + path: localSuccessHandler?.path, + extra_args: localSuccessHandler?.extra_args, + }, + }); + } + if (localSettings.deploy_to != settings.deploy_to) { log.debug(`Updating deploy to...`); await wmill.editDeployTo({ @@ -189,6 +283,7 @@ export async function pushWorkspaceSettings( }, }); } + if ( !deepEqual(localSettings.large_file_storage, settings.large_file_storage) ) { @@ -200,6 +295,7 @@ export async function pushWorkspaceSettings( }, }); } + if (!deepEqual(localSettings.git_sync, settings.git_sync)) { log.debug(`Updating git sync...`); await wmill.editWorkspaceGitSyncConfig({ @@ -209,6 +305,7 @@ export async function pushWorkspaceSettings( }, }); } + if (!deepEqual(localSettings.default_scripts, settings.default_scripts)) { log.debug(`Updating default scripts...`); await wmill.editDefaultScripts({ @@ -216,6 +313,7 @@ export async function pushWorkspaceSettings( requestBody: localSettings.default_scripts, }); } + if (localSettings.default_app != settings.default_app) { log.debug(`Updating default app...`); await wmill.editWorkspaceDefaultApp({ @@ -256,7 +354,7 @@ export async function pushWorkspaceSettings( }); } - if (localSettings.operator_settings != settings.operator_settings) { + if (!deepEqual(localSettings.operator_settings, settings.operator_settings)) { log.debug(`Updating operator settings...`); await wmill.updateOperatorSettings({ workspace, diff --git a/frontend/src/lib/components/settings/WorkspaceUserSettings.svelte b/frontend/src/lib/components/settings/WorkspaceUserSettings.svelte index 9f4064d579..ab0d7bc788 100644 --- a/frontend/src/lib/components/settings/WorkspaceUserSettings.svelte +++ b/frontend/src/lib/components/settings/WorkspaceUserSettings.svelte @@ -92,11 +92,19 @@ async function loadSettings(): Promise { const settings = await WorkspaceService.getSettings({ workspace: $workspaceStore! }) - auto_invite_domain = settings.auto_invite_domain - operatorOnly = settings.auto_invite_operator - autoAdd = settings.auto_add - autoAddInstanceGroups = settings.auto_add_instance_groups || [] - autoAddInstanceGroupsRoles = settings.auto_add_instance_groups_roles || {} + const autoInvite = settings.auto_invite as { + enabled?: boolean + domain?: string + operator?: boolean + mode?: string + instance_groups?: string[] + instance_groups_roles?: Record + } | undefined + auto_invite_domain = autoInvite?.enabled ? (autoInvite?.domain ?? '*') : undefined + operatorOnly = autoInvite?.operator ?? false + autoAdd = autoInvite?.mode === 'add' + autoAddInstanceGroups = autoInvite?.instance_groups || [] + autoAddInstanceGroupsRoles = autoInvite?.instance_groups_roles || {} } let getUsagePromise: CancelablePromise | undefined = undefined diff --git a/frontend/src/routes/(root)/(logged)/workspace_settings/+page.svelte b/frontend/src/routes/(root)/(logged)/workspace_settings/+page.svelte index b99f4f5c76..81219f13ab 100644 --- a/frontend/src/routes/(root)/(logged)/workspace_settings/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/workspace_settings/+page.svelte @@ -98,6 +98,7 @@ let errorHandlerItemKind: 'flow' | 'script' = $state('script') let errorHandlerExtraArgs: Record = $state({}) let errorHandlerMutedOnCancel: boolean | undefined = $state(undefined) + let errorHandlerMutedOnUserPath: boolean | undefined = $state(undefined) let successHandlerScriptPath: string | undefined = $state(undefined) let criticalAlertUIMuted: boolean | undefined = $state(undefined) let initialCriticalAlertUIMuted: boolean | undefined = $state(undefined) @@ -335,11 +336,14 @@ initialCodeCompletionModel = codeCompletionModel initialCustomPrompts = clone(customPrompts) initialMaxTokensPerModel = clone(maxTokensPerModel) - errorHandlerItemKind = settings.error_handler - ? (settings.error_handler.split('/')[0] as 'flow' | 'script') + const errorHandler = settings.error_handler as { path?: string; extra_args?: any; muted_on_cancel?: boolean; muted_on_user_path?: boolean } | undefined + const errorHandlerPath = errorHandler?.path ?? '' + errorHandlerItemKind = errorHandlerPath + ? (errorHandlerPath.split('/')[0] as 'flow' | 'script') : 'script' - errorHandlerScriptPath = (settings.error_handler ?? '').split('/').slice(1).join('/') - errorHandlerMutedOnCancel = settings.error_handler_muted_on_cancel + errorHandlerScriptPath = errorHandlerPath.split('/').slice(1).join('/') + errorHandlerMutedOnCancel = errorHandler?.muted_on_cancel + errorHandlerMutedOnUserPath = errorHandler?.muted_on_user_path criticalAlertUIMuted = settings.mute_critical_alerts initialCriticalAlertUIMuted = settings.mute_critical_alerts if (emptyString($enterpriseLicense)) { @@ -347,8 +351,9 @@ } else { errorHandlerSelected = getHandlerType(errorHandlerScriptPath) } - errorHandlerExtraArgs = settings.error_handler_extra_args ?? {} - successHandlerScriptPath = (settings.success_handler ?? '').split('/').slice(1).join('/') + errorHandlerExtraArgs = errorHandler?.extra_args ?? {} + const successHandler = settings.success_handler as { path?: string; extra_args?: any } | undefined + successHandlerScriptPath = (successHandler?.path ?? '').split('/').slice(1).join('/') workspaceDefaultAppPath = settings.default_app s3ResourceSettings = convertBackendSettingsToFrontendSettings( @@ -498,9 +503,10 @@ await WorkspaceService.editErrorHandler({ workspace: $workspaceStore!, requestBody: { - error_handler: `${errorHandlerItemKind}/${errorHandlerScriptPath}`, - error_handler_extra_args: errorHandlerExtraArgs, - error_handler_muted_on_cancel: errorHandlerMutedOnCancel + path: `${errorHandlerItemKind}/${errorHandlerScriptPath}`, + extra_args: errorHandlerExtraArgs, + muted_on_cancel: errorHandlerMutedOnCancel, + muted_on_user_path: errorHandlerMutedOnUserPath } }) sendUserToast(`workspace error handler set to ${errorHandlerScriptPath}`) @@ -508,9 +514,10 @@ await WorkspaceService.editErrorHandler({ workspace: $workspaceStore!, requestBody: { - error_handler: undefined, - error_handler_extra_args: undefined, - error_handler_muted_on_cancel: undefined + path: undefined, + extra_args: undefined, + muted_on_cancel: undefined, + muted_on_user_path: undefined } }) sendUserToast(`workspace error handler removed`) @@ -522,7 +529,7 @@ await WorkspaceService.editSuccessHandler({ workspace: $workspaceStore!, requestBody: { - success_handler: `script/${successHandlerScriptPath}` + path: `script/${successHandlerScriptPath}` } }) sendUserToast(`workspace success handler set to ${successHandlerScriptPath}`) @@ -530,7 +537,7 @@ await WorkspaceService.editSuccessHandler({ workspace: $workspaceStore!, requestBody: { - success_handler: undefined + path: undefined } }) sendUserToast(`workspace success handler removed`) @@ -1156,6 +1163,14 @@ bind:checked={errorHandlerMutedOnCancel} options={{ right: 'Do not run error handler for canceled jobs' }} /> +