Compare commits

...
168 Commits
Author SHA1 Message Date
Ruben Fiszelandrubenfiszel 2fda22427c chore(main): release 1.244.1 (#3001)
* chore(main): release 1.244.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-01-13 15:45:10 +01:00
Ruben Fiszel b84cd6d52d fix: menu colors 2024-01-13 15:38:59 +01:00
Ruben Fiszel 5201dcdd67 fix: menu colors 2024-01-13 15:34:48 +01:00
Ruben Fiszelandrubenfiszel 8f96a8d248 chore(main): release 1.244.0 (#3000)
* chore(main): release 1.244.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-01-13 13:50:49 +01:00
Ruben Fiszel dff1948a64 add step_id to errors in flows 2024-01-13 13:46:42 +01:00
Faton RamadaniandRuben Fiszel aaff17f813 feat(frontend): Operator mode (#2973)
* feat(frontend): Operator mode

* feat(frontend): Operator mode

* feat(frontend): operator menu v2

* feat(frontend): operator menu v2

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2024-01-13 13:25:37 +01:00
Ruben Fiszel 0733dd118e fix: add ability to set secret variable from python 2024-01-13 13:02:36 +01:00
Ruben Fiszel 16219755a7 fix: improve oauth accounts permissions 2024-01-13 13:00:04 +01:00
Ruben Fiszel f0c4901c21 fix: improve table behavior when searching 2024-01-13 10:57:49 +01:00
Ruben Fiszel dfa5eae6f2 nit vega-lite 2024-01-13 09:59:34 +01:00
Ruben Fiszel 3d93b5456f fix vegalite renderer 2024-01-13 09:42:12 +01:00
Ruben Fiszelandrubenfiszel ec22315fb1 chore(main): release 1.243.0 (#2997)
* chore(main): release 1.243.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-01-13 08:39:02 +01:00
Faton RamadaniandRuben Fiszel 3c59fb8b4d feat(frontend): s3 file upload (#2976)
* feat(frontend): wip

* feat(frontend): s3 file working

* feat(frontend): policy

* feat(frontend): policy

* feat(frontend): wip

* feat(frontend): merge main

* feat(frontend): update s3 upload logic

* feat(frontend): wip

* feat(frontend): wip

* feat(frontend): path template

* feat(frontend): done

* feat(frontend): clean up

* feat(frontend): fix dark mode

* feat(frontend): fix outputs + add component control

* feat(frontend): fix outputs + add component control

* Update components.ts

* Update components.ts

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2024-01-13 08:35:00 +01:00
Ruben Fiszel aafd7d9003 feat: support dynamic args in tags 2024-01-13 01:47:01 +01:00
Guillaume Bouvignies 99484bdc20 feat: Tag override for scheduled scripts (#2998)
* feat: Tag override for scheduled scripts

* Refactor WorkerTagPicker to component

* Fix test
2024-01-13 00:06:54 +01:00
Ruben Fiszel d785deff33 feat: add support for bun install scopes 2024-01-12 23:48:58 +01:00
Ruben Fiszel e200889cff fix: allow for any extra bunfig config 2024-01-12 20:38:15 +01:00
Guillaume Bouvignies fda0e28db3 feat: GIt sync to multiple repo (#2996) 2024-01-12 16:19:28 +01:00
Ruben Fiszelandrubenfiszel 13c5802670 chore(main): release 1.242.0 (#2995)
* chore(main): release 1.242.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-01-12 13:49:17 +01:00
Guillaume Bouvignies 5f54f55790 feat: Instance group management page (#2994) 2024-01-12 13:37:06 +01:00
Ruben Fiszel 01dbf548f7 fix: catch more agGrid errors 2024-01-12 13:29:01 +01:00
Ruben Fiszelandrubenfiszel 2e9e7c17ba chore(main): release 1.241.0 (#2993)
* chore(main): release 1.241.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-01-12 11:48:45 +01:00
Guillaume Bouvignies 5a157c415e feat: Instance group CRUD API (#2992) 2024-01-12 11:43:29 +01:00
Guillaume Bouvignies cde574b891 fix: git sync now works for delete and rename (#2988)
* fix: git sync now works for delete and rename

* update hub script

* sqlx prepare

* Archiving a script/flow removes it from repo
2024-01-12 11:42:54 +01:00
Ruben Fiszel 6f47c30c36 workspace error handler in ee 2024-01-12 11:38:01 +01:00
Ruben Fiszel 42f9ad2b90 schedule error handlers in ee 2024-01-12 11:25:44 +01:00
Ruben Fiszel a8c5bf7fa4 add dynamic enums to the approval page 2024-01-12 11:07:19 +01:00
Ruben Fiszel 9514b342dd improve nested flow flow renderer 2024-01-12 01:11:49 +01:00
Ruben Fiszel dd015fd869 improve argenum 2024-01-12 00:56:51 +01:00
Ruben Fiszel b3fa1cd77d improve schema reordering 2024-01-12 00:42:43 +01:00
Ruben Fiszel d59b89ec3c feat: make dedicated workers for flows able to share runtime for the same scripts 2024-01-12 00:21:52 +01:00
Ruben Fiszel 734e61931f ignore authToken for npm for deno 2024-01-11 22:24:04 +01:00
Ruben Fiszel 28ac7632cf fix: add cache for flow as flow step 2024-01-11 20:18:48 +01:00
Ruben Fiszel aa101b85af make aggrid column defs more resilient 2024-01-11 14:41:05 +01:00
Ruben Fiszelandrubenfiszel 7e3238418e chore(main): release 1.240.0 (#2987)
* chore(main): release 1.240.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-01-11 13:00:44 +01:00
Ruben Fiszel 39ecf8017e feat(cli): introduce --stateful for CLI, in preparation for --raw to become the default 2024-01-11 12:57:16 +01:00
Ruben Fiszelandrubenfiszel 7df446c23e chore(main): release 1.239.0 (#2985)
* chore(main): release 1.239.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-01-11 12:31:14 +01:00
Ruben Fiszel b9986df631 nit: remove isAnimated from editableschema form 2024-01-11 12:28:41 +01:00
Ruben Fiszel 6996c9083d fix(cli): improve .wmillignore handling of folders 2024-01-11 12:23:42 +01:00
Ruben Fiszel 5f892c0f40 improve oreder handling for schemaform 2024-01-11 11:38:31 +01:00
Ruben Fiszel 6869368f1c improve schema form to handle empty schemas 2024-01-11 11:37:12 +01:00
Rory Logue 308c4ceb47 fix: spelling error dtails -> details (#2986) 2024-01-11 11:32:14 +01:00
Ruben Fiszel 32b38401c8 filter nit 2024-01-11 10:04:15 +01:00
Ruben Fiszel 8c97ef0394 fix: handle Etc/Unknown timezone better 2024-01-11 08:27:44 +01:00
Ruben Fiszel 717ccc94a0 feat: add configurable ordering for script's generated UI 2024-01-11 00:45:36 +01:00
Ruben Fiszel 589e683c27 fix: only cache flow if it's a success 2024-01-10 21:31:58 +01:00
Faton Ramadani e3c166154d feat(frontend): AG chart (#2972)
* fix(frontend): Fix decision tree (#2928)

* fix(frontend): wip

* fix(frontend): wip

* fix(frontend): decision tree history

* fix(frontend): fix wording

* feat(frontend): wip

* feat(frontend): wip

* feat(frontend): ag charts

* feat(frontend): remove todo

* feat(frontend): remove todo

* feat(frontend): revert

* feat(frontend): fix converstion

* feat(frontend): ag charts ee

* feat(frontend): fix build

* feat(frontend): fix convertion to json

* feat(frontend): fix naming

* feat(frontend): fix id collision

* feat(frontend): fix initial load

* feat(frontend): fix initial load
2024-01-10 17:52:22 +01:00
Guillaume Bouvignies e19ac129aa docs: Update ECS terraform networking (#2984) 2024-01-10 17:41:36 +01:00
Ruben Fiszelandrubenfiszel f443873064 chore(main): release 1.238.0 (#2979)
* chore(main): release 1.238.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-01-10 16:54:24 +01:00
Guillaume Bouvignies eea0d92dd1 feat: S3 multipart upload accepts a custom S3 resource (#2982) 2024-01-10 16:42:32 +01:00
Guillaume Bouvignies 6d5c3f0f22 fix: main compile (#2983) 2024-01-10 15:31:05 +01:00
Ruben Fiszel 1dcb75cce3 fix submodule stripe 2024-01-10 14:59:18 +01:00
Ruben Fiszel 605a61c378 replace tracing log 2024-01-10 13:54:58 +01:00
Guillaume Bouvignies f8862bc3b3 docs: ECS log format JSON (#2981) 2024-01-10 13:50:46 +01:00
Ruben Fiszel 3517c7f283 feat: add ability to use secrets in pip requirements 2024-01-10 13:12:20 +01:00
Ruben Fiszel c8a793d35e feat: add support for multiselect in python 2024-01-10 13:12:20 +01:00
Guillaume Bouvignies 42b28822f3 docs: AWS ECS terraform deploy (#2980) 2024-01-10 08:17:38 +01:00
Faton Ramadani 4147d1604a feat(frontend): add shortcuts to audit logs (#2975)
* feat(frontend): add shortcuts to audit logs

* feat(frontend): simplify audit logs shortcuts
2024-01-09 15:43:13 +01:00
Faton Ramadani cffae36331 feat(frontend): Display the index by which a node can be selected with the setTab function + add tooltip (#2974) 2024-01-09 15:42:37 +01:00
Ruben Fiszel e16fde88a1 small QoL improvements on input type args 2024-01-09 06:48:52 +01:00
Ruben Fiszel 35c5684981 small QoL improvements on input type args 2024-01-09 05:22:54 +01:00
Ruben Fiszel 21c1f3c638 fix git sync resource picker 2024-01-08 22:39:47 +01:00
Ruben Fiszelandrubenfiszel ea273810a1 chore(main): release 1.237.0 (#2971)
* chore(main): release 1.237.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-01-08 15:40:02 +01:00
Ruben Fiszel 4f01ee89de feat: make okta SSO configurable using custom domain 2024-01-08 15:16:45 +01:00
Ruben Fiszel d340fbc02f fix: delete is captured in more fields in app 2024-01-08 11:33:28 +01:00
Ruben Fiszel bed2d1f417 chore: use dev instead of latest to publish to ecr 2024-01-08 10:12:24 +01:00
Ruben Fiszelandrubenfiszel 4377cb3bcc chore(main): release 1.236.2 (#2970)
* chore(main): release 1.236.2

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-01-08 10:05:25 +01:00
Ruben Fiszel 269afe41f7 fix: handle better non object result for app result loader 2024-01-08 10:02:24 +01:00
Ruben Fiszelandrubenfiszel 1d1f03691a chore(main): release 1.236.1 (#2969)
* chore(main): release 1.236.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-01-08 09:29:56 +01:00
Ruben Fiszel 6b075e92ef fix: fix app initialization 2024-01-08 09:27:50 +01:00
Ruben Fiszelandrubenfiszel be28522eb7 chore(main): release 1.236.0 (#2968)
* chore(main): release 1.236.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-01-08 06:49:31 +01:00
Ruben Fiszel 5749a3083a fix script deployment 2024-01-08 06:37:08 +01:00
Ruben Fiszel 2034aebb17 fix tests following parser update 2024-01-08 00:57:11 +01:00
Ruben Fiszel 4384617f1e fix: reduce stack size needed for workflow transitions 2024-01-08 00:47:59 +01:00
Ruben Fiszel 94493812ac feat: improve git sync and recompute dependents when common python script is redeployed (#2967)
* fix: allow extraConfig for agGrid to set onGridReady

* depmap

* depmap

* depmap

* depmap
2024-01-07 15:18:27 +01:00
Ruben Fiszelandrubenfiszel 2fc21d46fa chore(main): release 1.235.1 (#2966)
* chore(main): release 1.235.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-01-07 14:15:40 +01:00
Ruben Fiszel 2e023003da fix: fix resultjobloader 2024-01-07 14:12:20 +01:00
Guillaume Bouvignies 30a30f81e0 chore: Update git sync scripts (#2965) 2024-01-06 18:23:01 +01:00
Ruben Fiszelandrubenfiszel 723744e41e chore(main): release 1.235.0 (#2964)
* chore(main): release 1.235.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-01-06 17:32:41 +01:00
fffc5338ce feat(frontend): DB Explorer (#2892)
* feat(frontend): Make table cell editable

* feat(frontend): wip

* feat(frontend): wip

* feat(frontend): wip

* feat(frontend): wip

* feat(frontend): wip

* feat(frontend): wip

* feat(frontend): wip

* feat(frontend): clean up

* fix(frontend): extract db schemas

* feat(frontend): v0 done

* feat(frontend): v0 done

* feat(frontend): v0 done

* feat(frontend): fix insert

* feat(frontend): fix insert

* feat(frontend): remove temp data

* feat(frontend): align insert button to the right

* feat(frontend): rework columns

* feat(frontend): rework insert

* feat(frontend): rework insert

* feat(frontend): rework insert

* feat(frontend): rework insert

* feat(frontend): fix jsonb display

* feat(frontend): fix reloading issues

* feat(frontend): fix reloading issues

* feat(frontend): fix reacticity issue

* feat(frontend): fix insert

* feat(frontend): support nullable default value

* feat(frontend): fix build

* update

* update

* db studio v0

---------

Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2024-01-06 17:27:15 +01:00
Henri Courdent 3c2f753eac "Author" to "Developer", Premium plans tab, Flow inputs (#2952)
* Premium plans tab & Flow inputs

* Fix build
2024-01-06 16:55:43 +01:00
Ruben Fiszelandrubenfiszel aa3d802137 chore(main): release 1.234.1 (#2962)
* chore(main): release 1.234.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-01-05 18:30:26 +01:00
Ruben Fiszel 9efcb53bcd fix: fix double view runs button 2024-01-05 18:27:45 +01:00
Ruben Fiszel 4385edc6ff fix: fix typo "Wourker" -> "Worker" 2024-01-05 18:26:52 +01:00
Ruben Fiszelandrubenfiszel 3cadaa3f2b chore(main): release 1.234.0 (#2957)
* chore(main): release 1.234.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-01-05 18:24:22 +01:00
Guillaume Bouvignies c05d119ae5 fix: Deleting workspace delete all workspace related values in DB (#2961)
* fix: Deleting workspace delete all workspace related values in DB

* sqlx prepare
2024-01-05 18:02:40 +01:00
Guillaume Bouvignies fb5cd6a429 feat: git sync can now push commits to individual branches (#2959) 2024-01-05 15:52:18 +01:00
Guillaume Bouvignies 0710ce6ef2 feat: Detailed job memory footprint on job run page (#2956)
* feat: Detailed job memory footprint

* Address rubens comment

* double presion -> real

* sql query returns only metric_type
2024-01-05 09:13:05 +01:00
Martin Jul 8619f3102d Fix typo in README.md (#2955)
Fix typo
2024-01-04 11:24:33 +01:00
Guillaume Bouvignies 31d48533f4 S3 functions in Python SDK cannot return None (#2954) 2024-01-03 17:26:25 +01:00
Ruben Fiszelandrubenfiszel 6cd46ca7f8 chore(main): release 1.233.0 (#2926)
* chore(main): release 1.233.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-01-03 13:20:20 +01:00
Faton Ramadani 2494171b9c fix(frontend): handle case where large json has a file and a filename (#2951) 2024-01-03 09:17:30 +01:00
Guillaume Bouvignies 965571853c fix: Tag all docker images with latest and main tags (#2953) 2024-01-03 01:38:33 +09:00
Ruben Fiszel 486b3aeb17 chore: improve e2e 2023-12-30 05:25:10 +01:00
Ruben Fiszel 5ee0040840 chore: improve e2e 2023-12-30 04:47:08 +01:00
Ruben Fiszel 0f774b4485 chore: improve e2e 2023-12-30 04:43:51 +01:00
Ruben Fiszel 0c40c1324b chore: improve integration tests 2023-12-30 04:21:34 +01:00
Ruben Fiszel b17652ae60 chore: improve integration tests 2023-12-30 03:59:20 +01:00
Guillaume Bouvignies b44618f35b feat: Job caching now works with S3 files (#2934) 2023-12-30 03:40:05 +01:00
Faton RamadaniandRuben Fiszel 821d2d7ea1 feat(frontend): add the view runs buttons for operators (#2932)
* feat(frontend): add the view runs buttons for operators

* Update +page.svelte

* Update +page.svelte

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2023-12-30 03:39:22 +01:00
0299c656c3 fix: decision tree fix wording (#2941)
* fix(frontend): Fix decision tree (#2928)

* fix(frontend): wip

* fix(frontend): wip

* fix(frontend): decision tree history

* fix(frontend): fix wording

* fix(frontend): fix wording

* fix(frontend): fix wording

* Carousel link and trigger frontend (#2929)

Co-authored-by: Faton Ramadani <faton.ramadani14@gmail.com>

---------

Co-authored-by: Faton Ramadani <faton.ramadani14@gmail.com>
Co-authored-by: Henri Courdent <122811744+hcourdent@users.noreply.github.com>
2023-12-30 03:39:05 +01:00
e0140f175d feat: PIP_LOCAL_DEPENDENCIES now accepts regex and is configurable from the UI (#2940)
* fix(frontend): Fix decision tree (#2928)

* fix(frontend): wip

* fix(frontend): wip

* fix(frontend): decision tree history

* fix(frontend): fix wording

* feat: PIP_LOCAL_DEPENDENCIES now accepts regex

* Add worker config FE

---------

Co-authored-by: Faton Ramadani <faton.ramadani14@gmail.com>
Co-authored-by: gbouv <guillaume@windmill.dev>
2023-12-30 03:35:04 +01:00
65ad840dc4 docs: Carousel link and trigger frontend (#2939)
* Carousel link and trigger frontend

* fix(frontend): Fix decision tree (#2928)

* fix(frontend): wip

* fix(frontend): wip

* fix(frontend): decision tree history

* fix(frontend): fix wording

---------

Co-authored-by: hcourdent <henri@windmill.dev>
Co-authored-by: Faton Ramadani <faton.ramadani14@gmail.com>
2023-12-30 03:32:14 +01:00
Ruben Fiszelandgbouv f13fb4505c docs: Example on how to monitor Windmill (#2938)
* docs: Example on how to track arbitrary job metrics

* Add how to monitor Windmill servers and workers

* Update README.md

---------

Co-authored-by: gbouv <guillaume@windmill.dev>
2023-12-30 03:30:48 +01:00
Ruben Fiszelandgbouv c80e4c5ffd chore: eetest (#2937)
* chore: Windmill integration tests

* Revert github workflow temp changes

* fix ee

---------

Co-authored-by: gbouv <guillaume@windmill.dev>
2023-12-30 03:29:00 +01:00
Ruben FiszelandFaton Ramadani 787017f53c fix: decision tree nits (#2936)
* fix(frontend): wip

* fix(frontend): wip

* fix(frontend): decision tree history

* fix(frontend): fix wording

---------

Co-authored-by: Faton Ramadani <faton.ramadani14@gmail.com>
2023-12-30 03:06:37 +01:00
Ruben Fiszel c085f5fa7e fix: persist state for agGrid 2023-12-29 03:28:07 +01:00
Ruben Fiszel ad6c3e5cee small UX nits 2023-12-29 03:21:11 +01:00
Ruben Fiszel 74fc2d3f43 fix: allow extraConfig for agGrid to set onGridReady 2023-12-27 21:20:49 +01:00
Ruben Fiszelandrubenfiszel 3d1f707f36 chore(main): release 1.232.0 (#2924)
* chore(main): release 1.232.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-12-27 15:04:51 +01:00
Guillaume Bouvignies d316af993d chore: update snippets for Polars 0.20 (#2925) 2023-12-27 15:02:06 +01:00
Ruben Fiszel 263f41cda3 fix: fix file input 2023-12-27 14:55:01 +01:00
Ruben Fiszel b66fcbf626 fix: improve ata for bun 2023-12-27 02:29:38 +01:00
Ruben Fiszel 948b0da59e fix: use bun install in every case 2023-12-27 00:05:46 +01:00
Ruben Fiszel 49355280f6 feat: no overlap for flows togglable on schedules 2023-12-26 16:31:04 +01:00
Ruben Fiszel 606b03d761 feat: add summary to schedules 2023-12-26 14:15:46 +01:00
Ruben Fiszelandrubenfiszel 41062072d6 chore(main): release 1.231.0 (#2917)
* chore(main): release 1.231.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-12-26 07:10:27 +01:00
Ruben Fiszel 4b65594cbd fix: improve conditional wrapper 2023-12-26 07:02:01 +01:00
Guillaume Bouvignies 5fa5ab1514 feat: bun can read private npm package (#2915)
* feat: bun can read private package

* Add / at the end of url
2023-12-25 09:03:40 +01:00
Ruben Fiszel e583dd7894 restrict assignable tags to superadmins 2023-12-24 13:44:15 +01:00
Ruben Fiszel 4a28e23c03 improve worker tag UX 2023-12-24 11:20:56 +01:00
Ruben Fiszel d6237b9ecb itemslist nit 2023-12-23 05:12:54 +01:00
Ruben Fiszel fc07d58868 feat(cli): support skip args for push --raw 2023-12-23 04:51:18 +01:00
Guillaume Bouvignies 2aecc4d34f chore: test merge (#2914) 2023-12-22 19:01:29 +01:00
Ruben Fiszelandrubenfiszel eecca0fac2 chore(main): release 1.230.0 (#2905)
* chore(main): release 1.230.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-12-22 18:20:33 +01:00
Ruben Fiszel b5f1f99daa fix: fix multiplayer 2023-12-22 18:17:36 +01:00
Ruben Fiszel 5698a0ebf2 feat: quick search in flow support workspace scripts 2023-12-22 17:54:44 +01:00
Ruben Fiszel 2a4c370669 improve search perf 2023-12-22 16:05:47 +01:00
Ruben Fiszel 408cef3d77 improve search perf 2023-12-22 15:59:01 +01:00
Ruben Fiszel 9667eb74f9 fix: edit schedule from trigger page 2023-12-22 15:32:36 +01:00
Guillaume Bouvignies e40787c616 feat: move S3 file within bucket (#2913) 2023-12-22 15:02:16 +01:00
Guillaume Bouvignies 5f60d46836 feat: Retry for scheduled scripts (#2893)
* feat: Retry for scheduled scripts

* rename scheduldedscriptwithretry to singlescriptflow

* Schedule options under tabs
2023-12-22 14:59:46 +01:00
Faton Ramadani d7b777a8e9 fix(frontend): Fix debug condition + decision tree + add missing date to job preview + prevent header node from being selected in the flow editor (#2912) 2023-12-22 13:25:12 +01:00
Guillaume Bouvignies 3cbdd890cd fix: Update S3 TS code snippets and Python SDK for Polars 0.20.X (#2911)
* fix: update S3 TS code snippets

* use S3Object type from SDK

* update python snippets as well

* Update python SDK for Polars 0.20.X
2023-12-22 08:39:35 +01:00
Guillaume Bouvignies e27e887491 feat: S3 delete file and upload new (#2909) 2023-12-22 08:38:09 +01:00
Ruben Fiszel 6167cd65de ata nits 2023-12-22 07:59:13 +01:00
Ruben Fiszel 642c037c1a feat: add button for auto-formatting 2023-12-22 05:16:33 +01:00
Ruben Fiszel 6b570a779f fix: add relative path handling for bun ATA 2023-12-22 04:30:18 +01:00
2e180c8fe7 Helps in frontend (#2901)
* Helps in frontend

* fix doclink

* Fix build

* Removing tooltips Save

* fix: remove bad app type forcing (#2906)

* fix: remove bad type forcing

* fix: safety

---------

Co-authored-by: Faton Ramadani <faton.ramadani14@gmail.com>
Co-authored-by: HugoCasa <hugo@casademont.ch>
2023-12-21 17:00:02 +01:00
Ruben Fiszel ea322d9c14 fix: pin deno windmill-client version to relase 2023-12-21 15:25:40 +01:00
Ruben Fiszel b3527f5164 fix: improve javascript editors with semantic validation 2023-12-21 15:12:35 +01:00
HugoCasa 187c34a683 fix: remove bad app type forcing (#2906)
* fix: remove bad type forcing

* fix: safety
2023-12-21 13:28:26 +01:00
Guillaume BouvigniesandRuben Fiszel 5f85b67dfc fix: Failing jobs in dedicated worker mode are now marked as failing (#2894)
* fix: Failing jobs in dedicated worker mode are now marked as failing

* remove log line

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2023-12-21 10:29:26 +01:00
Guillaume Bouvignies 4c3c988f7b fix: Python wrapper catches BaseException instead of Exception (#2902) 2023-12-21 10:26:13 +01:00
Ruben Fiszelandrubenfiszel 4e383e8404 chore(main): release 1.229.0 (#2898)
* chore(main): release 1.229.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-12-21 09:07:18 +01:00
Faton Ramadani 0170fcc338 fix(frontend): fix save draft when the app has no versions (#2904) 2023-12-21 09:03:22 +01:00
Guillaume Bouvignies ebcc7ed2aa chore: add verify to Windmill python object for httpx (#2900)
* chore: add `verify` to Windmill python object for httpx

* move it before get_client
2023-12-21 02:38:16 +01:00
Ruben Fiszel 5a1902797c small ata fix 2023-12-21 02:15:16 +01:00
Ruben Fiszel db05299795 fix audit logs 2023-12-21 01:56:53 +01:00
Ruben Fiszel b43c5be108 improve ata 2023-12-20 18:36:06 +01:00
Ruben Fiszel f2bcd723a5 improve ata 2023-12-20 18:35:58 +01:00
Guillaume Bouvignies 5fafd71135 fix: Re-add TS SDK changes for S3 (#2899) 2023-12-20 18:12:37 +01:00
Faton Ramadani 3f7c19f267 feat(frontend): add a quick button to open decision tree graph (#2897) 2023-12-20 17:21:59 +01:00
Ruben Fiszelandrubenfiszel 54cb6c4e88 chore(main): release 1.228.1 (#2896)
* chore(main): release 1.228.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-12-20 16:57:28 +01:00
Guillaume Bouvignies 46e9818c00 fix: TS SDK not found upon install error (#2895) 2023-12-20 16:53:42 +01:00
Ruben Fiszelandrubenfiszel 1291b91b9b chore(main): release 1.228.0 (#2875)
* chore(main): release 1.228.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-12-20 14:30:58 +01:00
Ruben Fiszel 83aaa30967 small ata fix 2023-12-20 12:55:21 +01:00
Ruben Fiszel d507ce449b feat: bun automatic type acquisition in frontend directly (#2884)
* foo

* ata

* done

* remove bun from lsp

* update all
2023-12-20 12:52:01 +01:00
Guillaume Bouvignies 2155a6c133 feat: S3 objects are now typed in Python and TS SDK (#2878)
* feat: S3 objects are now typed in Python and TS SDK

* fix ts and python SDK after testing

* cleanup
2023-12-20 09:16:33 +01:00
Guillaume Bouvignies 4061bbf832 docs: How to query gRPC service using Bun (#2863)
* docs: How to query gRPC service using Bun

* move readme to blogpost
2023-12-20 09:14:01 +01:00
Guillaume Bouvignies 292b2309a7 fix: Python imports from git repository (#2886) 2023-12-20 13:00:51 +09:00
legalgig afbea19421 feat: Add Zitadel IDP (#2888) 2023-12-20 13:00:06 +09:00
HugoCasa e8b2cc8835 feat: not on latest app version warning (#2889) 2023-12-20 12:59:18 +09:00
Guillaume Bouvignies d0c75002f8 docs: Processing Kafka messages with Windmill Perpetual scripts (#2859)
* docs: Processing Kafka messages with Windmill Perpetual scripts

* Add while loop and update readme

* move README to blog post
2023-12-19 12:55:38 +01:00
Guillaume Bouvignies 5dd58094fd fix: Persistent script spinner (#2879) 2023-12-18 16:16:25 +01:00
Ruben Fiszel 59397e7445 fix: remove 0x00 from logs automatically from bashoutput 2023-12-18 03:30:47 +01:00
Ruben Fiszelandrubenfiszel 032a8e3dd1 chore(main): release 1.227.1 (#2874)
* chore(main): release 1.227.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-12-17 20:18:10 +01:00
Ruben Fiszel fa7261f273 fix: improve on click behavior of tree view 2023-12-17 20:15:09 +01:00
412 changed files with 66202 additions and 9853 deletions
+25
View File
@@ -0,0 +1,25 @@
#!/bin/bash
set -euo pipefail
script_dirpath="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
root_dirpath="$(cd "${script_dirpath}/.." && pwd)"
VERSION=$1
echo "Updating versions to: $VERSION"
sed -i '' -e "/^version =/s/= .*/= \"$VERSION\"/" ${root_dirpath}/backend/Cargo.toml
sed -i '' -e "/^export const VERSION =/s/= .*/= \"v$VERSION\";/" ${root_dirpath}/cli/main.ts
sed -i '' -e "/^export const VERSION =/s/= .*/= \"v$VERSION\";/" ${root_dirpath}/benchmarks/lib.ts
sed -i '' -e "/version: /s/: .*/: $VERSION/" ${root_dirpath}/backend/windmill-api/openapi.yaml
sed -i '' -e "/version: /s/: .*/: $VERSION/" ${root_dirpath}/openflow.openapi.yaml
sed -i '' -e "/\"version\": /s/: .*,/: \"$VERSION\",/" ${root_dirpath}/typescript-client/package.json
sed -i '' -e "/\"version\": /s/: .*,/: \"$VERSION\",/" ${root_dirpath}/frontend/package.json
sed -i '' -e "/^version =/s/= .*/= \"$VERSION\"/" ${root_dirpath}/python-client/wmill/pyproject.toml
sed -i '' -e "/^windmill-api =/s/= .*/= \"\\^$VERSION\"/" ${root_dirpath}/python-client/wmill/pyproject.toml
sed -i '' -e "/^version =/s/= .*/= \"$VERSION\"/" ${root_dirpath}/python-client/wmill_pg/pyproject.toml
# sed -i '' -e "/^wmill =/s/= .*/= \"\\^$VERSION\"/" python-client/wmill_pg/pyproject.toml
sed -i '' -e "/^wmill =/s/= .*/= \">=$VERSION\"/" ${root_dirpath}/lsp/Pipfile
sed -i '' -e "/^wmill_pg =/s/= .*/= \">=$VERSION\"/" ${root_dirpath}/lsp/Pipfile
sed -i '' -E "s/name = \"windmill\"\nversion = \"[^\"]*\"\\n(.*)/name = \"windmill\"\nversion = \"$VERSION\"\\n\\1/" ${root_dirpath}/backend/Cargo.lock
cd ${root_dirpath}/frontend && npm i --package-lock-only
+18 -15
View File
@@ -1,22 +1,25 @@
#!/bin/bash
set -euo pipefail
script_dirpath="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
root_dirpath="$(cd "${script_dirpath}/.." && pwd)"
VERSION=$1
echo "Updating versions to: $VERSION"
sed -i -e "/^version =/s/= .*/= \"$VERSION\"/" backend/Cargo.toml
sed -i -e "/^export const VERSION =/s/= .*/= \"v$VERSION\";/" cli/main.ts
sed -i -e "/^export const VERSION =/s/= .*/= \"v$VERSION\";/" benchmarks/lib.ts
sed -i -e "/version: /s/: .*/: $VERSION/" backend/windmill-api/openapi.yaml
sed -i -e "/version: /s/: .*/: $VERSION/" openflow.openapi.yaml
sed -i -e "/\"version\": /s/: .*,/: \"$VERSION\",/" typescript-client/package.json
sed -i -e "/\"version\": /s/: .*,/: \"$VERSION\",/" frontend/package.json
sed -i -e "/^version =/s/= .*/= \"$VERSION\"/" python-client/wmill/pyproject.toml
sed -i -e "/^windmill-api =/s/= .*/= \"\\^$VERSION\"/" python-client/wmill/pyproject.toml
sed -i -e "/^version =/s/= .*/= \"$VERSION\"/" python-client/wmill_pg/pyproject.toml
# sed -i -e "/^wmill =/s/= .*/= \"\\^$VERSION\"/" python-client/wmill_pg/pyproject.toml
sed -i -e "/^wmill =/s/= .*/= \">=$VERSION\"/" lsp/Pipfile
sed -i -e "/^wmill_pg =/s/= .*/= \">=$VERSION\"/" lsp/Pipfile
sed -i -e "/^version =/s/= .*/= \"$VERSION\"/" ${root_dirpath}/backend/Cargo.toml
sed -i -e "/^export const VERSION =/s/= .*/= \"v$VERSION\";/" ${root_dirpath}/cli/main.ts
sed -i -e "/^export const VERSION =/s/= .*/= \"v$VERSION\";/" ${root_dirpath}/benchmarks/lib.ts
sed -i -e "/version: /s/: .*/: $VERSION/" ${root_dirpath}/backend/windmill-api/openapi.yaml
sed -i -e "/version: /s/: .*/: $VERSION/" ${root_dirpath}/openflow.openapi.yaml
sed -i -e "/\"version\": /s/: .*,/: \"$VERSION\",/" ${root_dirpath}/typescript-client/package.json
sed -i -e "/\"version\": /s/: .*,/: \"$VERSION\",/" ${root_dirpath}/frontend/package.json
sed -i -e "/^version =/s/= .*/= \"$VERSION\"/" ${root_dirpath}/python-client/wmill/pyproject.toml
sed -i -e "/^windmill-api =/s/= .*/= \"\\^$VERSION\"/" ${root_dirpath}/python-client/wmill/pyproject.toml
sed -i -e "/^version =/s/= .*/= \"$VERSION\"/" ${root_dirpath}/python-client/wmill_pg/pyproject.toml
# sed -i -e "/^wmill =/s/= .*/= \"\\^$VERSION\"/" ${root_dirpath}/python-client/wmill_pg/pyproject.toml
sed -i -e "/^wmill =/s/= .*/= \">=$VERSION\"/" ${root_dirpath}/lsp/Pipfile
sed -i -e "/^wmill_pg =/s/= .*/= \">=$VERSION\"/" ${root_dirpath}/lsp/Pipfile
sed -i -zE "s/name = \"windmill\"\nversion = \"[^\"]*\"\\n(.*)/name = \"windmill\"\nversion = \"$VERSION\"\\n\\1/" backend/Cargo.lock
sed -i -zE "s/name = \"windmill\"\nversion = \"[^\"]*\"\\n(.*)/name = \"windmill\"\nversion = \"$VERSION\"\\n\\1/" ${root_dirpath}/backend/Cargo.lock
cd frontend && npm i --package-lock-only
cd ${root_dirpath}/frontend && npm i --package-lock-only
+80 -18
View File
@@ -43,8 +43,9 @@ jobs:
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: |
latest=false
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
@@ -56,7 +57,7 @@ jobs:
platforms: linux/amd64,linux/arm64
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
${{ steps.meta-public.outputs.tags }}
labels: |
${{ steps.meta-public.outputs.labels }}
@@ -79,8 +80,9 @@ jobs:
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee
flavor: |
latest=false
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
@@ -99,10 +101,10 @@ jobs:
platforms: linux/amd64,linux/arm64
push: true
build-args: |
features=enterprise,enterprise_saml
features=enterprise,enterprise_saml,stripe
nsjail=true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:latest
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
${{ steps.meta-ee-public.outputs.tags }}
labels: |
${{ steps.meta-ee-public.outputs.labels }}
@@ -198,25 +200,25 @@ jobs:
- run: |
# pulling docker image with desired arch so that actions-docker-extract doesn't do it
docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:latest
docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
- run: |
# Checks the image is in docker prior to running actions-docker-extract. It fails if not
# Also useful to visually check that the arch is the right opencontainers
docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:latest
docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
- uses: shrink/actions-docker-extract@v3
id: extract
with:
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
path: "/usr/src/app/windmill"
- uses: shrink/actions-docker-extract@v3
id: extract-ee
with:
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:latest
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
path: "/usr/src/app/windmill"
- name: Rename binary with corresponding architecture
@@ -242,25 +244,25 @@ jobs:
- run: |
# pulling docker image with desired arch so that actions-docker-extract doesn't do it
docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:latest
docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
- run: |
# Checks the image is in docker prior to running actions-docker-extract. It fails if not
# Also useful to visually check that the arch is the right opencontainers
docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:latest
docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
- uses: shrink/actions-docker-extract@v3
id: extract
with:
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
path: "/usr/src/app/windmill"
- uses: shrink/actions-docker-extract@v3
id: extract-ee
with:
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:latest
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
path: "/usr/src/app/windmill"
- name: Rename binary with corresponding architecture
@@ -305,7 +307,7 @@ jobs:
git_hash=$(git rev-parse --short "$GITHUB_SHA")
docker buildx imagetools create \
--tag ${{ env.ECR_REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${git_hash:0:7} \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:latest
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
echo "GIT_HASH=${git_hash:0:7}" >> "$GITHUB_OUTPUT"
- uses: shrink/actions-docker-extract@v3
@@ -322,3 +324,63 @@ jobs:
folder: ${{ steps.extract.outputs.destination }}
bucket: windmill-frontend
bucket-region: us-east-1
run_integration_test:
runs-on: ubicloud
needs: [build_ee]
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Prepare test run
if: ${{ ! startsWith(github.ref, 'refs/tags/') }}
run: cd integration_tests && ./build.sh
- name: Test run
if: ${{ ! startsWith(github.ref, 'refs/tags/') }}
timeout-minutes: 15
env:
LICENSE_KEY: ${{ secrets.WM_LICENSE_KEY_CI }}
run: cd integration_tests && ./run.sh
- name: Archive logs
uses: actions/upload-artifact@v3
if: always()
with:
name: Windmill Integration Tests Logs
path: |
integration_tests/logs
tag_latest:
runs-on: ubicloud
needs: [run_integration_test]
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Login to registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Tag main and latest
run: |
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main
tag_latest_ee:
runs-on: ubicloud
needs: [run_integration_test]
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Login to registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Tag main and latest for ee
run: |
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:latest
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:main
+292
View File
@@ -1,6 +1,298 @@
# Changelog
## [1.244.1](https://github.com/windmill-labs/windmill/compare/v1.244.0...v1.244.1) (2024-01-13)
### Bug Fixes
* menu colors ([b84cd6d](https://github.com/windmill-labs/windmill/commit/b84cd6d52d723fea55a407d347afec1c669da03a))
* menu colors ([5201dcd](https://github.com/windmill-labs/windmill/commit/5201dcdd679690bf82b2606d85829ae9333e09ac))
## [1.244.0](https://github.com/windmill-labs/windmill/compare/v1.243.0...v1.244.0) (2024-01-13)
### Features
* **frontend:** Operator mode ([#2973](https://github.com/windmill-labs/windmill/issues/2973)) ([aaff17f](https://github.com/windmill-labs/windmill/commit/aaff17f813ae3f31cae58bb40b9c8118e772a2d8))
### Bug Fixes
* add ability to set secret variable from python ([0733dd1](https://github.com/windmill-labs/windmill/commit/0733dd118e463f73caa9155b97fc8d9b02759e06))
* improve oauth accounts permissions ([1621975](https://github.com/windmill-labs/windmill/commit/16219755a7fa8b9ff5a901c18842a1eceb68086a))
* improve table behavior when searching ([f0c4901](https://github.com/windmill-labs/windmill/commit/f0c4901c218cfc4564f9f2be0cacaf3d9997d822))
## [1.243.0](https://github.com/windmill-labs/windmill/compare/v1.242.0...v1.243.0) (2024-01-13)
### Features
* add support for bun install scopes ([d785def](https://github.com/windmill-labs/windmill/commit/d785deff3312b076e3ed9043924f514f8823e041))
* **frontend:** s3 file upload ([#2976](https://github.com/windmill-labs/windmill/issues/2976)) ([3c59fb8](https://github.com/windmill-labs/windmill/commit/3c59fb8b4d8a80077c2f352ccf1314fd32ff442c))
* GIt sync to multiple repo ([#2996](https://github.com/windmill-labs/windmill/issues/2996)) ([fda0e28](https://github.com/windmill-labs/windmill/commit/fda0e28db31fba9f71be6db7280696c3949fd7be))
* support dynamic args in tags ([aafd7d9](https://github.com/windmill-labs/windmill/commit/aafd7d90037bae20808e836d34c352ca6b357155))
* Tag override for scheduled scripts ([#2998](https://github.com/windmill-labs/windmill/issues/2998)) ([99484bd](https://github.com/windmill-labs/windmill/commit/99484bdc20bac966ca1d9d45ca4278133ea91b87))
### Bug Fixes
* allow for any extra bunfig config ([e200889](https://github.com/windmill-labs/windmill/commit/e200889cff6c5166ce6564d7f9d606c1613fe03b))
## [1.242.0](https://github.com/windmill-labs/windmill/compare/v1.241.0...v1.242.0) (2024-01-12)
### Features
* Instance group management page ([#2994](https://github.com/windmill-labs/windmill/issues/2994)) ([5f54f55](https://github.com/windmill-labs/windmill/commit/5f54f557903792ea6307b17f966c216cbd402709))
### Bug Fixes
* catch more agGrid errors ([01dbf54](https://github.com/windmill-labs/windmill/commit/01dbf548f712c2e669038e4eb704c470d2cb0426))
## [1.241.0](https://github.com/windmill-labs/windmill/compare/v1.240.0...v1.241.0) (2024-01-12)
### Features
* Instance group CRUD API ([#2992](https://github.com/windmill-labs/windmill/issues/2992)) ([5a157c4](https://github.com/windmill-labs/windmill/commit/5a157c415e7f075d3f57872a9adf7f5130632bb6))
* make dedicated workers for flows able to share runtime for the same scripts ([d59b89e](https://github.com/windmill-labs/windmill/commit/d59b89ec3cdf2285c0eac56d7678fc0b9a2c2a32))
### Bug Fixes
* add cache for flow as flow step ([28ac763](https://github.com/windmill-labs/windmill/commit/28ac7632cf767b1c0df30d93c841ad32bf891202))
* git sync now works for delete and rename ([#2988](https://github.com/windmill-labs/windmill/issues/2988)) ([cde574b](https://github.com/windmill-labs/windmill/commit/cde574b8910bbaf737e0d7e515c42fc796911c5e))
## [1.240.0](https://github.com/windmill-labs/windmill/compare/v1.239.0...v1.240.0) (2024-01-11)
### Features
* **cli:** introduce --stateful for CLI, in preparation for --raw to become the default ([39ecf80](https://github.com/windmill-labs/windmill/commit/39ecf8017ee88c4311cb2dd580b20cd59066612e))
## [1.239.0](https://github.com/windmill-labs/windmill/compare/v1.238.0...v1.239.0) (2024-01-11)
### Features
* add configurable ordering for script's generated UI ([717ccc9](https://github.com/windmill-labs/windmill/commit/717ccc94a06ee65a5676c7d9091faf892396657e))
* **frontend:** AG chart ([#2972](https://github.com/windmill-labs/windmill/issues/2972)) ([e3c1661](https://github.com/windmill-labs/windmill/commit/e3c166154da4251e21a5f6a6fcf5b2e101b558e1))
### Bug Fixes
* **cli:** improve .wmillignore handling of folders ([6996c90](https://github.com/windmill-labs/windmill/commit/6996c9083d9ae93922caf68e5658f18b49678630))
* handle Etc/Unknown timezone better ([8c97ef0](https://github.com/windmill-labs/windmill/commit/8c97ef0394925b49a04c253259783be301e8f4ee))
* only cache flow if it's a success ([589e683](https://github.com/windmill-labs/windmill/commit/589e683c27f15fcb48993f2f6e91523abef1794a))
* spelling error dtails -&gt; details ([#2986](https://github.com/windmill-labs/windmill/issues/2986)) ([308c4ce](https://github.com/windmill-labs/windmill/commit/308c4ceb475fef033ea87af5f772e1f35b4fb16d))
## [1.238.0](https://github.com/windmill-labs/windmill/compare/v1.237.0...v1.238.0) (2024-01-10)
### Features
* add ability to use secrets in pip requirements ([3517c7f](https://github.com/windmill-labs/windmill/commit/3517c7f28389a4ddf46a6bef4f2044aa94174050))
* add support for multiselect in python ([c8a793d](https://github.com/windmill-labs/windmill/commit/c8a793d35ea843fb75428182edc06cfe7105af3b))
* **frontend:** add shortcuts to audit logs ([#2975](https://github.com/windmill-labs/windmill/issues/2975)) ([4147d16](https://github.com/windmill-labs/windmill/commit/4147d1604a4c50ad0c667b413d64b44b357fb7e4))
* **frontend:** Display the index by which a node can be selected with the setTab function + add tooltip ([#2974](https://github.com/windmill-labs/windmill/issues/2974)) ([cffae36](https://github.com/windmill-labs/windmill/commit/cffae3633107d9359e04354ba01876ccaf98193a))
* S3 multipart upload accepts a custom S3 resource ([#2982](https://github.com/windmill-labs/windmill/issues/2982)) ([eea0d92](https://github.com/windmill-labs/windmill/commit/eea0d92dd11a3607fbb156e2bf3a3a001ab5e6a0))
### Bug Fixes
* main compile ([#2983](https://github.com/windmill-labs/windmill/issues/2983)) ([6d5c3f0](https://github.com/windmill-labs/windmill/commit/6d5c3f0f2252be15bf0169a42f5baa06d592911c))
## [1.237.0](https://github.com/windmill-labs/windmill/compare/v1.236.2...v1.237.0) (2024-01-08)
### Features
* make okta SSO configurable using custom domain ([4f01ee8](https://github.com/windmill-labs/windmill/commit/4f01ee89de743eab6ae323dea1ec4190ee23e0bf))
### Bug Fixes
* delete is captured in more fields in app ([d340fbc](https://github.com/windmill-labs/windmill/commit/d340fbc02fd9500249c1d23799c1bdbfe9602cb8))
## [1.236.2](https://github.com/windmill-labs/windmill/compare/v1.236.1...v1.236.2) (2024-01-08)
### Bug Fixes
* handle better non object result for app result loader ([269afe4](https://github.com/windmill-labs/windmill/commit/269afe41f7e02da8d3a364e649e5fef00847d24a))
## [1.236.1](https://github.com/windmill-labs/windmill/compare/v1.236.0...v1.236.1) (2024-01-08)
### Bug Fixes
* fix app initialization ([6b075e9](https://github.com/windmill-labs/windmill/commit/6b075e92ef4e312e6a4ce582dc7c4d88456b2f5d))
## [1.236.0](https://github.com/windmill-labs/windmill/compare/v1.235.1...v1.236.0) (2024-01-08)
### Features
* improve git sync and recompute dependents when common python script is redeployed ([#2967](https://github.com/windmill-labs/windmill/issues/2967)) ([9449381](https://github.com/windmill-labs/windmill/commit/94493812ac0030cc6bba468d8ee24a14661716a8))
### Bug Fixes
* reduce stack size needed for workflow transitions ([4384617](https://github.com/windmill-labs/windmill/commit/4384617f1ec4c27de88ef6c895d6ef060a79f6bd))
## [1.235.1](https://github.com/windmill-labs/windmill/compare/v1.235.0...v1.235.1) (2024-01-07)
### Bug Fixes
* fix resultjobloader ([2e02300](https://github.com/windmill-labs/windmill/commit/2e023003da8bea9efa86c0fbc52bf9723c73907d))
## [1.235.0](https://github.com/windmill-labs/windmill/compare/v1.234.1...v1.235.0) (2024-01-06)
### Features
* **frontend:** DB Explorer ([#2892](https://github.com/windmill-labs/windmill/issues/2892)) ([fffc533](https://github.com/windmill-labs/windmill/commit/fffc5338ce76fda9a68b19f03cefe199cc09a905))
## [1.234.1](https://github.com/windmill-labs/windmill/compare/v1.234.0...v1.234.1) (2024-01-05)
### Bug Fixes
* fix double view runs button ([9efcb53](https://github.com/windmill-labs/windmill/commit/9efcb53bcd903bf756c4beb92a5c7634e8e016ad))
* fix typo "Wourker" -&gt; "Worker" ([4385edc](https://github.com/windmill-labs/windmill/commit/4385edc6ffdb1b2e4544347928b4cf08faf1eaa1))
## [1.234.0](https://github.com/windmill-labs/windmill/compare/v1.233.0...v1.234.0) (2024-01-05)
### Features
* Detailed job memory footprint on job run page ([#2956](https://github.com/windmill-labs/windmill/issues/2956)) ([0710ce6](https://github.com/windmill-labs/windmill/commit/0710ce6ef25d8f2344ac1350e96aa906f16d1be1))
* git sync can now push commits to individual branches ([#2959](https://github.com/windmill-labs/windmill/issues/2959)) ([fb5cd6a](https://github.com/windmill-labs/windmill/commit/fb5cd6a4298c88e26481b76258bf5b09a11cf4b0))
### Bug Fixes
* Deleting workspace delete all workspace related values in DB ([#2961](https://github.com/windmill-labs/windmill/issues/2961)) ([c05d119](https://github.com/windmill-labs/windmill/commit/c05d119ae5dfb6f1bae88fc9d53b48a707cd2f15))
## [1.233.0](https://github.com/windmill-labs/windmill/compare/v1.232.0...v1.233.0) (2024-01-03)
### Features
* **frontend:** add the view runs buttons for operators ([#2932](https://github.com/windmill-labs/windmill/issues/2932)) ([821d2d7](https://github.com/windmill-labs/windmill/commit/821d2d7ea15e828dbb3683c85025dbc09c7e8dc7))
* Job caching now works with S3 files ([#2934](https://github.com/windmill-labs/windmill/issues/2934)) ([b44618f](https://github.com/windmill-labs/windmill/commit/b44618f35b6fe4558a5cd9fddec1e82216a2cf5e))
* PIP_LOCAL_DEPENDENCIES now accepts regex and is configurable from the UI ([#2940](https://github.com/windmill-labs/windmill/issues/2940)) ([e0140f1](https://github.com/windmill-labs/windmill/commit/e0140f175d371e239f315613a537820b9a6ad25f))
### Bug Fixes
* allow extraConfig for agGrid to set onGridReady ([74fc2d3](https://github.com/windmill-labs/windmill/commit/74fc2d3f4397d976fec326893f8196008cb8b332))
* decision tree fix wording ([#2941](https://github.com/windmill-labs/windmill/issues/2941)) ([0299c65](https://github.com/windmill-labs/windmill/commit/0299c656c368876581dc228c76513f1171ffd682))
* decision tree nits ([#2936](https://github.com/windmill-labs/windmill/issues/2936)) ([787017f](https://github.com/windmill-labs/windmill/commit/787017f53c8227e1246b09e412e92316977937ae))
* **frontend:** handle case where large json has a file and a filename ([#2951](https://github.com/windmill-labs/windmill/issues/2951)) ([2494171](https://github.com/windmill-labs/windmill/commit/2494171b9cb3d1857235b6558d276451d5550560))
* persist state for agGrid ([c085f5f](https://github.com/windmill-labs/windmill/commit/c085f5fa7ec598d0abf6aad98de0e5c7b1c92985))
* Tag all docker images with latest and main tags ([#2953](https://github.com/windmill-labs/windmill/issues/2953)) ([9655718](https://github.com/windmill-labs/windmill/commit/965571853ca253b130bcc4473190b6c9c98f44e1))
## [1.232.0](https://github.com/windmill-labs/windmill/compare/v1.231.0...v1.232.0) (2023-12-27)
### Features
* add summary to schedules ([606b03d](https://github.com/windmill-labs/windmill/commit/606b03d7613cf15ab9a60ef34a82657ff49ccdbd))
* no overlap for flows togglable on schedules ([4935528](https://github.com/windmill-labs/windmill/commit/49355280f6442c342a46f90b2fe2cb089b3f4eff))
### Bug Fixes
* fix file input ([263f41c](https://github.com/windmill-labs/windmill/commit/263f41cda3dba24bcaa2bbabfc3f5bc54c70f9cb))
* improve ata for bun ([b66fcbf](https://github.com/windmill-labs/windmill/commit/b66fcbf6261f69a57e6711fa8b9d1d499c1b47ed))
* use bun install in every case ([948b0da](https://github.com/windmill-labs/windmill/commit/948b0da59e6bcd3b6312373a1bbe52d541e54d56))
## [1.231.0](https://github.com/windmill-labs/windmill/compare/v1.230.0...v1.231.0) (2023-12-26)
### Features
* bun can read private npm package ([#2915](https://github.com/windmill-labs/windmill/issues/2915)) ([5fa5ab1](https://github.com/windmill-labs/windmill/commit/5fa5ab1514a2d20083d30809c30e50bfcb29e82f))
* **cli:** support skip args for push --raw ([fc07d58](https://github.com/windmill-labs/windmill/commit/fc07d5886893c8d493c3ca507e0c9d4a066622fc))
### Bug Fixes
* improve conditional wrapper ([4b65594](https://github.com/windmill-labs/windmill/commit/4b65594cbd138452c471a339aaa548c27fc0a4ce))
## [1.230.0](https://github.com/windmill-labs/windmill/compare/v1.229.0...v1.230.0) (2023-12-22)
### Features
* add button for auto-formatting ([642c037](https://github.com/windmill-labs/windmill/commit/642c037c1a87efd8d63558cc0ca5e98fb7b51ff7))
* move S3 file within bucket ([#2913](https://github.com/windmill-labs/windmill/issues/2913)) ([e40787c](https://github.com/windmill-labs/windmill/commit/e40787c616ec288ffc83450fac6f6f7f127ac008))
* quick search in flow support workspace scripts ([5698a0e](https://github.com/windmill-labs/windmill/commit/5698a0ebf2052fd51b687951595a543f0b6c0308))
* Retry for scheduled scripts ([#2893](https://github.com/windmill-labs/windmill/issues/2893)) ([5f60d46](https://github.com/windmill-labs/windmill/commit/5f60d468365c65dc48e247107487f64adf77b123))
* S3 delete file and upload new ([#2909](https://github.com/windmill-labs/windmill/issues/2909)) ([e27e887](https://github.com/windmill-labs/windmill/commit/e27e8874918af84d8afffb26abd5828b4c43ed54))
### Bug Fixes
* add relative path handling for bun ATA ([6b570a7](https://github.com/windmill-labs/windmill/commit/6b570a779fa7592c527cee84b0ca238872d86347))
* edit schedule from trigger page ([9667eb7](https://github.com/windmill-labs/windmill/commit/9667eb74f99b74621e0f00e3f380bbd34dd9654f))
* Failing jobs in dedicated worker mode are now marked as failing ([#2894](https://github.com/windmill-labs/windmill/issues/2894)) ([5f85b67](https://github.com/windmill-labs/windmill/commit/5f85b67dfcf063fd8a3c3f69f0e7605fc40e473d))
* fix multiplayer ([b5f1f99](https://github.com/windmill-labs/windmill/commit/b5f1f99daa30627f224f262653e459799fc0d6fe))
* **frontend:** Fix debug condition + decision tree + add missing date to job preview + prevent header node from being selected in the flow editor ([#2912](https://github.com/windmill-labs/windmill/issues/2912)) ([d7b777a](https://github.com/windmill-labs/windmill/commit/d7b777a8e9b4a2a4f0772f9b29851476f9405dae))
* improve javascript editors with semantic validation ([b3527f5](https://github.com/windmill-labs/windmill/commit/b3527f5164f9e487840773df57846394f118ebbb))
* pin deno windmill-client version to relase ([ea322d9](https://github.com/windmill-labs/windmill/commit/ea322d9c14cc92b54cfc39791e162f8f248e74ab))
* Python wrapper catches BaseException instead of Exception ([#2902](https://github.com/windmill-labs/windmill/issues/2902)) ([4c3c988](https://github.com/windmill-labs/windmill/commit/4c3c988f7bd35f290b9d051369029e790b1e5c5b))
* remove bad app type forcing ([#2906](https://github.com/windmill-labs/windmill/issues/2906)) ([187c34a](https://github.com/windmill-labs/windmill/commit/187c34a6835e3e748bc609fb4096f42bf3e09a73))
* Update S3 TS code snippets and Python SDK for Polars 0.20.X ([#2911](https://github.com/windmill-labs/windmill/issues/2911)) ([3cbdd89](https://github.com/windmill-labs/windmill/commit/3cbdd890cd2aaef1d983da0270f8c230c9b9285e))
## [1.229.0](https://github.com/windmill-labs/windmill/compare/v1.228.1...v1.229.0) (2023-12-21)
### Features
* **frontend:** add a quick button to open decision tree graph ([#2897](https://github.com/windmill-labs/windmill/issues/2897)) ([3f7c19f](https://github.com/windmill-labs/windmill/commit/3f7c19f267a9965f21cf8a08040a45e088d24918))
### Bug Fixes
* **frontend:** fix save draft when the app has no versions ([#2904](https://github.com/windmill-labs/windmill/issues/2904)) ([0170fcc](https://github.com/windmill-labs/windmill/commit/0170fcc338e0b59f8af1894d868f3819cad8aca3))
* Re-add TS SDK changes for S3 ([#2899](https://github.com/windmill-labs/windmill/issues/2899)) ([5fafd71](https://github.com/windmill-labs/windmill/commit/5fafd71135c5d4d1764e9f96984bc5f5e5810465))
## [1.228.1](https://github.com/windmill-labs/windmill/compare/v1.228.0...v1.228.1) (2023-12-20)
### Bug Fixes
* TS SDK not found upon install error ([#2895](https://github.com/windmill-labs/windmill/issues/2895)) ([46e9818](https://github.com/windmill-labs/windmill/commit/46e9818c001b040c462ffe0b6f8681a97113ff04))
## [1.228.0](https://github.com/windmill-labs/windmill/compare/v1.227.1...v1.228.0) (2023-12-20)
### Features
* Add Zitadel IDP ([#2888](https://github.com/windmill-labs/windmill/issues/2888)) ([afbea19](https://github.com/windmill-labs/windmill/commit/afbea19421572f2986bf8ab1e5817f1902c0a22c))
* bun automatic type acquisition in frontend directly ([#2884](https://github.com/windmill-labs/windmill/issues/2884)) ([d507ce4](https://github.com/windmill-labs/windmill/commit/d507ce449b04bf0be5ea29ec2431ad1eea405f13))
* not on latest app version warning ([#2889](https://github.com/windmill-labs/windmill/issues/2889)) ([e8b2cc8](https://github.com/windmill-labs/windmill/commit/e8b2cc8835881a7b2c0a273cb8e505644e9be001))
* S3 objects are now typed in Python and TS SDK ([#2878](https://github.com/windmill-labs/windmill/issues/2878)) ([2155a6c](https://github.com/windmill-labs/windmill/commit/2155a6c1334b79272742ba4626abd9572c9130d2))
### Bug Fixes
* Persistent script spinner ([#2879](https://github.com/windmill-labs/windmill/issues/2879)) ([5dd5809](https://github.com/windmill-labs/windmill/commit/5dd58094fde38f13476e79b539e670ee150306d7))
* Python imports from git repository ([#2886](https://github.com/windmill-labs/windmill/issues/2886)) ([292b230](https://github.com/windmill-labs/windmill/commit/292b2309a7b7093c85614ac9764620c9dce0e316))
* remove 0x00 from logs automatically from bashoutput ([59397e7](https://github.com/windmill-labs/windmill/commit/59397e7445f78cca2e602d8b45591a0bb123a734))
## [1.227.1](https://github.com/windmill-labs/windmill/compare/v1.227.0...v1.227.1) (2023-12-17)
### Bug Fixes
* improve on click behavior of tree view ([fa7261f](https://github.com/windmill-labs/windmill/commit/fa7261f273b517acd85a8c3f465cd8095cdf9f3c))
## [1.227.0](https://github.com/windmill-labs/windmill/compare/v1.226.1...v1.227.0) (2023-12-17)
+1 -1
View File
@@ -209,7 +209,7 @@ RUN chmod 755 /usr/bin/deno
COPY --from=nsjail /nsjail/nsjail /bin/nsjail
COPY --from=oven/bun:1.0.8 /usr/local/bin/bun /usr/bin/bun
COPY --from=oven/bun:1.0.18 /usr/local/bin/bun /usr/bin/bun
# add the docker client to call docker from a worker if enabled
COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/
+1 -38
View File
@@ -2,7 +2,7 @@
<a href="https://www.windmill.dev/"><img src="./imgs/windmill-banner.png" alt="windmill.dev"></a>
</p>
<p align=center>
Open-source developer infrastructure for internal tools (APIs, background jobs, workflows and UIs). Self-hostable alternative to Airplane, Pipedream, Superblocks and a simplified Temporal with autogenerated UIsm and custom UIs to trigger workflows and scripts as internal apps.
Open-source developer infrastructure for internal tools (APIs, background jobs, workflows and UIs). Self-hostable alternative to Airplane, Pipedream, Superblocks and a simplified Temporal with autogenerated UIs and custom UIs to trigger workflows and scripts as internal apps.
<p align=center>
Scripts are turned into sharable UIs automatically, and can be composed together into flows or used into richer apps built with low-code. Supported script languages supported are: Python, TypeScript, Go, Bash, SQL, and GraphQL.
@@ -357,8 +357,6 @@ you to have it being synced automatically everyday.
| QUEUE_LIMIT_WAIT_RESULT | None | The number of max jobs in the queue before rejecting immediately the request in 'run_wait_result' endpoint. Takes precedence on the query arg. If none is specified, there are no limit. | Worker |
| DENO_AUTH_TOKENS | None | Custom DENO_AUTH_TOKENS to pass to worker to allow the use of private modules | Worker |
| DENO_FLAGS | None | Override the flags passed to deno (default --allow-all) to tighten permissions. Minimum permissions needed are "--allow-read=args.json --allow-write=result.json" | Worker |
| DENO_EXTRA_IMPORT_MAP | None | extra import map to use to run deno scripts (format: `key=value,key=value`) | Worker |
| NPM_CONFIG_REGISTRY | None | Registry to use for NPM dependencies, set if you have a private repository you need to use instead of the default public NPM registry | Worker |
| PIP_LOCAL_DEPENDENCIES | None | Specify dependencies that are installed locally and do not need to be solved nor installed again | |
| ADDITIONAL_PYTHON_PATHS | None | Specify python paths (separated by a :) to be appended to the PYTHONPATH of the python jobs. To be used with PIP_LOCAL_DEPENDENCIES to use python codebases within Windmill | Worker |
| INCLUDE_HEADERS | None | Whitelist of headers that are passed to jobs as args (separated by a comma) | Server |
@@ -395,41 +393,6 @@ you to have it being synced automatically everyday.
| DISABLE_NSJAIL | true | Disable Nsjail Sandboxing | Worker |
| DISABLE_SERVER | false | Disable the external API, operate as a worker only instance | Worker |
## Run a local dev setup
### only Frontend
This will use the backend of <https://app.windmill.dev> but your own frontend
with hot-code reloading.
1. Go to `frontend/`:
1. `npm install`
2. `npm run generate-backend-client`
3. `npm run dev`
2. Et voilà, windmill should be available at `http://localhost:3000/`
### Backend + Frontend
See the [./frontend/README_DEV.md](./frontend/README_DEV.md) file for all
running options.
1. Create a Postgres Database for Windmill and create an admin role inside your
Postgres setup. The easiest way to get a working postgres is running
`cargo install --version ^0.7 sqlx-cli && sqlx migrate run`. This will also
avoid compile time issue with sqlx's `query!` macro
2. Install [nsjail](https://github.com/google/nsjail) and have it accessible in
your PATH
3. Install deno and python3, have the bins at `/usr/bin/deno` and
`/usr/local/bin/python3`
4. Install [caddy](https://caddyserver.com)
5. Go to `frontend/`:
1. `npm install`, `npm run generate-backend-client` then `npm run dev`
2. In another shell `npm run build` otherwise the backend will not find the
`frontend/build` folder and will crash
3. In another shell `sudo caddy run --config Caddyfile`
6. Go to `backend/`:
`DATABASE_URL=<DATABASE_URL_TO_YOUR_WINDMILL_DB> RUST_LOG=info cargo run`
7. Et voilà, windmill should be available at `http://localhost/`
## Contributors
@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM account WHERE workspace_id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "08c943c4b9b76da43b8b754e5c66ab3b6397e7edc82666662a8ea466496290fb"
}
@@ -31,7 +31,8 @@
"postgresql",
"noop",
"appdependencies",
"deploymentcallback"
"deploymentcallback",
"singlescriptflow"
]
}
}
@@ -0,0 +1,35 @@
{
"db_name": "PostgreSQL",
"query": "SELECT value, is_secret, path from variable WHERE variable.path = $1 AND variable.workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "value",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "is_secret",
"type_info": "Bool"
},
{
"ordinal": 2,
"name": "path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false,
false,
false
]
},
"hash": "1587639f13a1a3ad97bd324df38661f3e846f1992704c86ddad74dea151b0ec4"
}
@@ -46,7 +46,8 @@
"postgresql",
"noop",
"appdependencies",
"deploymentcallback"
"deploymentcallback",
"singlescriptflow"
]
}
}
@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT git_sync FROM workspace_settings WHERE workspace_id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "git_sync",
"type_info": "Jsonb"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
true
]
},
"hash": "266741894b9a6ed650cf999b6e5b7c797424f8291517d3abf096fe45548a0273"
}
@@ -0,0 +1,18 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE job_stats SET timestamps = timestamps || $4, timeseries_float = timeseries_float || $5 WHERE workspace_id = $1 AND job_id = $2 AND metric_id = $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Uuid",
"Text",
"TimestamptzArray",
"Float4Array"
]
},
"nullable": []
},
"hash": "2ce7a562b1de63f767758642a4864ed982b74b53a143901480058f957b9641dc"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM dependency_map\n WHERE importer_path = $1 AND importer_kind = 'script'\n AND workspace_id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "30e9250edd0c4c6025322f51b758174435d6df4f835cb0883ba40897cb99a410"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT schedule.*, t.jobs FROM schedule, LATERAL ( SELECT ARRAY (SELECT json_build_object('id', id, 'success', success, 'duration_ms', duration_ms) FROM completed_job WHERE\n completed_job.schedule_path = schedule.path AND completed_job.workspace_id = $1 AND parent_job IS NULL ORDER BY started_at DESC LIMIT 20) AS jobs ) t\n WHERE schedule.workspace_id = $1 ORDER BY schedule.edited_at desc LIMIT $2 OFFSET $3",
"query": "SELECT schedule.*, t.jobs FROM schedule, LATERAL ( SELECT ARRAY (SELECT json_build_object('id', id, 'success', success, 'duration_ms', duration_ms) FROM completed_job WHERE\n completed_job.schedule_path = schedule.path AND completed_job.workspace_id = $1 AND parent_job IS NULL AND is_skipped = False ORDER BY started_at DESC LIMIT 20) AS jobs ) t\n WHERE schedule.workspace_id = $1 ORDER BY schedule.edited_at desc LIMIT $2 OFFSET $3",
"describe": {
"columns": [
{
@@ -110,6 +110,26 @@
},
{
"ordinal": 21,
"name": "retry",
"type_info": "Jsonb"
},
{
"ordinal": 22,
"name": "summary",
"type_info": "Varchar"
},
{
"ordinal": 23,
"name": "no_flow_overlap",
"type_info": "Bool"
},
{
"ordinal": 24,
"name": "tag",
"type_info": "Varchar"
},
{
"ordinal": 25,
"name": "jobs",
"type_info": "JsonArray"
}
@@ -143,8 +163,12 @@
true,
true,
false,
true,
true,
false,
true,
null
]
},
"hash": "7a00843921db4ac1eb20a1045e83363587c1a2cb62d2df25d5564ae78a03b097"
"hash": "4422b7183ede17a9cbde4afae41be4da5447020e39398deedbcca9121492834a"
}
@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM token WHERE workspace_id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "4c5276bbf6aaad004a58aaff6b2abd7e78903a0354dd041fcc0cc7cca2b60cc1"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM deployment_metadata WHERE path = $1 AND workspace_id = $2 AND script_hash IS NOT NULL",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "54bb09f68de5615a75b2174032e5fcebc314eeeccbcc609ba154fd062fe0f2fd"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO schedule (workspace_id, path, schedule, timezone, edited_by, script_path, is_flow, args, enabled, email, on_failure, on_failure_times, on_failure_exact, on_failure_extra_args, on_recovery, on_recovery_times, on_recovery_extra_args, ws_error_handler_muted) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) RETURNING *",
"query": "INSERT INTO schedule (workspace_id, path, schedule, timezone, edited_by, script_path, is_flow, args, enabled, email, on_failure, on_failure_times, on_failure_exact, on_failure_extra_args, on_recovery, on_recovery_times, on_recovery_extra_args, ws_error_handler_muted, retry, summary, no_flow_overlap, tag ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22 ) RETURNING *",
"describe": {
"columns": [
{
@@ -107,6 +107,26 @@
"ordinal": 20,
"name": "ws_error_handler_muted",
"type_info": "Bool"
},
{
"ordinal": 21,
"name": "retry",
"type_info": "Jsonb"
},
{
"ordinal": 22,
"name": "summary",
"type_info": "Varchar"
},
{
"ordinal": 23,
"name": "no_flow_overlap",
"type_info": "Bool"
},
{
"ordinal": 24,
"name": "tag",
"type_info": "Varchar"
}
],
"parameters": {
@@ -128,7 +148,11 @@
"Varchar",
"Int4",
"Json",
"Bool"
"Bool",
"Jsonb",
"Varchar",
"Bool",
"Varchar"
]
},
"nullable": [
@@ -152,8 +176,12 @@
true,
true,
true,
false
false,
true,
true,
false,
true
]
},
"hash": "47a01c0924df7bc2695f8238425aa7eadf845e4156da8cf645582737e046589d"
"hash": "597335a4a1eda7799303e0d930b5468af2364f5d2075aab48787b93e5774336d"
}
@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path)\n VALUES ($1, $2, 'script', $3)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "5fbd5dfb8df9aecce023c5d482ee58efcbc23c8682a3f0ec94977eb56023ed72"
}
@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM input WHERE workspace_id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "60cd5ea09b2eba9df97ab011dd8575940a9dd5e244f3e877d3802fc88087234b"
}
@@ -41,7 +41,8 @@
"postgresql",
"noop",
"appdependencies",
"deploymentcallback"
"deploymentcallback",
"singlescriptflow"
]
}
}
@@ -0,0 +1,24 @@
{
"db_name": "PostgreSQL",
"query": "SELECT true FROM job_stats WHERE workspace_id = $1 AND job_id = $2 AND metric_id = $3",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "?column?",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text",
"Uuid",
"Text"
]
},
"nullable": [
null
]
},
"hash": "661f472ff3860983322162420457f5033b9c9afc344d9c3e385ba20a3ad2197a"
}
@@ -0,0 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE job_stats SET scalar_int = $4 WHERE workspace_id = $1 AND job_id = $2 AND metric_id = $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Uuid",
"Text",
"Int4"
]
},
"nullable": []
},
"hash": "699427e7e3e6cd3ba4f61f77e171c5c27234cfa30460696ac619760667d7553c"
}
@@ -0,0 +1,18 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE job_stats SET timestamps = timestamps || $4, timeseries_int = timeseries_int || $5 WHERE workspace_id = $1 AND job_id = $2 AND metric_id = $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Uuid",
"Text",
"TimestamptzArray",
"Int4Array"
]
},
"nullable": []
},
"hash": "73a3a742702c1c8237a3216523dffe487146b14512d4882179978efe280956aa"
}
@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM job_stats WHERE job_id IN (SELECT id FROM completed_job WHERE created_at <= now() - ($1::bigint::text || ' s')::interval AND started_at + ((duration_ms/1000 + $1::bigint) || ' s')::interval <= now())",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": []
},
"hash": "74949628ea78a5fecfd415e6406636a9442edd5b7939f61894288103be0c03ef"
}
@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM dependency_map WHERE workspace_id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "76d73f2fe5ac75e5033fb93d60f2ef5ab2b0b73a80dcb6a250c17c3b2f15c6b4"
}
@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM deployment_metadata WHERE workspace_id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "813d3c65d4c4bc10f1ec16ed4ee0954f4b938f45751ac03fd45479e665db1d40"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE schedule SET schedule = $1, timezone = $2, args = $3, on_failure = $4, on_failure_times = $5, on_failure_exact = $6, on_failure_extra_args = $7, on_recovery = $8, on_recovery_times = $9, on_recovery_extra_args = $10, ws_error_handler_muted = $11 \n WHERE path = $12 AND workspace_id = $13 RETURNING *",
"query": "UPDATE schedule SET schedule = $1, timezone = $2, args = $3, on_failure = $4, on_failure_times = $5, on_failure_exact = $6, on_failure_extra_args = $7, on_recovery = $8, on_recovery_times = $9, on_recovery_extra_args = $10, ws_error_handler_muted = $11, retry = $12, summary = $13, no_flow_overlap = $14, tag = $15\n WHERE path = $16 AND workspace_id = $17 RETURNING *",
"describe": {
"columns": [
{
@@ -107,6 +107,26 @@
"ordinal": 20,
"name": "ws_error_handler_muted",
"type_info": "Bool"
},
{
"ordinal": 21,
"name": "retry",
"type_info": "Jsonb"
},
{
"ordinal": 22,
"name": "summary",
"type_info": "Varchar"
},
{
"ordinal": 23,
"name": "no_flow_overlap",
"type_info": "Bool"
},
{
"ordinal": 24,
"name": "tag",
"type_info": "Varchar"
}
],
"parameters": {
@@ -122,6 +142,10 @@
"Int4",
"Json",
"Bool",
"Jsonb",
"Varchar",
"Bool",
"Varchar",
"Text",
"Text"
]
@@ -147,8 +171,12 @@
true,
true,
true,
false
false,
true,
true,
false,
true
]
},
"hash": "2157813137b383411227b06814143df2d2187d1d43b84f67d22c0a2365b42ada"
"hash": "90435687152ccd37db7e0a6a5baeb558db50f5f96b1e8eff0b10e4398566f325"
}
@@ -107,6 +107,26 @@
"ordinal": 20,
"name": "ws_error_handler_muted",
"type_info": "Bool"
},
{
"ordinal": 21,
"name": "retry",
"type_info": "Jsonb"
},
{
"ordinal": 22,
"name": "summary",
"type_info": "Varchar"
},
{
"ordinal": 23,
"name": "no_flow_overlap",
"type_info": "Bool"
},
{
"ordinal": 24,
"name": "tag",
"type_info": "Varchar"
}
],
"parameters": {
@@ -137,7 +157,11 @@
true,
true,
true,
false
false,
true,
true,
false,
true
]
},
"hash": "911b1e1f2a5ba6d5159916e5598020e680c45043b0736ad0153ee261a151dd90"
@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM raw_app WHERE workspace_id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "96a77e839d6210629234879eeeaa23d9bddf5f751fe27d999659f6597f9759f1"
}
@@ -107,6 +107,26 @@
"ordinal": 20,
"name": "ws_error_handler_muted",
"type_info": "Bool"
},
{
"ordinal": 21,
"name": "retry",
"type_info": "Jsonb"
},
{
"ordinal": 22,
"name": "summary",
"type_info": "Varchar"
},
{
"ordinal": 23,
"name": "no_flow_overlap",
"type_info": "Bool"
},
{
"ordinal": 24,
"name": "tag",
"type_info": "Varchar"
}
],
"parameters": {
@@ -138,7 +158,11 @@
true,
true,
true,
false
false,
true,
true,
false,
true
]
},
"hash": "96dc1cd308f3c21cfb50b88048054dc03f93e261d25969d66aa48e9d0502960f"
@@ -25,7 +25,8 @@
"postgresql",
"noop",
"appdependencies",
"deploymentcallback"
"deploymentcallback",
"singlescriptflow"
]
}
}
@@ -0,0 +1,32 @@
{
"db_name": "PostgreSQL",
"query": "SELECT name, summary, array_remove(array_agg(email_to_igroup.email), null) as emails FROM email_to_igroup RIGHT JOIN instance_group ON instance_group.name = email_to_igroup.igroup GROUP BY name",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "name",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "summary",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "emails",
"type_info": "VarcharArray"
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
true,
null
]
},
"hash": "a00f3f18087326432c9114998e47cff4f78d1b28cdb8adc6b18b937e1cf142d1"
}
@@ -107,6 +107,26 @@
"ordinal": 20,
"name": "ws_error_handler_muted",
"type_info": "Bool"
},
{
"ordinal": 21,
"name": "retry",
"type_info": "Jsonb"
},
{
"ordinal": 22,
"name": "summary",
"type_info": "Varchar"
},
{
"ordinal": 23,
"name": "no_flow_overlap",
"type_info": "Bool"
},
{
"ordinal": 24,
"name": "tag",
"type_info": "Varchar"
}
],
"parameters": {
@@ -136,7 +156,11 @@
true,
true,
true,
false
false,
true,
true,
false,
true
]
},
"hash": "aa2800113a8a8805f47cdc1dd0f29d94c546fe531e7edd3e91da4978af5442fb"
@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT importer_path FROM dependency_map\n WHERE imported_path = $1 AND importer_kind = 'script'\n AND workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "importer_path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false
]
},
"hash": "aa44311f9bae8253c6c483d122597ce7e470c6ba1905e7e877dca35d9d68968f"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM deployment_metadata WHERE path = $1 AND workspace_id = $2 AND app_version IS NOT NULL",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "aee9f7f1d99910c669ef7224a6cc237ac7a0d7f5a24849bb62a6e1a8babd8ef9"
}
@@ -0,0 +1,24 @@
{
"db_name": "PostgreSQL",
"query": "SELECT count(*) FROM queue WHERE schedule_path = $1 AND workspace_id = $2 AND id != $3",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Text",
"Uuid"
]
},
"nullable": [
null
]
},
"hash": "aeedd3d92f7c50fe49d14053afebd82837093636da2950915ce4364106f5679c"
}
@@ -0,0 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE job_stats SET scalar_float = $4 WHERE workspace_id = $1 AND job_id = $2 AND metric_id = $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Uuid",
"Text",
"Float4"
]
},
"nullable": []
},
"hash": "b56111c5b81de2449a1b687656a4636457da851a4db5e916cf4f6847c48c6bd3"
}
@@ -107,6 +107,26 @@
"ordinal": 20,
"name": "ws_error_handler_muted",
"type_info": "Bool"
},
{
"ordinal": 21,
"name": "retry",
"type_info": "Jsonb"
},
{
"ordinal": 22,
"name": "summary",
"type_info": "Varchar"
},
{
"ordinal": 23,
"name": "no_flow_overlap",
"type_info": "Bool"
},
{
"ordinal": 24,
"name": "tag",
"type_info": "Varchar"
}
],
"parameters": {
@@ -137,7 +157,11 @@
true,
true,
true,
false
false,
true,
true,
false,
true
]
},
"hash": "c10348d26e3e3dc9e345d9044db35db9906617eb9c98aaf58a55d9681ffb85e3"
@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM job_stats WHERE workspace_id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "d77223fc94257711512b6ff21ce109b97324f2917113f04492f53b20860e9588"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO account (workspace_id, client, owner, expires_at, refresh_token) VALUES ($1, $2, $3, now() + ($4 || ' seconds')::interval, $5) RETURNING id",
"query": "INSERT INTO account (workspace_id, client, expires_at, refresh_token) VALUES ($1, $2, now() + ($3 || ' seconds')::interval, $4) RETURNING id",
"describe": {
"columns": [
{
@@ -11,7 +11,6 @@
],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar",
"Text",
@@ -22,5 +21,5 @@
false
]
},
"hash": "fa567c205929f41799a64aefac97c4504c7993b22478530e9345b9bc117e92e9"
"hash": "dc649111c4c862c36c26ad8a9eff8c31c2533a4a73184d0276f9a388ff2fa574"
}
@@ -107,6 +107,26 @@
"ordinal": 20,
"name": "ws_error_handler_muted",
"type_info": "Bool"
},
{
"ordinal": 21,
"name": "retry",
"type_info": "Jsonb"
},
{
"ordinal": 22,
"name": "summary",
"type_info": "Varchar"
},
{
"ordinal": 23,
"name": "no_flow_overlap",
"type_info": "Bool"
},
{
"ordinal": 24,
"name": "tag",
"type_info": "Varchar"
}
],
"parameters": {
@@ -137,7 +157,11 @@
true,
true,
true,
false
false,
true,
true,
false,
true
]
},
"hash": "dd74fa9468b5fe8c8ad657ded06076c11a78d0206af2af20685c691d4d9520bb"
@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT content FROM script WHERE hash = $1 AND workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "content",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Int8",
"Text"
]
},
"nullable": [
false
]
},
"hash": "deb2e4c21d4f9d0b9439780ca6ebee13450609b578c469cb437c5420ab520258"
}
@@ -1,26 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT name, array_remove(array_agg(email_to_igroup.email), null) as emails FROM email_to_igroup RIGHT JOIN instance_group ON instance_group.name = email_to_igroup.igroup GROUP BY name",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "name",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "emails",
"type_info": "VarcharArray"
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
null
]
},
"hash": "df8ebebb61194d9427ee922d037fc0f224dcb80d7097f4af0ebc8effd5e2205b"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM deployment_metadata WHERE path = $1 AND workspace_id = $2 AND script_hash IS NULL and app_version IS NULL",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "e517ebdbb5c81ea0cb7355aaea0c17ad9a45ddc77f236399b453f9ddb93acecd"
}
@@ -107,6 +107,26 @@
"ordinal": 20,
"name": "ws_error_handler_muted",
"type_info": "Bool"
},
{
"ordinal": 21,
"name": "retry",
"type_info": "Jsonb"
},
{
"ordinal": 22,
"name": "summary",
"type_info": "Varchar"
},
{
"ordinal": 23,
"name": "no_flow_overlap",
"type_info": "Bool"
},
{
"ordinal": 24,
"name": "tag",
"type_info": "Varchar"
}
],
"parameters": {
@@ -137,7 +157,11 @@
true,
true,
true,
false
false,
true,
true,
false,
true
]
},
"hash": "eced0a09ba547ce1dccb54a5419b22373603c9d01f77047b3553bde125bf71e8"
@@ -0,0 +1,34 @@
{
"db_name": "PostgreSQL",
"query": "SELECT name, summary, array_remove(array_agg(email_to_igroup.email), null) as emails FROM email_to_igroup RIGHT JOIN instance_group ON instance_group.name = email_to_igroup.igroup WHERE name = $1 GROUP BY name",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "name",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "summary",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "emails",
"type_info": "VarcharArray"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
true,
null
]
},
"hash": "ef0f4447498a117e4495ed9335d803403ad0055efb6da4bdd467b4ac9bf4e478"
}
@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT no_flow_overlap FROM schedule WHERE path = $1 AND workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "no_flow_overlap",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false
]
},
"hash": "f2173822c1b3d82afedf0f7dba31e218539f0b259eb5f2c98e4e8ada6665f5ee"
}
@@ -40,7 +40,8 @@
"postgresql",
"noop",
"appdependencies",
"deploymentcallback"
"deploymentcallback",
"singlescriptflow"
]
}
}
@@ -63,7 +64,8 @@
"postgresql",
"noop",
"appdependencies",
"deploymentcallback"
"deploymentcallback",
"singlescriptflow"
]
}
}
@@ -107,6 +107,26 @@
"ordinal": 20,
"name": "ws_error_handler_muted",
"type_info": "Bool"
},
{
"ordinal": 21,
"name": "retry",
"type_info": "Jsonb"
},
{
"ordinal": 22,
"name": "summary",
"type_info": "Varchar"
},
{
"ordinal": 23,
"name": "no_flow_overlap",
"type_info": "Bool"
},
{
"ordinal": 24,
"name": "tag",
"type_info": "Varchar"
}
],
"parameters": {
@@ -135,7 +155,11 @@
true,
true,
true,
false
false,
true,
true,
false,
true
]
},
"hash": "fdd3710a381dac33ef5ee5ec5564a4874e6d7807d43fa6ea7b8408cad9e97480"
+500 -709
View File
File diff suppressed because it is too large Load Diff
+13 -6
View File
@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.227.0"
version = "1.244.1"
authors.workspace = true
edition.workspace = true
@@ -11,6 +11,7 @@ members = [
"./windmill-worker",
"./windmill-common",
"./windmill-audit",
"./windmill-git-sync",
"./parsers/windmill-parser",
"./parsers/windmill-parser-ts",
"./parsers/windmill-parser-wasm",
@@ -21,7 +22,7 @@ members = [
]
[workspace.package]
version = "1.227.0"
version = "1.244.1"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -34,8 +35,9 @@ opt-level = 0
incremental = true
[features]
enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise"]
enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise", "windmill-git-sync/enterprise"]
enterprise_saml = ["windmill-api/enterprise_saml"]
stripe = ["windmill-api/stripe"]
benchmark = ["windmill-api/benchmark", "windmill-worker/benchmark", "windmill-queue/benchmark"]
flamegraph = ["windmill-common/flamegraph", "windmill-worker/flamegraph"]
loki = ["windmill-common/loki"]
@@ -46,6 +48,7 @@ anyhow.workspace = true
tokio.workspace = true
dotenv.workspace = true
windmill-common = { workspace = true, features = ["tracing_init"] }
windmill-git-sync.workspace = true
windmill-api.workspace = true
windmill-worker.workspace = true
futures.workspace = true
@@ -66,7 +69,7 @@ uuid.workspace = true
gethostname.workspace = true
serde_json.workspace = true
serde.workspace = true
pg-embed = {git = "https://github.com/faokunega/pg-embed", optional = true}
pg-embed = {git = "https://github.com/faokunega/pg-embed", optional = true, default-features = false, features = ['rt_tokio']}
[dev-dependencies]
serde_json.workspace = true
@@ -83,6 +86,7 @@ windmill-queue = { path = "./windmill-queue" }
windmill-worker = { path = "./windmill-worker" }
windmill-common = { path = "./windmill-common" }
windmill-audit = { path = "./windmill-audit" }
windmill-git-sync = { path = "./windmill-git-sync" }
windmill-parser = { path = "./parsers/windmill-parser" }
windmill-parser-ts = { path = "./parsers/windmill-parser-ts" }
windmill-parser-py = { path = "./parsers/windmill-parser-py" }
@@ -153,7 +157,7 @@ swc_ecma_visit = "0.93.7"
base64 = "0.21.0"
hmac = "0.12.1"
sha2 = "0.10.6"
sqlx = { version = "^0", features = [
sqlx = { version = "0.7.3", features = [
"macros",
"migrate",
"uuid",
@@ -208,4 +212,7 @@ candle-transformers = "0.3.0"
candle-nn = "0.3.0"
tiberius = { version = "0.12.2", default-features = false, features = ["rustls", "tds73", "chrono"] }
aws-sdk-s3 = "1.5.0"
aws-config = "1.0.3"
aws-config = "1.0.3"
polars = { version = "0.35.2", features = ["lazy", "parquet", "aws", "csv", "dtype-full"] }
polars-io = { version = "0.35.2", features = ["csv"] }
object_store = { version = "0.8.0", features = ["aws"] }
@@ -0,0 +1,2 @@
-- Add down migration script here
ALTER TABLE schedule DROP COLUMN IF EXISTS retry;
@@ -0,0 +1,3 @@
-- Add up migration script here
ALTER TABLE schedule ADD COLUMN IF NOT EXISTS retry JSONB;
ALTER TYPE JOB_KIND ADD VALUE IF NOT EXISTS 'singlescriptflow';
@@ -0,0 +1 @@
-- Add down migration script here
@@ -0,0 +1,3 @@
-- Add up migration script here
ALTER TABLE schedule ADD COLUMN summary VARCHAR(512);
ALTER TABLE schedule ADD COLUMN no_flow_overlap BOOLEAN NOT NULL DEFAULT FALSE;
@@ -0,0 +1,2 @@
-- Add down migration script here
DROP TABLE dependency_map;
@@ -0,0 +1,14 @@
-- Add up migration script here
CREATE TYPE IMPORTER_KIND AS ENUM ('script', 'flow', 'app');
CREATE TABLE IF NOT EXISTS dependency_map (
workspace_id VARCHAR(50) NOT NULL,
importer_path VARCHAR(510) NOT NULL,
importer_kind IMPORTER_KIND NOT NULL,
imported_path VARCHAR(510) NOT NULL,
PRIMARY KEY (workspace_id, importer_path, importer_kind, imported_path)
);
CREATE UNIQUE INDEX IF NOT EXISTS dependency_map_imported_path_idx ON dependency_map (workspace_id, imported_path);
@@ -0,0 +1,3 @@
-- Add down migration script here
DROP TABLE job_stats;
DROP TYPE METRIC_KIND;
@@ -0,0 +1,16 @@
-- Add up migration script here
CREATE TYPE METRIC_KIND AS ENUM ('scalar_int', 'scalar_float', 'timeseries_int', 'timeseries_float');
CREATE TABLE IF NOT EXISTS job_stats (
workspace_id VARCHAR(50) NOT NULL REFERENCES workspace(id),
job_id UUID NOT NULL,
metric_id VARCHAR(50) NOT NULL,
metric_name VARCHAR(255),
metric_kind METRIC_KIND NOT NULL,
scalar_int INTEGER,
scalar_float REAL,
timestamps TIMESTAMP WITH TIME ZONE[],
timeseries_int INTEGER[],
timeseries_float REAL[],
PRIMARY KEY (workspace_id, job_id, metric_id)
);
@@ -0,0 +1,2 @@
-- Add down migration script here
UPDATE workspace_settings SET git_sync = git_sync->0;
@@ -0,0 +1,2 @@
-- Add up migration script here
UPDATE workspace_settings SET git_sync = '[]'::jsonb || git_sync
@@ -0,0 +1,2 @@
-- Add down migration script here
ALTER TABLE schedule DROP COLUMN tag;
@@ -0,0 +1,2 @@
-- Add up migration script here
ALTER TABLE schedule ADD COLUMN tag VARCHAR(50);
@@ -0,0 +1 @@
-- Add down migration script here
@@ -0,0 +1,11 @@
-- Add up migration script here
DROP POLICY IF EXISTS see_own ON account;
DROP POLICY IF EXISTS see_member ON account;
DROP POLICY IF EXISTS see_folder_extra_perms_user on account;
ALTER TABLE account DISABLE ROW LEVEL SECURITY;
ALTER TABLE account DROP COLUMN IF EXISTS owner;
GRANT ALL ON account TO windmill_admin;
GRANT ALL ON account TO windmill_user;
@@ -63,7 +63,7 @@ lazy_static! {
fn process_import(module: Option<String>, path: &str, level: usize) -> Vec<String> {
if level > 0 {
let mut imports = vec!["requests".to_string()];
let mut imports = vec![];
let splitted_path = path.split("/");
let base = splitted_path
.clone()
@@ -78,10 +78,7 @@ fn process_import(module: Option<String>, path: &str, level: usize) -> Vec<Strin
} else if let Some(module) = module {
let imprt = module.split('.').next().unwrap_or("").replace("_", "-");
if imprt == "u" || imprt == "f" {
vec![
"requests".to_string(),
format!("relative:{}", module.replace(".", "/")),
]
vec![format!("relative:{}", module.replace(".", "/"))]
} else {
vec![imprt]
}
@@ -90,6 +87,57 @@ fn process_import(module: Option<String>, path: &str, level: usize) -> Vec<Strin
}
}
pub fn parse_relative_imports(code: &str, path: &str) -> error::Result<Vec<String>> {
let nimports = parse_code_for_imports(code, path)?;
return Ok(nimports
.into_iter()
.filter_map(|x| {
if x.starts_with("relative:") {
Some(x.replace("relative:", ""))
} else {
None
}
})
.collect());
}
fn parse_code_for_imports(code: &str, path: &str) -> error::Result<Vec<String>> {
let code = code.split(DEF_MAIN).next().unwrap_or("");
let ast = Suite::parse(code, "main.py").map_err(|e| {
error::Error::ExecutionErr(format!("Error parsing code: {}", e.to_string()))
})?;
let nimports: Vec<String> = ast
.into_iter()
.filter_map(|x| match x {
Stmt::Import(StmtImport { names, .. }) => Some(
names
.into_iter()
.map(|x| {
let name = x.name.to_string();
process_import(Some(name), path, 0)
})
.flatten()
.collect::<Vec<String>>(),
),
Stmt::ImportFrom(StmtImportFrom { level: Some(i), module, .. }) if i.to_u32() > 0 => {
Some(process_import(
module.map(|x| x.to_string()),
path,
i.to_usize(),
))
}
Stmt::ImportFrom(StmtImportFrom { level: _, module, .. }) => {
Some(process_import(module.map(|x| x.to_string()), path, 0))
}
_ => None,
})
.flatten()
.filter(|x| !STDIMPORTS.contains(&x.as_str()))
.unique()
.collect();
return Ok(nimports);
}
#[async_recursion]
pub async fn parse_python_imports(
code: &str,
@@ -127,41 +175,7 @@ pub async fn parse_python_imports(
imports.extend(lines);
}
let code = code.split(DEF_MAIN).next().unwrap_or("");
let ast = Suite::parse(code, "main.py").map_err(|e| {
error::Error::ExecutionErr(format!("Error parsing code: {}", e.to_string()))
})?;
let nimports: Vec<String> = ast
.into_iter()
.filter_map(|x| match x {
Stmt::Import(StmtImport { names, .. }) => Some(
names
.into_iter()
.map(|x| {
let name = x.name.to_string();
process_import(Some(name), path, 0)
})
.flatten()
.collect::<Vec<String>>(),
),
Stmt::ImportFrom(StmtImportFrom { level: Some(i), module, .. })
if i.to_u32() > 0 =>
{
Some(process_import(
module.map(|x| x.to_string()),
path,
i.to_usize(),
))
}
Stmt::ImportFrom(StmtImportFrom { level: _, module, .. }) => {
Some(process_import(module.map(|x| x.to_string()), path, 0))
}
_ => None,
})
.flatten()
.filter(|x| !STDIMPORTS.contains(&x.as_str()))
.unique()
.collect();
let nimports = parse_code_for_imports(code, path)?;
for n in nimports.iter() {
let nested = if n.starts_with("relative:") {
let rpath = n.replace("relative:", "");
@@ -20,7 +20,7 @@ def main():
";
let r = parse_python_imports(code, "test-workspace", "f/foo/bar", &db).await?;
// println!("{}", serde_json::to_string(&r)?);
assert_eq!(r, vec!["matplotlib", "requests", "wmill", "zanzibar"]);
assert_eq!(r, vec!["matplotlib", "wmill", "zanzibar"]);
Ok(())
}
@@ -73,7 +73,6 @@ def main():
"numpy",
"pandas",
"pandas2",
"requests"
]
);
@@ -13,4 +13,4 @@ windmill-parser.workspace = true
rustpython-parser.workspace = true
itertools.workspace = true
serde_json.workspace = true
anyhow.workspace = true
anyhow.workspace = true
+93 -16
View File
@@ -93,22 +93,7 @@ pub fn parse_python_signature(code: &str) -> anyhow::Result<MainArgSignature> {
.as_arg()
.annotation
.as_ref()
.map_or(Typ::Unknown, |e| match e.as_ref() {
Expr::Name(ExprName { id, .. }) => match id.as_ref() {
"str" => Typ::Str(None),
"float" => Typ::Float,
"int" => Typ::Int,
"bool" => Typ::Bool,
"dict" => Typ::Object(vec![]),
"list" => Typ::List(Box::new(Typ::Str(None))),
"bytes" => Typ::Bytes,
"datetime" => Typ::Datetime,
"datetime.datetime" => Typ::Datetime,
"Sql" | "sql" => Typ::Sql,
_ => Typ::Resource(id.to_string()),
},
_ => Typ::Unknown,
});
.map_or(Typ::Unknown, |e| parse_expr(e));
if typ == Typ::Unknown
&& default.is_some()
@@ -134,6 +119,58 @@ pub fn parse_python_signature(code: &str) -> anyhow::Result<MainArgSignature> {
}
}
fn parse_expr(e: &Box<Expr>) -> Typ {
match e.as_ref() {
Expr::Name(ExprName { id, .. }) => parse_typ(id.as_ref()),
Expr::Subscript(x) => match x.value.as_ref() {
Expr::Name(ExprName { id, .. }) => match id.as_str() {
"Literal" => {
let values = match x.slice.as_ref() {
Expr::Tuple(elts) => {
let v: Vec<String> = elts
.elts
.iter()
.map(|x| match x {
Expr::Constant(c) => c.value.as_str().map(|x| x.to_string()),
_ => None,
})
.filter_map(|x| x)
.collect();
if v.is_empty() {
None
} else {
Some(v)
}
}
_ => None,
};
Typ::Str(values)
}
"List" => Typ::List(Box::new(parse_expr(&x.slice))),
_ => Typ::Unknown,
},
_ => Typ::Unknown,
},
_ => Typ::Unknown,
}
}
fn parse_typ(id: &str) -> Typ {
match id {
"str" => Typ::Str(None),
"float" => Typ::Float,
"int" => Typ::Int,
"bool" => Typ::Bool,
"dict" => Typ::Object(vec![]),
"list" => Typ::List(Box::new(Typ::Str(None))),
"bytes" => Typ::Bytes,
"datetime" => Typ::Datetime,
"datetime.datetime" => Typ::Datetime,
"Sql" | "sql" => Typ::Sql,
_ => Typ::Resource(id.to_string()),
}
}
fn to_value<R>(et: &Expr<R>) -> Option<serde_json::Value> {
match et {
Expr::Constant(ExprConstant { value, .. }) => Some(constant_to_value(value)),
@@ -366,4 +403,44 @@ def main(test1: str,
Ok(())
}
#[test]
fn test_parse_python_sig_4() -> anyhow::Result<()> {
let code = r#"
import os
def main(test1: Literal["foo", "bar"], test2: List[Literal["foo", "bar"]]): return
"#;
//println!("{}", serde_json::to_string()?);
assert_eq!(
parse_python_signature(code)?,
MainArgSignature {
star_args: false,
star_kwargs: false,
args: vec![
Arg {
otyp: None,
name: "test1".to_string(),
typ: Typ::Str(Some(vec!["foo".to_string(), "bar".to_string()])),
default: None,
has_default: false
},
Arg {
otyp: None,
name: "test2".to_string(),
typ: Typ::List(Box::new(Typ::Str(Some(vec![
"foo".to_string(),
"bar".to_string()
])))),
default: None,
has_default: false
}
]
}
);
Ok(())
}
}
@@ -22,4 +22,5 @@ swc_ecma_visit.workspace = true
serde_json.workspace = true
anyhow.workspace = true
convert_case.workspace = true
regex.workspace = true
regex.workspace = true
lazy_static.workspace = true
+16 -12
View File
@@ -47,10 +47,9 @@ impl Visit for ImportsFinder {
pub fn parse_expr_for_imports(code: &str) -> anyhow::Result<Vec<String>> {
let cm: Lrc<SourceMap> = Default::default();
let fm = cm.new_source_file(FileName::Custom("main.ts".into()), code.into());
let fm = cm.new_source_file(FileName::Custom("main.d.ts".into()), code.into());
let lexer = Lexer::new(
// We want to parse ecmascript
Syntax::Es(EsConfig { jsx: false, ..Default::default() }),
Syntax::Typescript(TsConfig::default()),
// EsVersion defaults to es5
Default::default(),
StringInput::from(&*fm),
@@ -64,9 +63,9 @@ pub fn parse_expr_for_imports(code: &str) -> anyhow::Result<Vec<String>> {
err_s += &e.into_kind().msg().to_string();
}
let expr = parser
.parse_module()
.map_err(|_| anyhow::anyhow!("Error while parsing code, it is invalid TypeScript"))?;
let expr = parser.parse_module().map_err(|e| {
anyhow::anyhow!("Error while parsing code, it is invalid TypeScript: {err_s}, {e:?}")
})?;
let mut visitor = ImportsFinder { imports: HashSet::new() };
swc_ecma_visit::visit_module(&mut visitor, &expr);
@@ -119,9 +118,9 @@ pub fn parse_expr_for_ids(code: &str) -> anyhow::Result<Vec<(String, String)>> {
err_s += &e.into_kind().msg().to_string();
}
let expr = parser
.parse_module()
.map_err(|_| anyhow::anyhow!("Error while parsing code, it is invalid TypeScript"))?;
let expr = parser.parse_module().map_err(|e| {
anyhow::anyhow!("Error while parsing code, it is invalid TypeScript: {err_s}, {e:?}")
})?;
let mut visitor = OutputFinder { idents: HashSet::new() };
swc_ecma_visit::visit_module(&mut visitor, &expr);
@@ -150,7 +149,9 @@ pub fn parse_deno_signature(code: &str, skip_dflt: bool) -> anyhow::Result<MainA
let ast = parser
.parse_module()
.map_err(|_| anyhow::anyhow!("Error while parsing code, it is invalid TypeScript"))?
.map_err(|e| {
anyhow::anyhow!("Error while parsing code, it is invalid TypeScript: {err_s}, {e:?}")
})?
.body;
let params = ast.into_iter().find_map(|x| match x {
@@ -283,12 +284,15 @@ fn binding_ident_to_arg(BindingIdent { id, type_ann }: &BindingIdent) -> (String
(id.sym.to_string(), typ, nullable)
}
lazy_static::lazy_static! {
static ref RE_SNK_CASE: Regex = Regex::new(r"_(\d)").unwrap();
}
fn to_snake_case(s: &str) -> String {
let r = s.to_case(Case::Snake);
// s_3 => s3
let re = Regex::new(r"_(\d)").unwrap();
re.replace_all(&r, "$1").to_string()
RE_SNK_CASE.replace_all(&r, "$1").to_string()
}
fn tstype_to_typ(ts_type: &TsType) -> (Typ, bool) {
@@ -3,7 +3,7 @@
"collaborators": [
"Ruben Fiszel <ruben@windmill.dev>"
],
"version": "1.222.0",
"version": "1.237.0",
"files": [
"windmill_parser_wasm_bg.wasm",
"windmill_parser_wasm.js",
@@ -14,6 +14,11 @@ export function parse_outputs(code: string): string;
* @param {string} code
* @returns {string}
*/
export function parse_ts_imports(code: string): string;
/**
* @param {string} code
* @returns {string}
*/
export function parse_bash(code: string): string;
/**
* @param {string} code
@@ -67,6 +72,7 @@ export interface InitOutput {
readonly memory: WebAssembly.Memory;
readonly parse_deno: (a: number, b: number, c: number) => void;
readonly parse_outputs: (a: number, b: number, c: number) => void;
readonly parse_ts_imports: (a: number, b: number, c: number) => void;
readonly parse_bash: (a: number, b: number, c: number) => void;
readonly parse_powershell: (a: number, b: number, c: number) => void;
readonly parse_go: (a: number, b: number, c: number) => void;
@@ -97,6 +97,15 @@ function getInt32Memory0() {
return cachedInt32Memory0;
}
const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
function getStringFromWasm0(ptr, len) {
ptr = ptr >>> 0;
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
}
function addHeapObject(obj) {
if (heap_next === heap.length) heap.push(heap.length + 1);
const idx = heap_next;
@@ -115,15 +124,6 @@ function getFloat64Memory0() {
return cachedFloat64Memory0;
}
const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
function getStringFromWasm0(ptr, len) {
ptr = ptr >>> 0;
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
}
let cachedBigInt64Memory0 = null;
function getBigInt64Memory0() {
@@ -243,6 +243,29 @@ export function parse_outputs(code) {
}
}
/**
* @param {string} code
* @returns {string}
*/
export function parse_ts_imports(code) {
let deferred2_0;
let deferred2_1;
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
wasm.parse_ts_imports(retptr, ptr0, len0);
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
deferred2_0 = r0;
deferred2_1 = r1;
return getStringFromWasm0(r0, r1);
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
}
}
/**
* @param {string} code
* @returns {string}
@@ -515,13 +538,13 @@ async function __wbg_load(module, imports) {
function __wbg_get_imports() {
const imports = {};
imports.wbg = {};
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
takeObject(arg0);
};
imports.wbg.__wbg_eval_1081105c41705556 = function(arg0, arg1) {
imports.wbg.__wbg_eval_ff4183ac1495b791 = function(arg0, arg1) {
const ret = eval(getStringFromWasm0(arg0, arg1));
return addHeapObject(ret);
};
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
takeObject(arg0);
};
imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
const obj = getObject(arg1);
const ret = typeof(obj) === 'string' ? obj : undefined;
@@ -530,6 +553,10 @@ function __wbg_get_imports() {
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
};
imports.wbg.__wbindgen_error_new = function(arg0, arg1) {
const ret = new Error(getStringFromWasm0(arg0, arg1));
return addHeapObject(ret);
};
imports.wbg.__wbindgen_boolean_get = function(arg0) {
const v = getObject(arg0);
const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2;
@@ -566,10 +593,6 @@ function __wbg_get_imports() {
const ret = getObject(arg0) in getObject(arg1);
return ret;
};
imports.wbg.__wbindgen_error_new = function(arg0, arg1) {
const ret = new Error(getStringFromWasm0(arg0, arg1));
return addHeapObject(ret);
};
imports.wbg.__wbindgen_jsval_loose_eq = function(arg0, arg1) {
const ret = getObject(arg0) == getObject(arg1);
return ret;
@@ -3,6 +3,7 @@
export const memory: WebAssembly.Memory;
export function parse_deno(a: number, b: number, c: number): void;
export function parse_outputs(a: number, b: number, c: number): void;
export function parse_ts_imports(a: number, b: number, c: number): void;
export function parse_bash(a: number, b: number, c: number): void;
export function parse_powershell(a: number, b: number, c: number): void;
export function parse_go(a: number, b: number, c: number): void;
@@ -31,7 +31,7 @@ pub fn parse_outputs(code: &str) -> String {
pub fn parse_ts_imports(code: &str) -> String {
let parsed = parse_expr_for_imports(code);
let r = if let Ok(parsed) = parsed {
json!({ "imports6": parsed })
json!({ "imports": parsed })
} else {
json!({"error": parsed.err().unwrap().to_string()})
};
@@ -298,3 +298,15 @@ fn test_parse_imports() -> anyhow::Result<()> {
Ok(())
}
#[wasm_bindgen_test]
fn test_parse_imports_dts() -> anyhow::Result<()> {
let code = "
export type foo = number
";
let mut l = parse_expr_for_imports(code)?;
l.sort();
assert_eq!(l, vec![] as Vec<String>);
Ok(())
}
+14 -9
View File
@@ -22,11 +22,12 @@ use tokio::{
use windmill_api::HTTP_CLIENT;
use windmill_common::{
global_settings::{
BASE_URL_SETTING, CUSTOM_TAGS_SETTING, DISABLE_STATS_SETTING, ENV_SETTINGS,
EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING,
JOB_DEFAULT_TIMEOUT_SECS_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING,
NPM_CONFIG_REGISTRY_SETTING, OAUTH_SETTING, REQUEST_SIZE_LIMIT_SETTING,
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CUSTOM_TAGS_SETTING,
DISABLE_STATS_SETTING, ENV_SETTINGS, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING,
EXTRA_PIP_INDEX_URL_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, KEEP_JOB_DIR_SETTING,
LICENSE_KEY_SETTING, NPM_CONFIG_REGISTRY_SETTING, OAUTH_SETTING,
REQUEST_SIZE_LIMIT_SETTING, REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING,
RETENTION_PERIOD_SECS_SETTING,
},
stats::schedule_stats,
utils::{rd_string, Mode},
@@ -42,9 +43,10 @@ use windmill_worker::{
use crate::monitor::{
initial_load, load_keep_job_dir, load_require_preexisting_user, monitor_db, monitor_pool,
reload_base_url_setting, reload_extra_pip_index_url_setting,
reload_job_default_timeout_setting, reload_license_key, reload_npm_config_registry_setting,
reload_retention_period_setting, reload_server_config, reload_worker_config,
reload_base_url_setting, reload_bunfig_install_scopes_setting,
reload_extra_pip_index_url_setting, reload_job_default_timeout_setting, reload_license_key,
reload_npm_config_registry_setting, reload_retention_period_setting, reload_server_config,
reload_worker_config,
};
const GIT_VERSION: &str = git_version!(args = ["--tag", "--always"], fallback = "unknown-version");
@@ -426,12 +428,15 @@ Windmill Community Edition {GIT_VERSION}
NPM_CONFIG_REGISTRY_SETTING => {
reload_npm_config_registry_setting(&db).await
},
BUNFIG_INSTALL_SCOPES_SETTING => {
reload_bunfig_install_scopes_setting(&db).await
},
KEEP_JOB_DIR_SETTING => {
load_keep_job_dir(&db).await;
},
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING => {
load_require_preexisting_user(&db).await;
}
},
EXPOSE_METRICS_SETTING | EXPOSE_DEBUG_METRICS_SETTING => {
if n.payload() != EXPOSE_DEBUG_METRICS_SETTING || worker_mode {
tracing::info!("Metrics setting changed, restarting");
+81 -36
View File
@@ -21,9 +21,9 @@ use windmill_api::{
use windmill_common::{
error,
global_settings::{
BASE_URL_SETTING, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING,
EXTRA_PIP_INDEX_URL_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, KEEP_JOB_DIR_SETTING,
LICENSE_KEY_SETTING, NPM_CONFIG_REGISTRY_SETTING, OAUTH_SETTING,
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, EXPOSE_DEBUG_METRICS_SETTING,
EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING,
KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, NPM_CONFIG_REGISTRY_SETTING, OAUTH_SETTING,
REQUEST_SIZE_LIMIT_SETTING, REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING,
RETENTION_PERIOD_SECS_SETTING,
},
@@ -35,8 +35,9 @@ use windmill_common::{
BASE_URL, DB, METRICS_DEBUG_ENABLED, METRICS_ENABLED,
};
use windmill_worker::{
create_token_for_owner, handle_job_error, AuthedClient, JOB_DEFAULT_TIMEOUT, KEEP_JOB_DIR,
NPM_CONFIG_REGISTRY, PIP_EXTRA_INDEX_URL, SCRIPT_TOKEN_EXPIRY,
create_token_for_owner, handle_job_error, AuthedClient, SendResult, BUNFIG_INSTALL_SCOPES,
JOB_DEFAULT_TIMEOUT, KEEP_JOB_DIR, NPM_CONFIG_REGISTRY, PIP_EXTRA_INDEX_URL,
SCRIPT_TOKEN_EXPIRY,
};
#[cfg(feature = "enterprise")]
@@ -138,6 +139,9 @@ pub async fn initial_load(
if worker_mode {
reload_npm_config_registry_setting(&db).await;
}
if worker_mode {
reload_bunfig_install_scopes_setting(&db).await;
}
}
pub async fn load_metrics_enabled(db: &DB) -> error::Result<()> {
@@ -167,6 +171,7 @@ pub async fn load_metrics_debug_enabled(db: &DB) -> error::Result<()> {
};
Ok(())
}
pub async fn load_keep_job_dir(db: &DB) {
let value = sqlx::query_scalar!(
"SELECT value FROM global_settings WHERE name = $1",
@@ -250,53 +255,84 @@ pub async fn delete_expired_items(db: &DB) -> () {
let job_retention_secs = *JOB_RETENTION_SECS.read().await;
if job_retention_secs > 0 {
let deleted_jobs = sqlx::query_scalar!(
"DELETE FROM completed_job WHERE created_at <= now() - ($1::bigint::text || ' s')::interval AND started_at + ((duration_ms/1000 + $1::bigint) || ' s')::interval <= now() RETURNING id",
job_retention_secs
)
.fetch_all(db)
.await;
match db.begin().await {
Ok(mut tx) => {
let r = sqlx::query!(
"DELETE FROM job_stats WHERE job_id IN (SELECT id FROM completed_job WHERE created_at <= now() - ($1::bigint::text || ' s')::interval AND started_at + ((duration_ms/1000 + $1::bigint) || ' s')::interval <= now())",
job_retention_secs
)
.fetch_all(&mut *tx)
.await;
match r {
Ok(_) => {
let deleted_jobs = sqlx::query_scalar!(
"DELETE FROM completed_job WHERE created_at <= now() - ($1::bigint::text || ' s')::interval AND started_at + ((duration_ms/1000 + $1::bigint) || ' s')::interval <= now() RETURNING id",
job_retention_secs
)
.fetch_all(&mut *tx)
.await;
match deleted_jobs {
Ok(deleted_jobs) => {
if deleted_jobs.len() > 0 {
tracing::info!(
"deleted {} jobs completed JOB_RETENTION_SECS {} ago: {:?}",
deleted_jobs.len(),
job_retention_secs,
deleted_jobs,
)
match deleted_jobs {
Ok(deleted_jobs) => {
if deleted_jobs.len() > 0 {
tracing::info!(
"deleted {} jobs completed JOB_RETENTION_SECS {} ago: {:?}",
deleted_jobs.len(),
job_retention_secs,
deleted_jobs,
)
}
}
Err(e) => {
tracing::error!("Error deleting expired jobs: {:?}", e)
}
}
}
Err(err) => {
tracing::error!("Error deleting expired job stats: {:?}", err)
}
}
match tx.commit().await {
Ok(_) => (),
Err(err) => tracing::error!("Error deleting expired jobs: {:?}", err),
}
}
Err(e) => tracing::error!("Error deleting jobs: {}", e.to_string()),
Err(err) => {
tracing::error!("Error deleting expired jobs: {:?}", err)
}
}
}
}
pub async fn reload_extra_pip_index_url_setting(db: &DB) {
if let Err(e) = reload_option_setting(
reload_option_setting_with_tracing(
db,
EXTRA_PIP_INDEX_URL_SETTING,
"PIP_EXTRA_INDEX_URL",
PIP_EXTRA_INDEX_URL.clone(),
)
.await
{
tracing::error!("Error reloading extra_pip_index_url period: {:?}", e)
}
.await;
}
pub async fn reload_npm_config_registry_setting(db: &DB) {
if let Err(e) = reload_option_setting(
reload_option_setting_with_tracing(
db,
NPM_CONFIG_REGISTRY_SETTING,
"NPM_CONFIG_REGISTRY",
NPM_CONFIG_REGISTRY.clone(),
)
.await
{
tracing::error!("Error reloading npm_config_registry period: {:?}", e)
}
.await;
}
pub async fn reload_bunfig_install_scopes_setting(db: &DB) {
reload_option_setting_with_tracing(
db,
BUNFIG_INSTALL_SCOPES_SETTING,
"BUNFIG_INSTALL_SCOPES",
BUNFIG_INSTALL_SCOPES.clone(),
)
.await;
}
pub async fn reload_retention_period_setting(db: &DB) {
@@ -315,16 +351,13 @@ pub async fn reload_retention_period_setting(db: &DB) {
}
pub async fn reload_job_default_timeout_setting(db: &DB) {
if let Err(e) = reload_option_setting(
reload_option_setting_with_tracing(
db,
JOB_DEFAULT_TIMEOUT_SECS_SETTING,
"JOB_DEFAULT_TIMEOUT_SECS",
JOB_DEFAULT_TIMEOUT.clone(),
)
.await
{
tracing::error!("Error reloading job default timeout: {:?}", e)
}
.await;
}
pub async fn reload_request_size(db: &DB) {
@@ -372,6 +405,16 @@ pub async fn reload_license_key(db: &DB) -> error::Result<()> {
Ok(())
}
pub async fn reload_option_setting_with_tracing<T: FromStr + DeserializeOwned>(
db: &DB,
setting_name: &str,
std_env_var: &str,
lock: Arc<RwLock<Option<T>>>,
) {
if let Err(e) = reload_option_setting(db, setting_name, std_env_var, lock.clone()).await {
tracing::error!("Error reloading setting {}: {:?}", setting_name, e)
}
}
pub async fn reload_option_setting<T: FromStr + DeserializeOwned>(
db: &DB,
setting_name: &str,
@@ -729,6 +772,7 @@ async fn handle_zombie_jobs<R: rsmq_async::RsmqConnection + Send + Sync + Clone>
// since the job is unrecoverable, the same worker queue should never be sent anything
let (same_worker_tx_never_used, _same_worker_rx_never_used) = mpsc::channel::<Uuid>(1);
let (send_result_never_used, _send_result_rx_never_used) = mpsc::channel::<SendResult>(1);
let token = create_token_for_owner(
&db,
@@ -767,6 +811,7 @@ async fn handle_zombie_jobs<R: rsmq_async::RsmqConnection + Send + Sync + Clone>
"",
rsmq.clone(),
worker_name,
send_result_never_used,
)
.await;
}
+6 -5
View File
@@ -34,11 +34,12 @@ pub async fn start() -> anyhow::Result<(String, PgEmbed)> {
pg.start_db().await.expect("pg start db");
if !pg.database_exists("windmill").await.expect("db exists") {
pg.create_database("windmill")
.await
.expect("pg create database");
}
//TODO: re-enable this to make it work
// if !pg.database_exists("windmill").await.expect("db exists") {
// pg.create_database("windmill")
// .await
// .expect("pg create database");
// }
let uri = pg.full_db_uri("windmill");
Ok((uri, pg))
+17 -1
View File
@@ -1805,7 +1805,7 @@ def main():
}
#[sqlx::test(fixtures("base"))]
async fn test_empty_loop(db: Pool<Postgres>) {
async fn test_empty_loop_1(db: Pool<Postgres>) {
initialize_tracing().await;
let server = ApiServer::start(db.clone()).await;
let port = server.addr.port();
@@ -2870,6 +2870,10 @@ async fn test_script_schedule_handlers(db: Pool<Postgres>) {
timezone: "UTC".to_string(),
schedule: format!("{} {} * * * *", then.second(), then.minute()).to_string(),
ws_error_handler_muted: None,
retry: None,
no_flow_overlap: None,
summary: None,
tag: None,
};
let _ = client.create_schedule("test-workspace", &schedule).await;
@@ -2930,6 +2934,10 @@ async fn test_script_schedule_handlers(db: Pool<Postgres>) {
timezone: "UTC".to_string(),
schedule: format!("{} {} * * * *", then.second(), then.minute()).to_string(),
ws_error_handler_muted: None,
retry: None,
summary: None,
no_flow_overlap: None,
tag: None,
},
)
.await
@@ -3006,6 +3014,10 @@ async fn test_flow_schedule_handlers(db: Pool<Postgres>) {
timezone: "UTC".to_string(),
schedule: format!("{} {} * * * *", then.second(), then.minute()).to_string(),
ws_error_handler_muted: None,
retry: None,
no_flow_overlap: None,
summary: None,
tag: None,
};
let _ = client.create_schedule("test-workspace", &schedule).await;
@@ -3067,6 +3079,10 @@ async fn test_flow_schedule_handlers(db: Pool<Postgres>) {
timezone: "UTC".to_string(),
schedule: format!("{} {} * * * *", then.second(), then.minute()).to_string(),
ws_error_handler_muted: None,
retry: None,
summary: None,
no_flow_overlap: None,
tag: None,
},
)
.await
+6 -5
View File
@@ -9,7 +9,8 @@ name = "windmill_api"
path = "src/lib.rs"
[features]
enterprise = ["windmill-queue/enterprise", "async-stripe", "windmill-audit/enterprise"]
enterprise = ["windmill-queue/enterprise", "windmill-audit/enterprise", "windmill-git-sync/enterprise"]
stripe = ["async-stripe"]
enterprise_saml = ["samael"]
benchmark = []
@@ -27,6 +28,7 @@ windmill-common = { workspace = true, features = [
windmill-audit.workspace = true
windmill-parser.workspace = true
windmill-parser-py-imports.workspace = true
windmill-git-sync.workspace = true
tokio.workspace = true
anyhow.workspace = true
argon2.workspace = true
@@ -84,7 +86,6 @@ candle-core.workspace = true
candle-transformers.workspace = true
candle-nn.workspace = true
aws-sdk-s3.workspace = true
aws-config.workspace = true
polars = { version = "0.35.2", features = ["lazy", "parquet", "aws", "csv", "dtype-full"] }
polars-io = { version = "0.35.2", features = ["csv"] }
object_store = { version = "0.8.0", features = ["aws"] }
polars.workspace = true
polars-io.workspace = true
object_store.workspace = true
File diff suppressed because it is too large Load Diff
+408 -77
View File
@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.227.0
version: 1.244.1
title: Windmill API
contact:
@@ -1193,7 +1193,9 @@ paths:
large_file_storage:
$ref: "#/components/schemas/LargeFileStorage"
git_sync:
$ref: "#/components/schemas/WorkspaceGitSync"
type: array
items:
$ref: "#/components/schemas/WorkspaceGitSync"
required:
- code_completion_enabled
@@ -1532,7 +1534,9 @@ paths:
type: object
properties:
git_sync_settings:
$ref: "#/components/schemas/WorkspaceGitSync"
type: array
items:
$ref: "#/components/schemas/WorkspaceGitSync"
responses:
"200":
@@ -1994,13 +1998,10 @@ paths:
type: string
expires_in:
type: integer
owner:
type: string
client:
type: string
required:
- expires_in
- owner
- client
responses:
"200":
@@ -2231,8 +2232,7 @@ paths:
/w/{workspace}/resources/get_value_interpolated/{path}:
get:
summary:
get resource interpolated (variables and resources are fully unrolled)
summary: get resource interpolated (variables and resources are fully unrolled)
operationId: getResourceValueInterpolated
tags:
- resource
@@ -2920,8 +2920,7 @@ paths:
schema:
type: string
- name: first_parent_hash
description:
mask to filter scripts whom first direct parent has exact hash
description: mask to filter scripts whom first direct parent has exact hash
in: query
schema:
type: string
@@ -3116,8 +3115,7 @@ paths:
/workers/custom_tags:
get:
summary:
get all instance custom tags (tags are used to dispatch jobs to
summary: get all instance custom tags (tags are used to dispatch jobs to
different worker groups)
operationId: getCustomTags
tags:
@@ -3168,8 +3166,7 @@ paths:
/w/{workspace}/scripts/delete/h/{hash}:
post:
summary:
delete script by hash (erase content but keep hash, require admin)
summary: delete script by hash (erase content but keep hash, require admin)
operationId: deleteScriptByHash
tags:
- script
@@ -3408,8 +3405,7 @@ paths:
type: string
format: date-time
- name: scheduled_in_secs
description:
schedule the script to execute in the number of seconds starting now
description: schedule the script to execute in the number of seconds starting now
in: query
schema:
type: integer
@@ -3417,8 +3413,7 @@ paths:
- $ref: "#/components/parameters/WorkerTag"
- $ref: "#/components/parameters/NewJobId"
- name: invisible_to_owner
description:
make the run invisible to the the script owner (default false)
description: make the run invisible to the the script owner (default false)
in: query
schema:
type: boolean
@@ -4451,8 +4446,7 @@ paths:
type: string
format: date-time
- name: scheduled_in_secs
description:
schedule the script to execute in the number of seconds starting now
description: schedule the script to execute in the number of seconds starting now
in: query
schema:
type: integer
@@ -4461,8 +4455,7 @@ paths:
- $ref: "#/components/parameters/NewJobId"
- $ref: "#/components/parameters/IncludeHeader"
- name: invisible_to_owner
description:
make the run invisible to the the flow owner (default false)
description: make the run invisible to the the flow owner (default false)
in: query
schema:
type: boolean
@@ -4514,8 +4507,7 @@ paths:
type: string
format: date-time
- name: scheduled_in_secs
description:
schedule the script to execute in the number of seconds starting now
description: schedule the script to execute in the number of seconds starting now
in: query
schema:
type: integer
@@ -4524,8 +4516,7 @@ paths:
- $ref: "#/components/parameters/NewJobId"
- $ref: "#/components/parameters/IncludeHeader"
- name: invisible_to_owner
description:
make the run invisible to the the flow owner (default false)
description: make the run invisible to the the flow owner (default false)
in: query
schema:
type: boolean
@@ -4563,8 +4554,7 @@ paths:
type: string
format: date-time
- name: scheduled_in_secs
description:
schedule the script to execute in the number of seconds starting now
description: schedule the script to execute in the number of seconds starting now
in: query
schema:
type: integer
@@ -4573,8 +4563,7 @@ paths:
- $ref: "#/components/parameters/NewJobId"
- $ref: "#/components/parameters/IncludeHeader"
- name: invisible_to_owner
description:
make the run invisible to the the script owner (default false)
description: make the run invisible to the the script owner (default false)
in: query
schema:
type: boolean
@@ -4605,8 +4594,7 @@ paths:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/IncludeHeader"
- name: invisible_to_owner
description:
make the run invisible to the the script owner (default false)
description: make the run invisible to the the script owner (default false)
in: query
schema:
type: boolean
@@ -4639,8 +4627,7 @@ paths:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/IncludeHeader"
- name: invisible_to_owner
description:
make the run invisible to the the script owner (default false)
description: make the run invisible to the the script owner (default false)
in: query
schema:
type: boolean
@@ -4857,8 +4844,7 @@ paths:
- job
responses:
"200":
description:
the timestamp of the db that can be used to compute the drift
description: the timestamp of the db that can be used to compute the drift
content:
application/json:
schema:
@@ -5122,8 +5108,7 @@ paths:
/w/{workspace}/jobs/resume_urls/{id}/{resume_id}:
get:
summary:
get resume urls given a job_id, resume_id and a nonce to resume a flow
summary: get resume urls given a job_id, resume_id and a nonce to resume a flow
operationId: getResumeUrls
tags:
- job
@@ -5644,6 +5629,134 @@ paths:
schema:
$ref: "#/components/schemas/InstanceGroup"
/groups/create:
post:
summary: create instance group
operationId: createInstanceGroup
tags:
- group
requestBody:
description: create instance group
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
summary:
type: string
required:
- name
responses:
"200":
description: instance group created
content:
text/plain:
schema:
type: string
/groups/update/{name}:
post:
summary: update instance group
operationId: updateInstanceGroup
tags:
- group
parameters:
- $ref: "#/components/parameters/Name"
requestBody:
description: update instance group
required: true
content:
application/json:
schema:
type: object
properties:
new_summary:
type: string
required:
- new_summary
responses:
"200":
description: instance group updated
content:
text/plain:
schema:
type: string
/groups/delete/{name}:
delete:
summary: delete instance group
operationId: deleteInstanceGroup
tags:
- group
parameters:
- $ref: "#/components/parameters/Name"
responses:
"200":
description: instance group deleted
content:
text/plain:
schema:
type: string
/groups/adduser/{name}:
post:
summary: add user to instance group
operationId: addUserToInstanceGroup
tags:
- group
parameters:
- $ref: "#/components/parameters/Name"
requestBody:
description: user to add to instance group
required: true
content:
application/json:
schema:
type: object
properties:
email:
type: string
required:
- email
responses:
"200":
description: user added to instance group
content:
text/plain:
schema:
type: string
/groups/removeuser/{name}:
post:
summary: remove user from instance group
operationId: removeUserFromInstanceGroup
tags:
- group
parameters:
- $ref: "#/components/parameters/Name"
requestBody:
description: user to remove from instance group
required: true
content:
application/json:
schema:
type: object
properties:
email:
type: string
required:
- email
responses:
"200":
description: user removed from instance group
content:
text/plain:
schema:
type: string
/w/{workspace}/groups/list:
get:
summary: list groups
@@ -5674,8 +5787,7 @@ paths:
- $ref: "#/components/parameters/WorkspaceId"
- name: only_member_of
in: query
description:
only list the groups the user is member of (default false)
description: only list the groups the user is member of (default false)
schema:
type: boolean
responses:
@@ -5863,8 +5975,7 @@ paths:
- $ref: "#/components/parameters/WorkspaceId"
- name: only_member_of
in: query
description:
only list the folders the user is member of (default false)
description: only list the folders the user is member of (default false)
schema:
type: boolean
responses:
@@ -6670,7 +6781,7 @@ paths:
- use_ssl
- cache_regions
- client_kwargs
polars_cloud_options:
storage_options:
type: object
properties:
aws_endpoint_url:
@@ -6682,28 +6793,27 @@ paths:
aws_region:
type: string
aws_allow_http:
type: boolean
type: string
required:
- aws_endpoint_url
- aws_region
- aws_allow_http
required:
- s3fs_args
- polars_cloud_options
/w/{workspace}/job_helpers/v2/boto3_connection_settings:
- storage_options
/w/{workspace}/job_helpers/v2/s3_resource_info:
post:
summary:
Converts an S3 resource to the set of arguments necessary to connect to
an S3 bucket using boto3 in Python
operationId: boto3ConnectionSettingsV2
summary: Returns the s3 resource associated to the provided path, or the
workspace default S3 resource
operationId: s3ResourceInfo
tags:
- helpers
parameters:
- $ref: "#/components/parameters/WorkspaceId"
requestBody:
description:
S3 resource path to use to generate the connection settings. If empty,
the S3 resource defined in the workspace settings will be used
S3 resource path to use. If empty, the S3 resource defined in the
workspace settings will be used
required: true
content:
application/json:
@@ -6718,22 +6828,7 @@ paths:
content:
application/json:
schema:
type: object
properties:
endpoint_url:
type: string
region_name:
type: string
use_ssl:
type: boolean
aws_access_key_id:
type: string
aws_secret_access_key:
type: string
required:
- endpoint_url
- region_name
- use_ssl
$ref: "#/components/schemas/S3Resource"
/w/{workspace}/job_helpers/test_connection:
get:
@@ -6752,7 +6847,7 @@ paths:
/w/{workspace}/job_helpers/list_stored_files:
get:
summary: List the file keys available in the worspace files storage (S3)
summary: List the file keys available in the workspace files storage (S3)
operationId: listStoredFiles
tags:
- helpers
@@ -6850,6 +6945,159 @@ paths:
schema:
$ref: "#/components/schemas/WindmillFilePreview"
/w/{workspace}/job_helpers/delete_s3_file:
delete:
summary: Permanently delete file from S3
operationId: deleteS3File
tags:
- helpers
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- name: file_key
in: query
required: true
schema:
type: string
responses:
"200":
description: Confirmation
content:
application/json:
schema: {}
/w/{workspace}/job_helpers/move_s3_file:
get:
summary: Move a S3 file from one path to the other within the same bucket
operationId: moveS3File
tags:
- helpers
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- name: src_file_key
in: query
required: true
schema:
type: string
- name: dest_file_key
in: query
required: true
schema:
type: string
responses:
"200":
description: Confirmation
content:
application/json:
schema: {}
/w/{workspace}/job_helpers/multipart_upload_s3_file:
post:
summary: Upload file to S3 bucket using multipart upload
operationId: multipartFileUpload
tags:
- helpers
parameters:
- $ref: "#/components/parameters/WorkspaceId"
requestBody:
description: Query args for a multipart file upload to S3
required: true
content:
application/json:
schema:
type: object
properties:
file_key:
type: string
part_content:
type: array
items:
type: integer
upload_id:
type: string
parts:
type: array
items:
$ref: "#/components/schemas/UploadFilePart"
is_final:
type: boolean
cancel_upload:
type: boolean
s3_resource_path:
type: string
required:
- file_key
- part_content_base64
- parts
- is_final
- cancel_upload
responses:
"200":
description: Chunk upload status
content:
application/json:
schema:
type: object
properties:
upload_id:
type: string
parts:
type: array
items:
$ref: "#/components/schemas/UploadFilePart"
is_done:
type: boolean
required:
- upload_id
- parts
- is_done
/w/{workspace}/job_metrics/get/{id}:
post:
summary: get job metrics
operationId: getJobMetrics
tags:
- metrics
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/JobId"
requestBody:
description: parameters for statistics retrieval
required: true
content:
application/json:
schema:
type: object
properties:
timeseries_max_datapoints:
type: integer
from_timestamp:
type: string
format: date-time
to_timestamp:
type: string
format: date-time
responses:
"200":
description: job details
content:
application/json:
schema:
type: object
properties:
metrics_metadata:
type: array
items:
$ref: "#/components/schemas/MetricMetadata"
scalar_metrics:
type: array
items:
$ref: "#/components/schemas/ScalarMetric"
timeseries_metrics:
type: array
items:
$ref: "#/components/schemas/TimeseriesMetric"
components:
securitySchemes:
bearerAuth:
@@ -6948,8 +7196,7 @@ components:
type: integer
PerPage:
name: per_page
description:
number of items to return for a given page (default 30, max 100)
description: number of items to return for a given page (default 30, max 100)
in: query
schema:
type: integer
@@ -7077,8 +7324,7 @@ components:
type: boolean
ScheduledForBeforeNow:
name: scheduled_for_before_now
description:
filter on jobs scheduled_for before now (hence waitinf for a worker)
description: filter on jobs scheduled_for before now (hence waitinf for a worker)
in: query
schema:
type: boolean
@@ -7096,8 +7342,7 @@ components:
type: boolean
ArgsFilter:
name: args
description:
filter on jobs containing those args as a json subset (@> in postgres)
description: filter on jobs containing those args as a json subset (@> in postgres)
in: query
schema:
type: string
@@ -7109,8 +7354,7 @@ components:
type: string
ResultFilter:
name: result
description:
filter on jobs containing those result as a json subset (@> in postgres)
description: filter on jobs containing those result as a json subset (@> in postgres)
in: query
schema:
type: string
@@ -7517,6 +7761,7 @@ components:
"script_hub",
"identity",
"deploymentcallback",
"singlescriptflow",
]
schedule_path:
type: string
@@ -7626,6 +7871,7 @@ components:
"script_hub",
"identity",
"deploymentcallback",
"singlescriptflow",
]
schedule_path:
type: string
@@ -8332,6 +8578,14 @@ components:
$ref: "#/components/schemas/ScriptArgs"
ws_error_handler_muted:
type: boolean
retry:
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
summary:
type: string
no_flow_overlap:
type: boolean
tag:
type: string
required:
- path
- edited_by
@@ -8399,6 +8653,14 @@ components:
$ref: "#/components/schemas/ScriptArgs"
ws_error_handler_muted:
type: boolean
retry:
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
no_flow_overlap:
type: boolean
summary:
type: string
tag:
type: string
required:
- path
- schedule
@@ -8433,6 +8695,14 @@ components:
$ref: "#/components/schemas/ScriptArgs"
ws_error_handler_muted:
type: boolean
retry:
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
no_flow_overlap:
type: boolean
summary:
type: string
tag:
type: string
required:
- schedule
- timezone
@@ -8463,6 +8733,8 @@ components:
properties:
name:
type: string
summary:
type: string
emails:
type: array
items:
@@ -8979,6 +9251,65 @@ components:
type: string
git_repo_resource_path:
type: string
use_individual_branch:
type: boolean
required:
- script_path
- git_repo_resource_path
UploadFilePart:
type: object
properties:
part_number:
type: integer
tag:
type: string
required:
- part_number
- tag
MetricMetadata:
type: object
properties:
id:
type: string
name:
type: string
required:
- id
ScalarMetric:
type: object
properties:
metric_id:
type: string
value:
type: number
required:
- id
- value
TimeseriesMetric:
type: object
properties:
metric_id:
type: string
values:
type: array
items:
$ref: "#/components/schemas/MetricDataPoint"
required:
- id
- values
MetricDataPoint:
type: object
properties:
timestamp:
type: string
format: date-time
value:
type: number
required:
- timestamp
- value
+53 -46
View File
@@ -9,9 +9,7 @@ use std::collections::HashMap;
*/
use crate::{
db::{ApiAuthed, DB},
deployment_metadata_helpers,
users::{require_owner_of_path, OptAuthed},
variables::build_crypt,
webhook_util::{WebhookMessage, WebhookShared},
HTTP_CLIENT,
};
@@ -40,7 +38,9 @@ use windmill_common::{
utils::{
http_get_from_hub, not_found_if_none, paginate, query_elems_from_hub, Pagination, StripPath,
},
variables::build_crypt,
};
use windmill_git_sync::{handle_deployment_metadata, DeployedObject};
use windmill_queue::{push, PushArgs, PushIsolationLevel, QueueTransaction};
pub fn workspaced_service() -> Router {
@@ -588,13 +588,18 @@ async fn create_app(
)
.await?;
let mut tx = PushIsolationLevel::Transaction(tx);
let mut args: HashMap<String, serde_json::Value> = HashMap::new();
if let Some(dm) = app.deployment_message {
args.insert("deployment_message".to_string(), json!(dm));
}
let tx = PushIsolationLevel::Transaction(tx);
let (dependency_job_uuid, new_tx) = push(
&db,
tx,
&w_id,
JobPayload::AppDependencies { path: app.path.clone(), version: v_id },
PushArgs::empty(),
args,
&authed.username,
&authed.email,
windmill_common::users::username_to_permissioned_as(&authed.username),
@@ -615,25 +620,7 @@ async fn create_app(
.await?;
tracing::info!("Pushed app dependency job {}", dependency_job_uuid);
tx = PushIsolationLevel::Transaction(new_tx);
tx = deployment_metadata_helpers::handle_deployment_metadata(
tx,
&authed,
&db,
&w_id,
deployment_metadata_helpers::DeployedObject::App { path: app.path.clone(), version: v_id },
app.deployment_message,
)
.await?;
match tx {
PushIsolationLevel::Transaction(tx) => tx.commit().await?,
_ => {
return Err(Error::InternalErr(
"Expected a transaction here".to_string(),
));
}
}
new_tx.commit().await?;
webhook.send_message(
w_id.clone(),
@@ -678,7 +665,9 @@ pub async fn get_hub_app_by_id(
async fn delete_app(
authed: ApiAuthed,
Extension(db): Extension<DB>,
Extension(user_db): Extension<UserDB>,
Extension(rsmq): Extension<Option<rsmq_async::MultiplexedRsmq>>,
Extension(webhook): Extension<WebhookShared>,
Path((w_id, path)): Path<(String, StripPath)>,
) -> Result<String> {
@@ -703,10 +692,11 @@ async fn delete_app(
sqlx::query!(
"DELETE FROM app WHERE path = $1 AND workspace_id = $2",
path,
w_id
&w_id
)
.execute(&mut *tx)
.await?;
audit_log(
&mut *tx,
&authed.username,
@@ -718,6 +708,36 @@ async fn delete_app(
)
.await?;
tx.commit().await?;
handle_deployment_metadata(
&authed.email,
&authed.username,
&db,
&w_id,
DeployedObject::App {
path: path.to_string(),
parent_path: Some(path.to_string()),
version: 0, // dummy version as it will not get inserted in db
},
Some(format!("App '{}' deleted", path)),
rsmq,
true,
)
.await?;
sqlx::query!(
"DELETE FROM deployment_metadata WHERE path = $1 AND workspace_id = $2 AND app_version IS NOT NULL",
path,
w_id
)
.execute(&db)
.await
.map_err(|e| {
Error::InternalErr(format!(
"error deleting deployment metadata for script with path {path} in workspace {w_id}: {e}"
))
})?;
webhook.send_message(
w_id.clone().clone(),
WebhookMessage::DeleteApp { workspace: w_id, path: path.to_owned() },
@@ -849,15 +869,21 @@ async fn update_app(
)
.await?;
let mut tx: PushIsolationLevel<'_, rsmq_async::MultiplexedRsmq> =
let tx: PushIsolationLevel<'_, rsmq_async::MultiplexedRsmq> =
PushIsolationLevel::Transaction(tx);
if let Some(v_id) = v_id {
let mut args: HashMap<String, serde_json::Value> = HashMap::new();
if let Some(dm) = ns.deployment_message {
args.insert("deployment_message".to_string(), json!(dm));
}
args.insert("parent_path".to_string(), json!(path));
let (dependency_job_uuid, new_tx) = push(
&db,
tx,
&w_id,
JobPayload::AppDependencies { path: npath.clone(), version: v_id },
PushArgs::empty(),
args,
&authed.username,
&authed.email,
windmill_common::users::username_to_permissioned_as(&authed.username),
@@ -877,26 +903,7 @@ async fn update_app(
)
.await?;
tracing::info!("Pushed app dependency job {}", dependency_job_uuid);
tx = PushIsolationLevel::Transaction(new_tx);
tx = deployment_metadata_helpers::handle_deployment_metadata(
tx,
&authed,
&db,
&w_id,
deployment_metadata_helpers::DeployedObject::App { path: npath.clone(), version: v_id },
ns.deployment_message,
)
.await?;
}
match tx {
PushIsolationLevel::Transaction(tx) => tx.commit().await?,
_ => {
return Err(Error::InternalErr(
"Expected a transaction here".to_string(),
));
}
new_tx.commit().await?;
}
webhook.send_message(
+2 -2
View File
@@ -25,10 +25,10 @@ pub fn workspaced_service() -> Router {
async fn get_audit(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Path(id): Path<i32>,
Path((w_id, id)): Path<(String, i32)>,
) -> JsonResult<AuditLog> {
let tx = user_db.begin(&authed).await?;
let audit = windmill_audit::get_audit(tx, id).await?;
let audit = windmill_audit::get_audit(tx, id, &w_id).await?;
Ok(Json(audit))
}
async fn list_audit(
@@ -1,176 +0,0 @@
/*
* Author: Ruben Fiszel
* Copyright: Windmill Labs, Inc 2022
* This file and its contents are licensed under the AGPLv3 License.
* Please see the included NOTICE for copyright information and
* LICENSE-AGPL for a copy of the license.
*/
use std::collections::HashMap;
use crate::db::ApiAuthed;
use crate::db::DB;
use crate::workspaces::{WorkspaceGitRepo, WorkspaceSettings};
use serde_json::json;
use windmill_common::error::{Error, Result};
use windmill_common::jobs::JobPayload;
use windmill_common::scripts::ScriptHash;
use windmill_common::users::username_to_permissioned_as;
use windmill_queue::PushIsolationLevel;
#[derive(Clone)]
pub enum DeployedObject {
Script { hash: ScriptHash, path: String },
Flow { path: String },
App { path: String, version: i64 },
}
impl DeployedObject {
pub fn get_path(&self) -> &str {
match self {
DeployedObject::Script { path, .. } => path,
DeployedObject::Flow { path } => path,
DeployedObject::App { path, .. } => path,
}
}
}
pub async fn handle_deployment_metadata<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
mut tx: PushIsolationLevel<'c, R>,
authed: &ApiAuthed,
db: &DB,
w_id: &str,
obj: DeployedObject,
deployment_message: Option<String>,
) -> Result<PushIsolationLevel<'c, R>> {
let skip_git_sync = if obj.get_path().starts_with("u/") {
tracing::debug!(
"Ignoring {} from git sync as it's in a private user folder",
obj.get_path()
);
true
} else {
false
};
let workspace_git_repo_setting = sqlx::query_as::<_, WorkspaceSettings>(
"SELECT * FROM workspace_settings WHERE workspace_id = $1",
)
.bind(&w_id)
.fetch_optional(db)
.await?;
if workspace_git_repo_setting.is_none() {
return Err(Error::InternalErr(
"No workspace settings found for workspace ID".to_string(),
));
}
let workspace_git_repo = workspace_git_repo_setting
.unwrap()
.git_sync
.map(|conf| serde_json::from_value::<WorkspaceGitRepo>(conf).ok())
.flatten();
let (git_sync_job_uuid, mut new_tx) = if !skip_git_sync && workspace_git_repo.is_some() {
let workspace_git_repo = workspace_git_repo.unwrap();
let mut args: HashMap<String, serde_json::Value> = HashMap::new();
args.insert(
"repo_url_resource_path".to_string(),
json!(workspace_git_repo
.git_repo_resource_path
.strip_prefix("$res:")),
);
let default_commit_msg: String;
match obj.clone() {
DeployedObject::Script { path, .. } => {
args.insert("path".to_string(), json!(path.to_string()));
default_commit_msg = format!("Script '{}' deployed", path);
}
DeployedObject::Flow { path } => {
args.insert("path".to_string(), json!(path.to_string()));
default_commit_msg = format!("Flow '{}' deployed", path);
}
DeployedObject::App { path, .. } => {
args.insert("path".to_string(), json!(path.to_string()));
default_commit_msg = format!("App '{}' deployed", path);
}
}
args.insert(
"commit_msg".to_string(),
json!(deployment_message.clone().unwrap_or(default_commit_msg)),
);
let (job_uuid, new_tx) = windmill_queue::push(
&db,
tx,
&w_id,
JobPayload::DeploymentCallback { path: workspace_git_repo.script_path.clone() },
args,
&authed.username,
&authed.email,
username_to_permissioned_as(&authed.username),
None,
None,
None,
None,
None,
false,
false,
None,
true,
None,
None,
None,
None,
)
.await?;
(Some(job_uuid), new_tx)
} else {
let new_tx = match tx {
PushIsolationLevel::Isolated(user_db, authed, rsmq) => {
(rsmq, user_db.begin(&authed).await?).into()
}
PushIsolationLevel::IsolatedRoot(db, rsmq) => (rsmq, db.begin().await?).into(),
PushIsolationLevel::Transaction(tx) => tx,
};
(None, new_tx)
};
// We're not persisting the default commit msg as it's pretty useless. We will persist the ones manually set by users
let job_uuids = if git_sync_job_uuid.is_some() {
vec![git_sync_job_uuid.unwrap()]
} else {
vec![]
};
if deployment_message.is_some() || job_uuids.len() > 0 {
// if the git sync job hasn't been triggered, and there is not custom deployment message, there's not point adding an entry to the table
match obj.clone() {
DeployedObject::Script { path, hash, .. } => {
sqlx::query!(
"INSERT INTO deployment_metadata (workspace_id, path, script_hash, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)",
w_id, path, hash.0, &job_uuids, deployment_message,
)
},
DeployedObject::Flow { path } => {
sqlx::query!(
"INSERT INTO deployment_metadata (workspace_id, path, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path) WHERE script_hash IS NULL AND app_version IS NULL DO UPDATE SET callback_job_ids = $3, deployment_msg = $4",
w_id, path, &job_uuids, deployment_message,
)
}
DeployedObject::App { path, version } => {
sqlx::query!(
"INSERT INTO deployment_metadata (workspace_id, path, app_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)",
w_id, path, version, &job_uuids, deployment_message,
)
}
}.execute(&mut new_tx)
.await?;
}
tx = PushIsolationLevel::Transaction(new_tx);
return Ok(tx);
}
+78 -47
View File
@@ -6,8 +6,9 @@
* LICENSE-AGPL for a copy of the license.
*/
use std::collections::HashMap;
use crate::db::ApiAuthed;
use crate::deployment_metadata_helpers;
use crate::{
db::DB,
schedule::clear_schedule,
@@ -25,6 +26,7 @@ use axum::{
use hyper::StatusCode;
use serde::{Deserialize, Serialize};
use serde_json::json;
use sql_builder::prelude::*;
use sql_builder::SqlBuilder;
use sqlx::{FromRow, Postgres, Transaction};
@@ -39,7 +41,7 @@ use windmill_common::{
scripts::Schema,
utils::{http_get_from_hub, not_found_if_none, paginate, Pagination, StripPath},
};
use windmill_queue::PushArgs;
use windmill_git_sync::{handle_deployment_metadata, DeployedObject};
use windmill_queue::{push, schedule::push_scheduled_job, PushIsolationLevel, QueueTransaction};
pub fn workspaced_service() -> Router {
@@ -355,7 +357,12 @@ async fn create_flow(
)
.await?;
let mut tx = PushIsolationLevel::Transaction(tx);
let mut args: HashMap<String, serde_json::Value> = HashMap::new();
if let Some(dm) = nf.deployment_message {
args.insert("deployment_message".to_string(), json!(dm));
}
let tx = PushIsolationLevel::Transaction(tx);
let (dependency_job_uuid, mut new_tx) = push(
&db,
tx,
@@ -364,7 +371,7 @@ async fn create_flow(
path: nf.path.clone(),
dedicated_worker: nf.dedicated_worker,
},
PushArgs::empty(),
args,
&authed.username,
&authed.email,
windmill_common::users::username_to_permissioned_as(&authed.username),
@@ -393,26 +400,7 @@ async fn create_flow(
.execute(&mut new_tx)
.await?;
tx = PushIsolationLevel::Transaction(new_tx);
tx = deployment_metadata_helpers::handle_deployment_metadata(
tx,
&authed,
&db,
&w_id,
deployment_metadata_helpers::DeployedObject::Flow { path: nf.path.clone() },
nf.deployment_message,
)
.await?;
match tx {
PushIsolationLevel::Transaction(tx) => tx.commit().await?,
_ => {
return Err(Error::InternalErr(
"Expected a transaction here".to_string(),
));
}
}
new_tx.commit().await?;
webhook.send_message(
w_id.clone(),
WebhookMessage::CreateFlow { workspace: w_id.clone(), path: nf.path.clone() },
@@ -585,7 +573,13 @@ async fn update_flow(
},
);
let mut tx = PushIsolationLevel::Transaction(tx);
let tx = PushIsolationLevel::Transaction(tx);
let mut args: HashMap<String, serde_json::Value> = HashMap::new();
if let Some(dm) = nf.deployment_message {
args.insert("deployment_message".to_string(), json!(dm));
}
args.insert("parent_path".to_string(), json!(flow_path));
let (dependency_job_uuid, mut new_tx) = push(
&db,
@@ -595,7 +589,7 @@ async fn update_flow(
path: nf.path.clone(),
dedicated_worker: nf.dedicated_worker,
},
PushArgs::empty(),
args,
&authed.username,
&authed.email,
windmill_common::users::username_to_permissioned_as(&authed.username),
@@ -631,25 +625,7 @@ async fn update_flow(
.await?;
}
tx = PushIsolationLevel::Transaction(new_tx);
tx = deployment_metadata_helpers::handle_deployment_metadata(
tx,
&authed,
&db,
&w_id,
deployment_metadata_helpers::DeployedObject::Flow { path: nf.path.clone() },
nf.deployment_message,
)
.await?;
match tx {
PushIsolationLevel::Transaction(tx) => tx.commit().await?,
_ => {
return Err(Error::InternalErr(
"Expected a transaction here".to_string(),
));
}
}
new_tx.commit().await?;
Ok(nf.path.to_string())
}
@@ -744,8 +720,10 @@ struct Archived {
async fn archive_flow_by_path(
authed: ApiAuthed,
Extension(db): Extension<DB>,
Extension(user_db): Extension<UserDB>,
Extension(webhook): Extension<WebhookShared>,
Extension(rsmq): Extension<Option<rsmq_async::MultiplexedRsmq>>,
Path((w_id, path)): Path<(String, StripPath)>,
Json(archived): Json<Archived>,
) -> Result<String> {
@@ -772,6 +750,27 @@ async fn archive_flow_by_path(
)
.await?;
tx.commit().await?;
handle_deployment_metadata(
&authed.email,
&authed.username,
&db,
&w_id,
DeployedObject::Flow { path: path.to_string(), parent_path: Some(path.to_string()) },
Some(format!(
"Flow '{}' {}",
path,
if archived.archived.unwrap_or(true) {
"archived"
} else {
"unarchived"
}
)),
rsmq,
true,
)
.await?;
webhook.send_message(
w_id.clone(),
WebhookMessage::ArchiveFlow { workspace: w_id, path: path.to_owned() },
@@ -782,7 +781,9 @@ async fn archive_flow_by_path(
async fn delete_flow_by_path(
authed: ApiAuthed,
Extension(db): Extension<DB>,
Extension(user_db): Extension<UserDB>,
Extension(rsmq): Extension<Option<rsmq_async::MultiplexedRsmq>>,
Extension(webhook): Extension<WebhookShared>,
Path((w_id, path)): Path<(String, StripPath)>,
) -> Result<String> {
@@ -816,6 +817,32 @@ async fn delete_flow_by_path(
)
.await?;
tx.commit().await?;
handle_deployment_metadata(
&authed.email,
&authed.username,
&db,
&w_id,
DeployedObject::Flow { path: path.to_string(), parent_path: Some(path.to_string()) },
Some(format!("Flow '{}' deleted", path)),
rsmq,
true,
)
.await?;
sqlx::query!(
"DELETE FROM deployment_metadata WHERE path = $1 AND workspace_id = $2 AND script_hash IS NULL and app_version IS NULL",
path,
w_id
)
.execute(&db)
.await
.map_err(|e| {
Error::InternalErr(format!(
"error deleting deployment metadata for script with path {path} in workspace {w_id}: {e}"
))
})?;
webhook.send_message(
w_id.clone(),
WebhookMessage::DeleteFlow { workspace: w_id, path: path.to_owned() },
@@ -853,6 +880,7 @@ mod tests {
)]
.into(),
hash: None,
tag_override: None,
},
stop_after_if: None,
summary: None,
@@ -921,6 +949,7 @@ mod tests {
path: "test".to_string(),
input_transforms: HashMap::new(),
hash: None,
tag_override: None,
},
stop_after_if: Some(StopAfterIf {
expr: "previous.isEmpty()".to_string(),
@@ -956,7 +985,8 @@ mod tests {
}
},
"type": "script",
"path": "test"
"path": "test",
"tag_override": Option::<String>::None,
},
},
{
@@ -999,7 +1029,8 @@ mod tests {
"value": {
"input_transforms": {},
"type": "script",
"path": "test"
"path": "test",
"tag_override": Option::<String>::None,
},
"stop_after_if": {
"expr": "previous.isEmpty()",
+50 -7
View File
@@ -43,6 +43,7 @@ pub fn global_service() -> Router {
.route("/list", get(list_igroups))
.route("/get/:name", get(get_igroup))
.route("/create", post(create_igroup))
.route("/update/:name", post(update_igroup))
.route("/delete/:name", delete(delete_igroup))
.route("/adduser/:name", post(add_user_igroup))
.route("/removeuser/:name", post(remove_user_igroup))
@@ -292,14 +293,55 @@ async fn create_igroup(
Ok(format!("Created group {}", ng.name))
}
async fn delete_igroup(
#[derive(Deserialize)]
struct IGroupUpdate {
new_summary: String,
}
async fn update_igroup(
authed: ApiAuthed,
Extension(db): Extension<DB>,
Json(ng): Json<NewGroup>,
Path(name): Path<String>,
Json(igroup_update): Json<IGroupUpdate>,
) -> Result<String> {
require_super_admin(&db, &authed.email).await?;
let mut tx: Transaction<'_, Postgres> = db.begin().await?;
sqlx::query!("DELETE FROM instance_group WHERE name = $1", ng.name,)
let exists_opt = sqlx::query("SELECT 1 FROM instance_group WHERE name = $1")
.bind(name.clone())
.fetch_optional(&mut *tx)
.await?;
not_found_if_none(exists_opt, "instance_group", name.clone())?;
sqlx::query("UPDATE instance_group SET summary = $1 WHERE name = $2")
.bind(igroup_update.new_summary)
.bind(&name)
.execute(&mut *tx)
.await?;
audit_log(
&mut *tx,
&authed.username,
"igroup.updated",
ActionKind::Delete,
"global",
Some(&name.to_string()),
None,
)
.await?;
tx.commit().await?;
Ok(format!("Deleted group {}", name))
}
async fn delete_igroup(
authed: ApiAuthed,
Extension(db): Extension<DB>,
Path(name): Path<String>,
) -> Result<String> {
require_super_admin(&db, &authed.email).await?;
let mut tx: Transaction<'_, Postgres> = db.begin().await?;
sqlx::query!("DELETE FROM instance_group WHERE name = $1", name)
.execute(&mut *tx)
.await?;
@@ -309,13 +351,13 @@ async fn delete_igroup(
"igroup.delete",
ActionKind::Delete,
"global",
Some(&ng.name.to_string()),
Some(&name.to_string()),
None,
)
.await?;
tx.commit().await?;
Ok(format!("Created group {}", ng.name))
Ok(format!("Deleted group {}", name))
}
pub async fn get_group_opt<'c>(
@@ -535,6 +577,7 @@ async fn add_user_igroup(
#[derive(Serialize)]
struct IGroup {
name: String,
summary: Option<String>,
emails: Option<Vec<String>>,
}
async fn list_igroups(Extension(db): Extension<DB>) -> JsonResult<Vec<IGroup>> {
@@ -542,7 +585,7 @@ async fn list_igroups(Extension(db): Extension<DB>) -> JsonResult<Vec<IGroup>> {
let groups = sqlx::query_as!(
IGroup,
"SELECT name, array_remove(array_agg(email_to_igroup.email), null) as emails FROM email_to_igroup RIGHT JOIN instance_group ON instance_group.name = email_to_igroup.igroup GROUP BY name"
"SELECT name, summary, array_remove(array_agg(email_to_igroup.email), null) as emails FROM email_to_igroup RIGHT JOIN instance_group ON instance_group.name = email_to_igroup.igroup GROUP BY name"
)
.fetch_all(&mut *tx)
.await?;
@@ -554,7 +597,7 @@ async fn list_igroups(Extension(db): Extension<DB>) -> JsonResult<Vec<IGroup>> {
async fn get_igroup(Path(name): Path<String>, Extension(db): Extension<DB>) -> JsonResult<IGroup> {
let group = sqlx::query_as!(
IGroup,
"SELECT name, array_remove(array_agg(email_to_igroup.email), null) as emails FROM email_to_igroup RIGHT JOIN instance_group ON instance_group.name = email_to_igroup.igroup WHERE name = $1 GROUP BY name",
"SELECT name, summary, array_remove(array_agg(email_to_igroup.email), null) as emails FROM email_to_igroup RIGHT JOIN instance_group ON instance_group.name = email_to_igroup.igroup WHERE name = $1 GROUP BY name",
name
)
.fetch_optional(&db)
+292 -87
View File
@@ -1,19 +1,19 @@
use std::{cmp, time::Duration};
use crate::{
db::DB, resources::get_resource_value_interpolated_internal, users::Tokened,
workspaces::LargeFileStorage,
};
use crate::{db::DB, resources::get_resource_value_interpolated_internal, users::Tokened};
use anyhow::Context;
use aws_sdk_s3::{
config::{BehaviorVersion, Credentials, Region},
presigning::PresigningConfig,
primitives::ByteStream,
types::{CompletedMultipartUpload, CompletedPart},
};
use axum::{
extract::{Path, Query},
routing::{get, post},
routing::{delete, get, post},
Extension, Json, Router,
};
use hyper::http;
use itertools::Itertools;
use object_store::ClientConfigKey;
use polars::{
io::{
@@ -28,7 +28,11 @@ use polars::{
};
use serde::{Deserialize, Serialize};
use tower_http::cors::{Any, CorsLayer};
use windmill_common::{db::UserDB, error};
use windmill_common::{
db::UserDB,
error,
s3_helpers::{build_s3_client, render_endpoint, LargeFileStorage, S3Resource},
};
use crate::db::ApiAuthed;
@@ -56,8 +60,8 @@ pub fn workspaced_service() -> Router {
post(polars_connection_settings_v2).layer(cors.clone()),
)
.route(
"/v2/boto3_connection_settings",
post(boto3_connection_settings_v2).layer(cors.clone()),
"/v2/s3_resource_info",
post(s3_resource_info).layer(cors.clone()),
)
.route("/test_connection", get(test_connection).layer(cors.clone()))
.route(
@@ -72,23 +76,15 @@ pub fn workspaced_service() -> Router {
"/load_file_preview",
get(load_file_preview).layer(cors.clone()),
)
}
#[derive(Debug, Deserialize, Clone)]
struct S3Resource {
#[serde(rename = "bucket")]
bucket: String,
region: String,
#[serde(rename = "endPoint")]
endpoint: String,
#[serde(rename = "useSSL")]
use_ssl: bool,
#[serde(rename = "accessKey")]
access_key: Option<String>,
#[serde(rename = "secretKey")]
secret_key: Option<String>,
#[serde(rename = "pathStyle")]
path_style: bool,
.route(
"/delete_s3_file",
delete(delete_s3_file).layer(cors.clone()),
)
.route("/move_s3_file", get(move_s3_file).layer(cors.clone()))
.route(
"/multipart_upload_s3_file",
post(multipart_upload_s3_file).layer(cors.clone()),
)
}
#[derive(Deserialize)]
@@ -201,7 +197,7 @@ struct PolarsConnectionSettingsQueryV2 {
#[derive(Serialize)]
struct PolarsConnectionSettingsResponse {
s3fs_args: S3fsArgs,
polars_cloud_options: PolarsCloudOptions,
storage_options: PolarsStorageOptions,
}
#[derive(Serialize)]
@@ -217,14 +213,14 @@ struct S3fsArgs {
}
#[derive(Serialize)]
struct PolarsCloudOptions {
struct PolarsStorageOptions {
aws_endpoint_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
aws_access_key_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
aws_secret_access_key: Option<String>,
aws_region: String,
aws_allow_http: bool,
aws_allow_http: String,
}
async fn polars_connection_settings_v2(
@@ -260,39 +256,30 @@ async fn polars_connection_settings_v2(
.0;
let response = PolarsConnectionSettingsResponse {
s3fs_args: s3fs,
polars_cloud_options: PolarsCloudOptions {
storage_options: PolarsStorageOptions {
aws_endpoint_url: render_endpoint(&s3_resource),
aws_access_key_id: s3_resource.access_key,
aws_secret_access_key: s3_resource.secret_key,
aws_region: s3_resource.region,
aws_allow_http: !s3_resource.use_ssl,
aws_allow_http: (!s3_resource.use_ssl).to_string(),
},
};
return Ok(Json(response));
}
#[derive(Deserialize)]
struct Boto3ConnectionSettingsQueryV2 {
struct S3ResourceInfoQuery {
s3_resource_path: Option<String>,
}
#[derive(Serialize)]
struct Boto3ConnectionSettingsResponse {
region_name: String,
use_ssl: bool,
endpoint_url: String,
aws_access_key_id: Option<String>,
aws_secret_access_key: Option<String>,
}
async fn boto3_connection_settings_v2(
async fn s3_resource_info(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Extension(db): Extension<DB>,
Tokened { token }: Tokened,
Path(w_id): Path<String>,
Json(query): Json<Boto3ConnectionSettingsQueryV2>,
) -> error::JsonResult<Boto3ConnectionSettingsResponse> {
Json(query): Json<S3ResourceInfoQuery>,
) -> error::JsonResult<S3Resource> {
let s3_resource_opt = match query.s3_resource_path {
Some(s3_resource_path) => {
get_s3_resource(
@@ -310,14 +297,7 @@ async fn boto3_connection_settings_v2(
let s3_resource = s3_resource_opt.ok_or(error::Error::NotFound(
"No datasets storage resource defined at the workspace level".to_string(),
))?;
let response = Boto3ConnectionSettingsResponse {
endpoint_url: render_endpoint(&s3_resource),
region_name: s3_resource.region,
use_ssl: s3_resource.use_ssl,
aws_access_key_id: s3_resource.access_key,
aws_secret_access_key: s3_resource.secret_key,
};
return Ok(Json(response));
return Ok(Json(s3_resource));
}
#[derive(Serialize, Deserialize, Clone)]
@@ -349,7 +329,10 @@ async fn test_connection(
.max_keys(1)
.send()
.await
.map_err(|err| error::Error::InternalErr(err.to_string()))?;
.map_err(|err| {
tracing::error!("Error testing connection to S3 bucket: {:?}", err);
error::Error::InternalErr(err.to_string())
})?;
return Ok(Json(()));
}
@@ -679,6 +662,262 @@ async fn load_file_preview(
return Ok(Json(response));
}
#[derive(Deserialize)]
struct DeleteS3FileQuery {
pub file_key: String,
}
async fn delete_s3_file(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Extension(db): Extension<DB>,
Tokened { token }: Tokened,
Path(w_id): Path<String>,
Query(query): Query<DeleteS3FileQuery>,
) -> error::JsonResult<()> {
let file_key = query.file_key.clone();
let s3_resource_opt = get_workspace_s3_resource(&authed, &user_db, &db, &token, &w_id).await?;
let s3_resource = s3_resource_opt.ok_or(error::Error::InternalErr(
"No files storage resource defined at the workspace level".to_string(),
))?;
let s3_client = build_s3_client(&s3_resource);
let s3_bucket = s3_resource.bucket.clone();
s3_client
.delete_object()
.bucket(&s3_bucket)
.key(&file_key)
.send()
.await
.map_err(|err| {
tracing::error!("{:?}", err);
error::Error::InternalErr(err.to_string())
})?;
return Ok(Json(()));
}
#[derive(Deserialize)]
struct MoveS3FileQuery {
pub src_file_key: String,
pub dest_file_key: String,
}
async fn move_s3_file(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Extension(db): Extension<DB>,
Tokened { token }: Tokened,
Path(w_id): Path<String>,
Query(query): Query<MoveS3FileQuery>,
) -> error::JsonResult<()> {
let s3_resource_opt = get_workspace_s3_resource(&authed, &user_db, &db, &token, &w_id).await?;
let s3_resource = s3_resource_opt.ok_or(error::Error::InternalErr(
"No files storage resource defined at the workspace level".to_string(),
))?;
let s3_client = build_s3_client(&s3_resource);
let s3_bucket = s3_resource.bucket.clone();
let source_uri = format!("{}/{}", s3_bucket, query.src_file_key);
s3_client
.copy_object()
.copy_source(&source_uri)
.bucket(&s3_bucket)
.key(&query.dest_file_key)
.send()
.await
.map_err(|err| {
tracing::error!("{:?}", err);
error::Error::InternalErr(err.to_string())
})?;
s3_client
.delete_object()
.bucket(&s3_bucket)
.key(&query.src_file_key)
.send()
.await
.map_err(|err| {
tracing::error!("{:?}", err);
error::Error::InternalErr(err.to_string())
})?;
return Ok(Json(()));
}
#[derive(Deserialize)]
struct UploadFileQuery {
pub file_key: String,
pub part_content: Vec<u8>,
pub upload_id: Option<String>, // should be None for the first call to initiate the upload
pub parts: Vec<UploadFilePart>, // parts already uploaded, with their part_number and the tag associated
pub is_final: bool, // whether it's the final chunk
pub cancel_upload: bool, // whether the upload should be cancelled. upload_id should be set. subsequent calls with this upload_id will fail
pub s3_resource_path: Option<String>, // custom S3 resource to use for this upload. It None, the workspace S3 resource will be used
}
#[derive(Deserialize, Serialize, Clone)]
struct UploadFilePart {
pub part_number: u16,
pub tag: String,
}
#[derive(Serialize)]
struct UploadFileResponse {
pub upload_id: String,
pub parts: Vec<UploadFilePart>, // parts already uploaded, with their part_number and the tag associated
pub is_done: bool, // whether the transfer is finished, either b/c it got cancelled or because the last chunk was uploaded
}
async fn multipart_upload_s3_file(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Extension(db): Extension<DB>,
Tokened { token }: Tokened,
Path(w_id): Path<String>,
Json(query): Json<UploadFileQuery>,
) -> error::JsonResult<UploadFileResponse> {
tracing::debug!(
"Multi part file upload: part number: {} - is_final: {}",
query.parts.len(),
query.is_final
);
let file_key = query.file_key.clone();
let s3_resource_opt = match query.s3_resource_path.clone() {
Some(s3_resource_path) => {
get_s3_resource(
&authed,
&user_db,
&db,
&token,
&w_id,
s3_resource_path.as_str(),
)
.await?
}
None => get_workspace_s3_resource(&authed, &user_db, &db, &token, &w_id).await?,
};
let s3_resource = s3_resource_opt.ok_or(error::Error::InternalErr(
"No files storage resource defined at the workspace level".to_string(),
))?;
let bucket = s3_resource.bucket.clone();
let s3_client = build_s3_client(&s3_resource);
if query.cancel_upload && query.upload_id.clone().is_some() {
let upload_id = query.upload_id.unwrap();
s3_client
.abort_multipart_upload()
.bucket(&bucket)
.key(&file_key)
.upload_id(&upload_id)
.send()
.await
.map_err(|err| {
tracing::error!("{:?}", err);
error::Error::InternalErr(err.to_string())
})?;
return Ok(Json(UploadFileResponse {
upload_id: upload_id,
parts: vec![], // empty parts as the transfer has been cancelled
is_done: true,
}));
}
let (upload_id, part_number) = match query {
UploadFileQuery { upload_id: Some(upload_id), ref parts, .. } if parts.len() > 0 => {
(upload_id, parts.len() + 1)
}
UploadFileQuery { upload_id: None, ref parts, .. } if parts.len() == 0 => {
let multipart_upload_res = s3_client
.create_multipart_upload()
.bucket(&bucket)
.key(&file_key)
.send()
.await
.map_err(|err| {
tracing::error!("{:?}", err);
error::Error::InternalErr(err.to_string())
})?;
let upload_id = multipart_upload_res
.upload_id
.context("Upload ID is missing in the response")?;
(upload_id, 1)
}
_ => {
return Err(error::Error::BadRequest(
"parts should be empty when upload_id is not provided, as a new upload will be created."
.to_string(),
))
}
};
let chunk_content = query.part_content.clone();
let chunk_content_stream = ByteStream::from(chunk_content);
let multipart_upload_res = s3_client
.upload_part()
.bucket(&bucket)
.key(&file_key)
.upload_id(&upload_id)
.body(chunk_content_stream)
.part_number(part_number as i32)
.send()
.await
.map_err(|err| {
tracing::error!("{:?}", err);
error::Error::InternalErr(err.to_string())
})?;
let mut new_parts = query.parts.clone();
new_parts.push(UploadFilePart {
part_number: part_number as u16,
tag: multipart_upload_res.e_tag.unwrap_or_default(),
});
if query.is_final {
let completed_parts = new_parts
.iter()
.map(|part| {
CompletedPart::builder()
.e_tag(&part.tag)
.part_number(part.part_number as i32)
.build()
})
.collect_vec();
let _complete_multipart_upload_res = s3_client
.complete_multipart_upload()
.bucket(&s3_resource.bucket)
.key(&query.file_key)
.upload_id(&upload_id)
.multipart_upload(
CompletedMultipartUpload::builder()
.set_parts(Some(completed_parts))
.build(),
)
.send()
.await
.map_err(|err| {
tracing::error!("{:?}", err);
error::Error::InternalErr(err.to_string())
})?;
}
return Ok(Json(UploadFileResponse {
upload_id: upload_id,
parts: new_parts,
is_done: query.is_final,
}));
}
#[derive(Deserialize)]
pub struct S3Object {
pub s3: String,
}
async fn get_workspace_s3_resource<'c>(
authed: &ApiAuthed,
user_db: &UserDB,
@@ -751,30 +990,6 @@ async fn get_s3_resource<'c>(
return Ok(Some(s3_resource));
}
fn build_s3_client(s3_resource_ref: &S3Resource) -> aws_sdk_s3::Client {
let s3_resource = s3_resource_ref.clone();
let endpoint = render_endpoint(&s3_resource);
let mut s3_config_builder = aws_sdk_s3::Config::builder()
.endpoint_url(endpoint)
.behavior_version(BehaviorVersion::latest())
.region(Region::new(s3_resource.region));
if s3_resource.access_key.is_some() {
s3_config_builder = s3_config_builder.credentials_provider(Credentials::new(
s3_resource.access_key.unwrap_or_default(),
s3_resource.secret_key.unwrap_or_default(),
None,
None,
"s3_storage",
));
}
if s3_resource.path_style {
s3_config_builder = s3_config_builder.force_path_style(true);
}
let s3_config = s3_config_builder.build();
return aws_sdk_s3::Client::from_conf(s3_config);
}
fn build_polars_s3_config(s3_resource_ref: &S3Resource) -> CloudOptions {
let s3_resource = s3_resource_ref.to_owned();
let mut s3_configs: Vec<(AmazonS3ConfigKey, String)> = vec![
@@ -802,16 +1017,6 @@ fn build_polars_s3_config(s3_resource_ref: &S3Resource) -> CloudOptions {
return CloudOptions::default().with_aws(s3_configs);
}
fn render_endpoint(s3_resource: &S3Resource) -> String {
if s3_resource.endpoint.starts_with("http://") || s3_resource.endpoint.starts_with("https://") {
s3_resource.endpoint.clone()
} else if s3_resource.use_ssl {
format!("https://{}", s3_resource.endpoint)
} else {
format!("http://{}", s3_resource.endpoint)
}
}
async fn read_s3_text_object_head(
s3_client: &aws_sdk_s3::Client,
s3_bucket: &str,
+165
View File
@@ -0,0 +1,165 @@
use crate::db::DB;
use axum::{extract::Path, routing::post, Extension, Json, Router};
use hyper::http;
use serde::{Deserialize, Serialize};
use tower_http::cors::{Any, CorsLayer};
use uuid::Uuid;
use windmill_common::{
error,
job_metrics::{JobStatsRecord, MetricKind},
};
pub fn workspaced_service() -> Router {
let cors = CorsLayer::new()
.allow_methods([http::Method::GET, http::Method::POST])
.allow_headers([http::header::CONTENT_TYPE, http::header::AUTHORIZATION])
.allow_origin(Any);
Router::new().route("/get/:id", post(get_job_metrics).layer(cors.clone()))
}
#[derive(Deserialize)]
struct JobStatsRequest {
from_timestamp: Option<chrono::DateTime<chrono::Utc>>,
to_timestamp: Option<chrono::DateTime<chrono::Utc>>,
timeseries_max_datapoints: Option<u32>, // default to 100, any value lower than that will be ignored. Set to 0 to retrieve all
}
#[derive(Serialize)]
struct JobStatsResponse {
metrics_metadata: Vec<MetricsMetadata>,
scalar_metrics: Vec<ScalarMetric>,
timeseries_metrics: Vec<TimeseriesMetric>,
}
#[derive(Serialize)]
pub struct MetricsMetadata {
pub id: String,
pub name: Option<String>,
}
#[derive(Serialize)]
struct ScalarMetric {
metric_id: String,
value: f64,
}
#[derive(Serialize)]
struct TimeseriesMetric {
metric_id: String,
values: Vec<DataPoint>,
}
#[derive(Debug, Clone, Serialize)]
pub struct DataPoint {
pub timestamp: chrono::DateTime<chrono::Utc>,
pub value: f64,
}
async fn get_job_metrics(
Extension(db): Extension<DB>,
Path((w_id, job_id)): Path<(String, Uuid)>,
Json(JobStatsRequest { from_timestamp, to_timestamp, timeseries_max_datapoints }): Json<
JobStatsRequest,
>,
) -> error::JsonResult<JobStatsResponse> {
let records = sqlx::query_as::<_, JobStatsRecord>(
"SELECT * FROM job_stats where workspace_id = $1 and job_id = $2",
)
.bind(w_id)
.bind(job_id)
.fetch_all(&db)
.await?;
let mut metrics_metadata: Vec<MetricsMetadata> = vec![];
let mut scalar_metrics: Vec<ScalarMetric> = vec![];
let mut timeseries_metrics: Vec<TimeseriesMetric> = vec![];
for record in records {
let metric_id = record.metric_id;
match record.metric_kind {
MetricKind::ScalarInt => {
let value = record.scalar_int.unwrap_or_default() as f64;
scalar_metrics.push(ScalarMetric { metric_id: metric_id.clone(), value });
}
MetricKind::ScalarFloat => {
let value = record.scalar_float.unwrap_or_default() as f64;
scalar_metrics.push(ScalarMetric { metric_id: metric_id.clone(), value });
}
MetricKind::TimeseriesInt => {
if record.timestamps.clone().unwrap_or_default().len()
!= record.timeseries_int.clone().unwrap_or_default().len()
{
tracing::warn!("Timeseries metric {} has an invalid shape. It doesn't have one timestamp per measurement. (timestamps: {:?}, measurements: {:?})", metric_id, record.timestamps, record.timeseries_int)
}
let (timestamps, timeseries_int) = timeseries_sample(
from_timestamp,
to_timestamp,
timeseries_max_datapoints,
record.timestamps.unwrap_or_default(),
record.timeseries_int.unwrap_or_default(),
);
let mut values: Vec<DataPoint> = vec![];
for (idx, value) in timeseries_int.iter().enumerate() {
values.push(DataPoint {
timestamp: timestamps[idx],
value: value.to_owned() as f64,
});
}
timeseries_metrics.push(TimeseriesMetric { metric_id: metric_id.clone(), values });
}
MetricKind::TimeseriesFloat => {
if record.timestamps.clone().unwrap_or_default().len()
!= record.timeseries_int.clone().unwrap_or_default().len()
{
tracing::warn!("Timeseries metric {} has an invalid shape. It doesn't have one timestamp per measurement. (timestamps: {:?}, measurements: {:?})", metric_id, record.timestamps, record.timeseries_float)
}
let (timestamps, timeseries_float) = timeseries_sample(
from_timestamp,
to_timestamp,
timeseries_max_datapoints,
record.timestamps.unwrap_or_default(),
record.timeseries_float.unwrap_or_default(),
);
let mut values: Vec<DataPoint> = vec![];
for (idx, value) in timeseries_float.iter().enumerate() {
values.push(DataPoint {
timestamp: timestamps[idx],
value: value.to_owned() as f64,
});
}
timeseries_metrics.push(TimeseriesMetric { metric_id: metric_id.clone(), values });
}
};
metrics_metadata.push(MetricsMetadata { id: metric_id, name: record.metric_name });
}
let response = JobStatsResponse { metrics_metadata, scalar_metrics, timeseries_metrics };
Ok(Json(response))
}
fn timeseries_sample<T: Copy>(
from: Option<chrono::DateTime<chrono::Utc>>,
to: Option<chrono::DateTime<chrono::Utc>>,
_datapoints: Option<u32>,
timestamps: Vec<chrono::DateTime<chrono::Utc>>,
values: Vec<T>,
) -> (Vec<chrono::DateTime<chrono::Utc>>, Vec<T>) {
if timestamps.len() != values.len() {
tracing::warn!("Timeseries metric has an invalid shape. It doesn't have one timestamp per measurement. (timestamps: {:?}, measurements: {:?})", timestamps.len(), values.len());
return (vec![], vec![]);
}
let mut filtered_timestamp: Vec<chrono::DateTime<chrono::Utc>> = vec![];
let mut filtered_values: Vec<T> = vec![];
for (idx, timestamp) in timestamps.iter().enumerate() {
if *timestamp > from.unwrap_or(chrono::DateTime::<chrono::Utc>::MIN_UTC)
&& *timestamp < to.unwrap_or(chrono::DateTime::<chrono::Utc>::MAX_UTC)
{
filtered_timestamp.push(timestamps[idx]);
filtered_values.push(values[idx]);
}
}
// TODO: implement sampling
return (filtered_timestamp, filtered_values);
}
+5 -5
View File
@@ -10,6 +10,7 @@ use axum::http::HeaderValue;
use serde_json::value::RawValue;
use std::collections::HashMap;
use windmill_common::flow_status::RestartedFrom;
use windmill_common::variables::get_workspace_key;
use crate::db::ApiAuthed;
@@ -17,7 +18,6 @@ use crate::{
db::DB,
users::{check_scopes, require_owner_of_path, OptAuthed},
utils::require_super_admin,
variables::get_workspace_key,
};
use anyhow::Context;
use axum::{
@@ -2826,14 +2826,14 @@ pub struct JobUpdate {
async fn get_job_update(
Extension(db): Extension<DB>,
Path((w_id, id)): Path<(String, Uuid)>,
Path((w_id, job_id)): Path<(String, Uuid)>,
Query(JobUpdateQuery { running, log_offset }): Query<JobUpdateQuery>,
) -> error::JsonResult<JobUpdate> {
let record = sqlx::query!(
"SELECT running, substr(logs, $1) as logs, mem_peak FROM queue WHERE workspace_id = $2 AND id = $3",
log_offset,
&w_id,
&id
&job_id
)
.fetch_optional(&db)
.await?;
@@ -2855,11 +2855,11 @@ async fn get_job_update(
$3",
log_offset,
&w_id,
&id
&job_id
)
.fetch_optional(&db)
.await?;
let logs = not_found_if_none(logs, "Job Update", id.to_string())?;
let logs = not_found_if_none(logs, "Job Update", job_id.to_string())?;
Ok(Json(JobUpdate {
running: Some(false),
completed: Some(true),
+2 -1
View File
@@ -49,7 +49,6 @@ mod audit;
mod capture;
mod configs;
mod db;
mod deployment_metadata_helpers;
mod drafts;
pub mod ee;
pub mod embeddings;
@@ -61,6 +60,7 @@ mod groups;
mod inputs;
mod integration;
pub mod job_helpers;
pub mod job_metrics;
pub mod jobs;
pub mod oauth2;
mod openai;
@@ -198,6 +198,7 @@ pub async fn run_server(
.nest("/folders", folders::workspaced_service())
.nest("/groups", groups::workspaced_service())
.nest("/inputs", inputs::workspaced_service())
.nest("/job_metrics", job_metrics::workspaced_service())
.nest("/job_helpers", job_helpers::workspaced_service())
.nest("/jobs", jobs::workspaced_service())
.nest("/oauth", oauth2::workspaced_service())
+4 -9
View File
@@ -38,16 +38,13 @@ use windmill_common::jobs::JobPayload;
use windmill_common::more_serde::maybe_number_opt;
use windmill_common::users::username_to_permissioned_as;
use windmill_common::utils::{not_found_if_none, now_from_db};
use windmill_common::variables::build_crypt;
use crate::db::ApiAuthed;
use crate::saml::SamlSsoLogin;
use crate::users::{login_externally, LoginUserInfo};
use crate::webhook_util::{InstanceEvent, WebhookShared};
use crate::{
db::DB,
variables::{build_crypt, encrypt},
workspaces::WorkspaceSettings,
};
use crate::{db::DB, variables::encrypt, workspaces::WorkspaceSettings};
use crate::{BASE_URL, HTTP_CLIENT, IS_SECURE, OAUTH_CLIENTS, SLACK_SIGNING_SECRET};
use windmill_common::error::{self, to_anyhow, Error};
use windmill_common::oauth2::*;
@@ -385,7 +382,6 @@ async fn connect(
#[derive(Deserialize)]
struct CreateAccount {
client: String,
owner: String,
refresh_token: Option<String>,
expires_in: i64,
}
@@ -399,11 +395,10 @@ async fn create_account(
let mut tx = user_db.begin(&authed).await?;
let id = sqlx::query_scalar!(
"INSERT INTO account (workspace_id, client, owner, expires_at, refresh_token) VALUES ($1, \
$2, $3, now() + ($4 || ' seconds')::interval, $5) RETURNING id",
"INSERT INTO account (workspace_id, client, expires_at, refresh_token) VALUES ($1, \
$2, now() + ($3 || ' seconds')::interval, $4) RETURNING id",
w_id,
payload.client,
payload.owner,
payload.expires_in.to_string(),
payload.refresh_token
)
+4 -2
View File
@@ -2,7 +2,6 @@ use std::collections::HashMap;
use crate::{
db::{ApiAuthed, DB},
variables::build_crypt,
HTTP_CLIENT,
};
@@ -17,7 +16,10 @@ use magic_crypt::MagicCryptTrait;
use quick_cache::sync::Cache;
use serde_json::value::RawValue;
use windmill_audit::{audit_log, ActionKind};
use windmill_common::error::{to_anyhow, Error};
use windmill_common::{
error::{to_anyhow, Error},
variables::build_crypt,
};
use serde::Deserialize;
+32 -4
View File
@@ -54,6 +54,8 @@ pub struct NewSchedule {
pub path: String,
pub schedule: String,
pub timezone: String,
pub summary: Option<String>,
pub no_flow_overlap: Option<bool>,
pub script_path: String,
pub is_flow: bool,
pub args: Option<serde_json::Value>,
@@ -66,6 +68,8 @@ pub struct NewSchedule {
pub on_recovery_times: Option<i32>,
pub on_recovery_extra_args: Option<serde_json::Value>,
pub ws_error_handler_muted: Option<bool>,
pub retry: Option<serde_json::Value>,
pub tag: Option<String>,
}
#[derive(Serialize, Deserialize)]
@@ -150,7 +154,12 @@ async fn create_schedule(
let schedule = sqlx::query_as!(
Schedule,
"INSERT INTO schedule (workspace_id, path, schedule, timezone, edited_by, script_path, \
is_flow, args, enabled, email, on_failure, on_failure_times, on_failure_exact, on_failure_extra_args, on_recovery, on_recovery_times, on_recovery_extra_args, ws_error_handler_muted) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) RETURNING *",
is_flow, args, enabled, email, on_failure, on_failure_times, on_failure_exact, \
on_failure_extra_args, on_recovery, on_recovery_times, on_recovery_extra_args, \
ws_error_handler_muted, retry, summary, no_flow_overlap, tag \
) VALUES ( \
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22 \
) RETURNING *",
w_id,
ns.path,
ns.schedule,
@@ -169,6 +178,10 @@ async fn create_schedule(
ns.on_recovery_times,
ns.on_recovery_extra_args,
ns.ws_error_handler_muted.unwrap_or(false),
ns.retry,
ns.summary,
ns.no_flow_overlap.unwrap_or(false),
ns.tag,
)
.fetch_one(&mut tx)
.await
@@ -220,8 +233,11 @@ async fn edit_schedule(
clear_schedule(tx.transaction_mut(), path, &w_id).await?;
let schedule = sqlx::query_as!(
Schedule,
"UPDATE schedule SET schedule = $1, timezone = $2, args = $3, on_failure = $4, on_failure_times = $5, on_failure_exact = $6, on_failure_extra_args = $7, on_recovery = $8, on_recovery_times = $9, on_recovery_extra_args = $10, ws_error_handler_muted = $11
WHERE path = $12 AND workspace_id = $13 RETURNING *",
"UPDATE schedule SET schedule = $1, timezone = $2, args = $3, on_failure = $4, on_failure_times = $5, \
on_failure_exact = $6, on_failure_extra_args = $7, on_recovery = $8, on_recovery_times = $9, \
on_recovery_extra_args = $10, ws_error_handler_muted = $11, retry = $12, summary = $13, \
no_flow_overlap = $14, tag = $15
WHERE path = $16 AND workspace_id = $17 RETURNING *",
es.schedule,
es.timezone,
es.args,
@@ -233,6 +249,10 @@ async fn edit_schedule(
es.on_recovery_times,
es.on_recovery_extra_args,
es.ws_error_handler_muted.unwrap_or(false),
es.retry,
es.summary,
es.no_flow_overlap.unwrap_or(false),
es.tag,
path,
w_id,
)
@@ -324,7 +344,11 @@ pub struct ScheduleWJobs {
pub on_recovery_times: Option<i32>,
pub on_recovery_extra_args: Option<serde_json::Value>,
pub ws_error_handler_muted: bool,
pub retry: Option<serde_json::Value>,
pub jobs: Option<Vec<serde_json::Value>>,
pub summary: Option<String>,
pub no_flow_overlap: bool,
pub tag: Option<String>,
}
async fn list_schedule_with_jobs(
@@ -337,7 +361,7 @@ async fn list_schedule_with_jobs(
let (per_page, offset) = paginate(pagination);
let rows = sqlx::query_as!(ScheduleWJobs,
"SELECT schedule.*, t.jobs FROM schedule, LATERAL ( SELECT ARRAY (SELECT json_build_object('id', id, 'success', success, 'duration_ms', duration_ms) FROM completed_job WHERE
completed_job.schedule_path = schedule.path AND completed_job.workspace_id = $1 AND parent_job IS NULL ORDER BY started_at DESC LIMIT 20) AS jobs ) t
completed_job.schedule_path = schedule.path AND completed_job.workspace_id = $1 AND parent_job IS NULL AND is_skipped = False ORDER BY started_at DESC LIMIT 20) AS jobs ) t
WHERE schedule.workspace_id = $1 ORDER BY schedule.edited_at desc LIMIT $2 OFFSET $3",
w_id,
per_page as i64,
@@ -662,6 +686,7 @@ pub struct EditSchedule {
pub schedule: String,
pub timezone: String,
pub args: Option<serde_json::Value>,
pub summary: Option<String>,
pub on_failure: Option<String>,
pub on_failure_times: Option<i32>,
pub on_failure_exact: Option<bool>,
@@ -670,6 +695,9 @@ pub struct EditSchedule {
pub on_recovery_times: Option<i32>,
pub on_recovery_extra_args: Option<serde_json::Value>,
pub ws_error_handler_muted: Option<bool>,
pub retry: Option<serde_json::Value>,
pub no_flow_overlap: Option<bool>,
pub tag: Option<String>,
}
pub async fn clear_schedule<'c>(
+85 -40
View File
@@ -8,7 +8,6 @@
use crate::{
db::{ApiAuthed, DB},
deployment_metadata_helpers,
schedule::clear_schedule,
users::{maybe_refresh_folders, require_owner_of_path, AuthCache},
webhook_util::{WebhookMessage, WebhookShared},
@@ -28,7 +27,7 @@ use sql_builder::prelude::*;
use sql_builder::SqlBuilder;
use sqlx::{FromRow, Postgres, Transaction};
use std::{
collections::hash_map::DefaultHasher,
collections::{hash_map::DefaultHasher, HashMap},
hash::{Hash, Hasher},
sync::Arc,
};
@@ -47,9 +46,8 @@ use windmill_common::{
not_found_if_none, paginate, query_elems_from_hub, require_admin, Pagination, StripPath,
},
};
use windmill_queue::{
self, schedule::push_scheduled_job, PushArgs, PushIsolationLevel, QueueTransaction,
};
use windmill_git_sync::{handle_deployment_metadata, DeployedObject};
use windmill_queue::{self, schedule::push_scheduled_job, PushIsolationLevel, QueueTransaction};
const MAX_HASH_HISTORY_LENGTH_STORED: usize = 20;
@@ -325,7 +323,7 @@ async fn create_script(
let script_path = ns.path.clone();
let hash = ScriptHash(hash_script(&ns));
let authed = maybe_refresh_folders(&ns.path, &w_id, authed, &db).await;
let mut tx: QueueTransaction<'_, _> = (rsmq, user_db.begin(&authed).await?).into();
let mut tx: QueueTransaction<'_, _> = (rsmq.clone(), user_db.begin(&authed).await?).into();
if sqlx::query_scalar!(
"SELECT 1 FROM script WHERE hash = $1 AND workspace_id = $2",
@@ -509,7 +507,8 @@ async fn create_script(
.execute(&mut tx)
.await?;
if let Some(p_path) = parent_hashes_and_perms.as_ref().map(|x| x.p_path.clone()) {
let p_path_opt = parent_hashes_and_perms.as_ref().map(|x| x.p_path.clone());
if let Some(ref p_path) = p_path_opt {
sqlx::query!(
"DELETE FROM draft WHERE path = $1 AND workspace_id = $2 AND typ = 'script'",
p_path,
@@ -604,37 +603,37 @@ async fn create_script(
);
}
let mut tx = PushIsolationLevel::Transaction(tx);
let permissioned_as = username_to_permissioned_as(&authed.username);
if needs_lock_gen {
let dependencies = match ns.language {
ScriptLang::Python3 => {
windmill_parser_py_imports::parse_python_imports(&ns.content, &w_id, &ns.path, &db)
.await?
.join("\n")
}
_ => ns.content,
};
let tag = if ns.dedicated_worker.is_some_and(|x| x) {
Some(format!("{}:{}", &w_id, &ns.path,))
} else {
ns.tag
};
let mut args: HashMap<String, serde_json::Value> = HashMap::new();
if let Some(dm) = ns.deployment_message {
args.insert("deployment_message".to_string(), json!(dm));
}
if let Some(ref p_path) = p_path_opt {
args.insert("parent_path".to_string(), json!(p_path));
}
let tx = PushIsolationLevel::Transaction(tx);
let (_, new_tx) = windmill_queue::push(
&db,
tx,
&w_id,
JobPayload::Dependencies {
hash,
dependencies,
language: ns.language,
path: ns.path,
dedicated_worker: ns.dedicated_worker,
},
PushArgs::empty(),
args,
&authed.username,
&authed.email,
username_to_permissioned_as(&authed.username),
permissioned_as,
None,
None,
None,
@@ -650,26 +649,24 @@ async fn create_script(
None,
)
.await?;
tx = PushIsolationLevel::Transaction(new_tx);
}
tx = deployment_metadata_helpers::handle_deployment_metadata(
tx,
&authed,
&db,
&w_id,
deployment_metadata_helpers::DeployedObject::Script { hash: hash, path: script_path },
ns.deployment_message,
)
.await?;
match tx {
PushIsolationLevel::Transaction(tx) => tx.commit().await?,
_ => {
return Err(Error::InternalErr(
"Expected a transaction here".to_string(),
));
}
new_tx.commit().await?;
} else {
handle_deployment_metadata(
&authed.email,
&authed.username,
&db,
&w_id,
DeployedObject::Script {
hash: hash.clone(),
path: script_path.clone(),
parent_path: p_path_opt,
},
ns.deployment_message,
rsmq,
false,
)
.await?;
tx.commit().await?;
}
Ok((StatusCode::CREATED, format!("{}", hash)))
@@ -1016,6 +1013,7 @@ async fn archive_script_by_path(
Extension(webhook): Extension<WebhookShared>,
Extension(user_db): Extension<UserDB>,
Extension(db): Extension<DB>,
Extension(rsmq): Extension<Option<rsmq_async::MultiplexedRsmq>>,
Path((w_id, path)): Path<(String, StripPath)>,
) -> Result<()> {
let path = path.to_path();
@@ -1042,6 +1040,23 @@ async fn archive_script_by_path(
)
.await?;
tx.commit().await?;
handle_deployment_metadata(
&authed.email,
&authed.username,
&db,
&w_id,
DeployedObject::Script {
hash: ScriptHash(0), // dummy hash as it will not get inserted in db
path: path.to_string(),
parent_path: Some(path.to_string()),
},
Some(format!("Script '{}' archived", path)),
rsmq,
true,
)
.await?;
webhook.send_message(
w_id.clone(),
WebhookMessage::DeleteScript { workspace: w_id, hash: hash.to_string() },
@@ -1131,6 +1146,7 @@ async fn delete_script_by_path(
Extension(user_db): Extension<UserDB>,
Extension(webhook): Extension<WebhookShared>,
Extension(db): Extension<DB>,
Extension(rsmq): Extension<Option<rsmq_async::MultiplexedRsmq>>,
Path((w_id, path)): Path<(String, StripPath)>,
) -> JsonResult<String> {
let path = path.to_path();
@@ -1184,6 +1200,35 @@ async fn delete_script_by_path(
.await?;
tx.commit().await?;
handle_deployment_metadata(
&authed.email,
&authed.username,
&db,
&w_id,
DeployedObject::Script {
hash: ScriptHash(0), // Temporary value as it will get removed right after
path: path.to_string(),
parent_path: Some(path.to_string()),
},
Some(format!("Script '{}' deleted", path)),
rsmq,
true,
)
.await?;
sqlx::query!(
"DELETE FROM deployment_metadata WHERE path = $1 AND workspace_id = $2 AND script_hash IS NOT NULL",
path,
w_id
)
.execute(&db)
.await
.map_err(|e| {
Error::InternalErr(format!(
"error deleting deployment metadata for script with path {path} in workspace {w_id}: {e}"
))
})?;
webhook.send_message(
w_id.clone(),
WebhookMessage::DeleteScriptPath { workspace: w_id, path: path.to_string() },
+49 -74
View File
@@ -25,7 +25,9 @@ use windmill_common::{
db::UserDB,
error::{Error, JsonResult, Result},
utils::{not_found_if_none, StripPath},
variables::{get_reserved_variables, ContextualVariable, CreateVariable, ListableVariable},
variables::{
build_crypt, get_reserved_variables, ContextualVariable, CreateVariable, ListableVariable,
},
};
use lazy_static::lazy_static;
@@ -192,57 +194,6 @@ async fn get_value(
.map(Json);
}
pub async fn get_value_internal<'c>(
mut tx: Transaction<'c, Postgres>,
db: &DB,
w_id: &str,
path: &str,
username: &str,
) -> Result<String> {
let variable_o = sqlx::query!(
"SELECT value, account, (now() > account.expires_at) as is_expired, is_secret, path from variable
LEFT JOIN account ON variable.account = account.id WHERE variable.path = $1 AND variable.workspace_id = $2", path, w_id
)
.fetch_optional(&mut *tx)
.await?;
let variable = if let Some(variable) = variable_o {
variable
} else {
explain_variable_perm_error(path, w_id, db).await?;
unreachable!()
};
let r = if variable.is_secret {
audit_log(
&mut *tx,
username,
"variables.decrypt_secret",
ActionKind::Execute,
&w_id,
Some(&variable.path),
None,
)
.await?;
let value = variable.value;
if variable.is_expired.unwrap_or(false) && variable.account.is_some() {
_refresh_token(tx, &variable.path, &w_id, variable.account.unwrap()).await?
} else if !value.is_empty() {
let mc = build_crypt(&mut tx, &w_id).await?;
tx.commit().await?;
mc.decrypt_base64_to_string(value)
.map_err(|e| Error::InternalErr(e.to_string()))?
} else {
"".to_string()
}
} else {
variable.value
};
Ok(r)
}
async fn explain_variable_perm_error(
path: &str,
w_id: &str,
@@ -604,31 +555,55 @@ fn replace_path(v: serde_json::Value, path: &str, npath: &str) -> Value {
}
}
pub async fn build_crypt<'c>(
db: &mut Transaction<'c, Postgres>,
pub async fn get_value_internal<'c>(
mut tx: Transaction<'c, Postgres>,
db: &DB,
w_id: &str,
) -> Result<MagicCrypt256> {
let key = get_workspace_key(w_id, db).await?;
let crypt_key = if let Some(ref salt) = SECRET_SALT.as_ref() {
format!("{}{}", key, salt)
} else {
key
};
Ok(magic_crypt::new_magic_crypt!(crypt_key, 256))
}
pub async fn get_workspace_key<'c>(
w_id: &str,
db: &mut Transaction<'c, Postgres>,
path: &str,
username: &str,
) -> Result<String> {
let key = sqlx::query_scalar!(
"SELECT key FROM workspace_key WHERE workspace_id = $1 AND kind = 'cloud'",
w_id
let variable_o = sqlx::query!(
"SELECT value, account, (now() > account.expires_at) as is_expired, is_secret, path from variable
LEFT JOIN account ON variable.account = account.id WHERE variable.path = $1 AND variable.workspace_id = $2", path, w_id
)
.fetch_one(&mut **db)
.await
.map_err(|e| Error::InternalErr(format!("fetching workspace key: {e}")))?;
Ok(key)
.fetch_optional(&mut *tx)
.await?;
let variable = if let Some(variable) = variable_o {
variable
} else {
explain_variable_perm_error(path, w_id, db).await?;
unreachable!()
};
let r = if variable.is_secret {
audit_log(
&mut *tx,
username,
"variables.decrypt_secret",
ActionKind::Execute,
&w_id,
Some(&variable.path),
None,
)
.await?;
let value = variable.value;
if variable.is_expired.unwrap_or(false) && variable.account.is_some() {
_refresh_token(tx, &variable.path, &w_id, variable.account.unwrap()).await?
} else if !value.is_empty() {
let mc = build_crypt(&mut tx, &w_id).await?;
tx.commit().await?;
mc.decrypt_base64_to_string(value)
.map_err(|e| Error::InternalErr(e.to_string()))?
} else {
"".to_string()
}
} else {
variable.value
};
Ok(r)
}
pub fn encrypt(mc: &MagicCrypt256, value: &str) -> String {
+44 -31
View File
@@ -6,7 +6,7 @@
* LICENSE-AGPL for a copy of the license.
*/
#[cfg(feature = "enterprise")]
#[cfg(feature = "stripe")]
use std::str::FromStr;
use crate::db::ApiAuthed;
@@ -18,10 +18,9 @@ use crate::{
resources::{Resource, ResourceType},
users::{send_email_if_possible, WorkspaceInvite, VALID_USERNAME},
utils::require_super_admin,
variables::build_crypt,
webhook_util::{InstanceEvent, WebhookShared},
};
#[cfg(feature = "enterprise")]
#[cfg(feature = "stripe")]
use axum::response::Redirect;
use axum::{
body::StreamBody,
@@ -32,17 +31,20 @@ use axum::{
Json, Router,
};
use chrono::Utc;
#[cfg(feature = "enterprise")]
#[cfg(feature = "stripe")]
use chrono::{Datelike, TimeZone, Timelike};
use magic_crypt::MagicCryptTrait;
#[cfg(feature = "enterprise")]
#[cfg(feature = "stripe")]
use stripe::CustomerId;
use uuid::Uuid;
use windmill_audit::{audit_log, ActionKind};
use windmill_common::db::UserDB;
use windmill_common::s3_helpers::LargeFileStorage;
use windmill_common::schedule::Schedule;
use windmill_common::users::username_to_permissioned_as;
use windmill_common::variables::build_crypt;
use windmill_common::worker::CLOUD_HOSTED;
use windmill_common::workspaces::WorkspaceGitRepo;
use windmill_common::{
error::{to_anyhow, Error, JsonResult, Result},
flows::Flow,
@@ -81,7 +83,6 @@ pub fn workspaced_service() -> Router {
.route("/edit_deploy_to", post(edit_deploy_to))
.route("/tarball", get(tarball_workspace))
.route("/is_premium", get(is_premium))
.route("/premium_info", get(premium_info))
.route("/edit_copilot_config", post(edit_copilot_config))
.route("/get_copilot_info", get(get_copilot_info))
.route("/edit_error_handler", post(edit_error_handler))
@@ -92,7 +93,7 @@ pub fn workspaced_service() -> Router {
.route("/edit_git_sync_config", post(edit_git_sync_config))
.route("/leave", post(leave_workspace));
#[cfg(feature = "enterprise")]
#[cfg(feature = "stripe")]
{
if STRIPE_KEY.is_none() {
return router;
@@ -100,12 +101,13 @@ pub fn workspaced_service() -> Router {
tracing::info!("stripe enabled");
return router
.route("/premium_info", get(premium_info))
.route("/checkout", get(stripe_checkout))
.route("/billing_portal", get(stripe_portal));
}
}
#[cfg(not(feature = "enterprise"))]
#[cfg(not(feature = "stripe"))]
router
}
pub fn global_service() -> Router {
@@ -281,18 +283,6 @@ pub struct EditErrorHandler {
pub error_handler_muted_on_cancel: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type")]
pub enum LargeFileStorage {
S3Storage(S3Storage),
// TODO: Add a filesystem type here in the future if needed
}
#[derive(Serialize, Deserialize, Debug)]
pub struct S3Storage {
pub s3_resource_path: String,
}
async fn list_pending_invites(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
@@ -334,6 +324,7 @@ pub struct PremiumWorkspaceInfo {
pub usage: Option<i32>,
pub seats: Option<i32>,
}
#[cfg(feature = "stripe")]
async fn premium_info(
authed: ApiAuthed,
Extension(db): Extension<DB>,
@@ -405,14 +396,14 @@ async fn premium_info(
Ok(Json(result))
}
#[cfg(feature = "enterprise")]
#[cfg(feature = "stripe")]
#[derive(Deserialize)]
struct PlanQuery {
plan: String,
seats: Option<i32>,
}
#[cfg(feature = "enterprise")]
#[cfg(feature = "stripe")]
async fn stripe_checkout(
authed: ApiAuthed,
Path(w_id): Path<String>,
@@ -501,7 +492,7 @@ async fn stripe_checkout(
}
}
#[cfg(feature = "enterprise")]
#[cfg(feature = "stripe")]
async fn stripe_portal(
authed: ApiAuthed,
Path(w_id): Path<String>,
@@ -1053,13 +1044,7 @@ async fn edit_large_file_storage_config(
#[derive(Deserialize)]
struct EditGitSyncConfig {
git_sync_settings: Option<WorkspaceGitRepo>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct WorkspaceGitRepo {
pub script_path: String,
pub git_repo_resource_path: String,
git_sync_settings: Option<Vec<WorkspaceGitRepo>>,
}
async fn edit_git_sync_config(
@@ -1093,7 +1078,7 @@ async fn edit_git_sync_config(
.await?;
if let Some(git_sync_settings) = new_config.git_sync_settings {
let serialized_config = serde_json::to_value::<WorkspaceGitRepo>(git_sync_settings)
let serialized_config = serde_json::to_value::<Vec<WorkspaceGitRepo>>(git_sync_settings)
.map_err(|err| Error::InternalErr(err.to_string()))?;
sqlx::query!(
@@ -1515,6 +1500,9 @@ async fn delete_workspace(
let mut tx = db.begin().await?;
require_super_admin(&db, &email).await?;
sqlx::query!("DELETE FROM dependency_map WHERE workspace_id = $1", &w_id)
.execute(&mut *tx)
.await?;
sqlx::query!("DELETE FROM queue WHERE workspace_id = $1", &w_id)
.execute(&mut *tx)
.await?;
@@ -1533,6 +1521,12 @@ async fn delete_workspace(
sqlx::query!("DELETE FROM app WHERE workspace_id = $1", &w_id)
.execute(&mut *tx)
.await?;
sqlx::query!("DELETE FROM raw_app WHERE workspace_id = $1", &w_id)
.execute(&mut *tx)
.await?;
sqlx::query!("DELETE FROM input WHERE workspace_id = $1", &w_id)
.execute(&mut *tx)
.await?;
sqlx::query!("DELETE FROM variable WHERE workspace_id = $1", &w_id)
.execute(&mut *tx)
.await?;
@@ -1548,6 +1542,17 @@ async fn delete_workspace(
.execute(&mut *tx)
.await?;
sqlx::query!("DELETE FROM job_stats WHERE workspace_id = $1", &w_id)
.execute(&mut *tx)
.await?;
sqlx::query!(
"DELETE FROM deployment_metadata WHERE workspace_id = $1",
&w_id
)
.execute(&mut *tx)
.await?;
sqlx::query!("DELETE FROM usr WHERE workspace_id = $1", &w_id)
.execute(&mut *tx)
.await?;
@@ -1575,6 +1580,10 @@ async fn delete_workspace(
.execute(&mut *tx)
.await?;
sqlx::query!("DELETE FROM account WHERE workspace_id = $1", &w_id)
.execute(&mut *tx)
.await?;
sqlx::query!("DELETE FROM workspace_key WHERE workspace_id = $1", &w_id)
.execute(&mut *tx)
.await?;
@@ -1586,6 +1595,10 @@ async fn delete_workspace(
.execute(&mut *tx)
.await?;
sqlx::query!("DELETE FROM token WHERE workspace_id = $1", &w_id)
.execute(&mut *tx)
.await?;
sqlx::query!("DELETE FROM workspace WHERE id = $1", &w_id)
.execute(&mut *tx)
.await?;

Some files were not shown because too many files have changed in this diff Show More