mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-24 00:00:46 +00:00
Merge remote-tracking branch 'origin/main' into glm/replace_menuV2
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
+112
@@ -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)
|
||||
|
||||
|
||||
|
||||
+21
-3
@@ -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"
|
||||
}
|
||||
+4
-3
@@ -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"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DROP INDEX CONCURRENTLY IF EXISTS queue_sort",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "0efb16cbf130ec6e9922ecc82a95b252449bd569df374e40ce8820fc3d75a0f0"
|
||||
}
|
||||
+22
@@ -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"
|
||||
}
|
||||
+14
@@ -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"
|
||||
}
|
||||
+24
@@ -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<Box<RawValue>>\"",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "flow_status: Json<Box<RawValue>>",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Jsonb",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "30216cf02e972f961b7cc6054050fdc984be118df1ad68f7263c84e058bb1266"
|
||||
}
|
||||
+15
@@ -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"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DROP INDEX CONCURRENTLY IF EXISTS queue_sort_2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "3738096c29ab9d964be8a74bfd14ff1d599049ebefdaf97a017c9cef8d52ce20"
|
||||
}
|
||||
+22
@@ -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"
|
||||
}
|
||||
-12
@@ -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"
|
||||
}
|
||||
+12
@@ -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"
|
||||
}
|
||||
-12
@@ -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"
|
||||
}
|
||||
-12
@@ -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"
|
||||
}
|
||||
-12
@@ -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"
|
||||
}
|
||||
+12
@@ -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"
|
||||
}
|
||||
+196
@@ -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"
|
||||
}
|
||||
+26
@@ -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"
|
||||
}
|
||||
+16
@@ -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"
|
||||
}
|
||||
+19
-1
@@ -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
|
||||
]
|
||||
|
||||
-12
@@ -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"
|
||||
}
|
||||
+12
@@ -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"
|
||||
}
|
||||
-12
@@ -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"
|
||||
}
|
||||
+12
@@ -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"
|
||||
}
|
||||
-12
@@ -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"
|
||||
}
|
||||
-15
@@ -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"
|
||||
}
|
||||
-12
@@ -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"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DROP INDEX CONCURRENTLY IF EXISTS scheduled_root_job",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "93586f1ffc7b8dbe62b21a21acfa6854a34a99d2bf107feaafc499588f7330a0"
|
||||
}
|
||||
-12
@@ -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"
|
||||
}
|
||||
+12
@@ -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"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "ALTER TABLE v2_job ENABLE ROW LEVEL SECURITY",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c458012c39f1327fb2bfd6b087ee6dbe1380eca532f50bab0cb89a91d42131cf"
|
||||
}
|
||||
+12
@@ -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"
|
||||
}
|
||||
+2
-3
@@ -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"
|
||||
}
|
||||
-12
@@ -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"
|
||||
}
|
||||
+12
@@ -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"
|
||||
}
|
||||
-12
@@ -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"
|
||||
}
|
||||
-16
@@ -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"
|
||||
}
|
||||
-12
@@ -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"
|
||||
}
|
||||
+14
@@ -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"
|
||||
}
|
||||
-12
@@ -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"
|
||||
}
|
||||
+15
@@ -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"
|
||||
}
|
||||
-12
@@ -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"
|
||||
}
|
||||
-12
@@ -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"
|
||||
}
|
||||
+12
@@ -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"
|
||||
}
|
||||
+7
-7
@@ -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"
|
||||
}
|
||||
Generated
+116
-115
@@ -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",
|
||||
|
||||
+4
-4
@@ -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 <ruben@windmill.dev>"]
|
||||
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"
|
||||
|
||||
@@ -1 +1 @@
|
||||
8dab3198496461e40610145e4c818fce2345e20a
|
||||
5d25cf2cd15c1953794045fd7debea14a33c7519
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -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)));
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -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);
|
||||
@@ -60,6 +60,7 @@ fn filter_non_main(code: &str, main_name: &str) -> String {
|
||||
pub fn parse_python_signature(
|
||||
code: &str,
|
||||
override_main: Option<String>,
|
||||
skip_params: bool,
|
||||
) -> anyhow::Result<MainArgSignature> {
|
||||
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,
|
||||
|
||||
@@ -134,6 +134,7 @@ pub fn parse_expr_for_ids(code: &str) -> anyhow::Result<Vec<(String, String)>> {
|
||||
pub fn parse_deno_signature(
|
||||
code: &str,
|
||||
skip_dflt: bool,
|
||||
skip_params: bool,
|
||||
main_override: Option<String>,
|
||||
) -> anyhow::Result<MainArgSignature> {
|
||||
let cm: Lrc<SourceMap> = 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::<anyhow::Result<Vec<Arg>>>()?,
|
||||
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::<anyhow::Result<Vec<Arg>>>()
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or_else(|| vec![])
|
||||
},
|
||||
no_main_func: Some(no_main_func),
|
||||
has_preprocessor: Some(has_preprocessor),
|
||||
};
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
fn parse_param(
|
||||
|
||||
@@ -17,10 +17,11 @@ fn wrap_sig(r: anyhow::Result<MainArgSignature>) -> String {
|
||||
|
||||
#[cfg(feature = "ts-parser")]
|
||||
#[wasm_bindgen]
|
||||
pub fn parse_deno(code: &str, main_override: Option<String>) -> String {
|
||||
pub fn parse_deno(code: &str, main_override: Option<String>, skip_params: Option<bool>) -> 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>) -> String {
|
||||
wrap_sig(windmill_parser_py::parse_python_signature(
|
||||
code,
|
||||
main_override,
|
||||
false,
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
+16
-9
@@ -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<JoinHandle<()>> = 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,
|
||||
|
||||
@@ -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}"
|
||||
|
||||
Vendored
+68
-177
@@ -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;
|
||||
|
||||
+3
-3
@@ -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"}]'
|
||||
)
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+267
-122
@@ -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<Postgres>;
|
||||
|
||||
@@ -169,7 +171,7 @@ impl Migrate for CustomMigrator {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn migrate(db: &DB) -> Result<(), Error> {
|
||||
pub async fn migrate(db: &DB) -> Result<Option<JoinHandle<()>>, 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<Postgres> = $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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
|
||||
|
||||
+187
-141
@@ -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<Uuid>,
|
||||
pub created_by: String,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
pub started_at: chrono::DateTime<chrono::Utc>,
|
||||
pub started_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||
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::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
@@ -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<Vec<&str>>,
|
||||
) -> 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<Vec<Uuid>> {
|
||||
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::<Vec<_>>());
|
||||
@@ -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<i64>,
|
||||
@@ -1634,6 +1647,7 @@ struct QueueStats {
|
||||
#[derive(Deserialize)]
|
||||
pub struct CountQueueJobsQuery {
|
||||
all_workspaces: Option<bool>,
|
||||
tags: Option<String>,
|
||||
}
|
||||
|
||||
async fn count_queue_jobs(
|
||||
@@ -1641,12 +1655,16 @@ async fn count_queue_jobs(
|
||||
Path(w_id): Path<String>,
|
||||
Query(cq): Query<CountQueueJobsQuery>,
|
||||
) -> error::JsonResult<QueueStats> {
|
||||
let tags = cq
|
||||
.tags
|
||||
.map(|t| t.split(',').map(|s| s.to_string()).collect::<Vec<_>>());
|
||||
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<String>,
|
||||
Query(query): Query<CountCompletedJobsQuery>,
|
||||
) -> error::JsonResult<i64> {
|
||||
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<FlowValue>,
|
||||
path: Option<String>,
|
||||
rawscript: Option<BatchRawScript>,
|
||||
tag: Option<String>,
|
||||
}
|
||||
|
||||
#[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::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
@@ -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<Vec<&str>>,
|
||||
) -> 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,
|
||||
|
||||
@@ -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<Option<JoinHandle<()>>> {
|
||||
db::migrate(db)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("Error migrating db: {e:#}"))
|
||||
}
|
||||
|
||||
@@ -341,6 +341,7 @@ pub struct ScheduleLight {
|
||||
pub script_path: String,
|
||||
pub is_flow: bool,
|
||||
pub summary: Option<String>,
|
||||
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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<StatusCode, Error> {
|
||||
return Err(Error::BadRequest(
|
||||
"Teams only available on enterprise".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
pub async fn workspaces_list_available_teams_ids() -> Result<StatusCode, Error> {
|
||||
return Err(Error::BadRequest(
|
||||
"Teams only available on enterprise".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
pub async fn connect_teams() -> Result<StatusCode, Error> {
|
||||
return Err(Error::BadRequest(
|
||||
"Teams only available on enterprise".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
pub async fn run_teams_message_test_job() -> Result<StatusCode, Error> {
|
||||
return Err(Error::BadRequest(
|
||||
"Teams only available on enterprise".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
pub async fn workspaces_list_available_teams_channels() -> Result<StatusCode, Error> {
|
||||
return Err(Error::BadRequest(
|
||||
"Teams only available on enterprise".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub fn teams_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
@@ -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<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub teams_team_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub teams_team_name: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub slack_name: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub slack_command_script: Option<String>,
|
||||
pub teams_command_script: Option<String>,
|
||||
pub slack_email: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub auto_invite_domain: Option<String>,
|
||||
@@ -188,7 +212,7 @@ pub struct WorkspaceSettings {
|
||||
pub deploy_to: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ai_resource: Option<serde_json::Value>,
|
||||
pub ai_models: Vec<String>,
|
||||
pub ai_models: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub code_completion_model: Option<String>,
|
||||
#[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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -95,6 +95,7 @@ lazy_static::lazy_static! {
|
||||
.unwrap_or(false);
|
||||
|
||||
pub static ref MIN_VERSION: Arc<RwLock<Version>> = Arc::new(RwLock::new(Version::new(0, 0, 0)));
|
||||
pub static ref MIN_VERSION_IS_AT_LEAST_1_461: Arc<RwLock<bool>> = Arc::new(RwLock::new(false));
|
||||
pub static ref MIN_VERSION_IS_AT_LEAST_1_427: Arc<RwLock<bool>> = Arc::new(RwLock::new(false));
|
||||
pub static ref MIN_VERSION_IS_AT_LEAST_1_432: Arc<RwLock<bool>> = Arc::new(RwLock::new(false));
|
||||
pub static ref MIN_VERSION_IS_AT_LEAST_1_440: Arc<RwLock<bool>> = 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);
|
||||
|
||||
@@ -566,6 +566,9 @@ pub async fn add_completed_job<T: Serialize + Send + Sync + ValidableJson>(
|
||||
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<T: Serialize + Send + Sync + ValidableJson>(
|
||||
// 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<T: Serialize + Send + Sync + ValidableJson>(
|
||||
"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<Uuid>, 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<Transaction<'c, Postgres>> {
|
||||
#[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!(
|
||||
|
||||
@@ -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<Path>,
|
||||
destination: impl AsRef<Path>,
|
||||
skip: Option<&Vec<String>>,
|
||||
) -> 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<String> = 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| {
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -775,7 +775,7 @@ pub async fn eval_fetch_timeout(
|
||||
|
||||
let (sender, mut receiver) = oneshot::channel::<IsolateHandle>();
|
||||
|
||||
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| {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String, Box<RawValue>> = HashMap::new();
|
||||
}
|
||||
|
||||
struct PushNextFlowJobRec {
|
||||
flow_job: Arc<QueuedJob>,
|
||||
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<SendResult>,
|
||||
worker_name: &str,
|
||||
) -> error::Result<()> {
|
||||
) -> error::Result<Option<PushNextFlowJobRec>> {
|
||||
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<Box<RawValue>>\"",
|
||||
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::<FlowStatus>((**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<BranchChosen> },
|
||||
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::<Vec<_>>();
|
||||
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<JobPayloadWithTag> = branches
|
||||
@@ -3472,7 +3510,7 @@ async fn compute_next_flow_transform(
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
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(
|
||||
|
||||
@@ -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<number> {
|
||||
async function getCompletedJobsCount(tags?: string[]): Promise<number> {
|
||||
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 &&
|
||||
|
||||
@@ -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<Config> {
|
||||
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:number>", "Factor to multiply the number of jobs by.", {
|
||||
default: 1,
|
||||
})
|
||||
.action(main)
|
||||
.command(
|
||||
"upgrade",
|
||||
|
||||
+3
-3
@@ -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<string> {
|
||||
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`,
|
||||
},
|
||||
}
|
||||
],
|
||||
|
||||
+77
-22
@@ -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<string, number> = {};
|
||||
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<WebSocket> = 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}`);
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ export {
|
||||
// }
|
||||
// });
|
||||
|
||||
export const VERSION = "1.460.1";
|
||||
export const VERSION = "1.463.5";
|
||||
|
||||
const command = new Command()
|
||||
.name("wmill")
|
||||
|
||||
+13
-3
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
Generated
+2
-2
@@ -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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.460.1",
|
||||
"version": "1.463.5",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
|
||||
@@ -172,7 +172,7 @@
|
||||
<div class="py-1" />
|
||||
<OAuthSetting login={false} name="slack" bind:value={oauths['slack']} />
|
||||
<div class="py-1" />
|
||||
<OAuthSetting login={false} name="teams" bind:value={oauths['teams']} />
|
||||
<OAuthSetting login={false} name="teams" eeOnly={true} bind:value={oauths['teams']} />
|
||||
<div class="py-1" />
|
||||
|
||||
{#each Object.keys(oauths) as k}
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
<script lang="ts">
|
||||
import { Badge, Button } from '$lib/components/common'
|
||||
import Description from '$lib/components/Description.svelte'
|
||||
import { Slack, Code2, RefreshCcw } from 'lucide-svelte'
|
||||
import MsTeamsIcon from '$lib/components/icons/MSTeamsIcon.svelte'
|
||||
import BarsStaggered from '$lib/components/icons/BarsStaggered.svelte'
|
||||
import { hubBaseUrlStore, workspaceStore, enterpriseLicense } from '$lib/stores'
|
||||
import ScriptPicker from '$lib/components/ScriptPicker.svelte'
|
||||
import { WorkspaceService } from '$lib/gen'
|
||||
import type { ListAvailableTeamsIdsResponse } from '$lib/gen/types.gen'
|
||||
export let platform: 'slack' | 'teams'
|
||||
import { sendUserToast } from '$lib/utils'
|
||||
export let teamName: string | undefined
|
||||
export let display_name: string | undefined
|
||||
export let scriptPath: string
|
||||
export let initialPath: string
|
||||
export let onDisconnect: () => Promise<void>
|
||||
export let onSelect: () => Promise<void>
|
||||
export let connectHref: string | undefined
|
||||
export let createScriptHref: string
|
||||
export let createFlowHref: string
|
||||
export let documentationLink: string
|
||||
export let onLoadSettings: () => void
|
||||
export let itemKind: 'flow' | 'script' = 'script'
|
||||
|
||||
let isFetching = false
|
||||
|
||||
let teams: ListAvailableTeamsIdsResponse = []
|
||||
let selected_teams_team: string | undefined = undefined
|
||||
|
||||
async function loadTeams() {
|
||||
isFetching = true
|
||||
selected_teams_team = undefined
|
||||
teams = (await WorkspaceService.listAvailableTeamsIds({ workspace: $workspaceStore! })) ?? []
|
||||
isFetching = false
|
||||
}
|
||||
|
||||
$: workspaceStore && platform && $enterpriseLicense && loadTeams()
|
||||
|
||||
async function connectTeams() {
|
||||
const selectedTeam = teams.find((team) => team.team_id === selected_teams_team)
|
||||
const selectedTeamName = selectedTeam ? selectedTeam.team_name : undefined
|
||||
|
||||
await WorkspaceService.connectTeams({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
team_id: selected_teams_team,
|
||||
team_name: selectedTeamName
|
||||
}
|
||||
})
|
||||
sendUserToast('Connected to Teams to Workspace')
|
||||
onLoadSettings()
|
||||
loadTeams()
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="text-primary font-semibold"
|
||||
>Connect Workspace to {platform.charAt(0).toUpperCase() + platform.slice(1)}</div
|
||||
>
|
||||
<Description link={documentationLink}>
|
||||
Connect your Windmill workspace to your {platform} workspace to trigger a script or a flow with a
|
||||
'/windmill' command.
|
||||
</Description>
|
||||
</div>
|
||||
|
||||
{#if teamName}
|
||||
<div class="flex flex-col gap-2 max-w-sm">
|
||||
<div class="flex flex-row gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
endIcon={{ icon: platform === 'slack' ? Slack : MsTeamsIcon }}
|
||||
btnClasses="mt-2"
|
||||
variant="border"
|
||||
disabled={!$enterpriseLicense && platform === 'teams'}
|
||||
on:click={onDisconnect}
|
||||
>
|
||||
Disconnect {platform.charAt(0).toUpperCase() + platform.slice(1)}
|
||||
{!$enterpriseLicense && platform === 'teams' ? '(EE only)' : ''}
|
||||
</Button>
|
||||
{#if display_name}
|
||||
<Badge class="mt-2" color="green">Connected to Team '{display_name}'</Badge>
|
||||
{/if}
|
||||
</div>
|
||||
{#if $enterpriseLicense || platform === 'slack'}
|
||||
<Button size="sm" endIcon={{ icon: Code2 }} href={createScriptHref}>
|
||||
Create a script to handle {platform} commands
|
||||
</Button>
|
||||
<Button size="sm" endIcon={{ icon: BarsStaggered }} href={createFlowHref}>
|
||||
Create a flow to handle {platform} commands
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-row gap-2">
|
||||
{#if platform === 'teams'}
|
||||
<Button
|
||||
size="xs"
|
||||
color="dark"
|
||||
on:click={connectTeams}
|
||||
startIcon={{ icon: MsTeamsIcon }}
|
||||
disabled={!selected_teams_team || !enterpriseLicense}
|
||||
>
|
||||
Connect to {platform.charAt(0).toUpperCase() + platform.slice(1)}
|
||||
{$enterpriseLicense ? '' : '(EE only)'}
|
||||
</Button>
|
||||
{#if $enterpriseLicense}
|
||||
<div class="w-64 flex flex-row gap-2">
|
||||
<select bind:value={selected_teams_team}>
|
||||
{#if !isFetching}
|
||||
{#if teams.length === 0}
|
||||
<option value="" disabled selected>No unassigned teams found</option>
|
||||
{:else}
|
||||
<option value="" disabled selected>Select team</option>
|
||||
{#each teams as team}
|
||||
<option value={team.team_id}>
|
||||
{team.team_name}
|
||||
</option>
|
||||
{/each}
|
||||
{/if}
|
||||
{:else}
|
||||
<option value="" disabled selected>Loading...</option>
|
||||
{/if}
|
||||
</select>
|
||||
</div>
|
||||
<div class="pt-1">
|
||||
<button on:click={loadTeams} class="flex items-center gap-1 mt-2">
|
||||
<RefreshCcw size={16} class={isFetching ? 'animate-spin' : ''} />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<Button size="xs" color="dark" href={connectHref} startIcon={{ icon: Slack }}>
|
||||
Connect to {platform.charAt(0).toUpperCase() + platform.slice(1)}
|
||||
</Button>
|
||||
{/if}
|
||||
<Badge color="red">Not connected</Badge>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="bg-surface-disabled p-4 rounded-md flex flex-col gap-1">
|
||||
<div class="text-primary font-md font-semibold"> Script or flow to run on /windmill command </div>
|
||||
<div class="relative">
|
||||
{#if !teamName || (!$enterpriseLicense && platform === 'teams')}
|
||||
<div class="absolute top-0 right-0 bottom-0 left-0 bg-surface-disabled/50 z-40" />
|
||||
{/if}
|
||||
<ScriptPicker
|
||||
kinds={['script']}
|
||||
allowFlow
|
||||
bind:itemKind
|
||||
bind:scriptPath
|
||||
{initialPath}
|
||||
on:select={onSelect}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="prose text-2xs text-tertiary">
|
||||
Pick a script or flow meant to be triggered when the `/windmill` command is invoked. Upon
|
||||
connection, templates for a <a href="{$hubBaseUrlStore}/scripts/{platform}/1405/">script</a>
|
||||
and <a href="{$hubBaseUrlStore}/flows/28/">flow</a> are available.
|
||||
|
||||
<br /><br />
|
||||
|
||||
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.
|
||||
|
||||
<br /><br />
|
||||
|
||||
It can take additionally the following args: channel_id, user_name, user_id, command,
|
||||
trigger_id, api_app_id
|
||||
|
||||
<br /><br />
|
||||
|
||||
<span class="font-bold text-xs">
|
||||
The script or flow is permissioned as group "{platform}" that will be automatically created
|
||||
after connection to {platform.charAt(0).toUpperCase() + platform.slice(1)}.
|
||||
</span>
|
||||
|
||||
<br /><br />
|
||||
|
||||
See more on
|
||||
<a href={documentationLink}>documentation</a>.
|
||||
</div>
|
||||
</div>
|
||||
@@ -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 @@
|
||||
<Splitpanes horizontal class="h-full max-h-screen grow">
|
||||
<Pane size={67}>
|
||||
{#if $flowStore?.value?.modules}
|
||||
<div id="flow-editor" />
|
||||
<FlowModuleSchemaMap
|
||||
bind:modules={$flowStore.value.modules}
|
||||
disableAi
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<script lang="ts">
|
||||
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<string, any>
|
||||
@@ -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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
await sendMessage(channel, 'slack')
|
||||
}
|
||||
|
||||
async function sendTeamsMessage(channel: string): Promise<void> {
|
||||
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 @@
|
||||
<div>
|
||||
<Tabs bind:selected={handlerSelected} class="mt-2 mb-4">
|
||||
<Tab value="slack" disabled={!isEditable}>Slack</Tab>
|
||||
<Tab value="teams" disabled={!isEditable}>Teams</Tab>
|
||||
<Tab value="custom" disabled={!isEditable}>
|
||||
Custom
|
||||
<slot name="custom-tab-tooltip" />
|
||||
@@ -254,7 +340,7 @@
|
||||
<Toggle
|
||||
disabled={!$enterpriseLicense || !isEditable}
|
||||
checked={isSlackHandler(handlerPath)}
|
||||
options={{ right: slackToggleText }}
|
||||
options={{ right: toggleText }}
|
||||
on:change={async (e) => {
|
||||
if (e.detail && errorOrRecovery === 'error') {
|
||||
handlerPath = slackHandlerScriptPath
|
||||
@@ -313,11 +399,11 @@
|
||||
on:click={() => sendSlackMessage(handlerExtraArgs['channel'])}
|
||||
size="xs">Send test message</Button
|
||||
>
|
||||
{#if slackConnectionTestJob !== undefined}
|
||||
{#if connectionTestJob !== undefined}
|
||||
<p class="text-normal text-2xs mt-1 gap-2">
|
||||
{#if slackConnectionTestJob.in_progress}
|
||||
{#if connectionTestJob.in_progress}
|
||||
<RotateCw size={14} />
|
||||
{:else if slackConnectionTestJob.is_success}
|
||||
{:else if connectionTestJob.is_success}
|
||||
<CheckCircle2 size={14} class="text-green-600" />
|
||||
{:else}
|
||||
<XCircle size={14} class="text-red-700" />
|
||||
@@ -325,9 +411,114 @@
|
||||
Message sent via Windmill job
|
||||
<a
|
||||
target="_blank"
|
||||
href={`${base}/run/${slackConnectionTestJob.uuid}?workspace=${$workspaceStore}`}
|
||||
href={`${base}/run/${connectionTestJob.uuid}?workspace=${$workspaceStore}`}
|
||||
>
|
||||
{slackConnectionTestJob.uuid}
|
||||
{connectionTestJob.uuid}
|
||||
</a>
|
||||
</p>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
{:else if handlerSelected === 'teams'}
|
||||
<span class="w-full flex mb-3">
|
||||
<Toggle
|
||||
disabled={!$enterpriseLicense || !isEditable}
|
||||
checked={isTeamsHandler(handlerPath)}
|
||||
options={{ right: toggleText }}
|
||||
on:change={async (e) => {
|
||||
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
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
{#if workspaceConnectedToTeams}
|
||||
<div class="w-2/3 flex flex-row items-center gap-2">
|
||||
<div class="pt-1 flex-shrink-0">
|
||||
<MsTeamsIcon height="24px" width="24px" />
|
||||
</div>
|
||||
<p class="text-sm">Teams Channel</p>
|
||||
<div class="flex-grow">
|
||||
<select class="w-full" bind:value={handlerExtraArgs['channel']}>
|
||||
{#if teams_channels.length === 0}
|
||||
<option value="" disabled selected>Bot not connected to any channel</option>
|
||||
{:else}
|
||||
<option value="" disabled selected>Select Teams channel</option>
|
||||
{#each teams_channels as channel}
|
||||
<option value={channel.channel_id}>
|
||||
{channel.channel_name}
|
||||
</option>
|
||||
{/each}
|
||||
{/if}
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-shrink-0">
|
||||
<button on:click={loadTeamsResources} class="flex items-center gap-1 mt-2">
|
||||
<RefreshCcw size={16} class={isFetching ? 'animate-spin' : ''} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row gap-2 pb-4">
|
||||
<p class="text-sm">
|
||||
This workspace is connected to Team: <Badge color="blue" size="xs" class="mt-2">{teams_team_name}</Badge>
|
||||
</p>
|
||||
<Tooltip text={teams_team_name}>
|
||||
Each workspace can only be connected to one Microsoft Teams team. You can configure it under <a
|
||||
target="_blank"
|
||||
href="{base}/workspace_settings?tab=teams">workspace settings</a
|
||||
>.
|
||||
</Tooltip>
|
||||
</div>
|
||||
{:else if workspaceConnectedToTeams == undefined}
|
||||
<Loader2 class="animate-spin" size={10} />
|
||||
{/if}
|
||||
{#if $enterpriseLicense && isTeamsHandler(handlerPath)}
|
||||
{#if workspaceConnectedToTeams == false}
|
||||
<Alert type="error" title="Workspace not connected to Teams">
|
||||
<div class="flex flex-row gap-x-1 w-full items-center">
|
||||
<p class="text-clip grow min-w-0">
|
||||
The workspace needs to be connected to Teams to use this feature. You can configure it
|
||||
under <a target="_blank" href="{base}/workspace_settings?tab=teams"
|
||||
>workspace settings</a
|
||||
>.
|
||||
</p>
|
||||
<Button
|
||||
variant="border"
|
||||
color="light"
|
||||
on:click={loadTeamsResources}
|
||||
startIcon={{ icon: RotateCw }}
|
||||
/>
|
||||
</div>
|
||||
</Alert>
|
||||
{:else}
|
||||
<Button
|
||||
disabled={emptyString(handlerExtraArgs['channel'])}
|
||||
btnClasses="w-32 text-center"
|
||||
color="dark"
|
||||
on:click={() => sendTeamsMessage(handlerExtraArgs['channel'] ?? '')}
|
||||
size="xs">Send test message</Button
|
||||
>
|
||||
{#if connectionTestJob !== undefined}
|
||||
<p class="text-normal text-2xs mt-1 gap-2">
|
||||
{#if connectionTestJob.in_progress}
|
||||
<RotateCw size={14} />
|
||||
{:else if connectionTestJob.is_success}
|
||||
<CheckCircle2 size={14} class="text-green-600" />
|
||||
{:else}
|
||||
<XCircle size={14} class="text-red-700" />
|
||||
{/if}
|
||||
Message sent via Windmill job
|
||||
<a
|
||||
target="_blank"
|
||||
href={`${base}/run/${connectionTestJob.uuid}?workspace=${$workspaceStore}`}
|
||||
>
|
||||
{connectionTestJob.uuid}
|
||||
</a>
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -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"
|
||||
>
|
||||
<div class="bg-surface {col ? '' : 'max-h-80'} p-1 overflow-auto relative">
|
||||
<span class="text-tertiary">Result</span>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user