diff --git a/.env b/.env index d4a48661cd..da41f78d5c 100644 --- a/.env +++ b/.env @@ -7,3 +7,7 @@ WM_IMAGE=ghcr.io/windmill-labs/windmill:main # To use another port than :80, setup the Caddyfile and the caddy section of the docker-compose to your needs: https://caddyserver.com/docs/getting-started # To have caddy take care of automatic TLS + +# To rotate logs, set the following variables: +#LOG_MAX_SIZE=10m +#LOG_MAX_FILE=3 diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index f95dee00da..b48ad1c990 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -162,6 +162,7 @@ jobs: -c https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/suite_config.json --workers 4 + --factor 3 - name: Save benchmark results uses: actions/upload-artifact@v4 with: @@ -281,6 +282,7 @@ jobs: -c https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/suite_config.json --workers 8 + --factor 3 - name: Save benchmark results uses: actions/upload-artifact@v4 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index fc17ef924d..458b3a19ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,117 @@ # Changelog +## [1.463.5](https://github.com/windmill-labs/windmill/compare/v1.463.4...v1.463.5) (2025-02-18) + + +### Bug Fixes + +* fix teams cleanup preventing start ([1b46e0f](https://github.com/windmill-labs/windmill/commit/1b46e0f08426497d549cf5007c93981df9ab41e5)) + + +## [1.463.4](https://github.com/windmill-labs/windmill/compare/v1.463.3...v1.463.4) (2025-02-17) + + +### Bug Fixes + +* improve queue job indices for faster performances ([9530826](https://github.com/windmill-labs/windmill/commit/953082681e2c4fd71d5ac1acf372265ccc72297b)) +* improve teams settings in workspace settings ([#5316](https://github.com/windmill-labs/windmill/issues/5316)) ([935b5b7](https://github.com/windmill-labs/windmill/commit/935b5b799636c0f02597315837268d4a76f6709a)) + +## [1.463.3](https://github.com/windmill-labs/windmill/compare/v1.463.2...v1.463.3) (2025-02-17) + + +### Bug Fixes + +* windmill_admin has implicit bypass rls on v2_job even if role not set ([0208f53](https://github.com/windmill-labs/windmill/commit/0208f53541473aa51bed0e15d938def3d4530e3f)) + +## [1.463.2](https://github.com/windmill-labs/windmill/compare/v1.463.1...v1.463.2) (2025-02-16) + + +### Bug Fixes + +* show skipped flows as success ([#5304](https://github.com/windmill-labs/windmill/issues/5304)) ([062e6bc](https://github.com/windmill-labs/windmill/commit/062e6bc161b56215cb081209d37ad8e0cbd1dd99)) + +## [1.463.1](https://github.com/windmill-labs/windmill/compare/v1.463.0...v1.463.1) (2025-02-15) + + +### Bug Fixes + +* not able to filter runs by schedule ([#5302](https://github.com/windmill-labs/windmill/issues/5302)) ([53f47bc](https://github.com/windmill-labs/windmill/commit/53f47bcfc84ed747b55d3a7d84ccf13ff1c43c97)) + +## [1.463.0](https://github.com/windmill-labs/windmill/compare/v1.462.1...v1.463.0) (2025-02-14) + + +### Features + +* adding docker log rotation by default in docker compose ([#5295](https://github.com/windmill-labs/windmill/issues/5295)) ([dad829a](https://github.com/windmill-labs/windmill/commit/dad829adf4bff97e998f7d18e0bbafb8497d4198)) +* parse script for preprocessor/no_main_func on deploy ([#5292](https://github.com/windmill-labs/windmill/issues/5292)) ([28558e6](https://github.com/windmill-labs/windmill/commit/28558e674f60fef1b165a79c039b1b450759d500)) + + +### Bug Fixes + +* display branch chosen even if emoty branch ([77a8eed](https://github.com/windmill-labs/windmill/commit/77a8eedc96171e9f84463407bdc5aec9b7b10d62)) +* improve handling of empty branches and loops ([e7d4582](https://github.com/windmill-labs/windmill/commit/e7d458278969897aa7312dcd20a8091aaad772d7)) +* improve runs page load time ([266f820](https://github.com/windmill-labs/windmill/commit/266f82046ad287163d24910902393cd63156ca1d)) +* static website serving ([#5298](https://github.com/windmill-labs/windmill/issues/5298)) ([41eecc1](https://github.com/windmill-labs/windmill/commit/41eecc1437301bea557fb467cc48b502162de419)) +* users should be able to see their own jobs ([9ccadb6](https://github.com/windmill-labs/windmill/commit/9ccadb6085498119bdfcc172d52c7fce1eb3336e)) + +## [1.462.3](https://github.com/windmill-labs/windmill/compare/v1.462.1...v1.462.2) (2025-02-14) + + +### Bug Fixes + +* users should be able to see their own jobs ([9ccadb6](https://github.com/windmill-labs/windmill/commit/9ccadb6085498119bdfcc172d52c7fce1eb3336e)) + +## [1.462.2](https://github.com/windmill-labs/windmill/compare/v1.462.1...v1.462.2) (2025-02-14) + + +### Bug Fixes + +* display branch chosen even if emoty branch ([77a8eed](https://github.com/windmill-labs/windmill/commit/77a8eedc96171e9f84463407bdc5aec9b7b10d62)) +* improve handling of empty branches and loops ([e7d4582](https://github.com/windmill-labs/windmill/commit/e7d458278969897aa7312dcd20a8091aaad772d7)) + +## [1.462.1](https://github.com/windmill-labs/windmill/compare/v1.462.0...v1.462.1) (2025-02-14) + + +### Bug Fixes + +* ai_models in workspace_settings is now optional ([470d80e](https://github.com/windmill-labs/windmill/commit/470d80e219f3b8a3fc3f56802d0eaeffbb1d415f)) + +## [1.462.0](https://github.com/windmill-labs/windmill/compare/v1.461.1...v1.462.0) (2025-02-13) + + +### Features + +* teams workspace scripts ([#5238](https://github.com/windmill-labs/windmill/issues/5238)) ([149d5fb](https://github.com/windmill-labs/windmill/commit/149d5fb3e1d7c89a6005aa34ef34fa57657f507b)) + + +### Bug Fixes + +* **bun:** remove unecessary buntar in a bun bundle world ([1be335f](https://github.com/windmill-labs/windmill/commit/1be335f042727bbb33b5f515433b65c54bf841fe)) +* **bun:** remove unecessary buntar in a bun bundle world ([fe92211](https://github.com/windmill-labs/windmill/commit/fe922114a74b1757c37f7f7b76adb3aed1ffccc4)) +* **cli:** support lock in wmill dev ([dd695b4](https://github.com/windmill-labs/windmill/commit/dd695b40f41decdf9f2f3d6918d860249661fb36)) +* populate teams channel on initial load ([#5284](https://github.com/windmill-labs/windmill/issues/5284)) ([2ea3bde](https://github.com/windmill-labs/windmill/commit/2ea3bdec2d7a65f8ceeea84f9e677fb4d2c5e0f3)) + +## [1.461.1](https://github.com/windmill-labs/windmill/compare/v1.461.0...v1.461.1) (2025-02-13) + + +### Bug Fixes + +* **cli:** fix nits preventing release ([6fb8f7b](https://github.com/windmill-labs/windmill/commit/6fb8f7b45dd85fdf5edc5ca3948f767eb0a39629)) + +## [1.461.0](https://github.com/windmill-labs/windmill/compare/v1.460.1...v1.461.0) (2025-02-13) + + +### Features + +* **cli:** wmill dev works with flows ([956a5ac](https://github.com/windmill-labs/windmill/commit/956a5ac68236df1c1f9ea4facd7ad237457427cf)) + + +### Bug Fixes + +* **backend:** improve schedule queries plan to leverage indices better for performance ([#5273](https://github.com/windmill-labs/windmill/issues/5273)) ([bf20651](https://github.com/windmill-labs/windmill/commit/bf206515e8653bbe431e106277b72082e0c9e388)) +* better handling of null pre-processor return values ([2015e79](https://github.com/windmill-labs/windmill/commit/2015e79ff09293cafb799f4049de35f786059831)) +* remove variable pickers in app forms ([055c336](https://github.com/windmill-labs/windmill/commit/055c3367b7afd06a9c789d17fb29bf1d195055bc)) + ## [1.460.1](https://github.com/windmill-labs/windmill/compare/v1.460.0...v1.460.1) (2025-02-12) diff --git a/backend/.sqlx/query-1730f39fd1793d45fbb41b21389c61296a3ff7489ae12f52a19f9543173ac597.json b/backend/.sqlx/query-08f288d2781d823e109a9e5b8848234ca7d1efeee9661f3901f298da375e73f7.json similarity index 86% rename from backend/.sqlx/query-1730f39fd1793d45fbb41b21389c61296a3ff7489ae12f52a19f9543173ac597.json rename to backend/.sqlx/query-08f288d2781d823e109a9e5b8848234ca7d1efeee9661f3901f298da375e73f7.json index 03da2cee85..2be39fce26 100644 --- a/backend/.sqlx/query-1730f39fd1793d45fbb41b21389c61296a3ff7489ae12f52a19f9543173ac597.json +++ b/backend/.sqlx/query-08f288d2781d823e109a9e5b8848234ca7d1efeee9661f3901f298da375e73f7.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT * FROM workspace_settings WHERE workspace_id = $1", + "query": "SELECT * FROM workspace_settings WHERE teams_team_id = $1 AND teams_command_script IS NOT NULL", "describe": { "columns": [ { @@ -130,11 +130,26 @@ }, { "ordinal": 25, + "name": "teams_command_script", + "type_info": "Text" + }, + { + "ordinal": 26, + "name": "teams_team_id", + "type_info": "Text" + }, + { + "ordinal": 27, + "name": "teams_team_name", + "type_info": "Text" + }, + { + "ordinal": 28, "name": "ai_models", "type_info": "VarcharArray" }, { - "ordinal": 26, + "ordinal": 29, "name": "code_completion_model", "type_info": "Varchar" } @@ -170,9 +185,12 @@ true, true, true, + true, + true, + true, false, true ] }, - "hash": "1730f39fd1793d45fbb41b21389c61296a3ff7489ae12f52a19f9543173ac597" + "hash": "08f288d2781d823e109a9e5b8848234ca7d1efeee9661f3901f298da375e73f7" } diff --git a/backend/.sqlx/query-19cc8499f682ec34d54bc4f694cb281a9bd7f5431c646c6268513751fff95395.json b/backend/.sqlx/query-0cb0e912bc942af2b1ef784455f3f073a79e300f3dd48f14122d1782eee663cd.json similarity index 72% rename from backend/.sqlx/query-19cc8499f682ec34d54bc4f694cb281a9bd7f5431c646c6268513751fff95395.json rename to backend/.sqlx/query-0cb0e912bc942af2b1ef784455f3f073a79e300f3dd48f14122d1782eee663cd.json index 0c379a7bdb..0a9e91b206 100644 --- a/backend/.sqlx/query-19cc8499f682ec34d54bc4f694cb281a9bd7f5431c646c6268513751fff95395.json +++ b/backend/.sqlx/query-0cb0e912bc942af2b1ef784455f3f073a79e300f3dd48f14122d1782eee663cd.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT coalesce(COUNT(*) FILTER(WHERE suspend = 0 AND running = false), 0) as \"database_length!\", coalesce(COUNT(*) FILTER(WHERE suspend > 0), 0) as \"suspended!\" FROM v2_as_queue WHERE (workspace_id = $1 OR $2) AND scheduled_for <= now()", + "query": "SELECT coalesce(COUNT(*) FILTER(WHERE suspend = 0 AND running = false), 0) as \"database_length!\", coalesce(COUNT(*) FILTER(WHERE suspend > 0), 0) as \"suspended!\" FROM v2_as_queue WHERE (workspace_id = $1 OR $2) AND scheduled_for <= now() AND ($3::text[] IS NULL OR tag = ANY($3))", "describe": { "columns": [ { @@ -17,7 +17,8 @@ "parameters": { "Left": [ "Text", - "Bool" + "Bool", + "TextArray" ] }, "nullable": [ @@ -25,5 +26,5 @@ null ] }, - "hash": "19cc8499f682ec34d54bc4f694cb281a9bd7f5431c646c6268513751fff95395" + "hash": "0cb0e912bc942af2b1ef784455f3f073a79e300f3dd48f14122d1782eee663cd" } diff --git a/backend/.sqlx/query-0efb16cbf130ec6e9922ecc82a95b252449bd569df374e40ce8820fc3d75a0f0.json b/backend/.sqlx/query-0efb16cbf130ec6e9922ecc82a95b252449bd569df374e40ce8820fc3d75a0f0.json new file mode 100644 index 0000000000..2f17b5e8db --- /dev/null +++ b/backend/.sqlx/query-0efb16cbf130ec6e9922ecc82a95b252449bd569df374e40ce8820fc3d75a0f0.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "DROP INDEX CONCURRENTLY IF EXISTS queue_sort", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "0efb16cbf130ec6e9922ecc82a95b252449bd569df374e40ce8820fc3d75a0f0" +} diff --git a/backend/.sqlx/query-1ace9bdcde90fd2261fd64344a2d9474b17887711128dbb2ef15d247d50686b0.json b/backend/.sqlx/query-1ace9bdcde90fd2261fd64344a2d9474b17887711128dbb2ef15d247d50686b0.json new file mode 100644 index 0000000000..9a8ef973a4 --- /dev/null +++ b/backend/.sqlx/query-1ace9bdcde90fd2261fd64344a2d9474b17887711128dbb2ef15d247d50686b0.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT teams_team_id FROM workspace_settings WHERE workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "teams_team_id", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + true + ] + }, + "hash": "1ace9bdcde90fd2261fd64344a2d9474b17887711128dbb2ef15d247d50686b0" +} diff --git a/backend/.sqlx/query-23c37d36e16251763fabf194e41de63612a7506cc0671b0eb83e528c1c839db4.json b/backend/.sqlx/query-23c37d36e16251763fabf194e41de63612a7506cc0671b0eb83e528c1c839db4.json new file mode 100644 index 0000000000..917540aec4 --- /dev/null +++ b/backend/.sqlx/query-23c37d36e16251763fabf194e41de63612a7506cc0671b0eb83e528c1c839db4.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_settings\n SET teams_team_id = null, teams_team_name = null WHERE workspace_id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "23c37d36e16251763fabf194e41de63612a7506cc0671b0eb83e528c1c839db4" +} diff --git a/backend/.sqlx/query-30216cf02e972f961b7cc6054050fdc984be118df1ad68f7263c84e058bb1266.json b/backend/.sqlx/query-30216cf02e972f961b7cc6054050fdc984be118df1ad68f7263c84e058bb1266.json new file mode 100644 index 0000000000..3603ed5c5b --- /dev/null +++ b/backend/.sqlx/query-30216cf02e972f961b7cc6054050fdc984be118df1ad68f7263c84e058bb1266.json @@ -0,0 +1,24 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job_status\n SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2)\n WHERE id = $3\n RETURNING flow_status AS \"flow_status: Json>\"", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "flow_status: Json>", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Text", + "Jsonb", + "Uuid" + ] + }, + "nullable": [ + true + ] + }, + "hash": "30216cf02e972f961b7cc6054050fdc984be118df1ad68f7263c84e058bb1266" +} diff --git a/backend/.sqlx/query-303c7e92ce23dc367d97d813415cd9aef958c15df1c0a7b02318a756cd3589e9.json b/backend/.sqlx/query-303c7e92ce23dc367d97d813415cd9aef958c15df1c0a7b02318a756cd3589e9.json new file mode 100644 index 0000000000..58cfc98b09 --- /dev/null +++ b/backend/.sqlx/query-303c7e92ce23dc367d97d813415cd9aef958c15df1c0a7b02318a756cd3589e9.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "WITH job_result AS (\n SELECT result \n FROM v2_job_completed \n WHERE id = $1\n )\n UPDATE v2_job \n SET args = COALESCE(\n CASE \n WHEN job_result.result IS NULL THEN NULL\n WHEN jsonb_typeof(job_result.result) = 'object' \n THEN job_result.result\n WHEN jsonb_typeof(job_result.result) = 'null'\n THEN NULL\n ELSE jsonb_build_object('value', job_result.result)\n END, \n '{}'::jsonb\n ),\n preprocessed = TRUE\n FROM job_result\n WHERE v2_job.id = $2;\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Uuid", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "303c7e92ce23dc367d97d813415cd9aef958c15df1c0a7b02318a756cd3589e9" +} diff --git a/backend/.sqlx/query-3738096c29ab9d964be8a74bfd14ff1d599049ebefdaf97a017c9cef8d52ce20.json b/backend/.sqlx/query-3738096c29ab9d964be8a74bfd14ff1d599049ebefdaf97a017c9cef8d52ce20.json new file mode 100644 index 0000000000..d804949078 --- /dev/null +++ b/backend/.sqlx/query-3738096c29ab9d964be8a74bfd14ff1d599049ebefdaf97a017c9cef8d52ce20.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "DROP INDEX CONCURRENTLY IF EXISTS queue_sort_2", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "3738096c29ab9d964be8a74bfd14ff1d599049ebefdaf97a017c9cef8d52ce20" +} diff --git a/backend/.sqlx/query-3b02f3ec6b92706c26065b2fa703cd1a1d6eca4083286c6f29c9acba522d2fe3.json b/backend/.sqlx/query-3b02f3ec6b92706c26065b2fa703cd1a1d6eca4083286c6f29c9acba522d2fe3.json new file mode 100644 index 0000000000..7c35515d12 --- /dev/null +++ b/backend/.sqlx/query-3b02f3ec6b92706c26065b2fa703cd1a1d6eca4083286c6f29c9acba522d2fe3.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT EXISTS (SELECT 1\n FROM workspace_settings\n WHERE workspace_id <> $1\n AND teams_command_script IS NOT NULL\n AND teams_team_id IS NOT NULL\n AND teams_team_id = (SELECT teams_team_id FROM workspace_settings WHERE workspace_id = $1))\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "exists", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "3b02f3ec6b92706c26065b2fa703cd1a1d6eca4083286c6f29c9acba522d2fe3" +} diff --git a/backend/.sqlx/query-3bacf9cd9aa63f4bec5f983f4a0c3030216b5a4ed669f77962509d1c2c6cb780.json b/backend/.sqlx/query-3bacf9cd9aa63f4bec5f983f4a0c3030216b5a4ed669f77962509d1c2c6cb780.json deleted file mode 100644 index 200a5bf47f..0000000000 --- a/backend/.sqlx/query-3bacf9cd9aa63f4bec5f983f4a0c3030216b5a4ed669f77962509d1c2c6cb780.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "create index concurrently if not exists root_job_index_by_path_2 ON v2_job (workspace_id, runnable_path, created_at desc) WHERE parent_job IS NULL", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "3bacf9cd9aa63f4bec5f983f4a0c3030216b5a4ed669f77962509d1c2c6cb780" -} diff --git a/backend/.sqlx/query-3bbde0fa35d935ec2dd8bd1fb14cfecf48305f5f4b644b3c35355074e1ccce28.json b/backend/.sqlx/query-3bbde0fa35d935ec2dd8bd1fb14cfecf48305f5f4b644b3c35355074e1ccce28.json new file mode 100644 index 0000000000..815f1fed70 --- /dev/null +++ b/backend/.sqlx/query-3bbde0fa35d935ec2dd8bd1fb14cfecf48305f5f4b644b3c35355074e1ccce28.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "CREATE INDEX CONCURRENTLY queue_sort_v2 ON v2_job_queue (priority DESC NULLS LAST, scheduled_for, tag) WHERE running = false", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "3bbde0fa35d935ec2dd8bd1fb14cfecf48305f5f4b644b3c35355074e1ccce28" +} diff --git a/backend/.sqlx/query-42177e249794a4b7b945b93efd853afc1f1ad354599de11ebc6e256b58543265.json b/backend/.sqlx/query-42177e249794a4b7b945b93efd853afc1f1ad354599de11ebc6e256b58543265.json deleted file mode 100644 index 71171ad831..0000000000 --- a/backend/.sqlx/query-42177e249794a4b7b945b93efd853afc1f1ad354599de11ebc6e256b58543265.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new_6", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "42177e249794a4b7b945b93efd853afc1f1ad354599de11ebc6e256b58543265" -} diff --git a/backend/.sqlx/query-47455b0ebaf999ab58b2cba3d74cb4bdd64075939a4aa2c117e18372511ea7e0.json b/backend/.sqlx/query-47455b0ebaf999ab58b2cba3d74cb4bdd64075939a4aa2c117e18372511ea7e0.json deleted file mode 100644 index 6ccaeece1c..0000000000 --- a/backend/.sqlx/query-47455b0ebaf999ab58b2cba3d74cb4bdd64075939a4aa2c117e18372511ea7e0.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "VACUUM (skip_locked) v2_job_queue, v2_job_runtime, v2_job_status", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "47455b0ebaf999ab58b2cba3d74cb4bdd64075939a4aa2c117e18372511ea7e0" -} diff --git a/backend/.sqlx/query-4d3bbcc029ec0926bf97de4b5ef97dfd5eeda5977a8746be77c8aa65a7ec299e.json b/backend/.sqlx/query-4d3bbcc029ec0926bf97de4b5ef97dfd5eeda5977a8746be77c8aa65a7ec299e.json deleted file mode 100644 index 4c983c95a3..0000000000 --- a/backend/.sqlx/query-4d3bbcc029ec0926bf97de4b5ef97dfd5eeda5977a8746be77c8aa65a7ec299e.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DROP INDEX CONCURRENTLY IF EXISTS concurrency_limit_stats_completed_job", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "4d3bbcc029ec0926bf97de4b5ef97dfd5eeda5977a8746be77c8aa65a7ec299e" -} diff --git a/backend/.sqlx/query-4d4aa16b2a55e57f9376d0cb253e671525969fb5f528ae07576e9dc5e77af1f1.json b/backend/.sqlx/query-4d4aa16b2a55e57f9376d0cb253e671525969fb5f528ae07576e9dc5e77af1f1.json new file mode 100644 index 0000000000..3dea443954 --- /dev/null +++ b/backend/.sqlx/query-4d4aa16b2a55e57f9376d0cb253e671525969fb5f528ae07576e9dc5e77af1f1.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "DROP INDEX CONCURRENTLY IF EXISTS ix_job_workspace_id_created_at_new_7", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "4d4aa16b2a55e57f9376d0cb253e671525969fb5f528ae07576e9dc5e77af1f1" +} diff --git a/backend/.sqlx/query-4e9c2e0690eaca280ccb5e5160438f207d930d29e3000b9845eef89e87a35ad1.json b/backend/.sqlx/query-4e9c2e0690eaca280ccb5e5160438f207d930d29e3000b9845eef89e87a35ad1.json new file mode 100644 index 0000000000..3dd496788f --- /dev/null +++ b/backend/.sqlx/query-4e9c2e0690eaca280ccb5e5160438f207d930d29e3000b9845eef89e87a35ad1.json @@ -0,0 +1,196 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT workspace_id, slack_team_id, teams_team_id, teams_team_name, slack_name, slack_command_script, teams_command_script, slack_email, auto_invite_domain, auto_invite_operator, auto_add, customer_id, plan, webhook, deploy_to, ai_resource, ai_models, code_completion_model, error_handler, error_handler_extra_args, error_handler_muted_on_cancel, large_file_storage, git_sync, deploy_ui, default_app, automatic_billing, default_scripts, mute_critical_alerts, color, operator_settings FROM workspace_settings WHERE workspace_id = $1", + "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": "slack_name", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "slack_command_script", + "type_info": "Varchar" + }, + { + "ordinal": 6, + "name": "teams_command_script", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "slack_email", + "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "auto_invite_domain", + "type_info": "Varchar" + }, + { + "ordinal": 9, + "name": "auto_invite_operator", + "type_info": "Bool" + }, + { + "ordinal": 10, + "name": "auto_add", + "type_info": "Bool" + }, + { + "ordinal": 11, + "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_resource", + "type_info": "Jsonb" + }, + { + "ordinal": 16, + "name": "ai_models", + "type_info": "VarcharArray" + }, + { + "ordinal": 17, + "name": "code_completion_model", + "type_info": "Varchar" + }, + { + "ordinal": 18, + "name": "error_handler", + "type_info": "Varchar" + }, + { + "ordinal": 19, + "name": "error_handler_extra_args", + "type_info": "Json" + }, + { + "ordinal": 20, + "name": "error_handler_muted_on_cancel", + "type_info": "Bool" + }, + { + "ordinal": 21, + "name": "large_file_storage", + "type_info": "Jsonb" + }, + { + "ordinal": 22, + "name": "git_sync", + "type_info": "Jsonb" + }, + { + "ordinal": 23, + "name": "deploy_ui", + "type_info": "Jsonb" + }, + { + "ordinal": 24, + "name": "default_app", + "type_info": "Varchar" + }, + { + "ordinal": 25, + "name": "automatic_billing", + "type_info": "Bool" + }, + { + "ordinal": 26, + "name": "default_scripts", + "type_info": "Jsonb" + }, + { + "ordinal": 27, + "name": "mute_critical_alerts", + "type_info": "Bool" + }, + { + "ordinal": 28, + "name": "color", + "type_info": "Varchar" + }, + { + "ordinal": 29, + "name": "operator_settings", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + true, + true, + true, + true, + true, + true, + false, + true, + true, + true, + true, + true, + true, + true, + true, + false, + true, + true, + true, + false, + true, + true, + true, + true, + false, + true, + true, + true, + true + ] + }, + "hash": "4e9c2e0690eaca280ccb5e5160438f207d930d29e3000b9845eef89e87a35ad1" +} diff --git a/backend/.sqlx/query-50c17c7848760aaf0f869acfc444caecda6335eda5b2e97e5a7370361653ff48.json b/backend/.sqlx/query-50c17c7848760aaf0f869acfc444caecda6335eda5b2e97e5a7370361653ff48.json new file mode 100644 index 0000000000..f3dc153254 --- /dev/null +++ b/backend/.sqlx/query-50c17c7848760aaf0f869acfc444caecda6335eda5b2e97e5a7370361653ff48.json @@ -0,0 +1,26 @@ +{ + "db_name": "PostgreSQL", + "query": "\n WITH assigned_teams AS (\n SELECT teams_team_id\n FROM workspace_settings\n ),\n all_teams AS (\n SELECT jsonb_array_elements(CASE\n WHEN jsonb_typeof(value::jsonb) = 'array' THEN value::jsonb\n ELSE '[]'::jsonb\n END) AS team\n FROM global_settings\n WHERE name = 'teams'\n )\n SELECT team->>'team_name' AS team_name, team->>'team_internal_id' AS team_id\n FROM all_teams\n WHERE NOT EXISTS (\n SELECT 1\n FROM assigned_teams\n WHERE assigned_teams.teams_team_id = team->>'team_internal_id'\n )\n AND team->>'team_name' IS NOT NULL\n AND team->>'team_id' IS NOT NULL\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "team_name", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "team_id", + "type_info": "Text" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null, + null + ] + }, + "hash": "50c17c7848760aaf0f869acfc444caecda6335eda5b2e97e5a7370361653ff48" +} diff --git a/backend/.sqlx/query-551c78392919e18019bb0a4344fb1bd45853bf5b72e0ab991e0e61fedcfb42fc.json b/backend/.sqlx/query-551c78392919e18019bb0a4344fb1bd45853bf5b72e0ab991e0e61fedcfb42fc.json new file mode 100644 index 0000000000..4a9bcd8cf3 --- /dev/null +++ b/backend/.sqlx/query-551c78392919e18019bb0a4344fb1bd45853bf5b72e0ab991e0e61fedcfb42fc.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE workspace_settings\n SET teams_team_id = $1, teams_team_name = $2\n WHERE workspace_id = $3\n AND NOT EXISTS (\n SELECT 1 FROM workspace_settings\n WHERE teams_team_id = $1 AND workspace_id <> $2\n )\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "551c78392919e18019bb0a4344fb1bd45853bf5b72e0ab991e0e61fedcfb42fc" +} diff --git a/backend/.sqlx/query-55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2.json b/backend/.sqlx/query-55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2.json index 920176991b..9243288c9d 100644 --- a/backend/.sqlx/query-55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2.json +++ b/backend/.sqlx/query-55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2.json @@ -130,11 +130,26 @@ }, { "ordinal": 25, + "name": "teams_command_script", + "type_info": "Text" + }, + { + "ordinal": 26, + "name": "teams_team_id", + "type_info": "Text" + }, + { + "ordinal": 27, + "name": "teams_team_name", + "type_info": "Text" + }, + { + "ordinal": 28, "name": "ai_models", "type_info": "VarcharArray" }, { - "ordinal": 26, + "ordinal": 29, "name": "code_completion_model", "type_info": "Varchar" } @@ -170,6 +185,9 @@ true, true, true, + true, + true, + true, false, true ] diff --git a/backend/.sqlx/query-608393951c85d9e721b506d2f6a7a47667b6f9f3dc0d51987cce159433459ab0.json b/backend/.sqlx/query-608393951c85d9e721b506d2f6a7a47667b6f9f3dc0d51987cce159433459ab0.json deleted file mode 100644 index 2808d96d51..0000000000 --- a/backend/.sqlx/query-608393951c85d9e721b506d2f6a7a47667b6f9f3dc0d51987cce159433459ab0.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_started_at_new", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "608393951c85d9e721b506d2f6a7a47667b6f9f3dc0d51987cce159433459ab0" -} diff --git a/backend/.sqlx/query-65c339164e7669360d231d70105849e72bdc197c17c0fc51777c1dc9267e2daf.json b/backend/.sqlx/query-65c339164e7669360d231d70105849e72bdc197c17c0fc51777c1dc9267e2daf.json new file mode 100644 index 0000000000..d52d46f1dd --- /dev/null +++ b/backend/.sqlx/query-65c339164e7669360d231d70105849e72bdc197c17c0fc51777c1dc9267e2daf.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE workspace_settings\n SET teams_command_script = NULL,\n teams_team_id = NULL,\n teams_team_name = NULL\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "65c339164e7669360d231d70105849e72bdc197c17c0fc51777c1dc9267e2daf" +} diff --git a/backend/.sqlx/query-74754b03304a69391d61560848e89569fe5666e36ca2650dabd9b494dc30f435.json b/backend/.sqlx/query-74754b03304a69391d61560848e89569fe5666e36ca2650dabd9b494dc30f435.json deleted file mode 100644 index 50372c7cc6..0000000000 --- a/backend/.sqlx/query-74754b03304a69391d61560848e89569fe5666e36ca2650dabd9b494dc30f435.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new_2", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "74754b03304a69391d61560848e89569fe5666e36ca2650dabd9b494dc30f435" -} diff --git a/backend/.sqlx/query-81b06122c7a12a314d8905ba5c7c14aa7614f2610e79a8c7302eaa63fb74984d.json b/backend/.sqlx/query-81b06122c7a12a314d8905ba5c7c14aa7614f2610e79a8c7302eaa63fb74984d.json new file mode 100644 index 0000000000..91718513bb --- /dev/null +++ b/backend/.sqlx/query-81b06122c7a12a314d8905ba5c7c14aa7614f2610e79a8c7302eaa63fb74984d.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE global_settings\n SET value = (\n SELECT COALESCE(jsonb_agg(elem), '[]'::jsonb)\n FROM jsonb_array_elements(value) AS elem\n WHERE NOT (elem ? 'teams_channel')\n )\n WHERE name = 'critical_error_channels'\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "81b06122c7a12a314d8905ba5c7c14aa7614f2610e79a8c7302eaa63fb74984d" +} diff --git a/backend/.sqlx/query-85181656012b18cd26998128c30b1bf1f70e17486a9d315db130852d5e325200.json b/backend/.sqlx/query-85181656012b18cd26998128c30b1bf1f70e17486a9d315db130852d5e325200.json deleted file mode 100644 index 98238638c1..0000000000 --- a/backend/.sqlx/query-85181656012b18cd26998128c30b1bf1f70e17486a9d315db130852d5e325200.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new_3", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "85181656012b18cd26998128c30b1bf1f70e17486a9d315db130852d5e325200" -} diff --git a/backend/.sqlx/query-8a1c9119f6f4763f64597684dc7b312404e637212de1897e97999755a2e492fd.json b/backend/.sqlx/query-8a1c9119f6f4763f64597684dc7b312404e637212de1897e97999755a2e492fd.json deleted file mode 100644 index 8a92be2af8..0000000000 --- a/backend/.sqlx/query-8a1c9119f6f4763f64597684dc7b312404e637212de1897e97999755a2e492fd.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job SET\n args = (SELECT result FROM v2_job_completed WHERE id = $1),\n preprocessed = TRUE\n WHERE id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Uuid", - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "8a1c9119f6f4763f64597684dc7b312404e637212de1897e97999755a2e492fd" -} diff --git a/backend/.sqlx/query-8be277b89102a26dda506202a3ef7eb05342cfb3aa9b4f5d80c70fbc50d437ba.json b/backend/.sqlx/query-8be277b89102a26dda506202a3ef7eb05342cfb3aa9b4f5d80c70fbc50d437ba.json deleted file mode 100644 index e5819f5c6a..0000000000 --- a/backend/.sqlx/query-8be277b89102a26dda506202a3ef7eb05342cfb3aa9b4f5d80c70fbc50d437ba.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "create index concurrently if not exists ix_job_workspace_id_created_at_new_6 ON v2_job (workspace_id, created_at DESC) where kind in ('script', 'flow') AND parent_job IS NULL", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "8be277b89102a26dda506202a3ef7eb05342cfb3aa9b4f5d80c70fbc50d437ba" -} diff --git a/backend/.sqlx/query-93586f1ffc7b8dbe62b21a21acfa6854a34a99d2bf107feaafc499588f7330a0.json b/backend/.sqlx/query-93586f1ffc7b8dbe62b21a21acfa6854a34a99d2bf107feaafc499588f7330a0.json deleted file mode 100644 index b4e244e16d..0000000000 --- a/backend/.sqlx/query-93586f1ffc7b8dbe62b21a21acfa6854a34a99d2bf107feaafc499588f7330a0.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DROP INDEX CONCURRENTLY IF EXISTS scheduled_root_job", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "93586f1ffc7b8dbe62b21a21acfa6854a34a99d2bf107feaafc499588f7330a0" -} diff --git a/backend/.sqlx/query-ae8dfecd46425d5f86003eea9a578e9831fc0e700cc76ab9627afe9040a4efe0.json b/backend/.sqlx/query-ae8dfecd46425d5f86003eea9a578e9831fc0e700cc76ab9627afe9040a4efe0.json deleted file mode 100644 index c0703bc518..0000000000 --- a/backend/.sqlx/query-ae8dfecd46425d5f86003eea9a578e9831fc0e700cc76ab9627afe9040a4efe0.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "create index concurrently if not exists ix_job_workspace_id_created_at_new_7 ON v2_job (workspace_id, created_at DESC) where kind in ('script', 'flow') AND parent_job IS NULL", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "ae8dfecd46425d5f86003eea9a578e9831fc0e700cc76ab9627afe9040a4efe0" -} diff --git a/backend/.sqlx/query-b9b38d63af3670d1f11d5cbb82a8008a9479bf4b3d7231371ebf26382ecde365.json b/backend/.sqlx/query-b9b38d63af3670d1f11d5cbb82a8008a9479bf4b3d7231371ebf26382ecde365.json new file mode 100644 index 0000000000..641c94c555 --- /dev/null +++ b/backend/.sqlx/query-b9b38d63af3670d1f11d5cbb82a8008a9479bf4b3d7231371ebf26382ecde365.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "VACUUM v2_job_queue, v2_job_runtime, v2_job_status", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "b9b38d63af3670d1f11d5cbb82a8008a9479bf4b3d7231371ebf26382ecde365" +} diff --git a/backend/.sqlx/query-c458012c39f1327fb2bfd6b087ee6dbe1380eca532f50bab0cb89a91d42131cf.json b/backend/.sqlx/query-c458012c39f1327fb2bfd6b087ee6dbe1380eca532f50bab0cb89a91d42131cf.json new file mode 100644 index 0000000000..86f7f8605e --- /dev/null +++ b/backend/.sqlx/query-c458012c39f1327fb2bfd6b087ee6dbe1380eca532f50bab0cb89a91d42131cf.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "ALTER TABLE v2_job ENABLE ROW LEVEL SECURITY", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "c458012c39f1327fb2bfd6b087ee6dbe1380eca532f50bab0cb89a91d42131cf" +} diff --git a/backend/.sqlx/query-c481e5d63ebf1aa537cc4ce4e84f9a71af5996bc76f328b3ba1cf68a71880462.json b/backend/.sqlx/query-c481e5d63ebf1aa537cc4ce4e84f9a71af5996bc76f328b3ba1cf68a71880462.json new file mode 100644 index 0000000000..b3d20cccea --- /dev/null +++ b/backend/.sqlx/query-c481e5d63ebf1aa537cc4ce4e84f9a71af5996bc76f328b3ba1cf68a71880462.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "create index concurrently if not exists ix_job_root_job_index_by_path_2 ON v2_job (workspace_id, runnable_path, created_at desc) WHERE parent_job IS NULL", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "c481e5d63ebf1aa537cc4ce4e84f9a71af5996bc76f328b3ba1cf68a71880462" +} diff --git a/backend/.sqlx/query-d25c58d2722ad3dcd91101ce6f66e1d802dd5d82e1cd5f5ed3a15cbc75eb6745.json b/backend/.sqlx/query-c92cc71e6d10c41368f7aa75b0799c2e1e9ca0ed33077ade8d7560e7cc21fa06.json similarity index 60% rename from backend/.sqlx/query-d25c58d2722ad3dcd91101ce6f66e1d802dd5d82e1cd5f5ed3a15cbc75eb6745.json rename to backend/.sqlx/query-c92cc71e6d10c41368f7aa75b0799c2e1e9ca0ed33077ade8d7560e7cc21fa06.json index 2872c1655b..8a4b957c2a 100644 --- a/backend/.sqlx/query-d25c58d2722ad3dcd91101ce6f66e1d802dd5d82e1cd5f5ed3a15cbc75eb6745.json +++ b/backend/.sqlx/query-c92cc71e6d10c41368f7aa75b0799c2e1e9ca0ed33077ade8d7560e7cc21fa06.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "DELETE FROM v2_job_queue WHERE workspace_id = $1 AND id = $2 RETURNING 1", + "query": "DELETE FROM v2_job_queue WHERE id = $1 RETURNING 1", "describe": { "columns": [ { @@ -11,7 +11,6 @@ ], "parameters": { "Left": [ - "Text", "Uuid" ] }, @@ -19,5 +18,5 @@ null ] }, - "hash": "d25c58d2722ad3dcd91101ce6f66e1d802dd5d82e1cd5f5ed3a15cbc75eb6745" + "hash": "c92cc71e6d10c41368f7aa75b0799c2e1e9ca0ed33077ade8d7560e7cc21fa06" } diff --git a/backend/.sqlx/query-cb5a8545ea140ed69c7b70d8c08cb3c037309f09e84acbb7945a0466d6e9f576.json b/backend/.sqlx/query-cb5a8545ea140ed69c7b70d8c08cb3c037309f09e84acbb7945a0466d6e9f576.json deleted file mode 100644 index 27330c2def..0000000000 --- a/backend/.sqlx/query-cb5a8545ea140ed69c7b70d8c08cb3c037309f09e84acbb7945a0466d6e9f576.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "create index concurrently if not exists ix_v2_job_root_by_path\n on v2_job (workspace_id, runnable_path, created_at DESC)\n where parent_job is null", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "cb5a8545ea140ed69c7b70d8c08cb3c037309f09e84acbb7945a0466d6e9f576" -} diff --git a/backend/.sqlx/query-d585aa6301c41308b02a1f0fbf068221e732e48dfa6e34d5b025adbbdcbb03e0.json b/backend/.sqlx/query-d585aa6301c41308b02a1f0fbf068221e732e48dfa6e34d5b025adbbdcbb03e0.json new file mode 100644 index 0000000000..72169ec94b --- /dev/null +++ b/backend/.sqlx/query-d585aa6301c41308b02a1f0fbf068221e732e48dfa6e34d5b025adbbdcbb03e0.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "create index concurrently if not exists ix_v2_job_workspace_id_created_at ON v2_job (workspace_id, created_at DESC) where kind in ('script', 'flow', 'singlescriptflow') AND parent_job IS NULL", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "d585aa6301c41308b02a1f0fbf068221e732e48dfa6e34d5b025adbbdcbb03e0" +} diff --git a/backend/.sqlx/query-d949e8b91fbdf4c50c1c2cbc608cf0b385bff14faab495e049f2029790f76d25.json b/backend/.sqlx/query-d949e8b91fbdf4c50c1c2cbc608cf0b385bff14faab495e049f2029790f76d25.json deleted file mode 100644 index e792f43ad4..0000000000 --- a/backend/.sqlx/query-d949e8b91fbdf4c50c1c2cbc608cf0b385bff14faab495e049f2029790f76d25.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new_9", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "d949e8b91fbdf4c50c1c2cbc608cf0b385bff14faab495e049f2029790f76d25" -} diff --git a/backend/.sqlx/query-db91141ae55b96a3237e05e3f127386339cb3c7a6f88bb2102498ec5d34f9537.json b/backend/.sqlx/query-db91141ae55b96a3237e05e3f127386339cb3c7a6f88bb2102498ec5d34f9537.json deleted file mode 100644 index cc6474870c..0000000000 --- a/backend/.sqlx/query-db91141ae55b96a3237e05e3f127386339cb3c7a6f88bb2102498ec5d34f9537.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE v2_job_status\n SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2)\n WHERE id = $3", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Jsonb", - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "db91141ae55b96a3237e05e3f127386339cb3c7a6f88bb2102498ec5d34f9537" -} diff --git a/backend/.sqlx/query-e00144305b880cca3994b53c4a080e111a159a749728ce5363100cf883bdf02a.json b/backend/.sqlx/query-e00144305b880cca3994b53c4a080e111a159a749728ce5363100cf883bdf02a.json deleted file mode 100644 index d9fbfef7e2..0000000000 --- a/backend/.sqlx/query-e00144305b880cca3994b53c4a080e111a159a749728ce5363100cf883bdf02a.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new_8", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "e00144305b880cca3994b53c4a080e111a159a749728ce5363100cf883bdf02a" -} diff --git a/backend/.sqlx/query-e565f3b2e51059f563d18a8a9442bcae9640cee7b936820cb46c011222a77ff0.json b/backend/.sqlx/query-e565f3b2e51059f563d18a8a9442bcae9640cee7b936820cb46c011222a77ff0.json new file mode 100644 index 0000000000..4c2cd96ca9 --- /dev/null +++ b/backend/.sqlx/query-e565f3b2e51059f563d18a8a9442bcae9640cee7b936820cb46c011222a77ff0.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE global_settings SET value = $1 WHERE name = 'teams'", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Jsonb" + ] + }, + "nullable": [] + }, + "hash": "e565f3b2e51059f563d18a8a9442bcae9640cee7b936820cb46c011222a77ff0" +} diff --git a/backend/.sqlx/query-e9038a6fcfd8bdf4855c5919ce634b976cbe0280e7215862aaf5ef445663793d.json b/backend/.sqlx/query-e9038a6fcfd8bdf4855c5919ce634b976cbe0280e7215862aaf5ef445663793d.json deleted file mode 100644 index 13e6880b54..0000000000 --- a/backend/.sqlx/query-e9038a6fcfd8bdf4855c5919ce634b976cbe0280e7215862aaf5ef445663793d.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new_7", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "e9038a6fcfd8bdf4855c5919ce634b976cbe0280e7215862aaf5ef445663793d" -} diff --git a/backend/.sqlx/query-ebbe03cad470d0c6ae98964f630315ff25dad08e4cb714718505b77a75d44b95.json b/backend/.sqlx/query-ebbe03cad470d0c6ae98964f630315ff25dad08e4cb714718505b77a75d44b95.json new file mode 100644 index 0000000000..e087310961 --- /dev/null +++ b/backend/.sqlx/query-ebbe03cad470d0c6ae98964f630315ff25dad08e4cb714718505b77a75d44b95.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_settings SET teams_command_script = $1 WHERE workspace_id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "ebbe03cad470d0c6ae98964f630315ff25dad08e4cb714718505b77a75d44b95" +} diff --git a/backend/.sqlx/query-ebfd39f168722701fb63cd37aaef3741b9782d8e2d4b6ff6c104fdcac5f58323.json b/backend/.sqlx/query-ebfd39f168722701fb63cd37aaef3741b9782d8e2d4b6ff6c104fdcac5f58323.json deleted file mode 100644 index b82bb5a31c..0000000000 --- a/backend/.sqlx/query-ebfd39f168722701fb63cd37aaef3741b9782d8e2d4b6ff6c104fdcac5f58323.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_created_at", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "ebfd39f168722701fb63cd37aaef3741b9782d8e2d4b6ff6c104fdcac5f58323" -} diff --git a/backend/.sqlx/query-fa94a9ee5514f6808d3813394e62c86a69b7b7378333322a57856f8e43c6cd77.json b/backend/.sqlx/query-fa94a9ee5514f6808d3813394e62c86a69b7b7378333322a57856f8e43c6cd77.json deleted file mode 100644 index 6baa09af23..0000000000 --- a/backend/.sqlx/query-fa94a9ee5514f6808d3813394e62c86a69b7b7378333322a57856f8e43c6cd77.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new_5", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "fa94a9ee5514f6808d3813394e62c86a69b7b7378333322a57856f8e43c6cd77" -} diff --git a/backend/.sqlx/query-faa0e401e6beebde6c3fef06151d3e73a5806f61cae4a53b5bdc888ec7164395.json b/backend/.sqlx/query-faa0e401e6beebde6c3fef06151d3e73a5806f61cae4a53b5bdc888ec7164395.json new file mode 100644 index 0000000000..2824316ed3 --- /dev/null +++ b/backend/.sqlx/query-faa0e401e6beebde6c3fef06151d3e73a5806f61cae4a53b5bdc888ec7164395.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "DROP INDEX CONCURRENTLY IF EXISTS ix_job_workspace_id_created_at_new_6", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "faa0e401e6beebde6c3fef06151d3e73a5806f61cae4a53b5bdc888ec7164395" +} diff --git a/backend/.sqlx/query-eed16e356f3f36183c3db13fcc1950295e0d0fbdabb38434534fb3430eeddc25.json b/backend/.sqlx/query-fec6d5674dc6b5a6a0ece419c40508835affcb7679a48f2a443777e829bd1e74.json similarity index 52% rename from backend/.sqlx/query-eed16e356f3f36183c3db13fcc1950295e0d0fbdabb38434534fb3430eeddc25.json rename to backend/.sqlx/query-fec6d5674dc6b5a6a0ece419c40508835affcb7679a48f2a443777e829bd1e74.json index 5e0817ae84..649ab85650 100644 --- a/backend/.sqlx/query-eed16e356f3f36183c3db13fcc1950295e0d0fbdabb38434534fb3430eeddc25.json +++ b/backend/.sqlx/query-fec6d5674dc6b5a6a0ece419c40508835affcb7679a48f2a443777e829bd1e74.json @@ -1,31 +1,31 @@ { "db_name": "PostgreSQL", - "query": "SELECT workspace.id, workspace.name, workspace.owner, workspace.deleted, workspace.premium, workspace_settings.color\n FROM workspace\n LEFT JOIN workspace_settings ON workspace.id = workspace_settings.workspace_id\n LIMIT $1 OFFSET $2", + "query": "SELECT\n workspace.id AS \"id!\",\n workspace.name AS \"name!\",\n workspace.owner AS \"owner!\",\n workspace.deleted AS \"deleted!\",\n workspace.premium AS \"premium!\",\n workspace_settings.color AS \"color\"\n FROM workspace\n LEFT JOIN workspace_settings ON workspace.id = workspace_settings.workspace_id\n LIMIT $1 OFFSET $2", "describe": { "columns": [ { "ordinal": 0, - "name": "id", + "name": "id!", "type_info": "Varchar" }, { "ordinal": 1, - "name": "name", + "name": "name!", "type_info": "Varchar" }, { "ordinal": 2, - "name": "owner", + "name": "owner!", "type_info": "Varchar" }, { "ordinal": 3, - "name": "deleted", + "name": "deleted!", "type_info": "Bool" }, { "ordinal": 4, - "name": "premium", + "name": "premium!", "type_info": "Bool" }, { @@ -49,5 +49,5 @@ true ] }, - "hash": "eed16e356f3f36183c3db13fcc1950295e0d0fbdabb38434534fb3430eeddc25" + "hash": "fec6d5674dc6b5a6a0ece419c40508835affcb7679a48f2a443777e829bd1e74" } diff --git a/backend/Cargo.lock b/backend/Cargo.lock index a6473bff94..d0053f30fe 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -492,7 +492,7 @@ dependencies = [ "portable-atomic", "rand 0.8.5", "regex", - "ring 0.17.8", + "ring 0.17.9", "rustls-native-certs 0.7.3", "rustls-pemfile 2.2.0", "rustls-webpki 0.102.8", @@ -671,7 +671,7 @@ dependencies = [ "fastrand 2.3.0", "hex", "http 0.2.12", - "ring 0.17.8", + "ring 0.17.9", "time", "tokio", "tracing", @@ -713,14 +713,14 @@ dependencies = [ "percent-encoding", "pin-project-lite", "tracing", - "uuid 1.13.1", + "uuid 1.13.2", ] [[package]] name = "aws-sdk-sso" -version = "1.58.0" +version = "1.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ff718c9ee45cc1ebd4774a0e086bb80a6ab752b4902edf1c9f56b86ee1f770" +checksum = "00a35fc7e74f5be45839eb753568535c074a592185dd0a2d406685018d581c43" dependencies = [ "aws-credential-types", "aws-runtime", @@ -740,9 +740,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.59.0" +version = "1.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5183e088715cc135d8d396fdd3bc02f018f0da4c511f53cb8d795b6a31c55809" +checksum = "f8fa655b4f313124ce272cbc38c5fef13793c832279cec750103e5e6b71a54b8" dependencies = [ "aws-credential-types", "aws-runtime", @@ -762,9 +762,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.59.0" +version = "1.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9f944ef032717596639cea4a2118a3a457268ef51bbb5fde9637e54c465da00" +checksum = "dc1cfe5e16b90421ea031f4c6348b534ef442e76f6bf4a1b2b592c12cc2c6af9" dependencies = [ "aws-credential-types", "aws-runtime", @@ -785,9 +785,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.2.8" +version = "1.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bc5bbd1e4a2648fd8c5982af03935972c24a2f9846b396de661d351ee3ce837" +checksum = "9bfe75fad52793ce6dec0dc3d4b1f388f038b5eb866c8d4d7f3a8e21b5ea5051" dependencies = [ "aws-credential-types", "aws-smithy-http", @@ -1222,15 +1222,16 @@ dependencies = [ [[package]] name = "blake3" -version = "1.5.5" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e" +checksum = "1230237285e3e10cde447185e8975408ae24deaa67205ce684805c25bc0c7937" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", + "memmap2", ] [[package]] @@ -1552,9 +1553,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.13" +version = "1.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda" +checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9" dependencies = [ "jobserver", "libc", @@ -1668,9 +1669,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.29" +version = "4.5.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acebd8ad879283633b343856142139f2da2317c96b05b4dd6181c61e2480184" +checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d" dependencies = [ "clap_builder", "clap_derive", @@ -1678,9 +1679,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.29" +version = "4.5.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ba32cbda51c7e1dfd49acc1457ba1a7dec5b64fe360e828acb13ca8dc9c2f9" +checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c" dependencies = [ "anstream", "anstyle", @@ -2050,9 +2051,9 @@ dependencies = [ [[package]] name = "csv-core" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" dependencies = [ "memchr", ] @@ -2263,7 +2264,7 @@ dependencies = [ "tokio", "tokio-util", "url", - "uuid 1.13.1", + "uuid 1.13.2", "xz2", "zstd", ] @@ -2363,7 +2364,7 @@ dependencies = [ "regex", "sha2 0.10.8", "unicode-segmentation", - "uuid 1.13.1", + "uuid 1.13.2", ] [[package]] @@ -2530,7 +2531,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" dependencies = [ "serde", - "uuid 1.13.1", + "uuid 1.13.2", ] [[package]] @@ -2825,7 +2826,7 @@ dependencies = [ "serde", "thiserror 1.0.69", "tokio", - "uuid 1.13.1", + "uuid 1.13.2", ] [[package]] @@ -3209,9 +3210,9 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" @@ -5405,7 +5406,7 @@ dependencies = [ "sha2 0.10.8", "subprocess", "thiserror 1.0.69", - "uuid 1.13.1", + "uuid 1.13.2", "zstd", ] @@ -5673,7 +5674,7 @@ dependencies = [ "quick-xml 0.36.2", "rand 0.8.5", "reqwest 0.12.9", - "ring 0.17.8", + "ring 0.17.9", "serde", "serde_json", "snafu", @@ -5776,9 +5777,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.70" +version = "0.10.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6" +checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" dependencies = [ "bitflags 2.8.0", "cfg-if", @@ -5817,9 +5818,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.105" +version = "0.9.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" +checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" dependencies = [ "cc", "libc", @@ -6366,7 +6367,7 @@ dependencies = [ "postgres-protocol 0.6.8", "serde", "serde_json", - "uuid 1.13.1", + "uuid 1.13.2", ] [[package]] @@ -6608,9 +6609,9 @@ dependencies = [ [[package]] name = "psm" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810" +checksum = "f58e5423e24c18cc840e1c98370b3993c6649cd1678b4d24318bcf0a083cbe88" dependencies = [ "cc", ] @@ -6724,7 +6725,7 @@ dependencies = [ "bytes", "getrandom 0.2.15", "rand 0.8.5", - "ring 0.17.8", + "ring 0.17.9", "rustc-hash 2.1.1", "rustls 0.23.23", "rustls-pki-types", @@ -6737,9 +6738,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" +checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944" dependencies = [ "cfg_aliases", "libc", @@ -6795,8 +6796,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.0", - "zerocopy 0.8.17", + "rand_core 0.9.1", + "zerocopy 0.8.18", ] [[package]] @@ -6826,7 +6827,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.0", + "rand_core 0.9.1", ] [[package]] @@ -6849,12 +6850,12 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +checksum = "a88e0da7a2c97baa202165137c158d0a2e824ac465d13d81046727b34cb247d3" dependencies = [ "getrandom 0.3.1", - "zerocopy 0.8.17", + "zerocopy 0.8.18", ] [[package]] @@ -7195,15 +7196,14 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.8" +version = "0.17.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "e75ec5e92c4d8aede845126adc388046234541629e76029599ed35a003c7ed24" dependencies = [ "cc", "cfg-if", "getrandom 0.2.15", "libc", - "spin 0.9.8", "untrusted 0.9.0", "windows-sys 0.52.0", ] @@ -7223,7 +7223,7 @@ dependencies = [ "rkyv_derive", "seahash", "tinyvec", - "uuid 1.13.1", + "uuid 1.13.2", ] [[package]] @@ -7388,7 +7388,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.17.8", + "ring 0.17.9", "rustls-webpki 0.101.7", "sct", ] @@ -7401,7 +7401,7 @@ checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" dependencies = [ "log", "once_cell", - "ring 0.17.8", + "ring 0.17.9", "rustls-pki-types", "rustls-webpki 0.102.8", "subtle", @@ -7490,7 +7490,7 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.8", + "ring 0.17.9", "untrusted 0.9.0", ] @@ -7500,7 +7500,7 @@ version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ - "ring 0.17.8", + "ring 0.17.9", "rustls-pki-types", "untrusted 0.9.0", ] @@ -7576,9 +7576,9 @@ checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "ryu-js" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad97d4ce1560a5e27cec89519dc8300d1aa6035b099821261c651486a19e44d5" +checksum = "dd29631678d6fb0903b69223673e122c32e9ae559d0960a38d574695ebc0ea15" [[package]] name = "safetensors" @@ -7614,7 +7614,7 @@ dependencies = [ "serde", "thiserror 1.0.69", "url", - "uuid 1.13.1", + "uuid 1.13.2", ] [[package]] @@ -7652,7 +7652,7 @@ dependencies = [ "schemars_derive", "serde", "serde_json", - "uuid 1.13.1", + "uuid 1.13.2", ] [[package]] @@ -7685,7 +7685,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.8", + "ring 0.17.9", "untrusted 0.9.0", ] @@ -8151,9 +8151,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" dependencies = [ "serde", ] @@ -8387,7 +8387,7 @@ dependencies = [ "tokio-stream", "tracing", "url", - "uuid 1.13.1", + "uuid 1.13.2", "webpki-roots", ] @@ -8471,7 +8471,7 @@ dependencies = [ "stringprep", "thiserror 2.0.11", "tracing", - "uuid 1.13.1", + "uuid 1.13.2", "whoami", ] @@ -8512,7 +8512,7 @@ dependencies = [ "stringprep", "thiserror 2.0.11", "tracing", - "uuid 1.13.1", + "uuid 1.13.2", "whoami", ] @@ -8538,7 +8538,7 @@ dependencies = [ "sqlx-core", "tracing", "url", - "uuid 1.13.1", + "uuid 1.13.2", ] [[package]] @@ -9185,7 +9185,7 @@ dependencies = [ "tempfile", "thiserror 1.0.69", "time", - "uuid 1.13.1", + "uuid 1.13.2", "winapi", ] @@ -9298,9 +9298,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.16.0" +version = "3.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" +checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" dependencies = [ "cfg-if", "fastrand 2.3.0", @@ -9415,7 +9415,7 @@ dependencies = [ "tokio-rustls 0.24.1", "tokio-util", "tracing", - "uuid 1.13.1", + "uuid 1.13.2", ] [[package]] @@ -9763,7 +9763,7 @@ dependencies = [ "http 1.2.0", "httparse", "rand 0.8.5", - "ring 0.17.8", + "ring 0.17.9", "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", @@ -10122,9 +10122,9 @@ dependencies = [ [[package]] name = "tree-sitter-language" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38eee4db33814de3d004de9d8d825627ed3320d0989cce0dea30efaf5be4736c" +checksum = "c4013970217383f67b18aef68f6fb2e8d409bc5755227092d32efb0422ba24b8" [[package]] name = "triomphe" @@ -10196,9 +10196,9 @@ checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "typify" @@ -10251,7 +10251,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab82fc73182c29b02e2926a6df32f2241dbadb5cfc111fd595515b3598f46bb3" dependencies = [ "rand 0.9.0", - "uuid 1.13.1", + "uuid 1.13.2", "web-time", ] @@ -10533,9 +10533,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced87ca4be083373936a67f8de945faa23b6b42384bd5b64434850802c6dccd0" +checksum = "8c1f41ffb7cf259f1ecc2876861a17e7142e63ead296f671f81f6ae85903e0d6" dependencies = [ "getrandom 0.3.1", "serde", @@ -10859,7 +10859,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.460.1" +version = "1.463.5" dependencies = [ "anyhow", "axum", @@ -10888,7 +10888,7 @@ dependencies = [ "tokio", "tracing", "url", - "uuid 1.13.1", + "uuid 1.13.2", "v8", "windmill-api", "windmill-api-client", @@ -10902,7 +10902,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.460.1" +version = "1.463.5" dependencies = [ "anyhow", "argon2", @@ -10983,19 +10983,20 @@ dependencies = [ "ulid", "url", "urlencoding", - "uuid 1.13.1", + "uuid 1.13.2", "windmill-audit", "windmill-common", "windmill-git-sync", "windmill-indexer", "windmill-parser", + "windmill-parser-py", "windmill-parser-ts", "windmill-queue", ] [[package]] name = "windmill-api-client" -version = "1.460.1" +version = "1.463.5" dependencies = [ "base64 0.22.1", "chrono", @@ -11008,12 +11009,12 @@ dependencies = [ "serde", "serde_json", "syn 1.0.109", - "uuid 1.13.1", + "uuid 1.13.2", ] [[package]] name = "windmill-audit" -version = "1.460.1" +version = "1.463.5" dependencies = [ "chrono", "serde", @@ -11026,21 +11027,21 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.460.1" +version = "1.463.5" dependencies = [ "anyhow", "serde", "serde_json", "sqlx", "tracing", - "uuid 1.13.1", + "uuid 1.13.2", "windmill-common", "windmill-queue", ] [[package]] name = "windmill-common" -version = "1.460.1" +version = "1.463.5" dependencies = [ "anyhow", "async-stream", @@ -11093,27 +11094,27 @@ dependencies = [ "tracing-loki", "tracing-opentelemetry", "tracing-subscriber", - "uuid 1.13.1", + "uuid 1.13.2", "windmill-macros", ] [[package]] name = "windmill-git-sync" -version = "1.460.1" +version = "1.463.5" dependencies = [ "regex", "serde", "serde_json", "sqlx", "tracing", - "uuid 1.13.1", + "uuid 1.13.2", "windmill-common", "windmill-queue", ] [[package]] name = "windmill-indexer" -version = "1.460.1" +version = "1.463.5" dependencies = [ "anyhow", "bytes", @@ -11130,13 +11131,13 @@ dependencies = [ "tokio", "tokio-tar", "tracing", - "uuid 1.13.1", + "uuid 1.13.2", "windmill-common", ] [[package]] name = "windmill-macros" -version = "1.460.1" +version = "1.463.5" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -11148,7 +11149,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.460.1" +version = "1.463.5" dependencies = [ "convert_case 0.6.0", "serde", @@ -11157,7 +11158,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.460.1" +version = "1.463.5" dependencies = [ "anyhow", "lazy_static", @@ -11169,7 +11170,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.460.1" +version = "1.463.5" dependencies = [ "anyhow", "serde_json", @@ -11181,7 +11182,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.460.1" +version = "1.463.5" dependencies = [ "anyhow", "gosyn", @@ -11193,7 +11194,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.460.1" +version = "1.463.5" dependencies = [ "anyhow", "lazy_static", @@ -11205,7 +11206,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.460.1" +version = "1.463.5" dependencies = [ "anyhow", "itertools 0.14.0", @@ -11216,7 +11217,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.460.1" +version = "1.463.5" dependencies = [ "anyhow", "itertools 0.14.0", @@ -11227,7 +11228,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.460.1" +version = "1.463.5" dependencies = [ "anyhow", "async-recursion", @@ -11247,7 +11248,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.460.1" +version = "1.463.5" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -11264,7 +11265,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.460.1" +version = "1.463.5" dependencies = [ "anyhow", "lazy_static", @@ -11276,7 +11277,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.460.1" +version = "1.463.5" dependencies = [ "anyhow", "lazy_static", @@ -11294,7 +11295,7 @@ dependencies = [ [[package]] name = "windmill-parser-wasm" -version = "1.460.1" +version = "1.463.5" dependencies = [ "anyhow", "getrandom 0.2.15", @@ -11316,7 +11317,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.460.1" +version = "1.463.5" dependencies = [ "anyhow", "serde_json", @@ -11326,7 +11327,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.460.1" +version = "1.463.5" dependencies = [ "anyhow", "async-recursion", @@ -11352,14 +11353,14 @@ dependencies = [ "tokio", "tracing", "ulid", - "uuid 1.13.1", + "uuid 1.13.2", "windmill-audit", "windmill-common", ] [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.460.1" +version = "1.463.5" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -11369,7 +11370,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.460.1" +version = "1.463.5" dependencies = [ "anyhow", "async-recursion", @@ -11426,7 +11427,7 @@ dependencies = [ "tokio-util", "tracing", "urlencoding", - "uuid 1.13.1", + "uuid 1.13.2", "windmill-audit", "windmill-common", "windmill-git-sync", @@ -11783,11 +11784,11 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.17" +version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa91407dacce3a68c56de03abe2760159582b846c6a4acd2f456618087f12713" +checksum = "79386d31a42a4996e3336b0919ddb90f81112af416270cff95b5f5af22b839c2" dependencies = [ - "zerocopy-derive 0.8.17", + "zerocopy-derive 0.8.18", ] [[package]] @@ -11803,9 +11804,9 @@ dependencies = [ [[package]] name = "zerocopy-derive" -version = "0.8.17" +version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06718a168365cad3d5ff0bb133aad346959a2074bd4a85c121255a11304a8626" +checksum = "76331675d372f91bf8d17e13afbd5fe639200b73d01f0fc748bb059f9cca2db7" dependencies = [ "proc-macro2", "quote", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 21060519be..f3b95d4c78 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.460.1" +version = "1.463.5" authors.workspace = true edition.workspace = true @@ -30,7 +30,7 @@ members = [ ] [workspace.package] -version = "1.460.1" +version = "1.463.5" authors = ["Ruben Fiszel "] edition = "2021" @@ -174,7 +174,7 @@ uuid = { version = "^1", features = ["serde", "v4"] } thiserror = "^2" anyhow = "^1" chrono = { version = "0.4.35", features = ["serde"] } -chrono-tz = "^0" +chrono-tz = "^0.10.1" tracing = "^0" tracing-subscriber = { version = "^0", features = ["env-filter", "json"] } tracing-appender = "^0" @@ -187,7 +187,7 @@ hex = "^0" sql-builder = "^3" argon2 = "^0" quick_cache = "^0" -rand = "^0" +rand = "=0.9.0" rand_core = { version = "^0", features = ["std"] } magic-crypt = "^3" git-version = "^0" diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index b3154d15ef..562c984114 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -8dab3198496461e40610145e4c818fce2345e20a \ No newline at end of file +5d25cf2cd15c1953794045fd7debea14a33c7519 \ No newline at end of file diff --git a/backend/migrations/20250128201251_teams_workspace_command_script.down.sql b/backend/migrations/20250128201251_teams_workspace_command_script.down.sql new file mode 100644 index 0000000000..5b8b721e87 --- /dev/null +++ b/backend/migrations/20250128201251_teams_workspace_command_script.down.sql @@ -0,0 +1,3 @@ +ALTER TABLE workspace_settings DROP COLUMN teams_command_script; +ALTER TABLE workspace_settings DROP COLUMN teams_team_id; +ALTER TABLE workspace_settings DROP COLUMN teams_team_name; \ No newline at end of file diff --git a/backend/migrations/20250128201251_teams_workspace_command_script.up.sql b/backend/migrations/20250128201251_teams_workspace_command_script.up.sql new file mode 100644 index 0000000000..5373b6c57b --- /dev/null +++ b/backend/migrations/20250128201251_teams_workspace_command_script.up.sql @@ -0,0 +1,3 @@ +ALTER TABLE workspace_settings ADD COLUMN teams_command_script TEXT DEFAULT NULL; +ALTER TABLE workspace_settings ADD COLUMN teams_team_id TEXT DEFAULT NULL; +ALTER TABLE workspace_settings ADD COLUMN teams_team_name TEXT DEFAULT NULL; diff --git a/backend/migrations/20250205131518_see_own_fix_job.down.sql b/backend/migrations/20250205131518_see_own_fix_job.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20250205131518_see_own_fix_job.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20250205131518_see_own_fix_job.up.sql b/backend/migrations/20250205131518_see_own_fix_job.up.sql new file mode 100644 index 0000000000..554cf5647b --- /dev/null +++ b/backend/migrations/20250205131518_see_own_fix_job.up.sql @@ -0,0 +1,8 @@ +-- Add up migration script here +DROP POLICY IF EXISTS see_own ON v2_job; +CREATE POLICY see_own ON v2_job + AS PERMISSIVE + FOR ALL + TO windmill_user + USING ((SPLIT_PART((permissioned_as)::TEXT, '/'::TEXT, 1) = 'u'::TEXT) AND + (SPLIT_PART((permissioned_as)::TEXT, '/'::TEXT, 2) = CURRENT_SETTING('session.user'::TEXT))); \ No newline at end of file diff --git a/backend/migrations/20250205131519_windmill_admin_skip_bypassrls_on_v2_job.down.sql b/backend/migrations/20250205131519_windmill_admin_skip_bypassrls_on_v2_job.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20250205131519_windmill_admin_skip_bypassrls_on_v2_job.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20250205131519_windmill_admin_skip_bypassrls_on_v2_job.up.sql b/backend/migrations/20250205131519_windmill_admin_skip_bypassrls_on_v2_job.up.sql new file mode 100644 index 0000000000..f83ee7f348 --- /dev/null +++ b/backend/migrations/20250205131519_windmill_admin_skip_bypassrls_on_v2_job.up.sql @@ -0,0 +1,3 @@ +-- Add up migration script here +DROP POLICY IF EXISTS admin_policy ON v2_job; +CREATE POLICY admin_policy ON v2_job FOR ALL TO windmill_admin USING (true); diff --git a/backend/parsers/windmill-parser-py/src/lib.rs b/backend/parsers/windmill-parser-py/src/lib.rs index 738bc8ed7c..76f0c415ea 100644 --- a/backend/parsers/windmill-parser-py/src/lib.rs +++ b/backend/parsers/windmill-parser-py/src/lib.rs @@ -60,6 +60,7 @@ fn filter_non_main(code: &str, main_name: &str) -> String { pub fn parse_python_signature( code: &str, override_main: Option, + skip_params: bool, ) -> anyhow::Result { let main_name = override_main.unwrap_or("main".to_string()); @@ -78,11 +79,13 @@ pub fn parse_python_signature( let ast = Suite::parse(&filtered_code, "main.py") .map_err(|e| anyhow::anyhow!("Error parsing code: {}", e.to_string()))?; - let param = ast.into_iter().find_map(|x| match x { + let params = ast.into_iter().find_map(|x| match x { Stmt::FunctionDef(StmtFunctionDef { name, args, .. }) if &name == &main_name => Some(*args), _ => None, }); - if let Some(params) = param { + + if !skip_params && params.is_some() { + let params = params.unwrap(); //println!("{:?}", params); let def_arg_start = params.args.len() - params.defaults().count(); Ok(MainArgSignature { @@ -149,7 +152,7 @@ pub fn parse_python_signature( star_args: false, star_kwargs: false, args: vec![], - no_main_func: Some(true), + no_main_func: Some(params.is_none()), has_preprocessor: Some(has_preprocessor), }) } @@ -287,7 +290,7 @@ def main(test1: str, name: datetime.datetime = datetime.now(), byte: bytes = byt "; //println!("{}", serde_json::to_string()?); assert_eq!( - parse_python_signature(code, None)?, + parse_python_signature(code, None, false)?, MainArgSignature { star_args: false, star_kwargs: false, @@ -376,7 +379,7 @@ def main(test1: str, "; //println!("{}", serde_json::to_string()?); assert_eq!( - parse_python_signature(code, None)?, + parse_python_signature(code, None, false)?, MainArgSignature { star_args: false, star_kwargs: false, @@ -436,7 +439,7 @@ def main(test1: str, "; //println!("{}", serde_json::to_string()?); assert_eq!( - parse_python_signature(code, None)?, + parse_python_signature(code, None, false)?, MainArgSignature { star_args: false, star_kwargs: false, @@ -493,7 +496,7 @@ def main(test1: Literal["foo", "bar"], test2: List[Literal["foo", "bar"]]): retu "#; //println!("{}", serde_json::to_string()?); assert_eq!( - parse_python_signature(code, None)?, + parse_python_signature(code, None, false)?, MainArgSignature { star_args: false, star_kwargs: false, @@ -537,7 +540,7 @@ def main(test1: DynSelect_foo): return "#; //println!("{}", serde_json::to_string()?); assert_eq!( - parse_python_signature(code, None)?, + parse_python_signature(code, None, false)?, MainArgSignature { star_args: false, star_kwargs: false, @@ -568,7 +571,7 @@ def hello(): return "#; //println!("{}", serde_json::to_string()?); assert_eq!( - parse_python_signature(code, None)?, + parse_python_signature(code, None, false)?, MainArgSignature { star_args: false, star_kwargs: false, @@ -596,7 +599,7 @@ def main(): return "#; //println!("{}", serde_json::to_string()?); assert_eq!( - parse_python_signature(code, None)?, + parse_python_signature(code, None, false)?, MainArgSignature { star_args: false, star_kwargs: false, @@ -617,10 +620,10 @@ def main(a: list, e: List[int], b: list = [1,2,3,4], c = [1,2,3,4], d = ["a", "b "#; println!( "{}", - serde_json::to_string(&parse_python_signature(code, None)?)? + serde_json::to_string(&parse_python_signature(code, None, false)?)? ); assert_eq!( - parse_python_signature(code, None)?, + parse_python_signature(code, None, false)?, MainArgSignature { star_args: false, star_kwargs: false, diff --git a/backend/parsers/windmill-parser-ts/src/lib.rs b/backend/parsers/windmill-parser-ts/src/lib.rs index f69169c0e2..78a83cd929 100644 --- a/backend/parsers/windmill-parser-ts/src/lib.rs +++ b/backend/parsers/windmill-parser-ts/src/lib.rs @@ -134,6 +134,7 @@ pub fn parse_expr_for_ids(code: &str) -> anyhow::Result> { pub fn parse_deno_signature( code: &str, skip_dflt: bool, + skip_params: bool, main_override: Option, ) -> anyhow::Result { let cm: Lrc = Default::default(); @@ -179,27 +180,26 @@ pub fn parse_deno_signature( }); let mut c: u16 = 0; - if let Some(params) = params { - let r = MainArgSignature { - star_args: false, - star_kwargs: false, - args: params - .into_iter() - .map(|x| parse_param(x, &cm, skip_dflt, &mut c)) - .collect::>>()?, - no_main_func: Some(false), - has_preprocessor: Some(has_preprocessor), - }; - Ok(r) - } else { - Ok(MainArgSignature { - star_args: false, - star_kwargs: false, - args: vec![], - no_main_func: Some(true), - has_preprocessor: Some(has_preprocessor), - }) - } + let no_main_func = params.is_none(); + let r = MainArgSignature { + star_args: false, + star_kwargs: false, + args: if skip_params { + vec![] + } else { + params + .map(|x| { + x.into_iter() + .map(|x| parse_param(x, &cm, skip_dflt, &mut c)) + .collect::>>() + }) + .transpose()? + .unwrap_or_else(|| vec![]) + }, + no_main_func: Some(no_main_func), + has_preprocessor: Some(has_preprocessor), + }; + Ok(r) } fn parse_param( diff --git a/backend/parsers/windmill-parser-wasm/src/lib.rs b/backend/parsers/windmill-parser-wasm/src/lib.rs index e3de739f96..306d67751c 100644 --- a/backend/parsers/windmill-parser-wasm/src/lib.rs +++ b/backend/parsers/windmill-parser-wasm/src/lib.rs @@ -17,10 +17,11 @@ fn wrap_sig(r: anyhow::Result) -> String { #[cfg(feature = "ts-parser")] #[wasm_bindgen] -pub fn parse_deno(code: &str, main_override: Option) -> String { +pub fn parse_deno(code: &str, main_override: Option, skip_params: Option) -> String { wrap_sig(windmill_parser_ts::parse_deno_signature( code, false, + false, main_override, )) } @@ -73,6 +74,7 @@ pub fn parse_python(code: &str, main_override: Option) -> String { wrap_sig(windmill_parser_py::parse_python_signature( code, main_override, + false, )) } diff --git a/backend/parsers/windmill-parser-wasm/tests/wasm.rs b/backend/parsers/windmill-parser-wasm/tests/wasm.rs index 1c201ecd4e..9f6d452a21 100644 --- a/backend/parsers/windmill-parser-wasm/tests/wasm.rs +++ b/backend/parsers/windmill-parser-wasm/tests/wasm.rs @@ -18,7 +18,7 @@ export function main(test1?: string, test2: string = \"burkina\", } "; assert_eq!( - parse_deno_signature(code, false, None)?, + parse_deno_signature(code, false, false, None)?, MainArgSignature { star_args: false, star_kwargs: false, @@ -159,7 +159,7 @@ export function main(test2 = \"burkina\", } "; assert_eq!( - parse_deno_signature(code, false, None)?, + parse_deno_signature(code, false, false, None)?, MainArgSignature { star_args: false, star_kwargs: false, @@ -236,7 +236,7 @@ export function main(foo: FooBar, {a, b}: FooBar, {c, d}: FooBar = {a: \"foo\", } "; assert_eq!( - parse_deno_signature(code, false, None)?, + parse_deno_signature(code, false, false, None)?, MainArgSignature { star_args: false, star_kwargs: false, @@ -282,7 +282,7 @@ export function main(foo: (\"foo\" | \"bar\")[]) { } "; assert_eq!( - parse_deno_signature(code, false, None)?, + parse_deno_signature(code, false, false, None)?, MainArgSignature { star_args: false, star_kwargs: false, diff --git a/backend/pg_log_tail b/backend/pg_log_tail new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/src/main.rs b/backend/src/main.rs index d9ae5c6e58..58d22b6eee 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -21,7 +21,7 @@ use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, time::Duration, }; -use tokio::{fs::File, io::AsyncReadExt}; +use tokio::{fs::File, io::AsyncReadExt, task::JoinHandle}; use uuid::Uuid; use windmill_api::HTTP_CLIENT; @@ -66,12 +66,11 @@ use windmill_common::METRICS_ADDR; use windmill_common::global_settings::OBJECT_STORE_CACHE_CONFIG_SETTING; use windmill_worker::{ - get_hub_script_content_and_requirements, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR, - BUN_DEPSTAR_CACHE_DIR, CSHARP_CACHE_DIR, DENO_CACHE_DIR, DENO_CACHE_DIR_DEPS, - DENO_CACHE_DIR_NPM, GO_BIN_CACHE_DIR, GO_CACHE_DIR, LOCK_CACHE_DIR, PIP_CACHE_DIR, - POWERSHELL_CACHE_DIR, PY310_CACHE_DIR, PY311_CACHE_DIR, PY312_CACHE_DIR, PY313_CACHE_DIR, - RUST_CACHE_DIR, TAR_PIP_CACHE_DIR, TAR_PY310_CACHE_DIR, TAR_PY311_CACHE_DIR, - TAR_PY312_CACHE_DIR, TAR_PY313_CACHE_DIR, UV_CACHE_DIR, + get_hub_script_content_and_requirements, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR, CSHARP_CACHE_DIR, + DENO_CACHE_DIR, DENO_CACHE_DIR_DEPS, DENO_CACHE_DIR_NPM, GO_BIN_CACHE_DIR, GO_CACHE_DIR, + LOCK_CACHE_DIR, PIP_CACHE_DIR, POWERSHELL_CACHE_DIR, PY310_CACHE_DIR, PY311_CACHE_DIR, + PY312_CACHE_DIR, PY313_CACHE_DIR, RUST_CACHE_DIR, TAR_PIP_CACHE_DIR, TAR_PY310_CACHE_DIR, + TAR_PY311_CACHE_DIR, TAR_PY312_CACHE_DIR, TAR_PY313_CACHE_DIR, UV_CACHE_DIR, }; use crate::monitor::{ @@ -373,6 +372,7 @@ async fn windmill_main() -> anyhow::Result<()> { let is_agent = mode == Mode::Agent; + let mut migration_handle: Option> = None; #[cfg(feature = "parquet")] let disable_s3_store = std::env::var("DISABLE_S3_STORE") .ok() @@ -385,7 +385,7 @@ async fn windmill_main() -> anyhow::Result<()> { if !skip_migration { // migration code to avoid break - windmill_api::migrate_db(&db).await?; + migration_handle = windmill_api::migrate_db(&db).await?; } else { tracing::info!("SKIP_MIGRATION set, skipping db migration...") } @@ -683,6 +683,14 @@ Windmill Community Edition {GIT_VERSION} loop { tokio::select! { biased; + Some(_) = async { if let Some(jh) = migration_handle.take() { + tracing::info!("migration job finished"); + Some(jh.await) + } else { + None + }} => { + continue; + }, _ = monitor_killpill_rx.recv() => { tracing::info!("received killpill for monitor job"); break; @@ -1042,7 +1050,6 @@ pub async fn run_workers( TAR_PY312_CACHE_DIR, TAR_PY313_CACHE_DIR, PIP_CACHE_DIR, - BUN_DEPSTAR_CACHE_DIR, BUN_BUNDLE_CACHE_DIR, GO_CACHE_DIR, GO_BIN_CACHE_DIR, diff --git a/backend/substitute_ee_code.sh b/backend/substitute_ee_code.sh index c15d19a259..28b890990b 100755 --- a/backend/substitute_ee_code.sh +++ b/backend/substitute_ee_code.sh @@ -68,7 +68,7 @@ fi if [ "$REVERT" == "YES" ]; then for ee_file in $(find ${EE_CODE_DIR} -name "*ee.rs"); do - ce_file="${ee_file/${EE_CODE_DIR}/.}" + ce_file="${ee_file/${EE_CODE_DIR}/}" ce_file="${root_dirpath}/backend/${ce_file}" if [ "$REVERT_PREVIOUS" == "YES" ]; then git checkout HEAD@{3} ${ce_file} || true @@ -80,7 +80,7 @@ if [ "$REVERT" == "YES" ]; then else # This replaces all files in current repo with alternative EE files in windmill-ee-private for ee_file in $(find "${EE_CODE_DIR}" -name "*ee.rs"); do - ce_file="${ee_file/${EE_CODE_DIR}/.}" + ce_file="${ee_file/${EE_CODE_DIR}/}" ce_file="${root_dirpath}/backend/${ce_file}" if [[ -f "${ce_file}" ]]; then rm "${ce_file}" diff --git a/backend/tests/fixtures/base.sql b/backend/tests/fixtures/base.sql index 0e38b5c7bd..5e96b75b99 100644 --- a/backend/tests/fixtures/base.sql +++ b/backend/tests/fixtures/base.sql @@ -53,183 +53,74 @@ EXECUTE FUNCTION "notify_queue" (); WHEN (NEW.flow_status IS DISTINCT FROM OLD.flow_status) EXECUTE FUNCTION "notify_queue" (); --- TODO(uael): remove before phase 4 -CREATE OR REPLACE FUNCTION zzz_v2_job_queue_integrity_check() RETURNS TRIGGER AS $$ -DECLARE job v2_job; -DECLARE job_runtime v2_job_runtime; -DECLARE job_status v2_job_status; -BEGIN - IF (OLD.canceled_by IS NOT NULL) IS DISTINCT FROM OLD.__canceled THEN - RAISE EXCEPTION 'canceled mismatch'; - END IF; - -- v2_job: - SELECT * INTO job FROM v2_job WHERE id = OLD.id; - IF job.tag IS DISTINCT FROM OLD.tag THEN - RAISE EXCEPTION 'tag mismatch'; - END IF; - IF job.workspace_id IS DISTINCT FROM OLD.workspace_id THEN - RAISE EXCEPTION 'workspace_id mismatch'; - END IF; - IF job.created_at IS DISTINCT FROM OLD.created_at THEN - RAISE EXCEPTION 'created_at mismatch'; - END IF; - IF job.created_by IS DISTINCT FROM OLD.__created_by THEN - RAISE EXCEPTION 'created_by mismatch'; - END IF; - IF job.permissioned_as IS DISTINCT FROM OLD.__permissioned_as THEN - RAISE EXCEPTION 'permissioned_as mismatch'; - END IF; - IF job.permissioned_as_email IS DISTINCT FROM OLD.__email THEN - RAISE EXCEPTION 'permissioned_as_email mismatch'; - END IF; - IF job.kind IS DISTINCT FROM OLD.__job_kind THEN - RAISE EXCEPTION 'kind mismatch'; - END IF; - IF job.runnable_id IS DISTINCT FROM OLD.__script_hash THEN - RAISE EXCEPTION 'runnable_id mismatch'; - END IF; - IF job.runnable_path IS DISTINCT FROM OLD.__script_path THEN - RAISE EXCEPTION 'runnable_path mismatch'; - END IF; - IF job.parent_job IS DISTINCT FROM OLD.__parent_job THEN - RAISE EXCEPTION 'parent_job mismatch'; - END IF; - IF job.script_lang IS DISTINCT FROM OLD.__language THEN - RAISE EXCEPTION 'script_lang mismatch'; - END IF; - IF job.script_entrypoint_override IS DISTINCT FROM NULLIF(OLD.__args->>'_ENTRYPOINT_OVERRIDE', '__WM_PREPROCESSOR') - AND OLD.__args->>'reason' IS DISTINCT FROM 'PREPROCESSOR_ARGS_ARE_DISCARDED' - THEN - RAISE EXCEPTION 'script_entrypoint_override mismatch'; - END IF; - IF job.flow_step_id IS DISTINCT FROM OLD.__flow_step_id THEN - RAISE EXCEPTION 'flow_step_id mismatch'; - END IF; - IF (job.flow_step_id IS NOT NULL) IS DISTINCT FROM OLD.__is_flow_step THEN - RAISE EXCEPTION 'is_flow_step mismatch'; - END IF; - IF job.flow_innermost_root_job IS DISTINCT FROM OLD.__root_job THEN - RAISE EXCEPTION 'flow_innermost_root_job mismatch'; - END IF; - IF job.trigger IS DISTINCT FROM OLD.__schedule_path THEN - RAISE EXCEPTION 'trigger mismatch'; - END IF; - IF job.same_worker IS DISTINCT FROM OLD.__same_worker THEN - RAISE EXCEPTION 'same_worker mismatch'; - END IF; - IF job.visible_to_owner IS DISTINCT FROM OLD.__visible_to_owner THEN - RAISE EXCEPTION 'visible_to_owner mismatch'; - END IF; - IF job.concurrent_limit IS DISTINCT FROM OLD.__concurrent_limit THEN - RAISE EXCEPTION 'concurrent_limit mismatch'; - END IF; - IF job.concurrency_time_window_s IS DISTINCT FROM OLD.__concurrency_time_window_s THEN - RAISE EXCEPTION 'concurrency_time_window_s mismatch'; - END IF; - IF job.cache_ttl IS DISTINCT FROM OLD.__cache_ttl THEN - RAISE EXCEPTION 'cache_ttl mismatch'; - END IF; - IF job.timeout IS DISTINCT FROM OLD.__timeout THEN - RAISE EXCEPTION 'timeout mismatch'; - END IF; - IF job.priority IS DISTINCT FROM OLD.priority THEN - RAISE EXCEPTION 'priority mismatch'; - END IF; - IF job.args::TEXT IS DISTINCT FROM OLD.__args::TEXT AND OLD.__args->>'_ENTRYPOINT_OVERRIDE' IS DISTINCT FROM '__WM_PREPROCESSOR' THEN - RAISE EXCEPTION 'args mismatch'; - END IF; - IF job.pre_run_error IS DISTINCT FROM OLD.__pre_run_error THEN - RAISE EXCEPTION 'pre_run_error mismatch'; - END IF; - -- v2_job_runtime: - SELECT * INTO job_runtime FROM v2_job_runtime WHERE id = OLD.id; - IF job_runtime.ping IS DISTINCT FROM OLD.__last_ping THEN - RAISE EXCEPTION 'ping mismatch'; - END IF; - IF job_runtime.memory_peak IS DISTINCT FROM OLD.__mem_peak THEN - RAISE EXCEPTION 'memory_peak mismatch'; - END IF; - -- v2_job_status: - IF EXISTS(SELECT 1 FROM v2_job_status WHERE id = OLD.id) THEN - SELECT * INTO job_status FROM v2_job_status WHERE id = OLD.id; - IF COALESCE(job_status.flow_status, job_status.workflow_as_code_status)::TEXT IS DISTINCT FROM OLD.__flow_status::TEXT - THEN - RAISE EXCEPTION 'flow_status mismatch'; - END IF; - IF job_status.flow_leaf_jobs::TEXT IS DISTINCT FROM OLD.__leaf_jobs::TEXT THEN - RAISE EXCEPTION 'leaf_jobs mismatch'; - END IF; - END IF; - RETURN OLD; -END $$ LANGUAGE PLPGSQL; +-- Apply phase 4: +DROP FUNCTION IF EXISTS v2_job_after_update CASCADE; +DROP FUNCTION IF EXISTS v2_job_completed_before_insert CASCADE; +DROP FUNCTION IF EXISTS v2_job_completed_before_update CASCADE; +DROP FUNCTION IF EXISTS v2_job_queue_after_insert CASCADE; +DROP FUNCTION IF EXISTS v2_job_queue_before_insert CASCADE; +DROP FUNCTION IF EXISTS v2_job_queue_before_update CASCADE; +DROP FUNCTION IF EXISTS v2_job_runtime_before_insert CASCADE; +DROP FUNCTION IF EXISTS v2_job_runtime_before_update CASCADE; +DROP FUNCTION IF EXISTS v2_job_status_before_insert CASCADE; +DROP FUNCTION IF EXISTS v2_job_status_before_update CASCADE; -CREATE OR REPLACE TRIGGER zzz_v2_job_queue_integrity_check_before_delete - BEFORE DELETE ON v2_job_queue - FOR EACH ROW -EXECUTE FUNCTION zzz_v2_job_queue_integrity_check(); +DROP VIEW IF EXISTS completed_job, completed_job_view, job, queue, queue_view CASCADE; --- TODO(uael): remove before phase 4 -CREATE OR REPLACE FUNCTION zzz_v2_job_completed_integrity_check() RETURNS TRIGGER AS $$ -DECLARE job v2_job; -BEGIN - IF (NEW.canceled_by IS NOT NULL) IS DISTINCT FROM NEW.__canceled THEN - RAISE EXCEPTION 'canceled mismatch'; - END IF; - SELECT * INTO job FROM v2_job WHERE id = NEW.id; - IF job.tag IS DISTINCT FROM NEW.__tag THEN - RAISE EXCEPTION 'tag mismatch % %', job.tag, NEW.__tag; - END IF; - IF job.workspace_id IS DISTINCT FROM NEW.workspace_id THEN - RAISE EXCEPTION 'workspace_id mismatch'; - END IF; - IF job.created_at IS DISTINCT FROM NEW.__created_at THEN - RAISE EXCEPTION 'created_at mismatch'; - END IF; - IF job.created_by IS DISTINCT FROM NEW.__created_by THEN - RAISE EXCEPTION 'created_by mismatch'; - END IF; - IF job.permissioned_as IS DISTINCT FROM NEW.__permissioned_as THEN - RAISE EXCEPTION 'permissioned_as mismatch'; - END IF; - IF job.permissioned_as_email IS DISTINCT FROM NEW.__email THEN - RAISE EXCEPTION 'permissioned_as_email mismatch'; - END IF; - IF job.kind IS DISTINCT FROM NEW.__job_kind THEN - RAISE EXCEPTION 'kind mismatch'; - END IF; - IF job.runnable_id IS DISTINCT FROM NEW.__script_hash THEN - RAISE EXCEPTION 'runnable_id mismatch'; - END IF; - IF job.runnable_path IS DISTINCT FROM NEW.__script_path THEN - RAISE EXCEPTION 'runnable_path mismatch'; - END IF; - IF job.parent_job IS DISTINCT FROM NEW.__parent_job THEN - RAISE EXCEPTION 'parent_job mismatch'; - END IF; - IF job.script_lang IS DISTINCT FROM NEW.__language THEN - RAISE EXCEPTION 'script_lang mismatch'; - END IF; - IF job.script_entrypoint_override IS DISTINCT FROM NULLIF(NEW.__args->>'_ENTRYPOINT_OVERRIDE', '__WM_PREPROCESSOR') - AND NEW.__args->>'reason' IS DISTINCT FROM 'PREPROCESSOR_ARGS_ARE_DISCARDED' - THEN - RAISE EXCEPTION 'script_entrypoint_override mismatch'; - END IF; - IF (job.flow_step_id IS NOT NULL) IS DISTINCT FROM NEW.__is_flow_step THEN - RAISE EXCEPTION 'is_flow_step mismatch'; - END IF; - IF job.trigger IS DISTINCT FROM NEW.__schedule_path THEN - RAISE EXCEPTION 'trigger mismatch'; - END IF; - IF job.visible_to_owner IS DISTINCT FROM NEW.__visible_to_owner THEN - RAISE EXCEPTION 'visible_to_owner mismatch'; - END IF; - IF job.args::TEXT IS DISTINCT FROM NEW.__args::TEXT AND NEW.__args->>'_ENTRYPOINT_OVERRIDE' IS DISTINCT FROM '__WM_PREPROCESSOR' THEN - RAISE EXCEPTION 'args mismatch'; - END IF; - RETURN NEW; -END $$ LANGUAGE PLPGSQL; +ALTER TABLE v2_job_queue + DROP COLUMN IF EXISTS __parent_job CASCADE, + DROP COLUMN IF EXISTS __created_by CASCADE, + DROP COLUMN IF EXISTS __script_hash CASCADE, + DROP COLUMN IF EXISTS __script_path CASCADE, + DROP COLUMN IF EXISTS __args CASCADE, + DROP COLUMN IF EXISTS __logs CASCADE, + DROP COLUMN IF EXISTS __raw_code CASCADE, + DROP COLUMN IF EXISTS __canceled CASCADE, + DROP COLUMN IF EXISTS __last_ping CASCADE, + DROP COLUMN IF EXISTS __job_kind CASCADE, + DROP COLUMN IF EXISTS __env_id CASCADE, + DROP COLUMN IF EXISTS __schedule_path CASCADE, + DROP COLUMN IF EXISTS __permissioned_as CASCADE, + DROP COLUMN IF EXISTS __flow_status CASCADE, + DROP COLUMN IF EXISTS __raw_flow CASCADE, + DROP COLUMN IF EXISTS __is_flow_step CASCADE, + DROP COLUMN IF EXISTS __language CASCADE, + DROP COLUMN IF EXISTS __same_worker CASCADE, + DROP COLUMN IF EXISTS __raw_lock CASCADE, + DROP COLUMN IF EXISTS __pre_run_error CASCADE, + DROP COLUMN IF EXISTS __email CASCADE, + DROP COLUMN IF EXISTS __visible_to_owner CASCADE, + DROP COLUMN IF EXISTS __mem_peak CASCADE, + DROP COLUMN IF EXISTS __root_job CASCADE, + DROP COLUMN IF EXISTS __leaf_jobs CASCADE, + DROP COLUMN IF EXISTS __concurrent_limit CASCADE, + DROP COLUMN IF EXISTS __concurrency_time_window_s CASCADE, + DROP COLUMN IF EXISTS __timeout CASCADE, + DROP COLUMN IF EXISTS __flow_step_id CASCADE, + DROP COLUMN IF EXISTS __cache_ttl CASCADE; -CREATE OR REPLACE TRIGGER zzz_v2_job_completed_integrity_check_after_insert - AFTER INSERT ON v2_job_completed - FOR EACH ROW -EXECUTE FUNCTION zzz_v2_job_completed_integrity_check(); +LOCK TABLE v2_job_queue IN ACCESS EXCLUSIVE MODE; +ALTER TABLE v2_job_completed + DROP COLUMN IF EXISTS __parent_job CASCADE, + DROP COLUMN IF EXISTS __created_by CASCADE, + DROP COLUMN IF EXISTS __created_at CASCADE, + DROP COLUMN IF EXISTS __success CASCADE, + DROP COLUMN IF EXISTS __script_hash CASCADE, + DROP COLUMN IF EXISTS __script_path CASCADE, + DROP COLUMN IF EXISTS __args CASCADE, + DROP COLUMN IF EXISTS __logs CASCADE, + DROP COLUMN IF EXISTS __raw_code CASCADE, + DROP COLUMN IF EXISTS __canceled CASCADE, + DROP COLUMN IF EXISTS __job_kind CASCADE, + DROP COLUMN IF EXISTS __env_id CASCADE, + DROP COLUMN IF EXISTS __schedule_path CASCADE, + DROP COLUMN IF EXISTS __permissioned_as CASCADE, + DROP COLUMN IF EXISTS __raw_flow CASCADE, + DROP COLUMN IF EXISTS __is_flow_step CASCADE, + DROP COLUMN IF EXISTS __language CASCADE, + DROP COLUMN IF EXISTS __is_skipped CASCADE, + DROP COLUMN IF EXISTS __raw_lock CASCADE, + DROP COLUMN IF EXISTS __email CASCADE, + DROP COLUMN IF EXISTS __visible_to_owner CASCADE, + DROP COLUMN IF EXISTS __tag CASCADE, + DROP COLUMN IF EXISTS __priority CASCADE; diff --git a/backend/tests/fixtures/result_format.sql b/backend/tests/fixtures/result_format.sql index 2cf8fa7299..ff99891526 100644 --- a/backend/tests/fixtures/result_format.sql +++ b/backend/tests/fixtures/result_format.sql @@ -4,8 +4,8 @@ INSERT INTO public.v2_job ( '1eecb96a-c8b0-4a3d-b1b6-087878c55e41', 'test-workspace', 'test-user', '2023-01-01 00:00:00', 'script', 'postgresql' ); -INSERT INTO public.completed_job ( - id, workspace_id, created_by, created_at, duration_ms, success, flow_status, result, job_kind, language +INSERT INTO public.v2_job_completed ( + id, workspace_id, duration_ms, status, result_columns, result ) VALUES ( - '1eecb96a-c8b0-4a3d-b1b6-087878c55e41', 'test-workspace', 'test-user', '2023-01-01 00:00:00', 1000, true, '{"_metadata": {"column_order": ["b", "a"]}}', '[{"a": "second", "b": "first"}]', 'script', 'postgresql' + '1eecb96a-c8b0-4a3d-b1b6-087878c55e41', 'test-workspace', 1000, 'success'::job_status, '{b,a}', '[{"a": "second", "b": "first"}]' ) \ No newline at end of file diff --git a/backend/tests/worker.rs b/backend/tests/worker.rs index 7573e139ee..e1f73f5871 100644 --- a/backend/tests/worker.rs +++ b/backend/tests/worker.rs @@ -4005,11 +4005,6 @@ mod job_payload { let args = job.args.as_ref().unwrap(); assert_eq!(args.get("foo"), Some(&json!("bar"))); assert_eq!(args.get("bar"), Some(&json!("baz"))); - // TODO: remove this check on v2 phase 4 - assert_eq!( - job.flow_status.as_ref().unwrap().get("_metadata"), - Some(&json!({"preprocessed_args": true})) - ); assert_eq!(job.json_result().unwrap(), json!("Hello bar baz")); let job = sqlx::query!("SELECT preprocessed FROM v2_job WHERE id = $1", job.id) .fetch_one(db) diff --git a/backend/windmill-api/Cargo.toml b/backend/windmill-api/Cargo.toml index cbecf865de..675aeec0b1 100644 --- a/backend/windmill-api/Cargo.toml +++ b/backend/windmill-api/Cargo.toml @@ -36,6 +36,7 @@ windmill-common = { workspace = true, default-features = false } windmill-audit.workspace = true windmill-parser.workspace = true windmill-parser-ts.workspace = true +windmill-parser-py.workspace = true windmill-git-sync.workspace = true windmill-indexer = { workspace = true, optional = true } tokio.workspace = true diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index a34706a011..c41a002fc8 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.460.1 + version: 1.463.5 title: Windmill API contact: @@ -1728,6 +1728,12 @@ paths: type: string slack_command_script: type: string + teams_team_id: + type: string + teams_command_script: + type: string + teams_team_name: + type: string auto_invite_domain: type: string auto_invite_operator: @@ -1954,6 +1960,110 @@ paths: schema: type: string + /w/{workspace}/workspaces/edit_teams_command: + post: + summary: edit teams command + operationId: editTeamsCommand + tags: + - workspace + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + description: WorkspaceInvite + required: true + content: + application/json: + schema: + type: object + properties: + slack_command_script: + type: string + responses: + "200": + description: status + content: + text/plain: + schema: + type: string + + /w/{workspace}/workspaces/available_teams_ids: + get: + summary: list available teams ids + operationId: listAvailableTeamsIds + tags: + - workspace + parameters: + - $ref: "#/components/parameters/WorkspaceId" + responses: + "200": + description: status + content: + application/json: + schema: + type: array + items: + type: object + properties: + team_name: + type: string + team_id: + type: string + + /w/{workspace}/workspaces/available_teams_channels: + get: + summary: list available teams channels + operationId: listAvailableTeamsChannels + tags: + - workspace + parameters: + - $ref: "#/components/parameters/WorkspaceId" + responses: + "200": + description: status + content: + application/json: + schema: + type: array + items: + type: object + properties: + channel_name: + type: string + channel_id: + type: string + service_url: + type: string + tenant_id: + type: string + + /w/{workspace}/workspaces/connect_teams: + post: + summary: connect teams + operationId: connectTeams + tags: + - workspace + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + description: connect teams + required: true + content: + application/json: + schema: + type: object + properties: + team_id: + type: string + team_name: + type: string + responses: + "200": + description: status + content: + text/plain: + schema: + type: string + /w/{workspace}/workspaces/run_slack_message_test_job: post: summary: run a job that sends a message to Slack @@ -1987,6 +2097,40 @@ paths: properties: job_uuid: type: string + + /w/{workspace}/workspaces/run_teams_message_test_job: + post: + summary: run a job that sends a message to Teams + operationId: runTeamsMessageTestJob + tags: + - workspace + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + description: path to hub script to run and its corresponding args + required: true + content: + application/json: + schema: + type: object + properties: + hub_script_path: + type: string + channel: + type: string + test_msg: + type: string + + responses: + "200": + description: status + content: + text/json: + schema: + type: object + properties: + job_uuid: + type: string /w/{workspace}/workspaces/edit_deploy_to: post: @@ -3199,6 +3343,22 @@ paths: schema: type: string + /w/{workspace}/oauth/disconnect_teams: + post: + summary: disconnect teams + operationId: disconnectTeams + tags: + - oauth + parameters: + - $ref: "#/components/parameters/WorkspaceId" + responses: + "200": + description: disconnected teams + content: + text/plain: + schema: + type: string + /oauth/list_logins: get: summary: list oauth logins @@ -3289,6 +3449,41 @@ paths: items: $ref: '#/components/schemas/TeamInfo' + /teams/activities: + post: + summary: send update to Microsoft Teams activity + description: Respond to a Microsoft Teams activity after a workspace command is run + operationId: sendMessageToConversation + tags: + - teams + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - conversation_id + - text + properties: + conversation_id: + type: string + description: The ID of the Teams conversation/activity + success: + type: boolean + description: Used for styling the card conditionally + default: true + text: + type: string + description: The message text to be sent in the Teams card + card_block: + type: object + description: The card block to be sent in the Teams card + + responses: + '200': + description: Activity processed successfully + /w/{workspace}/resources/create: post: summary: create resource diff --git a/backend/windmill-api/src/db.rs b/backend/windmill-api/src/db.rs index c4ba1d5c5d..9b694ad3bf 100644 --- a/backend/windmill-api/src/db.rs +++ b/backend/windmill-api/src/db.rs @@ -6,20 +6,22 @@ * LICENSE-AGPL for a copy of the license. */ -use futures::FutureExt; -use sqlx::Executor; +use std::time::Duration; +use futures::FutureExt; use sqlx::{ migrate::{Migrate, MigrateError}, pool::PoolConnection, - PgConnection, Pool, Postgres, + Executor, PgConnection, Pool, Postgres, }; + +use tokio::task::JoinHandle; use windmill_audit::audit_ee::{AuditAuthor, AuditAuthorable}; -use windmill_common::utils::generate_lock_id; use windmill_common::{ db::{Authable, Authed}, error::Error, }; +use windmill_common::{utils::generate_lock_id, worker::MIN_VERSION_IS_AT_LEAST_1_461}; pub type DB = Pool; @@ -169,7 +171,7 @@ impl Migrate for CustomMigrator { } } -pub async fn migrate(db: &DB) -> Result<(), Error> { +pub async fn migrate(db: &DB) -> Result>, Error> { let migrator = db.acquire().await?; let mut custom_migrator = CustomMigrator { inner: migrator }; @@ -224,7 +226,31 @@ pub async fn migrate(db: &DB) -> Result<(), Error> { } }); - Ok(()) + let mut jh = None; + if !has_done_migration(db, "v2_finalize_job_completed").await { + let db2 = db.clone(); + let v2jh = tokio::task::spawn(async move { + loop { + if !*MIN_VERSION_IS_AT_LEAST_1_461.read().await { + tracing::info!("Waiting for all workers to be at least version 1.461 before applying v2 finalize migration, sleeping for 5s..."); + tokio::time::sleep(Duration::from_secs(5)).await; + continue; + } + if let Err(err) = v2_finalize(&db2).await { + tracing::error!( + "{err:#}: Could not apply v2 finalize migration, retry in 30s.." + ); + tokio::time::sleep(Duration::from_secs(30)).await; + continue; + } + tracing::info!("v2 finalization step successfully applied."); + break; + } + }); + jh = Some(v2jh) + } + + Ok(jh) } async fn fix_flow_versioning_migration( @@ -274,48 +300,51 @@ async fn fix_flow_versioning_migration( Ok(()) } +async fn has_done_migration(db: &DB, migration_job_name: &str) -> bool { + sqlx::query_scalar!( + "SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)", + migration_job_name + ) + .fetch_one(db) + .await + .ok() + .flatten() + .unwrap_or(false) +} + macro_rules! run_windmill_migration { - ($migration_job_name:expr, $db:expr, $code:block) => { + ($migration_job_name:expr, $db:expr, |$tx:ident| $code:block) => { { let migration_job_name = $migration_job_name; let db: &Pool = $db; - let has_done_migration = sqlx::query_scalar!( - "SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)", - migration_job_name - ) - .fetch_one(db) - .await? - .unwrap_or(false); - if !has_done_migration { + let has_done = has_done_migration(db, migration_job_name).await; + if !has_done { tracing::info!("Applying {migration_job_name} migration"); - let mut tx = db.begin().await?; + let mut $tx = db.begin().await?; let mut r = false; while !r { r = sqlx::query_scalar!("SELECT pg_try_advisory_lock(4242)") - .fetch_one(&mut *tx) + .fetch_one(&mut *$tx) .await .map_err(|e| { tracing::error!("Error acquiring {migration_job_name} lock: {e:#}"); sqlx::migrate::MigrateError::Execute(e) })? .unwrap_or(false); + if !r { tracing::info!("PG {migration_job_name} lock already acquired by another server or worker, retrying in 5s. (look for the advisory lock in pg_lock with granted = true)"); + drop($tx); tokio::time::sleep(std::time::Duration::from_secs(5)).await; + $tx = db.begin().await?; } } tracing::info!("acquired lock for {migration_job_name}"); - let has_done_migration = sqlx::query_scalar!( - "SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)", - migration_job_name - ) - .fetch_one(db) - .await? - .unwrap_or(false); + let has_done = has_done_migration(db, migration_job_name).await; - if !has_done_migration { + if !has_done { $code @@ -323,7 +352,7 @@ macro_rules! run_windmill_migration { "INSERT INTO windmill_migrations (name) VALUES ($1) ON CONFLICT DO NOTHING", migration_job_name ) - .execute(&mut *tx) + .execute(&mut *$tx) .await?; tracing::info!("Finished applying {migration_job_name} migration"); } else { @@ -331,9 +360,9 @@ macro_rules! run_windmill_migration { } let _ = sqlx::query("SELECT pg_advisory_unlock(4242)") - .execute(&mut *tx) + .execute(&mut *$tx) .await?; - tx.commit().await?; + $tx.commit().await?; tracing::info!("released lock for {migration_job_name}"); } else { tracing::debug!("migration {migration_job_name} already done"); @@ -343,6 +372,165 @@ macro_rules! run_windmill_migration { }; } +async fn v2_finalize(db: &DB) -> Result<(), Error> { + run_windmill_migration!("v2_finalize_disable_sync_III", db, |tx| { + tx.execute( + r#" + LOCK TABLE v2_job_queue IN ACCESS EXCLUSIVE MODE; + ALTER TABLE v2_job_queue DISABLE ROW LEVEL SECURITY; + "#, + ) + .await?; + }); + + run_windmill_migration!("v2_finalize_disable_sync_III_2", db, |tx| { + tx.execute( + r#" + LOCK TABLE v2_job_completed IN ACCESS EXCLUSIVE MODE; + ALTER TABLE v2_job_completed DISABLE ROW LEVEL SECURITY; + "#, + ) + .await?; + }); + + run_windmill_migration!("v2_finalize_disable_sync_III_3", db, |tx| { + tx.execute( + r#" + LOCK TABLE v2_job IN ACCESS EXCLUSIVE MODE; + DROP FUNCTION IF EXISTS v2_job_after_update CASCADE; + "#, + ) + .await?; + }); + + run_windmill_migration!("v2_finalize_disable_sync_III_4", db, |tx| { + tx.execute( + r#" + LOCK TABLE v2_job_completed IN ACCESS EXCLUSIVE MODE; + DROP FUNCTION IF EXISTS v2_job_completed_before_insert CASCADE; + DROP FUNCTION IF EXISTS v2_job_completed_before_update CASCADE; + "#, + ) + .await?; + }); + + run_windmill_migration!("v2_finalize_disable_sync_III_5", db, |tx| { + tx.execute( + r#" + LOCK TABLE v2_job_queue IN ACCESS EXCLUSIVE MODE; + DROP FUNCTION IF EXISTS v2_job_queue_after_insert CASCADE; + DROP FUNCTION IF EXISTS v2_job_queue_before_insert CASCADE; + DROP FUNCTION IF EXISTS v2_job_queue_before_update CASCADE; + "#, + ) + .await?; + }); + + run_windmill_migration!("v2_finalize_disable_sync_III_6", db, |tx| { + tx.execute( + r#" + LOCK TABLE v2_job_runtime IN ACCESS EXCLUSIVE MODE; + DROP FUNCTION IF EXISTS v2_job_runtime_before_insert CASCADE; + DROP FUNCTION IF EXISTS v2_job_runtime_before_update CASCADE; + "#, + ) + .await?; + }); + + run_windmill_migration!("v2_finalize_disable_sync_III_7", db, |tx| { + tx.execute( + r#" + LOCK TABLE v2_job_status IN ACCESS EXCLUSIVE MODE; + DROP FUNCTION IF EXISTS v2_job_status_before_insert CASCADE; + DROP FUNCTION IF EXISTS v2_job_status_before_update CASCADE; + "#, + ) + .await?; + }); + + run_windmill_migration!("v2_finalize_disable_sync_III_8", db, |tx| { + tx.execute( + r#" + DROP VIEW IF EXISTS completed_job, completed_job_view, job, queue, queue_view CASCADE; + "#, + ) + .await?; + }); + + run_windmill_migration!("v2_finalize_job_queue", db, |tx| { + tx.execute( + r#" + LOCK TABLE v2_job_queue IN ACCESS EXCLUSIVE MODE; + ALTER TABLE v2_job_queue + DROP COLUMN IF EXISTS __parent_job CASCADE, + DROP COLUMN IF EXISTS __created_by CASCADE, + DROP COLUMN IF EXISTS __script_hash CASCADE, + DROP COLUMN IF EXISTS __script_path CASCADE, + DROP COLUMN IF EXISTS __args CASCADE, + DROP COLUMN IF EXISTS __logs CASCADE, + DROP COLUMN IF EXISTS __raw_code CASCADE, + DROP COLUMN IF EXISTS __canceled CASCADE, + DROP COLUMN IF EXISTS __last_ping CASCADE, + DROP COLUMN IF EXISTS __job_kind CASCADE, + DROP COLUMN IF EXISTS __env_id CASCADE, + DROP COLUMN IF EXISTS __schedule_path CASCADE, + DROP COLUMN IF EXISTS __permissioned_as CASCADE, + DROP COLUMN IF EXISTS __flow_status CASCADE, + DROP COLUMN IF EXISTS __raw_flow CASCADE, + DROP COLUMN IF EXISTS __is_flow_step CASCADE, + DROP COLUMN IF EXISTS __language CASCADE, + DROP COLUMN IF EXISTS __same_worker CASCADE, + DROP COLUMN IF EXISTS __raw_lock CASCADE, + DROP COLUMN IF EXISTS __pre_run_error CASCADE, + DROP COLUMN IF EXISTS __email CASCADE, + DROP COLUMN IF EXISTS __visible_to_owner CASCADE, + DROP COLUMN IF EXISTS __mem_peak CASCADE, + DROP COLUMN IF EXISTS __root_job CASCADE, + DROP COLUMN IF EXISTS __leaf_jobs CASCADE, + DROP COLUMN IF EXISTS __concurrent_limit CASCADE, + DROP COLUMN IF EXISTS __concurrency_time_window_s CASCADE, + DROP COLUMN IF EXISTS __timeout CASCADE, + DROP COLUMN IF EXISTS __flow_step_id CASCADE, + DROP COLUMN IF EXISTS __cache_ttl CASCADE; + "#, + ) + .await?; + }); + run_windmill_migration!("v2_finalize_job_completed", db, |tx| { + tx.execute( + r#" + LOCK TABLE v2_job_completed IN ACCESS EXCLUSIVE MODE; + ALTER TABLE v2_job_completed + DROP COLUMN IF EXISTS __parent_job CASCADE, + DROP COLUMN IF EXISTS __created_by CASCADE, + DROP COLUMN IF EXISTS __created_at CASCADE, + DROP COLUMN IF EXISTS __success CASCADE, + DROP COLUMN IF EXISTS __script_hash CASCADE, + DROP COLUMN IF EXISTS __script_path CASCADE, + DROP COLUMN IF EXISTS __args CASCADE, + DROP COLUMN IF EXISTS __logs CASCADE, + DROP COLUMN IF EXISTS __raw_code CASCADE, + DROP COLUMN IF EXISTS __canceled CASCADE, + DROP COLUMN IF EXISTS __job_kind CASCADE, + DROP COLUMN IF EXISTS __env_id CASCADE, + DROP COLUMN IF EXISTS __schedule_path CASCADE, + DROP COLUMN IF EXISTS __permissioned_as CASCADE, + DROP COLUMN IF EXISTS __raw_flow CASCADE, + DROP COLUMN IF EXISTS __is_flow_step CASCADE, + DROP COLUMN IF EXISTS __language CASCADE, + DROP COLUMN IF EXISTS __is_skipped CASCADE, + DROP COLUMN IF EXISTS __raw_lock CASCADE, + DROP COLUMN IF EXISTS __email CASCADE, + DROP COLUMN IF EXISTS __visible_to_owner CASCADE, + DROP COLUMN IF EXISTS __tag CASCADE, + DROP COLUMN IF EXISTS __priority CASCADE; + "#, + ) + .await?; + }); + Ok(()) +} + async fn fix_job_completed_index(db: &DB) -> Result<(), Error> { // let has_done_migration = sqlx::query_scalar!( // "SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = 'fix_job_completed_index')" @@ -385,7 +573,7 @@ async fn fix_job_completed_index(db: &DB) -> Result<(), Error> { // tx.commit().await?; // } - run_windmill_migration!("fix_job_completed_index_2", &db, { + run_windmill_migration!("fix_job_completed_index_2", &db, |tx| { // sqlx::query( // "CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_completed_job_workspace_id_created_at_new_2 ON completed_job (workspace_id, job_kind, success, is_skipped, is_flow_step, created_at DESC)" // ).execute(db).await?; @@ -405,7 +593,7 @@ async fn fix_job_completed_index(db: &DB) -> Result<(), Error> { .await?; }); - run_windmill_migration!("fix_job_completed_index_3", &db, { + run_windmill_migration!("fix_job_completed_index_3", &db, |tx| { sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS index_completed_job_on_schedule_path") .execute(db) .await?; @@ -423,8 +611,8 @@ async fn fix_job_completed_index(db: &DB) -> Result<(), Error> { .await?; }); - run_windmill_migration!("fix_job_index_1", &db, { - let migration_job_name = "fix_job_completed_index_4"; + run_windmill_migration!("fix_job_index_1_II", &db, |tx| { + let migration_job_name = "fix_job_index_1_II"; let mut i = 1; tracing::info!("step {i} of {migration_job_name} migration"); sqlx::query!("create index concurrently if not exists ix_job_workspace_id_created_at_new_3 ON v2_job (workspace_id, created_at DESC)") @@ -451,31 +639,26 @@ async fn fix_job_completed_index(db: &DB) -> Result<(), Error> { i += 1; tracing::info!("step {i} of {migration_job_name} migration"); - sqlx::query!("create index concurrently if not exists ix_job_workspace_id_created_at_new_6 ON v2_job (workspace_id, created_at DESC) where kind in ('script', 'flow') AND parent_job IS NULL") - .execute(db) - .await?; - i += 1; - tracing::info!("step {i} of {migration_job_name} migration"); - - sqlx::query!("create index concurrently if not exists ix_job_workspace_id_created_at_new_7 ON v2_job (workspace_id, created_at DESC) where kind in ('script', 'flow') AND parent_job IS NULL") - .execute(db) - .await?; - i += 1; - tracing::info!("step {i} of {migration_job_name} migration"); - sqlx::query!("create index concurrently if not exists ix_completed_job_workspace_id_started_at_new_2 ON v2_job_completed (workspace_id, started_at DESC)") .execute(db) .await?; i += 1; tracing::info!("step {i} of {migration_job_name} migration"); - sqlx::query!("create index concurrently if not exists root_job_index_by_path_2 ON v2_job (workspace_id, runnable_path, created_at desc) WHERE parent_job IS NULL") + sqlx::query!("create index concurrently if not exists ix_job_root_job_index_by_path_2 ON v2_job (workspace_id, runnable_path, created_at desc) WHERE parent_job IS NULL") .execute(db) .await?; i += 1; tracing::info!("step {i} of {migration_job_name} migration"); + sqlx::query!("DROP INDEX CONCURRENTLY IF EXISTS root_job_index_by_path_2") + .execute(db) + .await?; + + i += 1; + tracing::info!("step {i} of {migration_job_name} migration"); + sqlx::query!("create index concurrently if not exists ix_job_created_at ON v2_job (created_at DESC)") .execute(db) .await?; @@ -504,82 +687,7 @@ async fn fix_job_completed_index(db: &DB) -> Result<(), Error> { .await?; }); - run_windmill_migration!("add_ix_v2_II", &db, { - sqlx::query!( - "create index concurrently if not exists ix_v2_job_root_by_path - on v2_job (workspace_id, runnable_path, created_at DESC) - where parent_job is null" - ) - .execute(db) - .await?; - - sqlx::query!( - "DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new_3" - ) - .execute(db) - .await?; - - sqlx::query!( - "DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new_5" - ) - .execute(db) - .await?; - - sqlx::query!( - "DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new_6" - ) - .execute(db) - .await?; - - sqlx::query!( - "DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new_7" - ) - .execute(db) - .await?; - - sqlx::query!( - "DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new_8" - ) - .execute(db) - .await?; - - sqlx::query!( - "DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new_9" - ) - .execute(db) - .await?; - - sqlx::query!("DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_created_at") - .execute(db) - .await?; - - sqlx::query!( - "DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new_2" - ) - .execute(db) - .await?; - - sqlx::query!( - "DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_started_at_new" - ) - .execute(db) - .await?; - - sqlx::query!("DROP INDEX CONCURRENTLY IF EXISTS root_job_index_by_path_2") - .execute(db) - .await?; - - sqlx::query!("DROP INDEX CONCURRENTLY IF EXISTS scheduled_root_job") - .execute(db) - .await?; - - sqlx::query!("DROP INDEX CONCURRENTLY IF EXISTS concurrency_limit_stats_completed_job") - .execute(db) - .await?; - tracing::info!("Finished adding ix_v2_II migration"); - }); - - run_windmill_migration!("fix_labeled_jobs_index", &db, { + run_windmill_migration!("fix_labeled_jobs_index", &db, |tx| { tracing::info!("Special migration to add index concurrently on job labels 2"); sqlx::query!("DROP INDEX CONCURRENTLY IF EXISTS labeled_jobs_on_jobs") .execute(db) @@ -589,7 +697,7 @@ async fn fix_job_completed_index(db: &DB) -> Result<(), Error> { ).execute(db).await?; }); - run_windmill_migration!("v2_labeled_jobs_index", &db, { + run_windmill_migration!("v2_labeled_jobs_index", &db, |tx| { tracing::info!("Special migration to add index concurrently on job labels"); sqlx::query!( "CREATE INDEX CONCURRENTLY ix_v2_job_labels ON v2_job @@ -600,6 +708,43 @@ async fn fix_job_completed_index(db: &DB) -> Result<(), Error> { .await?; }); + run_windmill_migration!("v2_jobs_rls", &db, |tx| { + sqlx::query!("ALTER TABLE v2_job ENABLE ROW LEVEL SECURITY") + .execute(db) + .await?; + }); + + run_windmill_migration!("v2_improve_v2_job_indices_ii", &db, |tx| { + sqlx::query!("create index concurrently if not exists ix_v2_job_workspace_id_created_at ON v2_job (workspace_id, created_at DESC) where kind in ('script', 'flow', 'singlescriptflow') AND parent_job IS NULL") + .execute(db) + .await?; + + sqlx::query!("DROP INDEX CONCURRENTLY IF EXISTS ix_job_workspace_id_created_at_new_6") + .execute(db) + .await?; + + sqlx::query!("DROP INDEX CONCURRENTLY IF EXISTS ix_job_workspace_id_created_at_new_7") + .execute(db) + .await?; + }); + + run_windmill_migration!("v2_improve_v2_queued_jobs_indices", &db, |tx| { + sqlx::query!("CREATE INDEX CONCURRENTLY queue_sort_v2 ON v2_job_queue (priority DESC NULLS LAST, scheduled_for, tag) WHERE running = false") + .execute(db) + .await?; + + // sqlx::query!("CREATE INDEX CONCURRENTLY queue_sort_2_v2 ON v2_job_queue (tag, priority DESC NULLS LAST, scheduled_for) WHERE running = false") + // .execute(db) + // .await?; + + sqlx::query!("DROP INDEX CONCURRENTLY IF EXISTS queue_sort") + .execute(db) + .await?; + + sqlx::query!("DROP INDEX CONCURRENTLY IF EXISTS queue_sort_2") + .execute(db) + .await?; + }); Ok(()) } diff --git a/backend/windmill-api/src/http_triggers.rs b/backend/windmill-api/src/http_triggers.rs index 0d6be258e1..722a639842 100644 --- a/backend/windmill-api/src/http_triggers.rs +++ b/backend/windmill-api/src/http_triggers.rs @@ -538,7 +538,7 @@ async fn get_http_route_trigger( let route_path = trigger.route_path.clone(); if trigger.is_static_website { router - .insert(format!("{}/*wm_subpath", route_path), idx) + .insert(format!("/{}/*wm_subpath", route_path), idx) .unwrap_or_else(|e| { tracing::warn!( "Failed to consider http trigger route {}: {:?}", @@ -547,19 +547,22 @@ async fn get_http_route_trigger( ); }); } - router.insert(route_path.as_str(), idx).unwrap_or_else(|e| { - tracing::warn!( - "Failed to consider http trigger route {}: {:?}", - route_path, - e, - ); - }); + router + .insert(format!("/{}", route_path), idx) + .unwrap_or_else(|e| { + tracing::warn!( + "Failed to consider http trigger route {}: {:?}", + route_path, + e, + ); + }); } - let trigger_idx = router.at(route_path.0.as_str()).ok(); + let requested_path = format!("/{}", route_path.0); + let trigger_idx = router.at(requested_path.as_str()).ok(); let matchit::Match { value: trigger_idx, params } = - not_found_if_none(trigger_idx, "Trigger", route_path.0.as_str())?; + not_found_if_none(trigger_idx, "Trigger", requested_path.as_str())?; let trigger = triggers.remove(trigger_idx.to_owned()); diff --git a/backend/windmill-api/src/inputs.rs b/backend/windmill-api/src/inputs.rs index 1ce3fa9226..ec200b4464 100644 --- a/backend/windmill-api/src/inputs.rs +++ b/backend/windmill-api/src/inputs.rs @@ -82,9 +82,9 @@ impl RunnableType { fn column_name(&self) -> &'static str { match self { - RunnableType::ScriptHash => "script_hash", - RunnableType::ScriptPath => "script_path", - RunnableType::FlowPath => "script_path", + RunnableType::ScriptHash => "runnable_id", + RunnableType::ScriptPath => "runnable_path", + RunnableType::FlowPath => "runnable_path", } } } @@ -133,9 +133,9 @@ async fn get_input_history( let mut tx = user_db.begin(&authed).await?; let sql = &format!( - "select id, created_at, created_by, 'null'::jsonb as args, success from v2_as_completed_job \ - where {} = $1 and job_kind = any($2) and workspace_id = $3 \ - order by created_at desc limit $4 offset $5", + "select id, v2_job.created_at, created_by, 'null'::jsonb as args, status = 'success' as success from v2_job JOIN v2_job_completed USING (id) \ + where {} = $1 and kind = any($2) and v2_job.workspace_id = $3 AND v2_job_completed.status != 'skipped' \ + order by v2_job.created_at desc limit $4 offset $5", r.runnable_type.column_name() ); diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index 90caf92518..b5e1bc5a08 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -717,7 +717,12 @@ macro_rules! get_job_query { const_format::formatcp!( "SELECT \ id, {table}.workspace_id, parent_job, created_by, {table}.created_at, started_at, script_hash, script_path, \ - CASE WHEN args is null or pg_column_size(args) < 90000 THEN args ELSE '{{\"reason\": \"WINDMILL_TOO_BIG\"}}'::jsonb END as args, \ + CASE WHEN args is null THEN NULL + WHEN pg_column_size(args) < 90000 THEN + CASE WHEN jsonb_typeof(args) = 'object' THEN args + ELSE jsonb_build_object('value', args) + END + ELSE '{{\"reason\": \"WINDMILL_TOO_BIG\"}}'::jsonb END as args, \ {logs} as logs, {code} as raw_code, canceled, canceled_by, canceled_reason, job_kind, \ schedule_path, permissioned_as, flow_status, {flow} as raw_flow, is_flow_step, language, \ {lock} as raw_lock, email, visible_to_owner, mem_peak, tag, priority, preprocessed, {additional_fields} \ @@ -1156,7 +1161,7 @@ pub struct ListableCompletedJob { pub parent_job: Option, pub created_by: String, pub created_at: chrono::DateTime, - pub started_at: chrono::DateTime, + pub started_at: Option>, pub duration_ms: i64, pub success: bool, #[serde(skip_serializing_if = "Option::is_none")] @@ -1294,27 +1299,30 @@ pub fn filter_list_queue_query( w_id: &str, join_outstanding_wait_times: bool, ) -> SqlBuilder { + sqlb.join("v2_job").on_eq("v2_job_queue.id", "v2_job.id"); + if join_outstanding_wait_times { sqlb.left() .join("outstanding_wait_time") - .on_eq("id", "outstanding_wait_time.job_id"); + .on_eq("v2_job.id", "outstanding_wait_time.job_id"); } if w_id != "admins" || !lq.all_workspaces.is_some_and(|x| x) { - sqlb.and_where_eq("workspace_id", "?".bind(&w_id)); + sqlb.and_where_eq("v2_job.workspace_id", "?".bind(&w_id)); } if let Some(ps) = &lq.script_path_start { - sqlb.and_where_like_left("script_path", ps); + sqlb.and_where_like_left("runnable_path", ps); } if let Some(p) = &lq.script_path_exact { - sqlb.and_where_eq("script_path", "?".bind(p)); + sqlb.and_where_eq("runnable_path", "?".bind(p)); } if let Some(p) = &lq.schedule_path { - sqlb.and_where_eq("schedule_path", "?".bind(p)); + sqlb.and_where_eq("trigger", "?".bind(p)); + sqlb.and_where_eq("trigger_kind", "'schedule'"); } if let Some(h) = &lq.script_hash { - sqlb.and_where_eq("script_hash", "?".bind(h)); + sqlb.and_where_eq("runnable_id", "?".bind(h)); } if let Some(cb) = &lq.created_by { sqlb.and_where_eq("created_by", "?".bind(cb)); @@ -1335,7 +1343,11 @@ pub fn filter_list_queue_query( sqlb.and_where_ge("started_at", "?".bind(&dt.to_rfc3339())); } if let Some(fs) = &lq.is_flow_step { - sqlb.and_where_eq("is_flow_step", fs); + if *fs { + sqlb.and_where_is_not_null("flow_step_id"); + } else { + sqlb.and_where_is_null("flow_step_id"); + } } if let Some(fs) = &lq.has_null_parent { if *fs { @@ -1344,20 +1356,20 @@ pub fn filter_list_queue_query( } if let Some(dt) = &lq.created_before { - sqlb.and_where_le("created_at", "?".bind(&dt.to_rfc3339())); + sqlb.and_where_le("v2_job.created_at", "?".bind(&dt.to_rfc3339())); } if let Some(dt) = &lq.created_after { - sqlb.and_where_ge("created_at", "?".bind(&dt.to_rfc3339())); + sqlb.and_where_ge("v2_job.created_at", "?".bind(&dt.to_rfc3339())); } if let Some(dt) = &lq.created_or_started_after { let ts = dt.timestamp_millis(); - sqlb.and_where(format!("(started_at IS NOT NULL AND started_at >= to_timestamp({} / 1000.0)) OR (started_at IS NULL AND created_at >= to_timestamp({} / 1000.0))", ts, ts)); + sqlb.and_where(format!("(started_at IS NOT NULL AND started_at >= to_timestamp({} / 1000.0)) OR (started_at IS NULL AND v2_job.created_at >= to_timestamp({} / 1000.0))", ts, ts)); } if let Some(dt) = &lq.created_or_started_before { let ts = dt.timestamp_millis(); - sqlb.and_where(format!("(started_at IS NOT NULL AND started_at < to_timestamp({} / 1000.0)) OR (started_at IS NULL AND created_at < to_timestamp({} / 1000.0))", ts, ts)); + sqlb.and_where(format!("(started_at IS NOT NULL AND started_at < to_timestamp({} / 1000.0)) OR (started_at IS NULL AND v2_job.created_at < to_timestamp({} / 1000.0))", ts, ts)); } if let Some(s) = &lq.suspended { @@ -1370,7 +1382,7 @@ pub fn filter_list_queue_query( if let Some(jk) = &lq.job_kinds { sqlb.and_where_in( - "job_kind", + "kind", &jk.split(',').into_iter().map(quote).collect::>(), ); } @@ -1384,7 +1396,8 @@ pub fn filter_list_queue_query( } if lq.is_not_schedule.unwrap_or(false) { - sqlb.and_where("schedule_path IS null"); + sqlb.and_where("trigger_kind != 'schedule'") + .or_where("trigger_kind IS NULL"); } sqlb @@ -1399,9 +1412,9 @@ pub fn list_queue_jobs_query( tags: Option>, ) -> SqlBuilder { let (limit, offset) = paginate_without_limits(pagination); - let mut sqlb = SqlBuilder::select_from("v2_as_queue") + let mut sqlb = SqlBuilder::select_from("v2_job_queue") .fields(fields) - .order_by("created_at", lq.order_desc.unwrap_or(true)) + .order_by("v2_job.created_at", lq.order_desc.unwrap_or(true)) .limit(limit) .offset(offset) .clone(); @@ -1446,26 +1459,25 @@ async fn list_queue_jobs( &w_id, &lq, &[ - "id", - "running", - "created_by", - "created_at", - "started_at", - "scheduled_for", - "script_hash", - "script_path", + "v2_job.id", + "v2_job_queue.running", + "v2_job.created_by", + "v2_job.created_at", + "v2_job_queue.started_at", + "v2_job_queue.scheduled_for", + "v2_job.runnable_id as script_hash", + "v2_job.runnable_path as script_path", "null as args", - "job_kind", - "schedule_path", - "permissioned_as", - "is_flow_step", - "language", - "same_worker", - "email", - "suspend", - "tag", - "priority", - "workspace_id", + "v2_job.kind as job_kind", + "CASE WHEN v2_job.trigger_kind = 'schedule' THEN v2_job.trigger END as schedule_path", + "v2_job.permissioned_as", + "v2_job.flow_step_id IS NOT NULL as is_flow_step", + "v2_job.script_lang as language", + "v2_job.permissioned_as_email as email", + "v2_job_queue.suspend", + "v2_job.tag", + "v2_job.priority", + "v2_job.workspace_id", ], pagination, false, @@ -1605,13 +1617,14 @@ async fn list_filtered_uuids( ) -> error::JsonResult> { require_admin(authed.is_admin, &authed.username)?; - let mut sqlb = SqlBuilder::select_from("v2_as_queue") - .fields(&["id"]) + let mut sqlb = SqlBuilder::select_from("v2_job_queue") + .fields(&["v2_job_queue.id"]) .clone(); sqlb = join_concurrency_key(lq.concurrency_key.as_ref(), sqlb); - sqlb.and_where_is_null("schedule_path"); + sqlb.and_where_ne("v2_job.trigger_kind", "'schedule'") + .or_where_is_null("v2_job.trigger_kind"); if let Some(tags) = get_scope_tags(&authed) { sqlb.and_where_in("tag", &tags.iter().map(|x| quote(x)).collect::>()); @@ -1625,7 +1638,7 @@ async fn list_filtered_uuids( Ok(Json(jobs)) } -#[derive(Serialize, Debug, FromRow)] +#[derive(Serialize)] struct QueueStats { database_length: i64, suspended: Option, @@ -1634,6 +1647,7 @@ struct QueueStats { #[derive(Deserialize)] pub struct CountQueueJobsQuery { all_workspaces: Option, + tags: Option, } async fn count_queue_jobs( @@ -1641,12 +1655,16 @@ async fn count_queue_jobs( Path(w_id): Path, Query(cq): Query, ) -> error::JsonResult { + let tags = cq + .tags + .map(|t| t.split(',').map(|s| s.to_string()).collect::>()); Ok(Json( sqlx::query_as!( QueueStats, - "SELECT coalesce(COUNT(*) FILTER(WHERE suspend = 0 AND running = false), 0) as \"database_length!\", coalesce(COUNT(*) FILTER(WHERE suspend > 0), 0) as \"suspended!\" FROM v2_as_queue WHERE (workspace_id = $1 OR $2) AND scheduled_for <= now()", + "SELECT coalesce(COUNT(*) FILTER(WHERE suspend = 0 AND running = false), 0) as \"database_length!\", coalesce(COUNT(*) FILTER(WHERE suspend > 0), 0) as \"suspended!\" FROM v2_as_queue WHERE (workspace_id = $1 OR $2) AND scheduled_for <= now() AND ($3::text[] IS NULL OR tag = ANY($3))", w_id, w_id == "admins" && cq.all_workspaces.unwrap_or(false), + tags.as_ref().map(|v| v.as_slice()) ) .fetch_one(&db) .await?, @@ -1666,23 +1684,28 @@ async fn count_completed_jobs_detail( Path(w_id): Path, Query(query): Query, ) -> error::JsonResult { - let mut sqlb = SqlBuilder::select_from("v2_as_completed_job"); + let mut sqlb = SqlBuilder::select_from("v2_job_completed"); + //FOR RLS + sqlb.join("v2_job USING (id)"); sqlb.field("COUNT(*) as count"); if !query.all_workspaces.unwrap_or(false) { - sqlb.and_where_eq("workspace_id", "?".bind(&w_id)); + sqlb.and_where_eq("v2_job.workspace_id", "?".bind(&w_id)); } if let Some(after_s_ago) = query.completed_after_s_ago { let after = Utc::now() - chrono::Duration::seconds(after_s_ago); - sqlb.and_where_gt( - "started_at + duration_ms / 1000 * interval '1 second'", - "?".bind(&after.to_rfc3339()), - ); + sqlb.and_where_gt("ended_at", "?".bind(&after.to_rfc3339())); } if let Some(success) = query.success { - sqlb.and_where_eq("success", "?".bind(&success)); + if success { + sqlb.and_where_eq("status", "'success'") + .or_where_eq("status", "'skipped'"); + } else { + sqlb.and_where_ne("status", "'success'") + .and_where_ne("status", "'skipped'"); + } } if let Some(tags) = query.tags { @@ -1782,13 +1805,13 @@ async fn list_jobs( } sqlc.unwrap().limit(per_page).offset(offset).query()? }; - let mut tx = user_db.begin(&authed).await?; + let mut tx: Transaction<'_, Postgres> = user_db.begin(&authed).await?; #[cfg(feature = "prometheus")] let start = Instant::now(); #[cfg(feature = "prometheus")] - if _api_list_jobs_query_duration.is_some() { + if _api_list_jobs_query_duration.is_some() || true { tracing::info!("list_jobs query: {}", sql); } @@ -2651,77 +2674,78 @@ pub struct UnifiedJob { const CJ_FIELDS: &[&str] = &[ "'CompletedJob' as typ", - "id", - "workspace_id", - "parent_job", - "created_by", - "created_at", - "started_at", + "v2_job.id", + "v2_job.workspace_id", + "v2_job.parent_job", + "v2_job.created_by", + "v2_job.created_at", + "v2_job_completed.started_at", "null as scheduled_for", "null as running", - "script_hash", - "script_path", + "v2_job.runnable_id as script_hash", + "v2_job.runnable_path as script_path", "null as args", - "duration_ms", - "success", - "deleted", - "canceled", - "canceled_by", - "job_kind", - "schedule_path", - "permissioned_as", - "is_flow_step", - "language", - "is_skipped", - "email", - "visible_to_owner", + "v2_job_completed.duration_ms", + "v2_job_completed.status = 'success' OR v2_job_completed.status = 'skipped' as success", + "false as deleted", + "v2_job_completed.status = 'canceled' as canceled", + "v2_job_completed.canceled_by", + "v2_job.kind as job_kind", + "CASE WHEN v2_job.trigger_kind = 'schedule' THEN v2_job.trigger END as schedule_path", + "v2_job.permissioned_as", + "v2_job.flow_step_id IS NOT NULL as is_flow_step", + "v2_job.script_lang as language", + "v2_job_completed.status = 'skipped' as is_skipped", + "v2_job.permissioned_as_email as email", + "v2_job.visible_to_owner", "null as suspend", - "mem_peak", - "tag", + "v2_job_completed.memory_peak as mem_peak", + "v2_job.tag", "null as concurrent_limit", "null as concurrency_time_window_s", - "priority", - "result->'wm_labels' as labels", + "v2_job.priority", + "v2_job_completed.result->'wm_labels' as labels", "self_wait_time_ms", "aggregate_wait_time_ms", - "preprocessed", + "v2_job.preprocessed", ]; + const QJ_FIELDS: &[&str] = &[ "'QueuedJob' as typ", - "id", - "workspace_id", - "parent_job", - "created_by", - "created_at", - "started_at", - "scheduled_for", - "running", - "script_hash", - "script_path", + "v2_job.id", + "v2_job.workspace_id", + "v2_job.parent_job", + "v2_job.created_by", + "v2_job.created_at", + "v2_job_queue.started_at", + "v2_job_queue.scheduled_for", + "v2_job_queue.running", + "v2_job.runnable_id as script_hash", + "v2_job.runnable_path as script_path", "null as args", "null as duration_ms", "null as success", "false as deleted", - "canceled", - "canceled_by", - "job_kind", - "schedule_path", - "permissioned_as", - "is_flow_step", - "language", + "v2_job_queue.canceled_by IS NOT NULL as canceled", + "v2_job_queue.canceled_by", + "v2_job.kind as job_kind", + "CASE WHEN v2_job.trigger_kind = 'schedule' THEN v2_job.trigger END as schedule_path", + "v2_job.permissioned_as", + "v2_job.flow_step_id IS NOT NULL as is_flow_step", + "v2_job.script_lang as language", "false as is_skipped", - "email", - "visible_to_owner", - "suspend", - "mem_peak", - "tag", - "concurrent_limit", - "concurrency_time_window_s", - "priority", + "v2_job.permissioned_as_email as email", + "v2_job.visible_to_owner", + "v2_job_queue.suspend", + "null as mem_peak", + "v2_job.tag", + "v2_job.concurrent_limit", + "v2_job.concurrency_time_window_s", + "v2_job.priority", "null as labels", "self_wait_time_ms", "aggregate_wait_time_ms", - "preprocessed", + "v2_job.preprocessed", ]; impl UnifiedJob { @@ -4676,6 +4700,7 @@ struct BatchInfo { flow_value: Option, path: Option, rawscript: Option, + tag: Option, } #[tracing::instrument(level = "trace", skip_all)] @@ -4844,6 +4869,8 @@ async fn add_batch_jobs( } else { format!("{}", language.as_str()) } + } else if let Some(tag) = batch_info.tag { + tag } else { format!("{}", language.as_str()) }; @@ -5253,10 +5280,13 @@ pub fn filter_list_completed_query( w_id: &str, join_outstanding_wait_times: bool, ) -> SqlBuilder { + sqlb.join("v2_job") + .on_eq("v2_job_completed.id", "v2_job.id"); + if join_outstanding_wait_times { sqlb.left() .join("outstanding_wait_time") - .on_eq("id", "outstanding_wait_time.job_id"); + .on_eq("v2_job.id", "outstanding_wait_time.job_id"); } if let Some(label) = &lq.label { @@ -5267,21 +5297,22 @@ pub fn filter_list_completed_query( } if w_id != "admins" || !lq.all_workspaces.is_some_and(|x| x) { - sqlb.and_where_eq("workspace_id", "?".bind(&w_id)); + sqlb.and_where_eq("v2_job.workspace_id", "?".bind(&w_id)); } if let Some(p) = &lq.schedule_path { - sqlb.and_where_eq("schedule_path", "?".bind(p)); + sqlb.and_where_eq("trigger", "?".bind(p)); + sqlb.and_where_eq("trigger_kind", "'schedule'"); } if let Some(ps) = &lq.script_path_start { - sqlb.and_where_like_left("script_path", ps); + sqlb.and_where_like_left("runnable_path", ps); } if let Some(p) = &lq.script_path_exact { - sqlb.and_where_eq("script_path", "?".bind(p)); + sqlb.and_where_eq("runnable_path", "?".bind(p)); } if let Some(h) = &lq.script_hash { - sqlb.and_where_eq("script_hash", "?".bind(h)); + sqlb.and_where_eq("runnable_id", "?".bind(h)); } if let Some(t) = &lq.tag { sqlb.and_where_eq("tag", "?".bind(t)); @@ -5290,7 +5321,13 @@ pub fn filter_list_completed_query( sqlb.and_where_eq("created_by", "?".bind(cb)); } if let Some(r) = &lq.success { - sqlb.and_where_eq("success", r); + if *r { + sqlb.and_where_eq("status", "'success'") + .or_where_eq("status", "'skipped'"); + } else { + sqlb.and_where_eq("status", "'failure'") + .or_where_eq("status", "'canceled'"); + } } if let Some(pj) = &lq.parent_job { sqlb.and_where_eq("parent_job", "?".bind(pj)); @@ -5321,10 +5358,18 @@ pub fn filter_list_completed_query( } if let Some(sk) = &lq.is_skipped { - sqlb.and_where_eq("is_skipped", sk); + if *sk { + sqlb.and_where_eq("status", "'skipped'"); + } else { + sqlb.and_where_ne("status", "'skipped'"); + } } if let Some(fs) = &lq.is_flow_step { - sqlb.and_where_eq("is_flow_step", fs); + if *fs { + sqlb.and_where_is_not_null("flow_step_id"); + } else { + sqlb.and_where_is_null("flow_step_id"); + } } if let Some(fs) = &lq.has_null_parent { if *fs { @@ -5333,7 +5378,7 @@ pub fn filter_list_completed_query( } if let Some(jk) = &lq.job_kinds { sqlb.and_where_in( - "job_kind", + "kind", &jk.split(',').into_iter().map(quote).collect::>(), ); } @@ -5347,7 +5392,8 @@ pub fn filter_list_completed_query( } if lq.is_not_schedule.unwrap_or(false) { - sqlb.and_where("schedule_path IS null"); + sqlb.and_where("trigger_kind != 'schedule'") + .or_where("trigger_kind IS NULL"); } sqlb @@ -5362,9 +5408,9 @@ pub fn list_completed_jobs_query( join_outstanding_wait_times: bool, tags: Option>, ) -> SqlBuilder { - let mut sqlb = SqlBuilder::select_from("v2_as_completed_job") + let mut sqlb = SqlBuilder::select_from("v2_job_completed") .fields(fields) - .order_by("created_at", lq.order_desc.unwrap_or(true)) + .order_by("v2_job.created_at", lq.order_desc.unwrap_or(true)) .offset(offset) .limit(per_page) .clone(); @@ -5427,35 +5473,35 @@ async fn list_completed_jobs( offset, &lq, &[ - "id", - "workspace_id", - "parent_job", - "created_by", - "created_at", - "started_at", - "duration_ms", - "success", - "script_hash", - "script_path", - "deleted", - "canceled", - "canceled_by", - "canceled_reason", - "job_kind", - "schedule_path", - "permissioned_as", + "v2_job.id", + "v2_job.workspace_id", + "v2_job.parent_job", + "v2_job.created_by", + "v2_job.created_at", + "v2_job_completed.started_at", + "v2_job_completed.duration_ms", + "v2_job_completed.status = 'success' OR v2_job_completed.status = 'skipped' as success", + "v2_job.runnable_id as script_hash", + "v2_job.runnable_path as script_path", + "false as deleted", + "v2_job_completed.status = 'canceled' as canceled", + "v2_job_completed.canceled_by", + "v2_job_completed.canceled_reason", + "v2_job.kind as job_kind", + "CASE WHEN v2_job.trigger_kind = 'schedule' THEN v2_job.trigger END as schedule_path", + "v2_job.permissioned_as", "null as raw_code", "null as flow_status", "null as raw_flow", - "is_flow_step", - "language", - "is_skipped", - "email", - "visible_to_owner", - "mem_peak", - "tag", - "priority", - "result->'wm_labels' as labels", + "v2_job.flow_step_id IS NOT NULL as is_flow_step", + "v2_job.script_lang as language", + "v2_job_completed.status = 'skipped' as is_skipped", + "v2_job.permissioned_as_email as email", + "v2_job.visible_to_owner", + "v2_job_completed.memory_peak as mem_peak", + "v2_job.tag", + "v2_job.priority", + "v2_job_completed.result->'wm_labels' as labels", "'CompletedJob' as type", ], false, diff --git a/backend/windmill-api/src/lib.rs b/backend/windmill-api/src/lib.rs index 7ab057292f..4199494ed4 100644 --- a/backend/windmill-api/src/lib.rs +++ b/backend/windmill-api/src/lib.rs @@ -34,6 +34,7 @@ use http::HeaderValue; use reqwest::Client; #[cfg(feature = "oauth2")] use std::collections::HashMap; +use tokio::task::JoinHandle; use windmill_common::global_settings::load_value_from_global_settings; use windmill_common::global_settings::EMAIL_DOMAIN_SETTING; use windmill_common::worker::HUB_CACHE_DIR; @@ -106,7 +107,6 @@ mod slack_approvals; mod smtp_server_ee; mod static_assets; mod stripe_ee; -#[cfg(feature = "enterprise")] mod teams_ee; mod tracing_init; mod triggers; @@ -642,7 +642,8 @@ async fn openapi_json() -> &'static str { include_str!("../openapi-deref.json") } -pub async fn migrate_db(db: &DB) -> anyhow::Result<()> { - db::migrate(db).await?; - Ok(()) +pub async fn migrate_db(db: &DB) -> anyhow::Result>> { + db::migrate(db) + .await + .map_err(|e| anyhow::anyhow!("Error migrating db: {e:#}")) } diff --git a/backend/windmill-api/src/schedule.rs b/backend/windmill-api/src/schedule.rs index a6b6abf7dc..6754d5cfba 100644 --- a/backend/windmill-api/src/schedule.rs +++ b/backend/windmill-api/src/schedule.rs @@ -341,6 +341,7 @@ pub struct ScheduleLight { pub script_path: String, pub is_flow: bool, pub summary: Option, + pub extra_perms: serde_json::Value, } async fn list_schedule( authed: ApiAuthed, @@ -351,7 +352,7 @@ async fn list_schedule( let mut tx = user_db.begin(&authed).await?; let (per_page, offset) = paginate(Pagination { per_page: lsq.per_page, page: lsq.page }); let mut sqlb = SqlBuilder::select_from("schedule") - .field("workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, summary") + .field("workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, summary, extra_perms") .order_by("edited_at", true) .and_where("workspace_id = ?".bind(&w_id)) .offset(offset) diff --git a/backend/windmill-api/src/scripts.rs b/backend/windmill-api/src/scripts.rs index cbeb13e106..0fd80d0fe4 100644 --- a/backend/windmill-api/src/scripts.rs +++ b/backend/windmill-api/src/scripts.rs @@ -625,6 +625,19 @@ async fn create_script_internal<'c>( } else { ns.language.clone() }; + + let (no_main_func, has_preprocessor) = match lang { + ScriptLang::Bun | ScriptLang::Bunnative | ScriptLang::Deno | ScriptLang::Nativets => { + let args = windmill_parser_ts::parse_deno_signature(&ns.content, true, true, None)?; + (args.no_main_func, args.has_preprocessor) + } + ScriptLang::Python3 => { + let args = windmill_parser_py::parse_python_signature(&ns.content, None, true)?; + (args.no_main_func, args.has_preprocessor) + } + _ => (ns.no_main_func, ns.has_preprocessor), + }; + sqlx::query!( "INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, \ content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, \ @@ -660,9 +673,9 @@ async fn create_script_internal<'c>( ns.timeout, ns.concurrency_key, ns.visible_to_runner_only, - ns.no_main_func, + no_main_func.filter(|x| *x), // should be Some(true) or None codebase, - ns.has_preprocessor, + has_preprocessor.filter(|x| *x), // should be Some(true) or None if ns.on_behalf_of_email.is_some() { Some(&authed.email) } else { diff --git a/backend/windmill-api/src/teams_ee.rs b/backend/windmill-api/src/teams_ee.rs index 2933d4214c..46cbe72059 100644 --- a/backend/windmill-api/src/teams_ee.rs +++ b/backend/windmill-api/src/teams_ee.rs @@ -1,5 +1,39 @@ +use http::status::StatusCode; +#[cfg(feature = "enterprise")] use axum::Router; +use windmill_common::error::Error; +pub async fn edit_teams_command() -> Result { + return Err(Error::BadRequest( + "Teams only available on enterprise".to_string(), + )); +} + +pub async fn workspaces_list_available_teams_ids() -> Result { + return Err(Error::BadRequest( + "Teams only available on enterprise".to_string(), + )); +} + +pub async fn connect_teams() -> Result { + return Err(Error::BadRequest( + "Teams only available on enterprise".to_string(), + )); +} + +pub async fn run_teams_message_test_job() -> Result { + return Err(Error::BadRequest( + "Teams only available on enterprise".to_string(), + )); +} + +pub async fn workspaces_list_available_teams_channels() -> Result { + return Err(Error::BadRequest( + "Teams only available on enterprise".to_string(), + )); +} + +#[cfg(feature = "enterprise")] pub fn teams_service() -> Router { Router::new() } \ No newline at end of file diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index f832b85f14..f5043052e9 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -58,6 +58,11 @@ use sqlx::{FromRow, Postgres, Transaction}; use windmill_common::oauth2::InstanceEvent; use windmill_common::utils::not_found_if_none; +use crate::teams_ee::{ + connect_teams, edit_teams_command, run_teams_message_test_job, + workspaces_list_available_teams_channels, workspaces_list_available_teams_ids, +}; + lazy_static::lazy_static! { static ref WORKSPACE_KEY_REGEXP: Regex = Regex::new("^[a-zA-Z0-9]{64}$").unwrap(); } @@ -73,10 +78,24 @@ pub fn workspaced_service() -> Router { .route("/get_settings", get(get_settings)) .route("/get_deploy_to", get(get_deploy_to)) .route("/edit_slack_command", post(edit_slack_command)) + .route("/edit_teams_command", post(edit_teams_command)) + .route( + "/available_teams_ids", + get(workspaces_list_available_teams_ids), + ) + .route( + "/available_teams_channels", + get(workspaces_list_available_teams_channels), + ) + .route("/connect_teams", post(connect_teams)) .route( "/run_slack_message_test_job", post(run_slack_message_test_job), ) + .route( + "/run_teams_message_test_job", + post(run_teams_message_test_job), + ) .route("/edit_webhook", post(edit_webhook)) .route("/edit_auto_invite", post(edit_auto_invite)) .route("/edit_deploy_to", post(edit_deploy_to)) @@ -168,9 +187,14 @@ pub struct WorkspaceSettings { #[serde(skip_serializing_if = "Option::is_none")] pub slack_team_id: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub teams_team_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub teams_team_name: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub slack_name: Option, #[serde(skip_serializing_if = "Option::is_none")] pub slack_command_script: Option, + pub teams_command_script: Option, pub slack_email: String, #[serde(skip_serializing_if = "Option::is_none")] pub auto_invite_domain: Option, @@ -188,7 +212,7 @@ pub struct WorkspaceSettings { pub deploy_to: Option, #[serde(skip_serializing_if = "Option::is_none")] pub ai_resource: Option, - pub ai_models: Vec, + pub ai_models: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub code_completion_model: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -429,7 +453,7 @@ async fn get_settings( let mut tx = user_db.begin(&authed).await?; let settings = sqlx::query_as!( WorkspaceSettings, - "SELECT * FROM workspace_settings WHERE workspace_id = $1", + "SELECT workspace_id, slack_team_id, teams_team_id, teams_team_name, slack_name, slack_command_script, teams_command_script, slack_email, auto_invite_domain, auto_invite_operator, auto_add, customer_id, plan, webhook, deploy_to, ai_resource, ai_models, code_completion_model, error_handler, error_handler_extra_args, error_handler_muted_on_cancel, large_file_storage, git_sync, deploy_ui, default_app, automatic_billing, default_scripts, mute_critical_alerts, color, operator_settings FROM workspace_settings WHERE workspace_id = $1", &w_id ) .fetch_one(&mut *tx) @@ -1380,9 +1404,15 @@ async fn list_workspaces_as_super_admin( let mut tx = user_db.begin(&authed).await?; let workspaces = sqlx::query_as!( Workspace, - "SELECT workspace.id, workspace.name, workspace.owner, workspace.deleted, workspace.premium, workspace_settings.color - FROM workspace - LEFT JOIN workspace_settings ON workspace.id = workspace_settings.workspace_id + "SELECT + workspace.id AS \"id!\", + workspace.name AS \"name!\", + workspace.owner AS \"owner!\", + workspace.deleted AS \"deleted!\", + workspace.premium AS \"premium!\", + workspace_settings.color AS \"color\" + FROM workspace + LEFT JOIN workspace_settings ON workspace.id = workspace_settings.workspace_id LIMIT $1 OFFSET $2", per_page as i32, offset as i32 diff --git a/backend/windmill-common/src/utils.rs b/backend/windmill-common/src/utils.rs index 735c3d4a27..0a6117999a 100644 --- a/backend/windmill-common/src/utils.rs +++ b/backend/windmill-common/src/utils.rs @@ -47,8 +47,11 @@ lazy_static::lazy_static! { .connect_timeout(std::time::Duration::from_secs(10)) .build().unwrap(); pub static ref GIT_SEM_VERSION: Version = Version::parse( - // skip first `v` character. - GIT_VERSION.split_at(1).1 + if GIT_VERSION.starts_with('v') { + &GIT_VERSION[1..] + } else { + GIT_VERSION + } ).unwrap_or(Version::new(0, 1, 0)); } diff --git a/backend/windmill-common/src/worker.rs b/backend/windmill-common/src/worker.rs index 23b5ed8f3e..eb5bd1d6f1 100644 --- a/backend/windmill-common/src/worker.rs +++ b/backend/windmill-common/src/worker.rs @@ -95,6 +95,7 @@ lazy_static::lazy_static! { .unwrap_or(false); pub static ref MIN_VERSION: Arc> = Arc::new(RwLock::new(Version::new(0, 0, 0))); + pub static ref MIN_VERSION_IS_AT_LEAST_1_461: Arc> = Arc::new(RwLock::new(false)); pub static ref MIN_VERSION_IS_AT_LEAST_1_427: Arc> = Arc::new(RwLock::new(false)); pub static ref MIN_VERSION_IS_AT_LEAST_1_432: Arc> = Arc::new(RwLock::new(false)); pub static ref MIN_VERSION_IS_AT_LEAST_1_440: Arc> = Arc::new(RwLock::new(false)); @@ -103,6 +104,8 @@ lazy_static::lazy_static! { pub static ref DISABLE_FLOW_SCRIPT: bool = std::env::var("DISABLE_FLOW_SCRIPT").ok().is_some_and(|x| x == "1" || x == "true"); } +pub static MIN_VERSION_IS_LATEST: AtomicBool = AtomicBool::new(false); + fn format_pull_query(peek: String) -> String { let r = format!( "WITH peek AS ( @@ -654,7 +657,7 @@ pub async fn update_min_version<'c, E: sqlx::Executor<'c, Database = sqlx::Postg let min_version = pings .iter() .filter(|x| !x.is_empty()) - .filter_map(|x| semver::Version::parse(x.split_at(1).1).ok()) + .filter_map(|x| semver::Version::parse(if x.starts_with('v') { &x[1..] } else { x }).ok()) .min() .unwrap_or_else(|| cur_version.clone()); @@ -662,6 +665,7 @@ pub async fn update_min_version<'c, E: sqlx::Executor<'c, Database = sqlx::Postg tracing::info!("Minimal worker version: {min_version}"); } + *MIN_VERSION_IS_AT_LEAST_1_461.write().await = min_version >= Version::new(1, 461, 0); *MIN_VERSION_IS_AT_LEAST_1_427.write().await = min_version >= Version::new(1, 427, 0); *MIN_VERSION_IS_AT_LEAST_1_432.write().await = min_version >= Version::new(1, 432, 0); *MIN_VERSION_IS_AT_LEAST_1_440.write().await = min_version >= Version::new(1, 440, 0); diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index 2d2025da92..e582fa39dc 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -566,6 +566,9 @@ pub async fn add_completed_job( let result_columns = result_columns.as_ref(); let _job_id = queued_job.id; let (opt_uuid, _duration, _skip_downstream_error_handlers) = (|| async { + + // let start = std::time::Instant::now(); + let mut tx = db.begin().await?; let job_id = queued_job.id; @@ -663,7 +666,7 @@ pub async fn add_completed_job( // tracing::error!("Added completed job {:#?}", queued_job); let mut _skip_downstream_error_handlers = false; - tx = delete_job(tx, &queued_job.workspace_id, job_id).await?; + tx = delete_job(tx, &job_id).await?; // tracing::error!("3 {:?}", start.elapsed()); if queued_job.is_flow_step { @@ -858,6 +861,7 @@ pub async fn add_completed_job( "inserted completed job: {} (success: {success})", queued_job.id ); + // tracing::info!("completed job: {:?}", start.elapsed().as_micros()); Ok((None, _duration, _skip_downstream_error_handlers)) as windmill_common::error::Result<(Option, i64, bool)> }) .retry( @@ -2108,12 +2112,15 @@ async fn pull_single_job_and_mark_as_running_no_concurrency_limit<'c>( for query in queries.iter() { // tracing::info!("Pulling job with query: {}", query); + // let instant = std::time::Instant::now(); let r = sqlx::query_as::<_, PulledJob>(query) .bind(worker_name) .fetch_optional(db) .await?; if let Some(pulled_job) = r { + // tracing::info!("pulled job: {:?}", instant.elapsed().as_micros()); + highest_priority_job = Some(pulled_job); break; } @@ -2594,21 +2601,17 @@ async fn extract_result_from_job_result( pub async fn delete_job<'c>( mut tx: Transaction<'c, Postgres>, - w_id: &str, - job_id: Uuid, + job_id: &Uuid, ) -> windmill_common::error::Result> { #[cfg(feature = "prometheus")] if METRICS_ENABLED.load(std::sync::atomic::Ordering::Relaxed) { QUEUE_DELETE_COUNT.inc(); } - let job_removed = sqlx::query_scalar!( - "DELETE FROM v2_job_queue WHERE workspace_id = $1 AND id = $2 RETURNING 1", - w_id, - job_id - ) - .fetch_optional(&mut *tx) - .await; + let job_removed = + sqlx::query_scalar!("DELETE FROM v2_job_queue WHERE id = $1 RETURNING 1", job_id,) + .fetch_optional(&mut *tx) + .await; if let Err(job_removed) = job_removed { tracing::error!( diff --git a/backend/windmill-worker/src/bun_executor.rs b/backend/windmill-worker/src/bun_executor.rs index 4fc471df91..ede17da758 100644 --- a/backend/windmill-worker/src/bun_executor.rs +++ b/backend/windmill-worker/src/bun_executor.rs @@ -1,14 +1,12 @@ #[cfg(feature = "deno_core")] use std::time::Instant; -use std::{collections::HashMap, fs, path::Path, process::Stdio}; +use std::{collections::HashMap, fs, process::Stdio}; -use anyhow::Context; use base64::Engine; use itertools::Itertools; use serde_json::value::RawValue; -use sha2::Digest; use uuid::Uuid; use windmill_parser_ts::remove_pinned_imports; use windmill_queue::{append_logs, CanceledBy}; @@ -23,8 +21,8 @@ use crate::{ }, handle_child::handle_child, AuthedClientBackgroundTask, BUNFIG_INSTALL_SCOPES, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR, - BUN_DEPSTAR_CACHE_DIR, BUN_PATH, DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NODE_BIN_PATH, - NODE_PATH, NPM_CONFIG_REGISTRY, NPM_PATH, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, TZ_ENV, + BUN_PATH, DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NODE_BIN_PATH, NODE_PATH, + NPM_CONFIG_REGISTRY, NPM_PATH, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, TZ_ENV, }; #[cfg(windows)] @@ -637,51 +635,6 @@ pub async fn pull_codebase(_w_id: &str, _id: &str, _job_dir: &str) -> Result<()> )); } -#[cfg(unix)] -pub fn copy_recursively( - source: impl AsRef, - destination: impl AsRef, - skip: Option<&Vec>, -) -> Result<()> { - let mut stack = Vec::new(); - stack.push(( - source.as_ref().to_path_buf(), - destination.as_ref().to_path_buf(), - 0, - )); - while let Some((current_source, current_destination, level)) = stack.pop() { - for entry in fs::read_dir(¤t_source) - .context(format!("reading directory {current_source:?}"))? - { - let entry = entry?; - let filetype = entry.file_type()?; - let destination = current_destination.join(entry.file_name()); - if level == 0 { - if let Some(skip) = skip { - if skip.contains(&entry.file_name().to_string_lossy().to_string()) { - continue; - } - } - } - - let original = entry.path(); - - if filetype.is_dir() { - fs::create_dir_all(&destination)?; - stack.push((entry.path(), destination, level + 1)); - } else { - fs::hard_link(&original, &destination).map_err(|e| { - error::Error::internal_err(format!( - "hard linking from {original:?} to {destination:?}: {e:#}" - )) - })?; - } - } - } - - Ok(()) -} - pub async fn prebundle_bun_script( inner_content: &str, lockfile: Option<&String>, @@ -889,7 +842,6 @@ pub async fn handle_bun_job( )); } - let mut gbuntar_name: Option = None; if has_bundle_cache { let target; let symlink; @@ -924,67 +876,23 @@ pub async fn handle_bun_job( let _ = write_file(job_dir, "package.json", pkg)?; let lock = if annotation.npm { "" } else { lock.unwrap() }; if !empty { - let mut skip_install = false; - let mut create_buntar = false; - let mut buntar_path = "".to_string(); - if !annotation.npm { let _ = write_lock(lock, job_dir, is_binary).await?; - - let mut sha_path = sha2::Sha256::new(); - sha_path.update(lock.as_bytes()); - - let buntar_name = - base64::engine::general_purpose::URL_SAFE.encode(sha_path.finalize()); - buntar_path = format!("{BUN_DEPSTAR_CACHE_DIR}/{buntar_name}"); - - #[cfg(unix)] - if tokio::fs::metadata(&buntar_path).await.is_ok() { - if let Err(e) = copy_recursively(&buntar_path, job_dir, None) { - tracing::error!("Could not extract buntar: {e:#}"); - } else { - gbuntar_name = Some(buntar_name.clone()); - skip_install = true; - } - } else { - create_buntar = true; - } } - if !skip_install { - install_bun_lockfile( - mem_peak, - canceled_by, - &job.id, - &job.workspace_id, - Some(db), - job_dir, - worker_name, - common_bun_proc_envs.clone(), - annotation.npm, - &mut Some(occupancy_metrics), - ) - .await?; - - #[cfg(unix)] - if create_buntar { - fs::create_dir_all(&buntar_path)?; - if let Err(e) = copy_recursively( - job_dir, - &buntar_path, - Some(&vec![ - "main.ts".to_string(), - "package.json".to_string(), - if is_binary { "bun.lockb" } else { "bun.lock" }.to_string(), - "shared".to_string(), - "bunfig.toml".to_string(), - ]), - ) { - fs::remove_dir_all(&buntar_path).context("deleting buntar directory")?; - tracing::error!("Could not create buntar: {e}"); - } - } - } + install_bun_lockfile( + mem_peak, + canceled_by, + &job.id, + &job.workspace_id, + Some(db), + job_dir, + worker_name, + common_bun_proc_envs.clone(), + annotation.npm, + &mut Some(occupancy_metrics), + ) + .await?; } } else { // if !*DISABLE_NSJAIL || !empty_trusted_deps || has_custom_config_registry { @@ -1031,13 +939,6 @@ pub async fn handle_bun_job( "\n\n--- BUN CODE EXECUTION ---\n".to_string() }; - if let Some(gbuntar_name) = gbuntar_name { - init_logs = format!( - "\nskipping install, using cached buntar based on lockfile hash: {gbuntar_name}{}", - init_logs - ); - } - if has_bundle_cache { init_logs = format!("\n{}{}", cache_logs, init_logs); } @@ -1050,6 +951,7 @@ pub async fn handle_bun_job( let args = windmill_parser_ts::parse_deno_signature( inner_content, true, + false, main_override.map(ToString::to_string), )? .args; @@ -1059,6 +961,7 @@ pub async fn handle_bun_job( windmill_parser_ts::parse_deno_signature( inner_content, true, + false, Some("preprocessor".to_string()), )? .args, @@ -1672,7 +1575,7 @@ pub async fn start_worker( { // let mut start = Instant::now(); - let args = windmill_parser_ts::parse_deno_signature(inner_content, true, None)?.args; + let args = windmill_parser_ts::parse_deno_signature(inner_content, true, false, None)?.args; let dates = args .iter() .filter_map(|x| { diff --git a/backend/windmill-worker/src/deno_executor.rs b/backend/windmill-worker/src/deno_executor.rs index 6a7bed5c38..40350fdeee 100644 --- a/backend/windmill-worker/src/deno_executor.rs +++ b/backend/windmill-worker/src/deno_executor.rs @@ -205,6 +205,7 @@ pub async fn handle_deno_job( let args = windmill_parser_ts::parse_deno_signature( inner_content, true, + false, main_override.map(ToString::to_string), )? .args; @@ -214,6 +215,7 @@ pub async fn handle_deno_job( windmill_parser_ts::parse_deno_signature( inner_content, true, + false, Some("preprocessor".to_string()), )? .args, @@ -533,7 +535,7 @@ pub async fn start_worker( { // let mut start = Instant::now(); - let args = windmill_parser_ts::parse_deno_signature(inner_content, true, None)?.args; + let args = windmill_parser_ts::parse_deno_signature(inner_content, true, false, None)?.args; let dates = args .iter() .filter_map(|x| { diff --git a/backend/windmill-worker/src/js_eval.rs b/backend/windmill-worker/src/js_eval.rs index 9aab144296..50f13788e7 100644 --- a/backend/windmill-worker/src/js_eval.rs +++ b/backend/windmill-worker/src/js_eval.rs @@ -775,7 +775,7 @@ pub async fn eval_fetch_timeout( let (sender, mut receiver) = oneshot::channel::(); - let parsed_args = windmill_parser_ts::parse_deno_signature(&ts_expr, true, None)?.args; + let parsed_args = windmill_parser_ts::parse_deno_signature(&ts_expr, true, false, None)?.args; let spread = parsed_args .into_iter() .map(|x| { diff --git a/backend/windmill-worker/src/python_executor.rs b/backend/windmill-worker/src/python_executor.rs index d86c84dd0d..531c41887a 100644 --- a/backend/windmill-worker/src/python_executor.rs +++ b/backend/windmill-worker/src/python_executor.rs @@ -1296,12 +1296,14 @@ async fn prepare_wrapper( let sig = windmill_parser_py::parse_python_signature( inner_content, main_override.map(ToString::to_string), + false, )?; let pre_sig = if apply_preprocessor { Some(windmill_parser_py::parse_python_signature( inner_content, Some("preprocessor".to_string()), + false, )?) } else { None @@ -1661,7 +1663,7 @@ async fn spawn_uv_install( .replace("{TARGET_DIR}", &venv_p) .replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()), )?; - + let mut nsjail_cmd = Command::new(NSJAIL_PATH.as_str()); nsjail_cmd .current_dir(job_dir) diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index 90b0444901..590b57227c 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -291,7 +291,6 @@ pub const RUST_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "rust"); pub const CSHARP_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "csharp"); pub const BUN_CACHE_DIR: &str = concatcp!(ROOT_CACHE_NOMOUNT_DIR, "bun"); pub const BUN_BUNDLE_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "bun"); -pub const BUN_DEPSTAR_CACHE_DIR: &str = concatcp!(ROOT_CACHE_NOMOUNT_DIR, "buntar"); pub const GO_BIN_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "gobin"); pub const POWERSHELL_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "powershell"); @@ -1278,7 +1277,7 @@ pub async fn run_worker( tokio::task::spawn( (async move { tracing::info!(worker = %worker_name, hostname = %hostname, "vacuuming queue"); - if let Err(e) = sqlx::query!("VACUUM (skip_locked) v2_job_queue, v2_job_runtime, v2_job_status") + if let Err(e) = sqlx::query!("VACUUM v2_job_queue, v2_job_runtime, v2_job_status") .execute(&db2) .await { @@ -2147,7 +2146,7 @@ async fn handle_queued_job( #[cfg(not(feature = "enterprise"))] if job.concurrent_limit.is_some() { logs.push_str("---\n"); - logs.push_str("WARNING: This job has concurrency limits enabled. Concurrency limits are going to become an Enterprise Edition feature in the near future.\n"); + logs.push_str("WARNING: This job has concurrency limits enabled. Concurrency limits are an EE feature and the setting is ignored.\n"); logs.push_str("---\n"); } diff --git a/backend/windmill-worker/src/worker_flow.rs b/backend/windmill-worker/src/worker_flow.rs index 5e4224681f..864f3002b1 100644 --- a/backend/windmill-worker/src/worker_flow.rs +++ b/backend/windmill-worker/src/worker_flow.rs @@ -408,10 +408,27 @@ pub async fn update_flow_status_after_job_completion_internal( if matches!(module_step, Step::PreprocessorStep) { sqlx::query!( - "UPDATE v2_job SET - args = (SELECT result FROM v2_job_completed WHERE id = $1), - preprocessed = TRUE - WHERE id = $2", + "WITH job_result AS ( + SELECT result + FROM v2_job_completed + WHERE id = $1 + ) + UPDATE v2_job + SET args = COALESCE( + CASE + WHEN job_result.result IS NULL THEN NULL + WHEN jsonb_typeof(job_result.result) = 'object' + THEN job_result.result + WHEN jsonb_typeof(job_result.result) = 'null' + THEN NULL + ELSE jsonb_build_object('value', job_result.result) + END, + '{}'::jsonb + ), + preprocessed = TRUE + FROM job_result + WHERE v2_job.id = $2; + ", job_id_for_status, flow ) @@ -1572,21 +1589,24 @@ pub async fn handle_flow( ); } } + let mut rec = Some(PushNextFlowJobRec { flow_job: flow_job, status: status }); + while let Some(nrec) = rec { + rec = push_next_flow_job( + nrec.flow_job, + nrec.status, + flow, + db, + client, + last_result.clone(), + same_worker_tx.clone(), + worker_dir, + job_completed_tx.clone(), + worker_name, + ) + .warn_after_seconds(10) + .await?; + } - push_next_flow_job( - flow_job, - status, - flow, - db, - client, - last_result, - same_worker_tx, - worker_dir, - job_completed_tx, - worker_name, - ) - .warn_after_seconds(10) - .await?; Ok(()) } @@ -1629,6 +1649,11 @@ fn potentially_crash_for_testing() { lazy_static::lazy_static! { pub static ref EHM: HashMap> = HashMap::new(); } + +struct PushNextFlowJobRec { + flow_job: Arc, + status: FlowStatus, +} // #[async_recursion] // #[instrument(level = "trace", skip_all)] async fn push_next_flow_job( @@ -1642,7 +1667,7 @@ async fn push_next_flow_job( worker_dir: &str, job_completed_tx: Sender, worker_name: &str, -) -> error::Result<()> { +) -> error::Result> { let job_root = flow_job .root_job .map(|x| x.to_string()) @@ -1697,7 +1722,7 @@ async fn push_next_flow_job( )) })?; - return Ok(()); + return Ok(None); } if matches!(step, Step::Step(0)) { @@ -1755,7 +1780,7 @@ async fn push_next_flow_job( )) })?; - return Ok(()); + return Ok(None); } } } @@ -1792,7 +1817,7 @@ async fn push_next_flow_job( )) })?; - return Ok(()); + return Ok(None); } } } @@ -2038,7 +2063,7 @@ async fn push_next_flow_job( .await?; tx.commit().await?; - return Ok(()); + return Ok(None); /* cancelled or we're WaitingForEvents but we don't have enough messages (timed out) */ } else { @@ -2095,7 +2120,7 @@ async fn push_next_flow_job( )) })?; - return Ok(()); + return Ok(None); } } } @@ -2394,33 +2419,44 @@ async fn push_next_flow_job( let (job_payloads, next_status) = match next_flow_transform { NextFlowTransform::Continue(job_payload, next_state) => (job_payload, next_state), - NextFlowTransform::EmptyInnerFlows => { - sqlx::query!( + NextFlowTransform::EmptyInnerFlows { branch_chosen } => { + let raw_status = sqlx::query_scalar!( "UPDATE v2_job_status SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2) - WHERE id = $3", + WHERE id = $3 + RETURNING flow_status AS \"flow_status: Json>\"", status.step.to_string(), json!(FlowStatusModule::Success { id: status_module.id(), job: Uuid::nil(), flow_jobs: Some(vec![]), flow_jobs_success: Some(vec![]), - branch_chosen: None, + branch_chosen: branch_chosen, approvers: vec![], failed_retries: vec![], skipped: false, }), flow_job.id ) - .execute(db) - .await?; - // flow is reprocessed by the worker in a state where the module has completed successfully. - // The next steps are pull -> handle flow -> push next flow job -> update flow status since module status is success - same_worker_tx - .send(SameWorkerPayload { job_id: flow_job.id, recoverable: true }) - .await - .expect("send to same worker"); - return Ok(()); + .fetch_optional(db) + .await? + .flatten(); + + let status = raw_status + .as_ref() + .and_then(|v| serde_json::from_str::((**v).get()).ok()); + + if let Some(status) = status { + // // flow is reprocessed by the worker in a state where the module has completed successfully. + return Ok(Some(PushNextFlowJobRec { + flow_job: flow_job, + status: status, + })); + } else { + return Err(Error::BadRequest( + "impossible to parse new flow status after applying innr flows".to_string(), + )); + } } }; @@ -2906,7 +2942,7 @@ async fn push_next_flow_job( .await .map_err(to_anyhow)?; } - return Ok(()); + return Ok(None); } // async fn jump_to_next_step( @@ -3028,7 +3064,7 @@ enum ContinuePayload { } enum NextFlowTransform { - EmptyInnerFlows, + EmptyInnerFlows { branch_chosen: Option }, Continue(ContinuePayload, NextStatus), } @@ -3294,7 +3330,9 @@ async fn compute_next_flow_transform( .await?; match next_loop_status { - ForLoopStatus::EmptyIterator => Ok(NextFlowTransform::EmptyInnerFlows), + ForLoopStatus::EmptyIterator => { + Ok(NextFlowTransform::EmptyInnerFlows { branch_chosen: None }) + } ForLoopStatus::NextIteration(ns) => { next_loop_iteration( flow, @@ -3346,7 +3384,7 @@ async fn compute_next_flow_transform( }) .collect::>(); if payloads.is_empty() { - return Ok(NextFlowTransform::EmptyInnerFlows); + return Ok(NextFlowTransform::EmptyInnerFlows { branch_chosen: None }); } Ok(NextFlowTransform::Continue( ContinuePayload::ParallelJobs(payloads), @@ -3402,12 +3440,12 @@ async fn compute_next_flow_transform( )))?, }; - let (modules, modules_node) = match branch { - BranchChosen::Default => (default, default_node), + let (modules, modules_node, branch_idx) = match branch { + BranchChosen::Default => (default, default_node, 0), BranchChosen::Branch { branch } => branches .into_iter() .nth(branch) - .map(|Branch { modules, modules_node, .. }| (modules, modules_node)) + .map(|Branch { modules, modules_node, .. }| (modules, modules_node, branch + 1)) .ok_or_else(|| { Error::BadRequest(format!( "Unrecognized branch for BranchOne {status_module:?}" @@ -3421,10 +3459,10 @@ async fn compute_next_flow_transform( flow.failure_module.as_ref(), flow.same_worker, || status.step.to_string(), - || format!("{}/branchone-{}", flow_job.script_path(), status.step), + || format!("{}/branchone-{}", flow_job.script_path(), branch_idx), true, ) else { - return Ok(NextFlowTransform::EmptyInnerFlows); + return Ok(NextFlowTransform::EmptyInnerFlows { branch_chosen: Some(branch) }); }; Ok(NextFlowTransform::Continue( @@ -3444,7 +3482,7 @@ async fn compute_next_flow_transform( | FlowStatusModule::WaitingForEvents { .. } | FlowStatusModule::WaitingForExecutor { .. } => { if branches.is_empty() { - return Ok(NextFlowTransform::EmptyInnerFlows); + return Ok(NextFlowTransform::EmptyInnerFlows { branch_chosen: None }); } else if parallel { let len = branches.len(); let payloads: Vec = branches @@ -3472,7 +3510,7 @@ async fn compute_next_flow_transform( }) .collect::>(); if payloads.is_empty() { - return Ok(NextFlowTransform::EmptyInnerFlows); + return Ok(NextFlowTransform::EmptyInnerFlows { branch_chosen: None }); } return Ok(NextFlowTransform::Continue( ContinuePayload::ParallelJobs(payloads), @@ -3530,7 +3568,9 @@ async fn compute_next_flow_transform( }, false, ) else { - return Ok(NextFlowTransform::EmptyInnerFlows); + return Ok(NextFlowTransform::EmptyInnerFlows { + branch_chosen: Some(BranchChosen::Default), + }); }; Ok(NextFlowTransform::Continue( @@ -3592,7 +3632,7 @@ async fn next_loop_iteration( inner_path, true, ) else { - return Ok(NextFlowTransform::EmptyInnerFlows); + return Ok(NextFlowTransform::EmptyInnerFlows { branch_chosen: None }); }; Ok(NextFlowTransform::Continue( diff --git a/benchmarks/benchmark_oneoff.ts b/benchmarks/benchmark_oneoff.ts index 41321b3ec6..6685eae1c5 100644 --- a/benchmarks/benchmark_oneoff.ts +++ b/benchmarks/benchmark_oneoff.ts @@ -26,7 +26,7 @@ async function verifyOutputs(uuids: string[], workspace: string) { incorrectResults++; } if (job.result !== uuid) { - console.log(`Job ${uuid} did not output the correct value`); + console.log(`Job ${uuid} did not output the correct value: ${JSON.stringify(job)}`); incorrectResults++; } } catch (_) { @@ -37,6 +37,7 @@ async function verifyOutputs(uuids: string[], workspace: string) { console.log(`Incorrect results: ${incorrectResults}`); } +export const NON_TEST_TAGS = ["deno", "python", "go", "bash", "dedicated", "bun", "nativets", "flow"] export async function main({ host, email, @@ -96,11 +97,11 @@ export async function main({ windmill.setClient(final_token, host); const enc = (s: string) => new TextEncoder().encode(s); - async function getQueueCount() { + async function getQueueCount(tags?: string[]) { return ( await ( await fetch( - config.server + "/api/w/" + config.workspace_id + "/jobs/queue/count", + config.server + "/api/w/" + config.workspace_id + "/jobs/queue/count" + (tags && tags.length > 0 ? "?tags=" + tags.join(",") : ""), { headers: { ["Authorization"]: "Bearer " + config.token } } ) ).json() @@ -132,11 +133,11 @@ export async function main({ } let pastJobs = 0; - async function getCompletedJobsCount(): Promise { + async function getCompletedJobsCount(tags?: string[]): Promise { const completedJobs = ( await ( await fetch( - host + "/api/w/" + config.workspace_id + "/jobs/completed/count", + host + "/api/w/" + config.workspace_id + "/jobs/completed/count" + (tags && tags.length > 0 ? "?tags=" + tags.join(",") : ""), { headers: { ["Authorization"]: "Bearer " + config.token } } ) ).json() @@ -152,7 +153,6 @@ export async function main({ await createBenchScript(kind, workspace); } - pastJobs = await getCompletedJobsCount(); const jobsSent = jobs; console.log(`Bulk creating ${jobsSent} jobs`); @@ -201,18 +201,51 @@ export async function main({ kind: "rawscript", rawscript: { language: api.RawScript.language.BASH, - content: "# let's bloat that bash script, 3.. 2.. 1.. BOOM\n".repeat(25000) + "echo \"$WM_FLOW_JOB_ID\"\n", + content: "# let's bloat that bash script, 3.. 2.. 1.. BOOM\n".repeat(100) + "echo \"$WM_FLOW_JOB_ID\"\n", }, }); } else { throw new Error("Unknown script pattern " + kind); } - const response = await fetch( - config.server + + let testOtherTag = false; + if (testOtherTag) { + const otherTagTodo = 2000000; + + let parsed = JSON.parse(body); + parsed.tag = "test"; + let nbody = JSON.stringify(parsed); + let response2 = await fetch( + config.server + "/api/w/" + config.workspace_id + - `/jobs/add_batch_jobs/${jobsSent}`, + `/jobs/add_batch_jobs/${otherTagTodo}`, + { + method: "POST", + headers: { + ["Authorization"]: "Bearer " + config.token, + "Content-Type": "application/json", + }, + body: nbody, + } + ); + if (!response2.ok) { + throw new Error( + "Failed to create jobs: " + + response2.statusText + + " " + + (await response2.text()) + ); + } + } + + pastJobs = await getCompletedJobsCount(NON_TEST_TAGS); + + const response = await fetch( + config.server + + "/api/w/" + + config.workspace_id + + `/jobs/add_batch_jobs/${jobsSent}`, { method: "POST", headers: { @@ -222,20 +255,24 @@ export async function main({ body, } ); + + + + + if (!response.ok) { throw new Error( "Failed to create jobs: " + - response.statusText + - " " + - (await response.text()) + response.statusText + + " " + + (await response.text()) ); } const uuids = await response.json(); const end_create = Date.now(); const create_duration = end_create - start_create; console.log( - `Jobs successfully added to the queue in ${ - create_duration / 1000 + `Jobs successfully added to the queue in ${create_duration / 1000 }s. Windmill will start pulling them\n` ); let start = Date.now(); @@ -248,14 +285,14 @@ export async function main({ while (completedJobs < jobsSent) { const loopStart = Date.now(); if (!didStart) { - const actual_queue = await getQueueCount(); + const actual_queue = await getQueueCount(NON_TEST_TAGS); if (actual_queue < jobsSent) { start = Date.now(); didStart = true; } } else { const elapsed = start ? Date.now() - start : 0; - completedJobs = await getCompletedJobsCount(); + completedJobs = await getCompletedJobsCount(NON_TEST_TAGS); if (nStepsFlow > 0) { completedJobs = Math.floor(completedJobs / (nStepsFlow + 1)); } @@ -263,9 +300,9 @@ export async function main({ const instThr = lastElapsed > 0 ? ( - ((completedJobs - lastCompletedJobs) / (elapsed - lastElapsed)) * - 1000 - ).toFixed(2) + ((completedJobs - lastCompletedJobs) / (elapsed - lastElapsed)) * + 1000 + ).toFixed(2) : 0; lastElapsed = elapsed; @@ -275,8 +312,7 @@ export async function main({ enc( `elapsed: ${(elapsed / 1000).toFixed( 2 - )} | jobs executed: ${completedJobs}/${jobsSent} (thr: inst ${instThr} - avg ${avgThr}) | remaining: ${ - jobsSent - completedJobs + )} | jobs executed: ${completedJobs}/${jobsSent} (thr: inst ${instThr} - avg ${avgThr}) | remaining: ${jobsSent - completedJobs } \r` ) ); @@ -294,7 +330,7 @@ export async function main({ console.log(`avg. throughput (jobs/time): ${jobsSent / total_duration_sec}`); console.log("completed jobs", completedJobs); - console.log("queue length:", await getQueueCount()); + console.log("queue length:", await getQueueCount(NON_TEST_TAGS)); if ( !noVerify && diff --git a/benchmarks/benchmark_suite.ts b/benchmarks/benchmark_suite.ts index e03148ce99..f4840dda05 100644 --- a/benchmarks/benchmark_suite.ts +++ b/benchmarks/benchmark_suite.ts @@ -39,6 +39,7 @@ async function main({ workspace, configPath, workers, + factor }: { host: string; email?: string; @@ -47,6 +48,7 @@ async function main({ workspace: string; configPath: string; workers: number; + factor?: number; }) { async function getConfig(configPath: string): Promise { if (configPath.startsWith("http")) { @@ -77,7 +79,7 @@ async function main({ token, workspace, kind: benchmark.kind, - jobs: benchmark.jobs, + jobs: benchmark.jobs * (factor ?? 1), }); if (benchmark.noSave) { @@ -153,6 +155,9 @@ await new Command() "Number of workers that are used to run the benchmarks (only affect graph title)", { default: 1 } ) + .option("--factor ", "Factor to multiply the number of jobs by.", { + default: 1, + }) .action(main) .command( "upgrade", diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index 7e4f36a358..311c804d6c 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.460.1"; +export const VERSION = "v1.463.5"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ @@ -25,7 +25,7 @@ async function waitForDeployment(workspace: string, hash: string) { if (resp.lock !== null) { return; } - } catch (err) {} + } catch (err) { } await sleep(0.5); } throw new Error("Script did not deploy in time"); @@ -246,7 +246,7 @@ export const getFlowPayload = (flowPattern: string): api.FlowPreview => { input_transforms: {}, language: api.RawScript.language.BASH, type: "rawscript", - content: "# let's bloat that bash script, 3.. 2.. 1.. BOOM\n".repeat(25000) + "echo \"$WM_FLOW_JOB_ID\"\n", + content: "# let's bloat that bash script, 3.. 2.. 1.. BOOM\n".repeat(100) + `if [[ -z $\{WM_FLOW_JOB_ID+x\} ]]; then\necho "not set"\nelif [[ -z "$WM_FLOW_JOB_ID" ]]; then\necho "empty"\nelse\necho "$WM_FLOW_JOB_ID"\nfi`, }, } ], diff --git a/cli/dev.ts b/cli/dev.ts index 2da25ec4ac..7cc92c45ff 100644 --- a/cli/dev.ts +++ b/cli/dev.ts @@ -8,8 +8,9 @@ import { log, open, WebSocket, + yamlParseFile, } from "./deps.ts"; -import { GlobalOptions } from "./types.ts"; +import { getTypeStrFromPath, GlobalOptions } from "./types.ts"; import { ignoreF } from "./sync.ts"; import { requireLogin, resolveWorkspace } from "./context.ts"; import { @@ -17,8 +18,11 @@ import { mergeConfigWithConfigFile, readConfigFile, } from "./conf.ts"; -import { exts } from "./script.ts"; +import { exts, findGlobalDeps, removeExtensionToPath } from "./script.ts"; import { inferContentTypeFromFilePath } from "./script_common.ts"; +import { OpenFlow } from "./gen/types.gen.ts"; +import { FlowFile, replaceInlineScripts } from "./flow.ts"; +import { parseMetadataFile } from "./metadata.ts"; const PORT = 3001; async function dev(opts: GlobalOptions & SyncOptions) { @@ -27,54 +31,105 @@ async function dev(opts: GlobalOptions & SyncOptions) { log.info("Started dev mode"); const conf = await readConfigFile(); - let currentLastEdit: LastEdit | undefined = undefined; + let currentLastEdit: LastEditScript | LastEditFlow | undefined = undefined; const watcher = Deno.watchFs("."); const base = await Deno.realPath("."); opts = await mergeConfigWithConfigFile(opts); const ignore = await ignoreF(opts); + const changesTimeouts: Record = {}; async function watchChanges() { for await (const event of watcher) { - log.debug(">>>> event", event); - // Example event: { kind: "create", paths: [ "/home/alice/deno/foo.txt" ] } - await loadPaths(event.paths); + // console.log(">>>> event", event); + const key = event.paths.join(","); + if (changesTimeouts[key]) { + clearTimeout(changesTimeouts[key]); + } + // @ts-ignore + changesTimeouts[key] = setTimeout(async () => { + delete changesTimeouts[key]; + await loadPaths(event.paths); + }, 100); } } + const DOT_FLOW_SEP = ".flow" + SEP; async function loadPaths(pathsToLoad: string[]) { const paths = pathsToLoad.filter((path) => - exts.some((ext) => path.endsWith(ext)) + exts.some( + (ext) => path.endsWith(ext) || path.endsWith(DOT_FLOW_SEP + "flow.yaml") + ) ); if (paths.length == 0) { return; } const cpath = (await Deno.realPath(paths[0])).replace(base + SEP, ""); - console.log("Detected change in " + cpath); if (!ignore(cpath, false)) { - const content = await Deno.readTextFile(cpath); - const splitted = cpath.split("."); - const wmPath = splitted[0]; - const lang = inferContentTypeFromFilePath(cpath, conf.defaultTs); - currentLastEdit = { - content, - path: wmPath, - language: lang, - }; - broadcastChanges(currentLastEdit); - log.info("Updated " + wmPath); + const typ = getTypeStrFromPath(cpath); + log.info("Detected change in " + cpath + " (" + typ + ")"); + if (typ == "flow") { + const localPath = cpath.split(DOT_FLOW_SEP)[0] + DOT_FLOW_SEP; + const localFlow = (await yamlParseFile( + localPath + "flow.yaml" + )) as FlowFile; + replaceInlineScripts(localFlow.value.modules, localPath, undefined); + currentLastEdit = { + type: "flow", + flow: localFlow, + uriPath: localPath, + }; + log.info("Updated " + localPath); + broadcastChanges(currentLastEdit); + } else if (typ == "script") { + const content = await Deno.readTextFile(cpath); + const splitted = cpath.split("."); + const wmPath = splitted[0]; + const lang = inferContentTypeFromFilePath(cpath, conf.defaultTs); + const globalDeps = await findGlobalDeps(); + const typed = + (await parseMetadataFile( + removeExtensionToPath(cpath), + undefined, + globalDeps, + [] + ) + )?.payload + + + currentLastEdit = { + type: "script", + content, + path: wmPath, + language: lang, + tag: typed?.tag, + lock: typed?.lock, + }; + log.info("Updated " + wmPath); + broadcastChanges(currentLastEdit); + } } } - type LastEdit = { + type LastEditScript = { + type: "script"; content: string; path: string; language: string; + tag?: string; + lock?: string; + + }; + + type LastEditFlow = { + type: "flow"; + flow: OpenFlow; + uriPath: string; }; const connectedClients: Set = new Set(); // Function to send a message to all connected clients - function broadcastChanges(lastEdit: LastEdit) { + function broadcastChanges(lastEdit: LastEditScript | LastEditFlow) { for (const client of connectedClients.values()) { client.send(JSON.stringify(lastEdit)); } @@ -119,7 +174,7 @@ async function dev(opts: GlobalOptions & SyncOptions) { // Start the server const port = await getPort.default({ port: 3001 }); const url = - `${workspace.remote}scripts/dev?workspace=${workspace.workspaceId}&local=true` + + `${workspace.remote}dev?workspace=${workspace.workspaceId}&local=true&wm_token=${workspace.token}` + (port === PORT ? "" : `&port=${port}`); console.log(`Go to ${url}`); diff --git a/cli/main.ts b/cli/main.ts index bd377e5168..34d7133577 100644 --- a/cli/main.ts +++ b/cli/main.ts @@ -62,7 +62,7 @@ export { // } // }); -export const VERSION = "1.460.1"; +export const VERSION = "1.463.5"; const command = new Command() .name("wmill") diff --git a/cli/metadata.ts b/cli/metadata.ts index e78b0bf539..d547cee29f 100644 --- a/cli/metadata.ts +++ b/cli/metadata.ts @@ -313,10 +313,16 @@ export async function updateScriptSchema( path ); metadataContent.schema = result.schema; - if (result.has_preprocessor == true) + if (result.has_preprocessor) { metadataContent.has_preprocessor = result.has_preprocessor; - if (result.no_main_func === true) + } else { + delete metadataContent.has_preprocessor; + } + if (result.no_main_func) { metadataContent.no_main_func = result.no_main_func; + } else { + delete metadataContent.no_main_func; + } } async function updateScriptLock( @@ -441,7 +447,11 @@ export function inferSchema( content: string, currentSchema: any, path: string -) { +): { + schema: any; + has_preprocessor: boolean | undefined; + no_main_func: boolean | undefined; +} { let inferedSchema: any; if (language === "python3") { inferedSchema = JSON.parse(parse_python(content)); diff --git a/docker-compose.yml b/docker-compose.yml index df2a99b38d..a8ac7ba565 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,12 @@ version: "3.7" +x-logging: &default-logging + driver: "json-file" + options: + max-size: "${LOG_MAX_SIZE:-20m}" + max-file: "${LOG_MAX_FILE:-10}" + compress: "true" + services: db: deploy: @@ -22,6 +29,7 @@ services: interval: 10s timeout: 5s retries: 5 + logging: *default-logging windmill_server: image: ${WM_IMAGE} @@ -40,6 +48,7 @@ services: condition: service_healthy volumes: - worker_logs:/tmp/windmill/logs + logging: *default-logging windmill_worker: image: ${WM_IMAGE} @@ -65,6 +74,7 @@ services: - /var/run/docker.sock:/var/run/docker.sock - worker_dependency_cache:/tmp/windmill/cache - worker_logs:/tmp/windmill/logs + logging: *default-logging ## This worker is specialized for "native" jobs. Native jobs run in-process and thus are much more lightweight than other jobs windmill_worker_native: @@ -90,6 +100,7 @@ services: condition: service_healthy volumes: - worker_logs:/tmp/windmill/logs + logging: *default-logging # This worker is specialized for reports or scraping jobs. It is assigned the "reports" worker group which has an init script that installs chromium and can be targeted by using the "chromium" worker tag. # windmill_worker_reports: # image: ${WM_IMAGE} @@ -135,6 +146,7 @@ services: volumes: - windmill_index:/tmp/windmill/search - worker_logs:/tmp/windmill/logs + logging: *default-logging lsp: image: ghcr.io/windmill-labs/windmill-lsp:latest @@ -144,6 +156,7 @@ services: - 3001 volumes: - lsp_cache:/pyls/.cache + logging: *default-logging multiplayer: image: ghcr.io/windmill-labs/windmill-multiplayer:latest @@ -152,6 +165,7 @@ services: restart: unless-stopped expose: - 3002 + logging: *default-logging caddy: image: ghcr.io/windmill-labs/caddy-l4:latest @@ -170,6 +184,7 @@ services: - BASE_URL=":80" # - BASE_URL=":443" # uncomment and comment line above to enable HTTPS via custom certificate and key files # - BASE_URL=mydomain.com # Uncomment and comment line above to enable HTTPS handling by Caddy + logging: *default-logging volumes: db_data: null diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6edef5b202..22ed0a5141 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.460.1", + "version": "1.463.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.460.1", + "version": "1.463.5", "license": "AGPL-3.0", "dependencies": { "@anthropic-ai/sdk": "^0.32.1", diff --git a/frontend/package.json b/frontend/package.json index 167351899e..84c3029eaf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.460.1", + "version": "1.463.5", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/frontend/src/lib/components/AuthSettings.svelte b/frontend/src/lib/components/AuthSettings.svelte index f6db65526a..b9ea98aeb9 100644 --- a/frontend/src/lib/components/AuthSettings.svelte +++ b/frontend/src/lib/components/AuthSettings.svelte @@ -172,7 +172,7 @@
- +
{#each Object.keys(oauths) as k} diff --git a/frontend/src/lib/components/ConnectionSection.svelte b/frontend/src/lib/components/ConnectionSection.svelte new file mode 100644 index 0000000000..c0ad6eea20 --- /dev/null +++ b/frontend/src/lib/components/ConnectionSection.svelte @@ -0,0 +1,184 @@ + + +
+
Connect Workspace to {platform.charAt(0).toUpperCase() + platform.slice(1)}
+ + Connect your Windmill workspace to your {platform} workspace to trigger a script or a flow with a + '/windmill' command. + +
+ +{#if teamName} +
+
+ + {#if display_name} + Connected to Team '{display_name}' + {/if} +
+ {#if $enterpriseLicense || platform === 'slack'} + + + {/if} +
+{:else} +
+ {#if platform === 'teams'} + + {#if $enterpriseLicense} +
+ +
+
+ +
+ {/if} + {:else} + + {/if} + Not connected +
+{/if} + +
+
Script or flow to run on /windmill command
+
+ {#if !teamName || (!$enterpriseLicense && platform === 'teams')} +
+ {/if} + +
+ +
+ Pick a script or flow meant to be triggered when the `/windmill` command is invoked. Upon + connection, templates for a script + and flow are available. + +

+ + The script or flow chosen is passed the parameters `response_url: string` and `text: string` + respectively the url to reply directly to the trigger and the text of the command. + +

+ + It can take additionally the following args: channel_id, user_name, user_id, command, + trigger_id, api_app_id + +

+ + + The script or flow is permissioned as group "{platform}" that will be automatically created + after connection to {platform.charAt(0).toUpperCase() + platform.slice(1)}. + + +

+ + See more on + documentation. +
+
diff --git a/frontend/src/lib/components/Dev.svelte b/frontend/src/lib/components/Dev.svelte index e53d49eb72..30c2fb45cc 100644 --- a/frontend/src/lib/components/Dev.svelte +++ b/frontend/src/lib/components/Dev.svelte @@ -207,7 +207,6 @@ runTest() event.preventDefault() } else if (event.data.type == 'replaceScript') { - mode = 'script' replaceScript(event.data) } else if (event.data.type == 'testBundle') { if (event.data.id == lastCommandId) { @@ -235,7 +234,6 @@ true ) } else if (event.data.type == 'replaceFlow') { - mode = 'flow' lockChanges = true replaceFlow(event.data) timeout && clearTimeout(timeout) @@ -333,6 +331,13 @@ }) function connectWs() { + try { + if (socket) { + socket.close() + } + } catch (e) { + console.error('Failed to close websocket', e) + } const port = searchParams?.get('port') || '3001' try { socket = new WebSocket(`ws://localhost:${port}/ws`) @@ -350,7 +355,13 @@ console.log('Received invalid JSON: ' + msg) return } - replaceScript(data) + if (data.type == 'script') { + replaceScript(data) + } else if (data.type == 'flow') { + replaceFlow(data) + } else { + sendUserToast(`Received invalid message type ${data.type}`, true) + } } } catch (e) { sendUserToast('Failed to connect to local server', true) @@ -419,6 +430,7 @@ let relativePaths: any[] = [] let lastPath: string | undefined = undefined async function replaceScript(lastEdit: LastEditScript) { + mode = 'script' currentScript = lastEdit if (lastPath !== lastEdit.path) { schema = emptySchema() @@ -455,6 +467,7 @@ } let lastUriPath: string | undefined = undefined async function replaceFlow(lastEdit: LastEditFlow) { + mode = 'flow' lastUriPath = lastEdit.uriPath // sendUserToast(JSON.stringify(lastEdit.flow), true) // return @@ -715,6 +728,7 @@ {#if $flowStore?.value?.modules} +
- import { Alert, Button, Tab, Tabs } from '$lib/components/common' + import { Alert, Button, Tab, Tabs, Badge } from '$lib/components/common' import ScriptPicker from '$lib/components/ScriptPicker.svelte' import Toggle from '$lib/components/Toggle.svelte' + import Tooltip from '$lib/components/Tooltip.svelte' + import type { Schema, SupportedLanguage } from '$lib/common' import { base } from '$lib/base' import { enterpriseLicense, workspaceStore } from '$lib/stores' + import MsTeamsIcon from '$lib/components/icons/MSTeamsIcon.svelte' import { emptySchema, emptyString, sendUserToast, tryEvery } from '$lib/utils' import { FlowService, @@ -14,21 +17,26 @@ WorkspaceService, type Flow } from '$lib/gen' + import type { ListAvailableTeamsChannelsResponse } from '$lib/gen/types.gen' import { inferArgs } from '$lib/infer' import { hubBaseUrlStore } from '$lib/stores' - import { CheckCircle2, Loader2, RotateCw, XCircle } from 'lucide-svelte' + import { CheckCircle2, Loader2, RotateCw, XCircle, RefreshCcw } from 'lucide-svelte' import { hubPaths } from '$lib/hub' const slackRecoveryHandler = hubPaths.slackRecoveryHandler const slackHandlerScriptPath = hubPaths.slackErrorHandler const slackSuccessHandler = hubPaths.slackSuccessHandler + const teamsRecoveryHandler = hubPaths.teamsRecoveryHandler + const teamsHandlerScriptPath = hubPaths.teamsErrorHandler + const teamsSuccessHandler = hubPaths.teamsSuccessHandler + export let errorOrRecovery: 'error' | 'recovery' | 'success' export let isEditable: boolean - export let slackToggleText: string = 'Enable' + export let toggleText: string = 'Enable' export let showScriptHelpText: boolean = false - export let handlerSelected: 'custom' | 'slack' + export let handlerSelected: 'custom' | 'slack' | 'teams' export let handlerPath: string | undefined export let handlerExtraArgs: Record @@ -36,13 +44,18 @@ export let customInitialScriptPath: string | undefined export let customScriptTemplate: string export let customHandlerKind: 'flow' | 'script' = 'script' - let customHandlerSchema: Schema | undefined + let customHandlerSchema: Schema | undefined let slackHandlerSchema: Schema | undefined + let isFetching: boolean = false + + let teams_channels: ListAvailableTeamsChannelsResponse = [] + let teams_team_name: string | undefined = undefined + let workspaceConnectedToSlack: boolean | undefined = undefined - let slackConnectionTestJob: - | { uuid: string; is_success: boolean; in_progress: boolean } - | undefined + let workspaceConnectedToTeams: boolean | undefined = undefined + + let connectionTestJob: { uuid: string; is_success: boolean; in_progress: boolean } | undefined async function loadSlackResources() { const settings = await WorkspaceService.getSettings({ workspace: $workspaceStore! }) @@ -53,16 +66,39 @@ } } - async function sendSlackMessage(channel: string): Promise { - let submitted_job = await WorkspaceService.runSlackMessageTestJob({ + async function loadTeamsResources() { + isFetching = true + const settings = await WorkspaceService.getSettings({ workspace: $workspaceStore! }) + if (!emptyString(settings.teams_team_name) && !emptyString(settings.teams_team_id)) { + workspaceConnectedToTeams = true + } else { + workspaceConnectedToTeams = false + } + if (workspaceConnectedToTeams) { + teams_team_name = settings.teams_team_name + teams_channels = await WorkspaceService.listAvailableTeamsChannels({ + workspace: $workspaceStore! + }) + } + isFetching = false + } + + async function sendMessage(channel: string, platform: 'teams' | 'slack'): Promise { + const testJobFunction = + platform === 'slack' + ? WorkspaceService.runSlackMessageTestJob + : WorkspaceService.runTeamsMessageTestJob + + let submitted_job = await testJobFunction({ workspace: $workspaceStore!, requestBody: { hub_script_path: handlerPath, channel: channel, - test_msg: `This is a notification to test the connection between Slack and Windmill workspace '${$workspaceStore!}'` + test_msg: `This is a notification to test the connection between ${platform} and Windmill workspace '${$workspaceStore!}'` } }) - slackConnectionTestJob = { + + connectionTestJob = { uuid: submitted_job.job_uuid!, in_progress: true, is_success: false @@ -71,16 +107,16 @@ tryCode: async () => { const testResult = await JobService.getCompletedJob({ workspace: $workspaceStore!, - id: slackConnectionTestJob!.uuid + id: connectionTestJob!.uuid }) - slackConnectionTestJob!.in_progress = false - slackConnectionTestJob!.is_success = testResult.success + connectionTestJob!.in_progress = false + connectionTestJob!.is_success = testResult.success }, timeoutCode: async () => { try { await JobService.cancelQueuedJob({ workspace: $workspaceStore!, - id: slackConnectionTestJob!.uuid, + id: connectionTestJob!.uuid, requestBody: { reason: 'Slack message not sent after 5s' } @@ -94,6 +130,14 @@ }) } + async function sendSlackMessage(channel: string): Promise { + await sendMessage(channel, 'slack') + } + + async function sendTeamsMessage(channel: string): Promise { + await sendMessage(channel, 'teams') + } + async function loadHandlerScriptArgs(p: string, defaultArgs: string[] = []) { try { let schema: Schema | undefined = emptySchema() @@ -145,14 +189,55 @@ } } + function isTeamsHandler(scriptPath: string | undefined) { + if (scriptPath === undefined) { + return false + } + if (errorOrRecovery == 'error') { + return ( + scriptPath.startsWith('hub/') && + scriptPath.endsWith('/workspace-or-schedule-error-handler-teams') + ) + } else if (errorOrRecovery == 'recovery') { + return ( + scriptPath.startsWith('hub/') && scriptPath.endsWith('/schedule-recovery-handler-teams') + ) + } else { + return scriptPath.startsWith('hub/') && scriptPath.endsWith('/schedule-success-handler-teams') + } + } + $: { if ($workspaceStore) { loadSlackResources() + loadTeamsResources() } } + let lastHandlerSelected: 'slack' | 'teams' | 'custom' | undefined = undefined + let channelCache = { + slack: undefined as string | undefined, + teams: undefined as string | undefined + } + $: { + if (lastHandlerSelected !== handlerSelected && lastHandlerSelected !== undefined) { + if (lastHandlerSelected === 'teams' || lastHandlerSelected === 'slack') { + channelCache[lastHandlerSelected] = handlerExtraArgs['channel'] + } + + if (handlerSelected === 'custom') { + handlerExtraArgs['channel'] = '' + } else { + handlerExtraArgs['channel'] = channelCache[handlerSelected] ?? '' + } + } + + lastHandlerSelected = handlerSelected + } + $: handlerPath && !isSlackHandler(handlerPath) && + !isTeamsHandler(handlerPath) && loadHandlerScriptArgs(handlerPath, [ 'path', 'workspace_id', @@ -192,6 +277,7 @@
Slack + Teams Custom @@ -254,7 +340,7 @@ { if (e.detail && errorOrRecovery === 'error') { handlerPath = slackHandlerScriptPath @@ -313,11 +399,11 @@ on:click={() => sendSlackMessage(handlerExtraArgs['channel'])} size="xs">Send test message - {#if slackConnectionTestJob !== undefined} + {#if connectionTestJob !== undefined}

- {#if slackConnectionTestJob.in_progress} + {#if connectionTestJob.in_progress} - {:else if slackConnectionTestJob.is_success} + {:else if connectionTestJob.is_success} {:else} @@ -325,9 +411,114 @@ Message sent via Windmill job - {slackConnectionTestJob.uuid} + {connectionTestJob.uuid} + +

+ {/if} + {/if} + {/if} +{:else if handlerSelected === 'teams'} + + { + if (e.detail && errorOrRecovery === 'error') { + handlerPath = teamsHandlerScriptPath + } else if (e.detail && errorOrRecovery === 'recovery') { + handlerPath = teamsRecoveryHandler + } else if (e.detail && errorOrRecovery === 'success') { + handlerPath = teamsSuccessHandler + } else { + handlerPath = undefined + } + }} + /> + + {#if workspaceConnectedToTeams} +
+
+ +
+

Teams Channel

+
+ +
+
+ +
+
+
+

+ This workspace is connected to Team: {teams_team_name} +

+ + Each workspace can only be connected to one Microsoft Teams team. You can configure it under workspace settings. + +
+ {:else if workspaceConnectedToTeams == undefined} + + {/if} + {#if $enterpriseLicense && isTeamsHandler(handlerPath)} + {#if workspaceConnectedToTeams == false} + +
+

+ The workspace needs to be connected to Teams to use this feature. You can configure it + under workspace settings. +

+
+
+ {:else} + + {#if connectionTestJob !== undefined} +

+ {#if connectionTestJob.in_progress} + + {:else if connectionTestJob.is_success} + + {:else} + + {/if} + Message sent via Windmill job + + {connectionTestJob.uuid}

{/if} diff --git a/frontend/src/lib/components/FlowJobResult.svelte b/frontend/src/lib/components/FlowJobResult.svelte index cd83372389..ca071370a4 100644 --- a/frontend/src/lib/components/FlowJobResult.svelte +++ b/frontend/src/lib/components/FlowJobResult.svelte @@ -29,17 +29,21 @@ $: jobId != lastJobId && diffJobId() + let iteration = 0 + let logOffset = 0 + async function diffJobId() { if (jobId != lastJobId) { lastJobId = jobId logs = undefined logOffset = 0 + iteration = 0 getLogs() } } - let logOffset = 0 async function getLogs() { + iteration += 1 if (jobId) { const getUpdate = await JobService.getJobUpdates({ workspace: workspaceId ?? $workspaceStore!, @@ -51,11 +55,14 @@ logOffset = getUpdate.log_offset ?? 0 } if (refreshLog) { - setTimeout(() => { - if (refreshLog) { - getLogs() - } - }, 1000) + setTimeout( + () => { + if (refreshLog) { + getLogs() + } + }, + iteration < 10 ? 1000 : iteration < 20 ? 2000 : 5000 + ) } } @@ -69,7 +76,7 @@ class:border={!noBorder} class="grid {!col ? 'grid-cols-2' - : 'grid-rows-2'} shadow border border-tertiary-inverse grow overflow-hidden" + : 'grid-rows-2 max-h-screen'} shadow border border-tertiary-inverse grow overflow-hidden" >
Result diff --git a/frontend/src/lib/components/FlowStatusViewerInner.svelte b/frontend/src/lib/components/FlowStatusViewerInner.svelte index 5f9faa8ed9..d356196aa3 100644 --- a/frontend/src/lib/components/FlowStatusViewerInner.svelte +++ b/frontend/src/lib/components/FlowStatusViewerInner.svelte @@ -88,7 +88,7 @@ flowJobIds?.flowJobs?.map((x, id) => `iter #${id + 1} not loaded by frontend yet`) ?? [] let retry_selected = '' - let timeout: NodeJS.Timeout + let timeout: NodeJS.Timeout | undefined = undefined let localModuleStates: Writable> = writable({}) let localDurationStatuses: Writable> = writable({}) @@ -403,7 +403,7 @@ } } - $: isForloopSelected && globalModuleStates && loadJobInProgress() + $: isForloopSelected && globalModuleStates && debounceLoadJobInProgress() async function getNewJob(jobId: string, initialJob: Job | undefined) { if ( @@ -421,10 +421,33 @@ } } + let debounceJobId: string | undefined = undefined + let lastRefreshed: Date | undefined = undefined + function debounceLoadJobInProgress() { + const pollingRate = reducedPolling ? 5000 : 1000 + if ( + lastRefreshed && + new Date().getTime() - lastRefreshed.getTime() < pollingRate && + debounceJobId == jobId + ) { + timeout && clearTimeout(timeout) + } + timeout = setTimeout(() => { + loadJobInProgress() + lastRefreshed = new Date() + debounceJobId = jobId + timeout = undefined + }, pollingRate) + } + let errorCount = 0 let notAnonynmous = false + let started = false async function loadJobInProgress() { - dispatch('start') + if (!started) { + started = true + dispatch('start') + } if (jobId != '00000000-0000-0000-0000-000000000000') { try { const newJob = await getNewJob(jobId, initialJob) @@ -447,7 +470,7 @@ } } if (job?.type !== 'CompletedJob' && errorCount < 4 && !destroyed) { - timeout = setTimeout(() => loadJobInProgress(), reducedPolling ? 5000 : 1000) + debounceLoadJobInProgress() } else { dispatch('done', job) } @@ -1286,7 +1309,7 @@ durationStatuses={localDurationStatuses} /> {:else if rightColumnSelect == 'node_status'} -
+
{#if selectedNode} {@const node = $localModuleStates[selectedNode]} @@ -1365,7 +1388,6 @@ />
{/if} - { if (!v) return 'email' @@ -456,15 +459,17 @@ selected={!$values['critical_error_channels'][i]?.teams_channel ?.team_id}>Select team - {#each $values['teams'] as team} - - {/each} + {#if $values['teams']} + {#each $values['teams'] as team} + + {/each} + {/if} {#if $values['critical_error_channels'][i]?.teams_channel?.team_id}