Compare commits

...

63 Commits

Author SHA1 Message Date
Ruben Fiszel
0a585eccd2 chore(main): release 1.167.0 (#2227)
* chore(main): release 1.167.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-09-04 02:39:09 +02:00
Ruben Fiszel
d09788c216 fix(cli): prioritize correctly content file to resolve for ts types 2023-09-04 02:38:39 +02:00
Ruben Fiszel
2906d535a1 fix(cli): prioritize correctly content file to resolve for ts types 2023-09-04 02:34:51 +02:00
Ruben Fiszel
dff1cd9a64 feat: submit result in background thread (unify architecture for dedicated worker) (#2226)
* merge

* all

* all
2023-09-04 01:56:23 +02:00
Ruben Fiszel
76edfd6d9e chore(main): release 1.166.1 (#2225)
* chore(main): release 1.166.1

* Apply automatic changes

* Update CHANGELOG.md

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-09-03 18:19:42 +02:00
Ruben Fiszel
b3d7661114 fix initialization 2023-09-03 18:12:44 +02:00
Ruben Fiszel
12ef348b7f fix initialization 2023-09-03 18:05:23 +02:00
Ruben Fiszel
b0ed0f9648 fix: fix setting is ready for s3 workers 2023-09-03 16:52:44 +02:00
Ruben Fiszel
a4d1c987d4 fix: fix setting is ready for s3 workers 2023-09-03 16:47:05 +02:00
Ruben Fiszel
8f8806a1b9 fix: fix setting is ready for s3 workers 2023-09-03 16:18:23 +02:00
Ruben Fiszel
37ec5107c4 improve webhook curl 2023-09-03 14:55:52 +02:00
Ruben Fiszel
6417c07aa3 chore(main): release 1.166.0 (#2211)
* chore(main): release 1.166.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-09-03 14:03:25 +02:00
Ruben Fiszel
f912f1de86 fix: update wmill python generator thus updating windmill-api 2023-09-03 14:01:51 +02:00
Ruben Fiszel
b35ffd435d fix: fix datetime handling for python 2023-09-03 11:19:22 +02:00
Ruben Fiszel
ffcb977c47 update benchmark action 2023-09-03 10:37:29 +02:00
Ruben Fiszel
0a20c683d2 update benchmark action 2023-09-03 10:35:56 +02:00
Ruben Fiszel
65f193e9e7 update benchmark action 2023-09-03 10:31:48 +02:00
Ruben Fiszel
073b8d7319 update benchmark action 2023-09-03 10:29:49 +02:00
Ruben Fiszel
4316214f32 update benchmark action 2023-09-03 10:23:50 +02:00
Ruben Fiszel
135444d228 add regular benchmarks 2023-09-03 09:57:25 +02:00
Ruben Fiszel
069e2d18d5 perf: improve queue performance (#2222)
* benchs

* progress

* prog

* progress

* progress

* progress

* update

* progress

* progress

* progress

* progress

* progress

* all

* all

* improve queries

* improve queries

* rm imv

* all

* all
2023-09-03 09:44:16 +02:00
Ruben Fiszel
e31d843bf9 bump deno to 1.36.3 2023-09-01 14:53:17 +02:00
Ruben Fiszel
719a7b11da fix: see run detail in a new tab 2023-09-01 09:32:05 +02:00
Faton Ramadani
77f8eac21e feat(frontend): App stepper debug (#2202)
* fix(frontend): Fix overflow issue

* feat(frontend): Add Stepper debug + add last action output

* feat(frontend): revert

* feat(frontend): correctly handled debug mode

* fix(frontend): PR comments
2023-09-01 08:03:45 +02:00
Faton Ramadani
9787edb67c fix(frontend): Fix App Modal z-index (#2210)
* fix(frontend): Fix App Modal z-index

* fix(frontend): fix build
2023-09-01 08:01:26 +02:00
Ruben Fiszel
a34b91b386 chore(main): release 1.165.0 (#2207)
* chore(main): release 1.165.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-09-01 00:05:37 +02:00
Ruben Fiszel
ada88a2bf9 feat: improve queue performance when queue grows large 2023-08-31 23:41:04 +02:00
Ruben Fiszel
41107c7cfa feat: support partial go dependency pinning 2023-08-31 22:12:47 +02:00
Ruben Fiszel
16fa99a4ea clarify cache usage for scripts/flows 2023-08-31 15:19:01 +02:00
Ruben Fiszel
d311d76557 fix: uniformize that all job links specify the workspace 2023-08-31 12:55:28 +02:00
Ruben Fiszel
1998c2c6e0 chore(main): release 1.164.0 (#2205)
* chore(main): release 1.164.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-08-31 12:30:37 +02:00
Ruben Fiszel
276cd6dac3 feat: add workspace variable to worker tag 2023-08-31 11:00:57 +02:00
Bryan
bb749c14f8 fix(frontend): allow using Docker in Flow (#2201) 2023-08-31 08:52:05 +02:00
Faton Ramadani
482e46e61b Fix run page navigation (#2200)
* fix(frontend): Fix overflow issue

* fix(frontend): Properly handle browser navigation

* fix(frontend): revert changes

* fix(frontend): remove debug code
2023-08-31 00:17:52 +02:00
Ruben Fiszel
00df43c68c make frontend bundlable 2023-08-30 19:31:34 +02:00
Ruben Fiszel
e780131bea reworks consts 2023-08-30 18:43:21 +02:00
Ruben Fiszel
d7580fefb9 chore(main): release 1.163.1 (#2199)
* chore(main): release 1.163.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-08-30 18:31:44 +02:00
Ruben Fiszel
11e1ecbcda fix: avoid perpetual spinning of recompute all component 2023-08-30 18:27:12 +02:00
Ruben Fiszel
200fc0720e clarify cache usage for scripts/flows 2023-08-30 17:44:40 +02:00
Ruben Fiszel
e6f56d4dec chore(main): release 1.163.0 (#2198)
* chore(main): release 1.163.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-08-30 17:26:31 +02:00
Ruben Fiszel
380267a965 nits 2023-08-30 17:26:30 +02:00
Ruben Fiszel
61df339343 fix: fix cyclical loop in apps 2023-08-30 17:12:55 +02:00
Ruben Fiszel
a002b242fc reduce barrier sync logs 2023-08-30 14:55:03 +02:00
Ruben Fiszel
cf012114c5 improve global cache to s3 panel 2023-08-30 14:09:54 +02:00
Ruben Fiszel
6200f91ecd fix build 2023-08-30 13:43:40 +02:00
Ruben Fiszel
7c5ea569a8 feat: add global cache configuration 2023-08-30 13:35:41 +02:00
Ruben Fiszel
7f7a97f009 fix premium info computation 2023-08-29 21:41:26 +02:00
Ruben Fiszel
39b30a7ca3 fix premium info computation 2023-08-29 21:27:53 +02:00
Ruben Fiszel
ffb61afc3f fix premium info computation 2023-08-29 21:23:15 +02:00
Ruben Fiszel
1b3f78f585 exclude cache and state from tarball export 2023-08-29 19:36:25 +02:00
Ruben Fiszel
1f56b27598 chore(main): release 1.162.2 (#2197)
* chore(main): release 1.162.2

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-08-29 16:45:39 +02:00
Ruben Fiszel
4704899a81 fix: fix incorrect bump 2023-08-29 16:44:05 +02:00
Ruben Fiszel
c6ff9e38da chore(main): release 1.162.1 (#2196) 2023-08-29 16:39:46 +02:00
Ruben Fiszel
6c5a8a3613 fix: fix deps incompatibilities 2023-08-29 16:38:02 +02:00
Ruben Fiszel
2e7862d84f chore(main): release 1.162.0 (#2194)
* chore(main): release 1.162.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-08-29 15:59:24 +02:00
Ruben Fiszel
bf0014c387 feat: add cache to inline scripts 2023-08-29 15:57:01 +02:00
Faton Ramadani
095969f125 feat(frontend): Filter runs by user (#2187)
* feat(frontend): Filter runs by user

* feat(frontend): Improve UI

* feat(frontend): Add filter by folder

* feat(frontend): Clean up filters

* feat(frontend): Clean up filters

* feat(frontend): add shortcut to filter by folder

* fix(frontend): Fix filters

* fix(frontend): remove logs

* fix(frontend): Fix filter by syncing

* fix(frontend): refactor all filters

* fix(frontend): refactor all filters

* fix(frontend): refactor all filters

* fix(frontend): refactor all filters

* fix(frontend): add missing tooltips

* fix(frontend): correcly set args and result filters + handle status filter
2023-08-29 15:42:56 +02:00
Ruben Fiszel
dfa9ed5c00 improve cache paths 2023-08-29 15:29:55 +02:00
Ruben Fiszel
67f868f08e fix: add setState, getState to client 2023-08-29 15:26:23 +02:00
Ruben Fiszel
81575468e7 fix get latest deployed for path 2023-08-29 15:12:53 +02:00
Ruben Fiszel
30ea354cae fix: relative imports in deno 2023-08-29 15:05:00 +02:00
Ruben Fiszel
03e48a4ca5 feat: add caching to flows and scripts (#2193)
* cachettl

* all
2023-08-29 13:00:20 +02:00
Ruben Fiszel
af2a417902 fix list state preservation across re-renders 2023-08-28 20:42:53 +02:00
140 changed files with 6564 additions and 1632 deletions

2
.github/Dockerfile vendored
View File

@@ -1,7 +1,7 @@
FROM nikolaik/python-nodejs
RUN npm install -g @apidevtools/swagger-cli
RUN pip install openapi-python-client
RUN pip install openapi-python-client==0.15.1
RUN pip install poetry

View File

@@ -4,4 +4,4 @@ RUN python3 -m pip install pipx poetry
RUN python3 -m pipx ensurepath
ENV PATH="/root/.local/bin:${PATH}"
ENV PATH="/usr/local/bin:${PATH}"
RUN pipx install openapi-python-client==0.11.6 --include-deps
RUN pipx install openapi-python-client==0.15.1 --include-deps

41
.github/workflows/benchmark.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: Run benchmarks
on:
schedule:
- cron: "0 0 */1 * *"
workflow_dispatch:
jobs:
benchmark:
runs-on: [self-hosted, new]
services:
postgres:
image: postgres
env:
POSTGRES_DB: windmill
POSTGRES_PASSWORD: changeme
options: >-
--health-cmd pg_isready --health-interval 10s --health-timeout 5s
--health-retries 5
windmill:
image: ghcr.io/windmill-labs/windmill:main
env:
DATABASE_URL: postgres://postgres:changeme@postgres:5432/windmill
options: >-
--pull always --health-interval 10s --health-timeout 5s
--health-retries 5 --health-cmd "curl
http://localhost:8000/api/version"
ports:
- 8000:8000
steps:
- uses: denoland/setup-deno@v1
with:
deno-version: v1.x
- name: benchmark
timeout-minutes: 10
run:
deno run --unstable -A
https://raw.githubusercontent.com/windmill-labs/windmill/main/benchmarks/benchmark_noop.ts
--host http://localhost:8000 -e admin@windmill.dev -p changeme -j
10000

View File

@@ -3,7 +3,10 @@ env:
IMAGE_NAME: ${{ github.repository }}
name: Build windmill:mssql
on: workflow_dispatch
on:
workflow_dispatch:
schedule:
- cron: "0 0 */4 * *"
concurrency:
group: ${{ github.ref }}-mssql

View File

@@ -1,6 +1,118 @@
# Changelog
## [1.167.0](https://github.com/windmill-labs/windmill/compare/v1.166.1...v1.167.0) (2023-09-04)
### Features
* submit result in background thread (unify architecture for dedicated worker) ([#2226](https://github.com/windmill-labs/windmill/issues/2226)) ([dff1cd9](https://github.com/windmill-labs/windmill/commit/dff1cd9a64f755f239eb57599c104c47f4d33b12))
### Bug Fixes
* **cli:** prioritize correctly content file to resolve for ts types ([2906d53](https://github.com/windmill-labs/windmill/commit/2906d535a126f4fe2cfe6dffda46e5fe841056da))
## [1.166.1](https://github.com/windmill-labs/windmill/compare/v1.166.0...v1.166.1) (2023-09-03)
### Bug Fixes
* fix setting is ready for s3 workers ([b0ed0f9](https://github.com/windmill-labs/windmill/commit/b0ed0f964843247d11ecfe586f1565589df95ff6))
## [1.166.0](https://github.com/windmill-labs/windmill/compare/v1.165.0...v1.166.0) (2023-09-03)
### Features
* **frontend:** App stepper debug ([#2202](https://github.com/windmill-labs/windmill/issues/2202)) ([77f8eac](https://github.com/windmill-labs/windmill/commit/77f8eac21e0edfa1eada617d78a498a3a6ae1dce))
### Bug Fixes
* fix datetime handling for python ([b35ffd4](https://github.com/windmill-labs/windmill/commit/b35ffd435de97ed34fcda69490abd734ea3229fa))
* **frontend:** Fix App Modal z-index ([#2210](https://github.com/windmill-labs/windmill/issues/2210)) ([9787edb](https://github.com/windmill-labs/windmill/commit/9787edb67c329265bf179fe304d00cdc1df7042e))
* see run detail in a new tab ([719a7b1](https://github.com/windmill-labs/windmill/commit/719a7b11da81f68452ba9fc22ff456fe1ddde1de))
* update wmill python generator thus updating windmill-api ([f912f1d](https://github.com/windmill-labs/windmill/commit/f912f1de86e91c5cdbc0012e2362467c4965936a))
### Performance Improvements
* improve queue performance ([#2222](https://github.com/windmill-labs/windmill/issues/2222)) ([069e2d1](https://github.com/windmill-labs/windmill/commit/069e2d18d586aa3d407e3b089d1ad94b2b838af0))
## [1.165.0](https://github.com/windmill-labs/windmill/compare/v1.164.0...v1.165.0) (2023-08-31)
### Features
* improve queue performance when queue grows large ([ada88a2](https://github.com/windmill-labs/windmill/commit/ada88a2bf94fec71187bbdb210065de43d4cd3fb))
* support partial go dependency pinning ([41107c7](https://github.com/windmill-labs/windmill/commit/41107c7cfa7b56099a9c8b08cfb16ff3cf840ff2))
### Bug Fixes
* uniformize that all job links specify the workspace ([d311d76](https://github.com/windmill-labs/windmill/commit/d311d76557432a72a5d6d7ab010aeb1fe0e599de))
## [1.164.0](https://github.com/windmill-labs/windmill/compare/v1.163.1...v1.164.0) (2023-08-31)
### Features
* add workspace variable to worker tag ([276cd6d](https://github.com/windmill-labs/windmill/commit/276cd6dac39b7cb181ac46e3edea79a3a3bcff8d))
### Bug Fixes
* **frontend:** allow using Docker in Flow ([#2201](https://github.com/windmill-labs/windmill/issues/2201)) ([bb749c1](https://github.com/windmill-labs/windmill/commit/bb749c14f877f7cb1e8642b881a00aedfeb08f7d))
## [1.163.1](https://github.com/windmill-labs/windmill/compare/v1.163.0...v1.163.1) (2023-08-30)
### Bug Fixes
* avoid perpetual spinning of recompute all component ([11e1ecb](https://github.com/windmill-labs/windmill/commit/11e1ecbcda92f5ab643b776094ef10005d51b579))
## [1.163.0](https://github.com/windmill-labs/windmill/compare/v1.162.2...v1.163.0) (2023-08-30)
### Features
* add global cache configuration ([7c5ea56](https://github.com/windmill-labs/windmill/commit/7c5ea569a8102ef052d42216e2ff8d4c3169a7a5))
### Bug Fixes
* fix cyclical loop in apps ([61df339](https://github.com/windmill-labs/windmill/commit/61df339343767e63cbe7a4e75f1fd4f848dbd7e0))
## [1.162.2](https://github.com/windmill-labs/windmill/compare/v1.162.1...v1.162.2) (2023-08-29)
### Bug Fixes
* fix incorrect bump ([4704899](https://github.com/windmill-labs/windmill/commit/4704899a81cb281b99949c934184e23b199b2ed8))
## [1.162.1](https://github.com/windmill-labs/windmill/compare/v1.162.0...v1.162.1) (2023-08-29)
### Bug Fixes
* fix deps incompatibilities ([6c5a8a3](https://github.com/windmill-labs/windmill/commit/6c5a8a3613b4608e6d2b57e7f40cd4ab2d1af9ae))
## [1.162.0](https://github.com/windmill-labs/windmill/compare/v1.161.0...v1.162.0) (2023-08-29)
### Features
* add cache to inline scripts ([bf0014c](https://github.com/windmill-labs/windmill/commit/bf0014c387361ce358d31c7cbc44a9c4c97606df))
* add caching to flows and scripts ([#2193](https://github.com/windmill-labs/windmill/issues/2193)) ([03e48a4](https://github.com/windmill-labs/windmill/commit/03e48a4ca557cd2c385988d3a935cea38bc6e81e))
* **frontend:** Filter runs by user ([#2187](https://github.com/windmill-labs/windmill/issues/2187)) ([095969f](https://github.com/windmill-labs/windmill/commit/095969f125e9186cb4f02f75e914ef9a70e3abc4))
### Bug Fixes
* add setState, getState to client ([67f868f](https://github.com/windmill-labs/windmill/commit/67f868f08ed10f3f7c185af67bff7080c339e974))
* relative imports in deno ([30ea354](https://github.com/windmill-labs/windmill/commit/30ea354cae91ea040b3112c4138a1e5f0d7ab530))
## [1.161.0](https://github.com/windmill-labs/windmill/compare/v1.160.0...v1.161.0) (2023-08-28)

View File

@@ -96,8 +96,8 @@ SHELL ["/bin/bash", "-c"]
RUN apt update -y
RUN apt install -y unzip curl
RUN [ "$TARGETPLATFORM" == "linux/arm64" ] && curl -Lsf https://github.com/LukeChannings/deno-arm64/releases/download/v1.36.2/deno-linux-arm64.zip -o deno.zip || true
RUN [ "$TARGETPLATFORM" == "linux/amd64" ] && curl -Lsf https://github.com/denoland/deno/releases/download/v1.36.2/deno-x86_64-unknown-linux-gnu.zip -o deno.zip || true
RUN [ "$TARGETPLATFORM" == "linux/arm64" ] && curl -Lsf https://github.com/LukeChannings/deno-arm64/releases/download/v1.36.3/deno-linux-arm64.zip -o deno.zip || true
RUN [ "$TARGETPLATFORM" == "linux/amd64" ] && curl -Lsf https://github.com/denoland/deno/releases/download/v1.36.3/deno-x86_64-unknown-linux-gnu.zip -o deno.zip || true
RUN unzip deno.zip && rm deno.zip

View File

@@ -85,7 +85,7 @@ https://github.com/windmill-labs/windmill/assets/122811744/0b132cd1-ee67-4505-82
shared on [WindmillHub](https://hub.windmill.dev).
![Step 3](./imgs/windmill-flow.png)
4. Build complex UI on top of your scripts and flows.
4. Build complex UIs on top of your scripts and flows.
![Step 4](./imgs/windmill-builder.png)
Scripts and flows can also be triggered by a cron schedule '_/5 _ \* \* \*' or

3
backend/.gitignore vendored
View File

@@ -1,4 +1,5 @@
target/
.env
oauth.json
windmill-api/openapi-deref.yaml
windmill-api/openapi-deref.yaml
tracing.folded

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "select tag, concurrent_limit, concurrency_time_window_s from script where hash = $1 AND workspace_id = $2",
"query": "select tag, concurrent_limit, concurrency_time_window_s, cache_ttl from script where hash = $1 AND workspace_id = $2",
"describe": {
"columns": [
{
@@ -17,6 +17,11 @@
"ordinal": 2,
"name": "concurrency_time_window_s",
"type_info": "Int4"
},
{
"ordinal": 3,
"name": "cache_ttl",
"type_info": "Int4"
}
],
"parameters": {
@@ -26,10 +31,11 @@
]
},
"nullable": [
true,
true,
true,
true
]
},
"hash": "8f0d98661f2a32659f37a123320d14288ce88c9d6e6279bdc36334410023f5e1"
"hash": "0694f4be1a7966126f20afbd9ee094088db8ce98aefdbe96144c42e38e5bd813"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT value FROM global_settings WHERE name = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "value",
"type_info": "Jsonb"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false
]
},
"hash": "1c1577b9963d907c4245a027fece57285ce64ac41a84681b32deb71e452334c1"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "VACUUM queue",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "1e232d4b978d133ba542d0abe5afaa0031f630c5336c0b805590b5ea4508ed04"
}

View File

@@ -0,0 +1,30 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COALESCE(j.min_started_at, q.min_started_at) AS min_started_at, COALESCE(completed_count, 0) AS completed_count\n FROM\n (SELECT script_path, MIN(started_at) as min_started_at, COUNT(*) as completed_count\n FROM completed_job\n WHERE script_path = $1 AND job_kind != 'dependencies' AND started_at + INTERVAL '1 MILLISECOND' * duration_ms > (now() - INTERVAL '1 second' * $2) AND workspace_id = $3 AND canceled = false\n GROUP BY script_path) as j\n FULL OUTER JOIN\n (SELECT script_path, MIN(started_at) as min_started_at\n FROM queue\n WHERE script_path = $1 AND job_kind != 'dependencies' AND running = true AND workspace_id = $3 AND canceled = false\n GROUP BY script_path) as q\n ON q.script_path = j.script_path",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "min_started_at",
"type_info": "Timestamptz"
},
{
"ordinal": 1,
"name": "completed_count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Float8",
"Text"
]
},
"nullable": [
true,
true
]
},
"hash": "330e85c6fe52355971262d3a44c2b75a95b1d042b9e0995186aa5a2d0b7ee552"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT * FROM resource WHERE workspace_id = $1",
"query": "SELECT * FROM resource WHERE workspace_id = $1 AND resource_type != 'state' AND resource_type != 'cache'",
"describe": {
"columns": [
{
@@ -48,5 +48,5 @@
false
]
},
"hash": "fa258894bd90ea6586669e5810c5c6bcb42d5e1e68fab27fb185d06962b6454a"
"hash": "45e4d13f5806122faecdb1d9ab18159555b652869a036b006f4a151e999b17b7"
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE concurrency_counter SET counter = counter - 1 WHERE concurrency_id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "467247beca4dae321ab3acc3a99f655412761157cda54bbd3e58cada3de3396c"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)",
"query": "INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)",
"describe": {
"columns": [],
"parameters": {
@@ -57,10 +57,11 @@
"Bool",
"VarcharArray",
"Int4",
"Int4",
"Int4"
]
},
"nullable": []
},
"hash": "9ee1423945740c17dedc2fc7ea7e7183380c43d0feeb615c651aa486eef3bd53"
"hash": "52dbe77fad5729e9e121609eeb6a3573e6d0e1d1c16e29b15c7ad810536e249c"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE concurrency_counter SET counter = counter - 1 WHERE concurrency_id = $1 RETURNING counter",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "counter",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false
]
},
"hash": "71879f3644dde27f2d93e5511e420579dfd2ffab72bb8480a1622b68703d4ea7"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "select hash, tag, concurrent_limit, concurrency_time_window_s from script where path = $1 AND workspace_id = $2 AND\n created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2 AND\n deleted = false AND archived = false)",
"query": "select hash, tag, concurrent_limit, concurrency_time_window_s, cache_ttl from script where path = $1 AND workspace_id = $2 AND\n created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2 AND\n deleted = false AND lock IS not NULL AND lock_error_logs IS NULL)",
"describe": {
"columns": [
{
@@ -22,6 +22,11 @@
"ordinal": 3,
"name": "concurrency_time_window_s",
"type_info": "Int4"
},
{
"ordinal": 4,
"name": "cache_ttl",
"type_info": "Int4"
}
],
"parameters": {
@@ -34,8 +39,9 @@
false,
true,
true,
true,
true
]
},
"hash": "9289709b2727425be0e7f1e341d6acd46e1e79d5018f6a18cb41d50ca3f4ba11"
"hash": "8062bb1ac475c4a2a016d9dfa63c95765356fe7744b817ad0c320a54a33b24f3"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO concurrency_counter VALUES ($1, 1)\n ON CONFLICT (concurrency_id) \n DO UPDATE SET counter = concurrency_counter.counter + 1\n RETURNING concurrency_counter.counter",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "counter",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Varchar"
]
},
"nullable": [
false
]
},
"hash": "86652246b20f9bd023d3419ec382176780a87f32673b53de6f899564c1494147"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO queue\n (workspace_id, id, running, parent_job, created_by, permissioned_as, scheduled_for, \n script_hash, script_path, raw_code, raw_lock, args, job_kind, schedule_path, raw_flow, flow_status, is_flow_step, language, started_at, same_worker, pre_run_error, email, visible_to_owner, root_job, tag, concurrent_limit, concurrency_time_window_s, timeout, flow_step_id)\n VALUES ($1, $2, $3, $4, $5, $6, COALESCE($7, now()), $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, CASE WHEN $3 THEN now() END, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28) RETURNING id",
"query": "INSERT INTO queue\n (workspace_id, id, running, parent_job, created_by, permissioned_as, scheduled_for, \n script_hash, script_path, raw_code, raw_lock, args, job_kind, schedule_path, raw_flow, flow_status, is_flow_step, language, started_at, same_worker, pre_run_error, email, visible_to_owner, root_job, tag, concurrent_limit, concurrency_time_window_s, timeout, flow_step_id, cache_ttl)\n VALUES ($1, $2, $3, $4, $5, $6, COALESCE($7, now()), $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, CASE WHEN $3 THEN now() END, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29) RETURNING id",
"describe": {
"columns": [
{
@@ -80,12 +80,13 @@
"Int4",
"Int4",
"Int4",
"Varchar"
"Varchar",
"Int4"
]
},
"nullable": [
false
]
},
"hash": "01d5e4e371a507602fca01190965a4f20ff806315a9ed02b372977e2d9479338"
"hash": "9e8c3ff3d6b31e366e15beda1e96e03e870ccc3b353401439bc0ed8ff219249b"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO global_settings (name, value) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET value = $2, updated_at = now()",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Jsonb"
]
},
"nullable": []
},
"hash": "a59b70164dc87224d09a04d5469ca217eb19a15a250c3b83ca63f606f89b9681"
}

View File

@@ -1,30 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COALESCE(j.min_started_at, q.min_started_at) AS min_started_at, COALESCE(completed_count, 0) + COALESCE(running_count, 0) AS total_count\n FROM\n (SELECT script_path, MIN(started_at) as min_started_at, COUNT(*) as completed_count\n FROM completed_job\n WHERE script_path = $1 AND job_kind != 'dependencies' AND started_at + INTERVAL '1 MILLISECOND' * duration_ms > (now() - INTERVAL '1 second' * $2) AND workspace_id = $3 AND canceled = false\n GROUP BY script_path) as j\n FULL OUTER JOIN\n (SELECT script_path, MIN(started_at) as min_started_at, COUNT(*) as running_count\n FROM queue\n WHERE script_path = $1 AND job_kind != 'dependencies' AND running = true AND workspace_id = $3 AND canceled = false\n GROUP BY script_path) as q\n ON q.script_path = j.script_path",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "min_started_at",
"type_info": "Timestamptz"
},
{
"ordinal": 1,
"name": "total_count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Float8",
"Text"
]
},
"nullable": [
true,
true
]
},
"hash": "afbae51dae93e19045aee8d2d9ea1d31609aa7947a52545dce4455a4983493a7"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "select hash, tag, concurrent_limit, concurrency_time_window_s from script where path = $1 AND workspace_id = $2 AND\n created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2 AND\n deleted = false AND archived = false AND lock IS not NULL AND lock_error_logs IS NULL)",
"query": "select hash, tag, concurrent_limit, concurrency_time_window_s, cache_ttl from script where path = $1 AND workspace_id = $2 AND\n created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2 AND\n deleted = false AND archived = false)",
"describe": {
"columns": [
{
@@ -22,6 +22,11 @@
"ordinal": 3,
"name": "concurrency_time_window_s",
"type_info": "Int4"
},
{
"ordinal": 4,
"name": "cache_ttl",
"type_info": "Int4"
}
],
"parameters": {
@@ -34,8 +39,9 @@
false,
true,
true,
true,
true
]
},
"hash": "7435be9a71a6b30980d180047486a47d69e36132d40ef0f53448e96a9866842b"
"hash": "cf21b61aa5b5e47744235c860e2d9b90d7654a4b5d0ec32085b178399f701bfc"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "select path, tag, concurrent_limit, concurrency_time_window_s from script where hash = $1 AND workspace_id = $2",
"query": "select path, tag, concurrent_limit, concurrency_time_window_s, cache_ttl from script where hash = $1 AND workspace_id = $2",
"describe": {
"columns": [
{
@@ -22,6 +22,11 @@
"ordinal": 3,
"name": "concurrency_time_window_s",
"type_info": "Int4"
},
{
"ordinal": 4,
"name": "cache_ttl",
"type_info": "Int4"
}
],
"parameters": {
@@ -34,8 +39,9 @@
false,
true,
true,
true,
true
]
},
"hash": "d164caec6637ab53d8c2e0faad653a458f31a976bb23c3a43d35b8476b066954"
"hash": "ebac391c83dae67831502fbe3f1034210afce7902a69f7c42bb68160e0464d71"
}

266
backend/Cargo.lock generated
View File

@@ -64,9 +64,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
version = "1.0.4"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a"
checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783"
dependencies = [
"memchr",
]
@@ -204,7 +204,7 @@ dependencies = [
"proc-macro2",
"quote",
"swc_macros_common",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -237,9 +237,9 @@ dependencies = [
[[package]]
name = "async-compression"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b74f44609f0f91493e3082d3734d98497e094777144380ea4db9f9905dd5b6"
checksum = "d495b6dc0184693324491a5ac05f559acc97bf937ab31d7a1c33dd0016be6d2b"
dependencies = [
"brotli",
"flate2",
@@ -328,13 +328,13 @@ dependencies = [
[[package]]
name = "async-recursion"
version = "1.0.4"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba"
checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -411,7 +411,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -604,13 +604,13 @@ dependencies = [
"lazycell",
"log",
"peeking_take_while",
"prettyplease 0.2.12",
"prettyplease 0.2.14",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn 2.0.29",
"syn 2.0.31",
"which",
]
@@ -631,7 +631,7 @@ dependencies = [
"regex",
"rustc-hash",
"shlex",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -922,9 +922,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.26"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
checksum = "95ed24df0632f708f5f6d8082675bef2596f7084dee3dd55f632290bf35bfe0f"
dependencies = [
"android-tzdata",
"iana-time-zone",
@@ -933,7 +933,7 @@ dependencies = [
"serde",
"time 0.1.45",
"wasm-bindgen",
"winapi",
"windows-targets",
]
[[package]]
@@ -980,20 +980,19 @@ dependencies = [
[[package]]
name = "clap"
version = "4.4.1"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c8d502cbaec4595d2e7d5f61e318f05417bd2b66fdc3809498f0d3fdf0bea27"
checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6"
dependencies = [
"clap_builder",
"clap_derive",
"once_cell",
]
[[package]]
name = "clap_builder"
version = "4.4.1"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5891c7bc0edb3e1c2204fc5e94009affabeb1821c9e5fdc3959536c5c0bb984d"
checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08"
dependencies = [
"anstream",
"anstyle",
@@ -1003,14 +1002,14 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.4.0"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a"
checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -1317,7 +1316,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -1339,14 +1338,14 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
dependencies = [
"darling_core 0.20.3",
"quote",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
name = "dashmap"
version = "5.5.1"
version = "5.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edd72493923899c6f10c641bdbdeddc7183d6396641d99c1a0d1597f37f92e28"
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
dependencies = [
"cfg-if",
"hashbrown 0.14.0",
@@ -1397,7 +1396,7 @@ checksum = "3c65c2ffdafc1564565200967edc4851c7b55422d3913466688907efd05ea26f"
dependencies = [
"deno-proc-macro-rules-macros",
"proc-macro2",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -1409,7 +1408,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -1529,7 +1528,7 @@ dependencies = [
"strum",
"strum_macros",
"syn 1.0.109",
"syn 2.0.29",
"syn 2.0.31",
"thiserror",
]
@@ -1795,9 +1794,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.2"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f"
checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd"
dependencies = [
"errno-dragonfly",
"libc",
@@ -1933,7 +1932,7 @@ dependencies = [
"pmutil",
"proc-macro2",
"swc_macros_common",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -1961,7 +1960,7 @@ checksum = "b0fa992f1656e1707946bbba340ad244f0814009ef8c0118eb7b658395f19a2e"
dependencies = [
"frunk_proc_macro_helpers",
"quote",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -1973,7 +1972,7 @@ dependencies = [
"frunk_core",
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -1985,7 +1984,7 @@ dependencies = [
"frunk_core",
"frunk_proc_macro_helpers",
"quote",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -2086,7 +2085,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -2317,21 +2316,20 @@ dependencies = [
[[package]]
name = "hashlink"
version = "0.8.3"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f"
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
dependencies = [
"hashbrown 0.14.0",
]
[[package]]
name = "headers"
version = "0.3.8"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584"
checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270"
dependencies = [
"base64 0.13.1",
"bitflags 1.3.2",
"base64 0.21.3",
"bytes",
"headers-core",
"http",
@@ -2618,7 +2616,7 @@ dependencies = [
"pmutil",
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -2628,7 +2626,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [
"hermit-abi",
"rustix 0.38.9",
"rustix 0.38.11",
"windows-sys",
]
@@ -3077,9 +3075,9 @@ dependencies = [
[[package]]
name = "memchr"
version = "2.6.0"
version = "2.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76fc44e2588d5b436dbc3c6cf62aef290f90dab6235744a93dfe1cc18f451e2c"
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
[[package]]
name = "memoffset"
@@ -3146,7 +3144,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.31",
"termcolor",
"thiserror",
]
@@ -3349,9 +3347,9 @@ dependencies = [
[[package]]
name = "object"
version = "0.32.0"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe"
checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
dependencies = [
"memchr",
]
@@ -3402,7 +3400,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -3557,10 +3555,11 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]]
name = "pest"
version = "2.7.2"
version = "2.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a"
checksum = "d7a4d085fd991ac8d5b05a147b437791b4260b76326baf0fc60cf7c9c27ecd33"
dependencies = [
"memchr",
"thiserror",
"ucd-trie",
]
@@ -3660,7 +3659,7 @@ dependencies = [
"phf_shared 0.11.2",
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -3698,7 +3697,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -3770,7 +3769,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -3874,12 +3873,12 @@ dependencies = [
[[package]]
name = "prettyplease"
version = "0.2.12"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62"
checksum = "8832c0f9be7e3cae60727e6256cfd2cd3c3e2b6cd5dad4190ecb2fd658c9030b"
dependencies = [
"proc-macro2",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -3999,7 +3998,7 @@ dependencies = [
"schemars",
"serde",
"serde_json",
"syn 2.0.29",
"syn 2.0.31",
"thiserror",
"typify",
"unicode-ident",
@@ -4019,7 +4018,7 @@ dependencies = [
"serde_json",
"serde_tokenstream",
"serde_yaml",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -4175,9 +4174,9 @@ dependencies = [
[[package]]
name = "redis"
version = "0.23.2"
version = "0.23.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffd6543a7bc6428396845f6854ccf3d1ae8823816592e2cbe74f20f50f209d02"
checksum = "4f49cdc0bb3f412bf8e7d1bd90fe1d9eb10bc5c399ba90973c14662a27b3f8ba"
dependencies = [
"async-std",
"async-trait",
@@ -4226,13 +4225,13 @@ dependencies = [
[[package]]
name = "regex"
version = "1.9.4"
version = "1.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29"
checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.3.7",
"regex-automata 0.3.8",
"regex-syntax 0.7.5",
]
@@ -4247,9 +4246,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.3.7"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629"
checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795"
dependencies = [
"aho-corasick",
"memchr",
@@ -4293,7 +4292,7 @@ version = "0.11.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1"
dependencies = [
"async-compression 0.4.1",
"async-compression 0.4.2",
"base64 0.21.3",
"bytes",
"encoding_rs",
@@ -4466,7 +4465,7 @@ dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn 2.0.29",
"syn 2.0.31",
"walkdir",
]
@@ -4556,9 +4555,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.9"
version = "0.38.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bfe0f2582b4931a45d1fa608f8a8722e8b3c7ac54dd6d5f3b3212791fedef49"
checksum = "c0c3dde1fc030af041adc40e79c0e7fbcf431dd24870053d187d7c66e4b87453"
dependencies = [
"bitflags 2.4.0",
"errno",
@@ -4569,9 +4568,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.21.6"
version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb"
checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8"
dependencies = [
"log",
"ring",
@@ -4739,9 +4738,9 @@ dependencies = [
[[package]]
name = "schemars"
version = "0.8.12"
version = "0.8.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f"
checksum = "763f8cd0d4c71ed8389c90cb8100cba87e763bd01a8e614d4f0af97bcd50a161"
dependencies = [
"chrono",
"dyn-clone",
@@ -4753,9 +4752,9 @@ dependencies = [
[[package]]
name = "schemars_derive"
version = "0.8.12"
version = "0.8.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c"
checksum = "ec0f696e21e10fa546b7ffb1c9672c6de8fbc7a81acf59524386d8639bf12737"
dependencies = [
"proc-macro2",
"quote",
@@ -4904,7 +4903,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -4980,7 +4979,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -5078,9 +5077,9 @@ dependencies = [
[[package]]
name = "sharded-slab"
version = "0.1.5"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6805d8ff0f66aa61fb79a97a51ba210dcae753a797336dea8a36a3168196fab"
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
dependencies = [
"lazy_static",
]
@@ -5555,7 +5554,7 @@ dependencies = [
"proc-macro2",
"quote",
"swc_macros_common",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -5593,7 +5592,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -5675,7 +5674,7 @@ dependencies = [
"proc-macro2",
"quote",
"swc_macros_common",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -5724,7 +5723,7 @@ dependencies = [
"proc-macro2",
"quote",
"swc_macros_common",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -5807,7 +5806,7 @@ dependencies = [
"proc-macro2",
"quote",
"swc_macros_common",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -5911,7 +5910,7 @@ dependencies = [
"pmutil",
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -5923,7 +5922,7 @@ dependencies = [
"pmutil",
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -5947,7 +5946,7 @@ dependencies = [
"proc-macro2",
"quote",
"swc_macros_common",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -5963,9 +5962,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.29"
version = "2.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398"
dependencies = [
"proc-macro2",
"quote",
@@ -5993,7 +5992,7 @@ dependencies = [
"cfg-if",
"fastrand 2.0.0",
"redox_syscall 0.3.5",
"rustix 0.38.9",
"rustix 0.38.11",
"windows-sys",
]
@@ -6028,22 +6027,22 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.47"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f"
checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.47"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b"
checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -6158,7 +6157,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -6361,9 +6360,9 @@ dependencies = [
[[package]]
name = "tower-http"
version = "0.4.3"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55ae70283aba8d2a8b411c695c437fe25b8b5e44e23e780662002fc72fb47a82"
checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140"
dependencies = [
"bitflags 2.4.0",
"bytes",
@@ -6411,7 +6410,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.31",
]
[[package]]
@@ -6424,6 +6423,17 @@ dependencies = [
"valuable",
]
[[package]]
name = "tracing-flame"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bae117ee14789185e129aaee5d93750abe67fdc5a9a62650452bfe4e122a3a9"
dependencies = [
"lazy_static",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "tracing-futures"
version = "0.2.5"
@@ -6538,7 +6548,7 @@ dependencies = [
"regress",
"schemars",
"serde_json",
"syn 2.0.29",
"syn 2.0.31",
"thiserror",
"unicode-ident",
]
@@ -6555,7 +6565,7 @@ dependencies = [
"serde",
"serde_json",
"serde_tokenstream",
"syn 2.0.29",
"syn 2.0.31",
"typify-impl",
]
@@ -6567,9 +6577,9 @@ checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
[[package]]
name = "ulid"
version = "1.0.0"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13a3aaa69b04e5b66cc27309710a569ea23593612387d67daaf102e73aa974fd"
checksum = "0f9d3475df4ff8a8f7804c0fc3394b44fdcfc4fb635717bf05fbb7c41c83a376"
dependencies = [
"rand 0.8.5",
"uuid 1.4.1",
@@ -6879,7 +6889,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.31",
"wasm-bindgen-shared",
]
@@ -6913,7 +6923,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.31",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -6973,9 +6983,9 @@ dependencies = [
[[package]]
name = "webpki"
version = "0.22.0"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
checksum = "f0e74f82d49d545ad128049b7e88f6576df2da6b02e9ce565c6f533be576957e"
dependencies = [
"ring",
"untrusted",
@@ -7068,7 +7078,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill"
version = "1.161.0"
version = "1.167.0"
dependencies = [
"anyhow",
"axum",
@@ -7103,7 +7113,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.161.0"
version = "1.167.0"
dependencies = [
"anyhow",
"argon2",
@@ -7162,10 +7172,11 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.161.0"
version = "1.167.0"
dependencies = [
"base64 0.21.3",
"chrono",
"openapiv3",
"prettyplease 0.1.25",
"progenitor",
"progenitor-client",
@@ -7179,7 +7190,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.161.0"
version = "1.167.0"
dependencies = [
"chrono",
"serde",
@@ -7192,7 +7203,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.161.0"
version = "1.167.0"
dependencies = [
"anyhow",
"axum",
@@ -7211,13 +7222,14 @@ dependencies = [
"thiserror",
"tokio",
"tracing",
"tracing-flame",
"tracing-subscriber",
"uuid 1.4.1",
]
[[package]]
name = "windmill-parser"
version = "1.161.0"
version = "1.167.0"
dependencies = [
"serde",
"serde_json",
@@ -7225,7 +7237,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.161.0"
version = "1.167.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -7236,17 +7248,19 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.161.0"
version = "1.167.0"
dependencies = [
"anyhow",
"gosyn",
"itertools 0.11.0",
"lazy_static",
"regex",
"windmill-parser",
]
[[package]]
name = "windmill-parser-graphql"
version = "1.161.0"
version = "1.167.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -7257,7 +7271,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.161.0"
version = "1.167.0"
dependencies = [
"anyhow",
"itertools 0.11.0",
@@ -7268,7 +7282,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-imports"
version = "1.161.0"
version = "1.167.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -7285,7 +7299,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
version = "1.161.0"
version = "1.167.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -7296,7 +7310,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.161.0"
version = "1.167.0"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@@ -7313,7 +7327,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wasm"
version = "1.161.0"
version = "1.167.0"
dependencies = [
"anyhow",
"getrandom 0.2.10",
@@ -7331,7 +7345,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.161.0"
version = "1.167.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -7360,7 +7374,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.161.0"
version = "1.167.0"
dependencies = [
"anyhow",
"async-recursion",

View File

@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.161.0"
version = "1.167.0"
authors.workspace = true
edition.workspace = true
@@ -22,7 +22,7 @@ members = [
]
[workspace.package]
version = "1.161.0"
version = "1.167.0"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -36,7 +36,8 @@ incremental = true
[features]
enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise"]
benchmark = ["windmill-api/benchmark"]
benchmark = ["windmill-api/benchmark", "windmill-worker/benchmark", "windmill-queue/benchmark"]
flamegraph = ["windmill-common/flamegraph", "windmill-worker/flamegraph"]
[dependencies]
anyhow.workspace = true
@@ -188,4 +189,4 @@ samael = { version = "0.0.12", features = ["xmlsec"] }
gcp_auth = "0.9.0"
rust_decimal = {version = "1.31.0", features = ["db-postgres"]}
jsonwebtoken = "8.3.0"
pem = "3.0.1"
pem = "3.0.1"

View File

@@ -0,0 +1 @@
-- Add down migration script here

View File

@@ -0,0 +1,3 @@
-- Add up migration script here
ALTER TABLE script ADD COLUMN cache_ttl INTEGER;
ALTER TABLE queue ADD COLUMN cache_ttl INTEGER;

View File

@@ -0,0 +1 @@
-- Add down migration script here

View File

@@ -0,0 +1,6 @@
-- Add up migration script here
CREATE TABLE global_settings (
name VARCHAR(255) PRIMARY KEY,
value JSONB NOT NULL,
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);

View File

@@ -0,0 +1 @@
-- Add down migration script here

View File

@@ -0,0 +1,2 @@
-- Add up migration script here
CREATE INDEX IF NOT EXISTS queue_sort ON queue (scheduled_for, created_at) WHERE running = false;

View File

@@ -0,0 +1 @@
-- Add down migration script here

View File

@@ -0,0 +1,13 @@
-- Add up migration script here
DROP INDEX IF EXISTS queue_sort;
CREATE INDEX IF NOT EXISTS queue_sort ON queue (scheduled_for, created_at, tag) WHERE running = false;
CREATE TABLE IF NOT EXISTS concurrency_counter (
concurrency_id VARCHAR(1000) PRIMARY KEY,
counter INTEGER NOT NULL
);
DROP INDEX IF EXISTS root_queue_index_suspended;
DROP INDEX IF EXISTS queue_suspended;
CREATE INDEX IF NOT EXISTS queue_suspended ON queue (created_at, suspend_until, suspend, tag) WHERE suspend_until is not null;

1
backend/out.json Normal file
View File

@@ -0,0 +1 @@
{"completed":true,"result":{"error":{"name":"Exception","stack":" File \"/tmp/u/ruben/gentle_script.py\", line 35, in main\n print(wmill.get_variable(\"u/admin/adorable_postgresql\"))\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n File \"/tmp/windmill/cache/pip/wmill==1.166.0/wmill/client.py\", line 336, in get_variable\n raise Exception(\n","message":"Variable at path u/admin/adorable_postgresql does not exist or you do not have read permissions on it"}}}

View File

@@ -12,4 +12,6 @@ path = "./src/lib.rs"
windmill-parser.workspace = true
itertools.workspace = true
anyhow.workspace = true
gosyn.workspace = true
gosyn.workspace = true
lazy_static.workspace = true
regex.workspace = true

View File

@@ -6,8 +6,13 @@ use gosyn::{
};
use itertools::Itertools;
use regex::Regex;
use windmill_parser::{Arg, MainArgSignature, ObjectProperty, Typ};
lazy_static::lazy_static! {
pub static ref REQUIRE_PARSE: Regex = Regex::new(r"//require (.*)\n").unwrap();
}
pub fn parse_go_sig(code: &str) -> anyhow::Result<MainArgSignature> {
let filtered_code = filter_non_main(code);
let file = parse_source(&filtered_code).map_err(|x| anyhow::anyhow!(x.to_string()))?;
@@ -46,7 +51,14 @@ pub fn parse_go_imports(code: &str) -> anyhow::Result<Vec<String>> {
})
.collect();
imports.sort();
Ok(imports)
Ok([
imports,
REQUIRE_PARSE
.captures_iter(code)
.map(|x| x[1].to_string())
.collect_vec(),
]
.concat())
}
fn get_name(param: &Field) -> String {

View File

@@ -20,7 +20,7 @@ use tokio::{
sync::RwLock,
};
use windmill_api::{LICENSE_KEY, OAUTH_CLIENTS, SMTP_CLIENT};
use windmill_common::{utils::rd_string, METRICS_ADDR};
use windmill_common::{global_settings::ENV_SETTINGS, utils::rd_string, METRICS_ADDR};
use windmill_worker::{
BUN_CACHE_DIR, BUN_TMP_CACHE_DIR, DENO_CACHE_DIR, DENO_CACHE_DIR_DEPS, DENO_CACHE_DIR_NPM,
DENO_TMP_CACHE_DIR, DENO_TMP_CACHE_DIR_DEPS, DENO_TMP_CACHE_DIR_NPM, GO_BIN_CACHE_DIR,
@@ -40,15 +40,19 @@ mod monitor;
async fn main() -> anyhow::Result<()> {
dotenv::dotenv().ok();
#[cfg(not(feature = "flamegraph"))]
windmill_common::tracing_init::initialize_tracing();
#[cfg(feature = "flamegraph")]
let _guard = windmill_common::tracing_init::setup_flamegraph();
let num_workers = std::env::var("NUM_WORKERS")
.ok()
.and_then(|x| x.parse::<i32>().ok())
.unwrap_or(DEFAULT_NUM_WORKERS as i32);
if num_workers > 1 {
tracing::warn!("We recommend using at most 1 worker per container, use more only if you know what you are doing.");
tracing::warn!("We STRONGLY recommend using at most 1 worker per container, unless this worker is dedicated to native jobs only. ");
}
let metrics_addr: Option<SocketAddr> = *METRICS_ADDR;
@@ -92,6 +96,7 @@ async fn main() -> anyhow::Result<()> {
tracing::info!("Database connected");
let rsmq = if let Some(config) = rsmq_config {
tracing::info!("Redis config set: {:?}", config);
Some(rsmq_async::MultiplexedRsmq::new(config).await.unwrap())
} else {
None
@@ -119,68 +124,7 @@ Windmill Community Edition {GIT_VERSION}
##############################"
);
display_config(vec![
"DISABLE_NSJAIL",
"DISABLE_SERVER",
"NUM_WORKERS",
"METRICS_ADDR",
"JSON_FMT",
"BASE_URL",
"TIMEOUT",
"ZOMBIE_JOB_TIMEOUT",
"RESTART_ZOMBIE_JOBS",
"SLEEP_QUEUE",
"MAX_LOG_SIZE",
"SERVER_BIND_ADDR",
"PORT",
"KEEP_JOB_DIR",
"S3_CACHE_BUCKET",
"TAR_CACHE_RATE",
"COOKIE_DOMAIN",
"PYTHON_PATH",
"DENO_PATH",
"GO_PATH",
"GOPRIVATE",
"GOPROXY",
"NETRC",
"PIP_INDEX_URL",
"PIP_EXTRA_INDEX_URL",
"PIP_TRUSTED_HOST",
"PATH",
"HOME",
"DATABASE_CONNECTIONS",
"TIMEOUT_WAIT_RESULT",
"QUEUE_LIMIT_WAIT_RESULT",
"DENO_AUTH_TOKENS",
"DENO_FLAGS",
"NPM_CONFIG_REGISTRY",
"PIP_LOCAL_DEPENDENCIES",
"ADDITIONAL_PYTHON_PATHS",
"INCLUDE_HEADERS",
"INSTANCE_EVENTS_WEBHOOK",
"CLOUD_HOSTED",
"GLOBAL_CACHE_INTERVAL",
"WORKER_TAGS",
"CUSTOM_TAGS",
"JOB_RETENTION_SECS",
"WAIT_RESULT_FAST_POLL_DURATION_SECS",
"WAIT_RESULT_SLOW_POLL_INTERVAL_MS",
"WAIT_RESULT_FAST_POLL_INTERVAL_MS",
"EXIT_AFTER_NO_JOB_FOR_SECS",
"REQUEST_SIZE_LIMIT",
"SMTP_HOST",
"SMTP_USERNAME",
"SMTP_PORT",
"SMTP_TLS_IMPLICIT",
"CREATE_WORKSPACE_REQUIRE_SUPERADMIN",
"GLOBAL_ERROR_HANDLER_PATH_IN_ADMINS_WORKSPACE",
]);
if std::env::var("WHITELIST_WORKSPACES").is_ok()
|| std::env::var("BLACKLIST_WORKSPACES").is_ok()
{
panic!("WHITELIST_WORKSPACES and BLACKLIST_WORKSPACES have been removed, please use Worker Groups instead");
}
display_config(&ENV_SETTINGS);
tracing::info!("Loading OAuth providers...: {:#?}", *OAUTH_CLIENTS);
if let Some(ref smtp) = *SMTP_CLIENT {
@@ -264,7 +208,7 @@ Windmill Community Edition {GIT_VERSION}
Ok(())
}
fn display_config(envs: Vec<&str>) {
fn display_config(envs: &[&str]) {
tracing::info!(
"config: {}",
envs.iter()

View File

@@ -133,7 +133,7 @@ async fn handle_zombie_jobs<R: rsmq_async::RsmqConnection + Send + Sync + Clone>
let _ = handle_job_error(
db,
&client,
job,
&job,
error::Error::ExecutionErr(format!(
"Job timed out after no ping from job since {} (ZOMBIE_JOB_TIMEOUT: {})",
last_ping
@@ -145,7 +145,6 @@ async fn handle_zombie_jobs<R: rsmq_async::RsmqConnection + Send + Sync + Clone>
true,
same_worker_tx_never_used,
"",
base_internal_url,
rsmq.clone(),
)
.await;

View File

@@ -1619,6 +1619,7 @@ func main(derp string) (string, error) {
language: ScriptLang::Go,
concurrent_limit: None,
concurrency_time_window_s: None,
cache_ttl: None
}))
.arg("derp", json!("world"))
.run_until_complete(&db, port)
@@ -1648,6 +1649,7 @@ echo "hello $msg"
language: ScriptLang::Bash,
concurrent_limit: None,
concurrency_time_window_s: None,
cache_ttl: None
}))
.arg("msg", json!("world"))
.run_until_complete(&db, port)
@@ -1675,6 +1677,7 @@ def main():
lock: None,
concurrent_limit: None,
concurrency_time_window_s: None,
cache_ttl: None
});
let result = run_job_in_new_worker_until_complete(&db, job, port)
@@ -1707,6 +1710,7 @@ def main():
lock: None,
concurrent_limit: None,
concurrency_time_window_s: None,
cache_ttl: None
});
let result = run_job_in_new_worker_until_complete(&db, job, port)
@@ -1738,6 +1742,7 @@ def main():
lock: None,
concurrent_limit: None,
concurrency_time_window_s: None,
cache_ttl: None
});
let result = run_job_in_new_worker_until_complete(&db, job, port)

View File

@@ -19,9 +19,11 @@ uuid.workspace = true
serde_json.workspace = true
rand.workspace = true
base64.workspace = true
openapiv3 = "=1.0.2"
[build-dependencies]
prettyplease = "0.1.25"
progenitor = { git = "https://github.com/oxidecomputer/progenitor", rev = "3d96016ae8d422e90513b2d34fb5b63eeab30b01" }
serde_json = "1.0"
syn = "1.0"
syn = "1.0"
openapiv3 = "=1.0.2"

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.161.0
version: 1.167.0
title: Windmill API
contact:
@@ -522,6 +522,59 @@ paths:
schema:
type: boolean
/settings/global/{key}:
get:
summary: get global settings
operationId: getGlobal
tags:
- setting
parameters:
- $ref: "#/components/parameters/Key"
responses:
"200":
description: status
content:
application/json:
schema: {}
post:
summary: post global settings
operationId: setGlobal
tags:
- setting
parameters:
- $ref: "#/components/parameters/Key"
requestBody:
description: value set
required: true
content:
application/json:
schema:
type: object
properties:
value: {}
responses:
"200":
description: status
content:
text/plain:
schema:
type: string
/settings/local:
get:
summary: get local settings
operationId: getLocal
tags:
- setting
responses:
"200":
description: status
content:
application/json:
schema: {}
/users/email:
get:
summary: get current user email (if logged in)
@@ -3597,6 +3650,8 @@ paths:
type: string
path:
type: string
cache_ttl:
type: integer
required:
- content
- language
@@ -5448,6 +5503,12 @@ components:
name: token
parameters:
Key:
name: key
in: path
required: true
schema:
type: string
WorkspaceId:
name: workspace
in: path
@@ -5831,7 +5892,8 @@ components:
type: array
items:
type: integer
cache_ttl:
type: number
required:
- hash
- path
@@ -5905,6 +5967,8 @@ components:
type: array
items:
type: integer
cache_ttl:
type: number
required:
- path
- summary

View File

@@ -210,7 +210,7 @@ async fn create_flow(
w_id,
nf.path,
nf.summary,
nf.description,
nf.description.unwrap_or_else(String::new),
nf.value,
&authed.username,
nf.schema.and_then(|x| serde_json::to_string(&x.0).ok()),
@@ -352,7 +352,7 @@ async fn update_flow(
edited_at = now(), schema = $6::text::json, dependency_job = NULL, draft_only = NULL, tag = $9 WHERE path = $7 AND workspace_id = $8",
nf.path,
nf.summary,
nf.description,
nf.description.unwrap_or_else(String::new),
nf.value,
&authed.username,
schema.and_then(|x| serde_json::to_string(&x).ok()),
@@ -752,6 +752,7 @@ mod tests {
concurrent_limit: None,
concurrency_time_window_s: None,
skip_expr: None,
cache_ttl: None,
};
let expect = serde_json::json!({
"modules": [

View File

@@ -305,13 +305,19 @@ pub async fn get_path_for_hash<'c>(
Ok(path)
}
pub async fn get_path_tag_and_limits_for_hash(
pub async fn get_path_tag_limits_cache_for_hash(
db: &DB,
w_id: &str,
hash: i64,
) -> error::Result<(String, Option<String>, Option<i32>, Option<i32>)> {
) -> error::Result<(
String,
Option<String>,
Option<i32>,
Option<i32>,
Option<i32>,
)> {
let script = sqlx::query!(
"select path, tag, concurrent_limit, concurrency_time_window_s from script where hash = $1 AND workspace_id = $2",
"select path, tag, concurrent_limit, concurrency_time_window_s, cache_ttl from script where hash = $1 AND workspace_id = $2",
hash,
w_id
)
@@ -327,6 +333,7 @@ pub async fn get_path_tag_and_limits_for_hash(
script.tag,
script.concurrent_limit,
script.concurrency_time_window_s,
script.cache_ttl,
))
}
@@ -1340,6 +1347,7 @@ impl From<UnifiedJob> for Job {
concurrency_time_window_s: uj.concurrency_time_window_s,
timeout: None,
flow_step_id: None,
cache_ttl: None,
}),
t => panic!("job type {} not valid", t),
}
@@ -2027,8 +2035,8 @@ pub async fn run_wait_result_script_by_hash(
check_queue_too_long(&db, run_query.queue_limit).await?;
let hash = script_hash.0;
let (path, tag, concurrent_limit, concurrency_time_window_s) =
get_path_tag_and_limits_for_hash(&db, &w_id, hash).await?;
let (path, tag, concurrent_limit, concurrency_time_window_s, cache_ttl) =
get_path_tag_limits_cache_for_hash(&db, &w_id, hash).await?;
check_scopes(&authed, || format!("run:script/{path}"))?;
let args = run_query.add_include_headers(headers, args.unwrap_or_default());
@@ -2045,6 +2053,7 @@ pub async fn run_wait_result_script_by_hash(
path: path,
concurrent_limit: concurrent_limit,
concurrency_time_window_s: concurrency_time_window_s,
cache_ttl,
},
args,
&authed.username,
@@ -2218,6 +2227,7 @@ async fn run_preview_job(
lock: None,
concurrent_limit: None, // TODO(gbouv): once I find out how to store limits in the content of a script, should be easy to plug limits here
concurrency_time_window_s: None, // TODO(gbouv): same as above
cache_ttl: None,
}),
},
args,
@@ -2243,6 +2253,7 @@ async fn run_preview_job(
Ok((StatusCode::CREATED, uuid.to_string()))
}
#[tracing::instrument(level = "trace", skip_all)]
async fn add_noop_jobs(
authed: ApiAuthed,
Extension(db): Extension<DB>,
@@ -2350,8 +2361,8 @@ pub async fn run_job_by_hash(
JsonOrForm(args, raw_string): JsonOrForm,
) -> error::Result<(StatusCode, String)> {
let hash = script_hash.0;
let (path, tag, concurrent_limit, concurrency_time_window_s) =
get_path_tag_and_limits_for_hash(&db, &w_id, hash).await?;
let (path, tag, concurrent_limit, concurrency_time_window_s, cache_ttl) =
get_path_tag_limits_cache_for_hash(&db, &w_id, hash).await?;
check_scopes(&authed, || format!("run:script/{path}"))?;
let scheduled_for = run_query.get_scheduled_for(&db).await?;
@@ -2369,6 +2380,7 @@ pub async fn run_job_by_hash(
path: path,
concurrent_limit: concurrent_limit,
concurrency_time_window_s: concurrency_time_window_s,
cache_ttl,
},
args,
&authed.username,

View File

@@ -59,6 +59,7 @@ mod saml;
mod schedule;
mod scim;
mod scripts;
mod settings;
mod static_assets;
mod tracing_init;
mod users;
@@ -97,7 +98,7 @@ lazy_static::lazy_static! {
.build().unwrap();
pub static ref OAUTH_CLIENTS: AllClients = build_oauth_clients(&BASE_URL)
.map_err(|e| tracing::error!("Error building oauth clients: {}", e))
.map_err(|e| tracing::error!("Error building oauth clients (is the oauth.json mounted and in correct format? Use '{}' as minimal oauth.json): {}", "{}", e))
.unwrap();
pub static ref SMTP_CLIENT: Option<SmtpClientBuilder<String>> = {
@@ -155,7 +156,9 @@ pub async fn run_server(
for tag in ALL_TAGS.clone() {
let r =
rsmq_async::RsmqConnection::create_queue(&mut rsmq, &tag, None, None, None).await;
if r.is_ok() {
if let Err(e) = r {
tracing::info!("Redis queue {tag} could not be created: {e}");
} else {
tracing::info!("Redis queue {tag} created");
}
}
@@ -233,6 +236,7 @@ pub async fn run_server(
"/users",
users::global_service().layer(Extension(argon2.clone())),
)
.nest("/settings", settings::global_service())
.nest("/jobs", jobs::global_root_service())
.nest("/workers", workers::global_service())
.nest("/scripts", scripts::global_service())

View File

@@ -799,7 +799,7 @@ async fn slack_command(
(JobPayload::Flow(path.to_string()), None)
} else {
let path = path.strip_prefix("script/").unwrap_or_else(|| path);
let (script_hash, tag, concurrent_limit, concurrency_time_window_s) =
let (script_hash, tag, concurrent_limit, concurrency_time_window_s, cache_ttl) =
windmill_common::get_latest_deployed_hash_for_path(
&db,
&settings.workspace_id,
@@ -812,6 +812,7 @@ async fn slack_command(
path: path.to_owned(),
concurrent_limit,
concurrency_time_window_s,
cache_ttl,
},
tag,
)

View File

@@ -69,6 +69,8 @@ pub struct ScriptWDraft {
pub concurrent_limit: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub concurrency_time_window_s: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_ttl: Option<i32>,
}
pub fn global_service() -> Router {
@@ -79,10 +81,12 @@ pub fn global_service() -> Router {
}
pub fn global_unauthed_service() -> Router {
Router::new().route(
"/tokened_raw/:workspace/:token/*path",
get(get_tokened_raw_script_by_path),
)
Router::new()
.route(
"/tokened_raw/:workspace/:token/*path",
get(get_tokened_raw_script_by_path),
)
.route("/empty_ts/*path", get(get_empty_ts_script_by_path))
}
pub fn workspaced_service() -> Router {
@@ -370,8 +374,8 @@ async fn create_script(
sqlx::query!(
"INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, \
content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, \
draft_only, envs, concurrent_limit, concurrency_time_window_s) \
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)",
draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl) \
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)",
&w_id,
&hash.0,
ns.path,
@@ -390,7 +394,8 @@ async fn create_script(
ns.draft_only,
envs,
ns.concurrent_limit,
ns.concurrency_time_window_s
ns.concurrency_time_window_s,
ns.cache_ttl,
)
.execute(&mut tx)
.await?;
@@ -587,7 +592,7 @@ async fn get_script_by_path_w_draft(
let mut tx = user_db.begin(&authed).await?;
let script_o = sqlx::query_as::<_, ScriptWDraft>(
"SELECT hash, script.path, summary, description, content, language, kind, tag, schema, draft_only, envs, concurrent_limit, concurrency_time_window_s, draft.value as draft FROM script LEFT JOIN draft ON
"SELECT hash, script.path, summary, description, content, language, kind, tag, schema, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, draft.value as draft FROM script LEFT JOIN draft ON
script.path = draft.path AND script.workspace_id = draft.workspace_id AND draft.typ = 'script'
WHERE script.path = $1 AND script.workspace_id = $2 \
AND script.created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND \
@@ -633,6 +638,10 @@ async fn get_tokened_raw_script_by_path(
return raw_script_by_path(authed, Extension(user_db), Path((w_id, path))).await;
}
async fn get_empty_ts_script_by_path() -> String {
return String::new();
}
async fn raw_script_by_path(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,

View File

@@ -0,0 +1,83 @@
/*
* Author: Ruben Fiszel
* Copyright: Windmill Labs, Inc 2023
* 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 crate::{
db::{ApiAuthed, DB},
utils::require_super_admin,
};
use axum::{
extract::{Extension, Path},
routing::{get, post},
Json, Router,
};
use windmill_common::{
error::{self, JsonResult},
global_settings::ENV_SETTINGS,
};
pub fn global_service() -> Router {
Router::new()
.route("/local", get(get_local_settings))
.route(
"/global/:key",
post(set_global_setting).get(get_global_setting),
)
}
pub async fn get_local_settings(
Extension(db): Extension<DB>,
authed: ApiAuthed,
) -> error::JsonResult<serde_json::Value> {
require_super_admin(&db, &authed.email).await?;
let mut settings = serde_json::Map::new();
for key in ENV_SETTINGS.iter() {
if let Some(value) = std::env::var(key).ok() {
settings.insert(key.to_string(), serde_json::Value::String(value));
}
}
Ok(Json(serde_json::Value::Object(settings)))
}
#[derive(serde::Deserialize)]
pub struct Value {
pub value: serde_json::Value,
}
pub async fn set_global_setting(
Extension(db): Extension<DB>,
authed: ApiAuthed,
Path(key): Path<String>,
Json(value): Json<Value>,
) -> error::Result<()> {
require_super_admin(&db, &authed.email).await?;
sqlx::query!(
"INSERT INTO global_settings (name, value) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET value = $2, updated_at = now()",
key,
value.value
)
.execute(&db)
.await?;
tracing::info!("Set global setting {} to {}", key, value.value);
Ok(())
}
pub async fn get_global_setting(
Extension(db): Extension<DB>,
authed: ApiAuthed,
Path(key): Path<String>,
) -> JsonResult<serde_json::Value> {
require_super_admin(&db, &authed.email).await?;
let value = sqlx::query!("SELECT value FROM global_settings WHERE name = $1", key)
.fetch_optional(&db)
.await?
.map(|x| x.value);
Ok(Json(value.unwrap_or_else(|| serde_json::Value::Null)))
}

View File

@@ -252,7 +252,7 @@ async fn premium_info(
require_admin(authed.is_admin, &authed.username)?;
let mut tx = db.begin().await?;
let row = sqlx::query_as::<_, PremiumWorkspaceInfo>(
"SELECT premium, usage.usage FROM workspace LEFT JOIN usage ON workspace.id = $1 AND usage.is_workspace IS true WHERE workspace.id = $1",
"SELECT premium, usage.usage FROM workspace LEFT JOIN usage ON usage.id = $1 AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date) AND usage.is_workspace IS true WHERE workspace.id = $1",
)
.bind(w_id)
.fetch_one(&mut *tx)
@@ -1515,7 +1515,7 @@ async fn tarball_workspace(
if !skip_resources.unwrap_or(false) {
let resources = sqlx::query_as!(
Resource,
"SELECT * FROM resource WHERE workspace_id = $1",
"SELECT * FROM resource WHERE workspace_id = $1 AND resource_type != 'state' AND resource_type != 'cache'",
&w_id
)
.fetch_all(&db)

View File

@@ -16,6 +16,7 @@ tracing_init = [
"dep:tracing",
"dep:tracing-subscriber",
]
flamegraph = ["dep:tracing-flame"]
[lib]
@@ -41,4 +42,5 @@ hyper = { workspace = true, optional = true }
tokio = { workspace = true, optional = true }
reqwest = { workspace = true, optional = true }
tracing-subscriber = { workspace = true, optional = true }
lazy_static.workspace = true
lazy_static.workspace = true
tracing-flame = { version = "^0", optional = true }

View File

@@ -60,7 +60,7 @@ pub struct ListableFlow {
pub struct NewFlow {
pub path: String,
pub summary: String,
pub description: String,
pub description: Option<String>,
pub value: serde_json::Value,
pub schema: Option<Schema>,
pub draft_only: Option<bool>,
@@ -83,6 +83,8 @@ pub struct FlowValue {
pub concurrency_time_window_s: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub skip_expr: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_ttl: Option<u32>,
}
#[derive(Deserialize, Serialize, Debug, Clone)]

View File

@@ -0,0 +1,58 @@
pub const WORKER_S3_BUCKET_SYNC: &str = "worker_s3_bucket_sync";
pub const ENV_SETTINGS: [&str; 54] = [
"DISABLE_NSJAIL",
"DISABLE_SERVER",
"NUM_WORKERS",
"METRICS_ADDR",
"JSON_FMT",
"BASE_URL",
"TIMEOUT",
"ZOMBIE_JOB_TIMEOUT",
"RESTART_ZOMBIE_JOBS",
"SLEEP_QUEUE",
"MAX_LOG_SIZE",
"SERVER_BIND_ADDR",
"PORT",
"KEEP_JOB_DIR",
"S3_CACHE_BUCKET",
"TAR_CACHE_RATE",
"COOKIE_DOMAIN",
"PYTHON_PATH",
"DENO_PATH",
"GO_PATH",
"GOPRIVATE",
"GOPROXY",
"NETRC",
"PIP_INDEX_URL",
"PIP_EXTRA_INDEX_URL",
"PIP_TRUSTED_HOST",
"PATH",
"HOME",
"DATABASE_CONNECTIONS",
"TIMEOUT_WAIT_RESULT",
"QUEUE_LIMIT_WAIT_RESULT",
"DENO_AUTH_TOKENS",
"DENO_FLAGS",
"NPM_CONFIG_REGISTRY",
"PIP_LOCAL_DEPENDENCIES",
"ADDITIONAL_PYTHON_PATHS",
"INCLUDE_HEADERS",
"INSTANCE_EVENTS_WEBHOOK",
"CLOUD_HOSTED",
"GLOBAL_CACHE_INTERVAL",
"WORKER_TAGS",
"CUSTOM_TAGS",
"JOB_RETENTION_SECS",
"WAIT_RESULT_FAST_POLL_DURATION_SECS",
"WAIT_RESULT_SLOW_POLL_INTERVAL_MS",
"WAIT_RESULT_FAST_POLL_INTERVAL_MS",
"EXIT_AFTER_NO_JOB_FOR_SECS",
"REQUEST_SIZE_LIMIT",
"SMTP_HOST",
"SMTP_USERNAME",
"SMTP_PORT",
"SMTP_TLS_IMPLICIT",
"CREATE_WORKSPACE_REQUIRE_SUPERADMIN",
"GLOBAL_ERROR_HANDLER_PATH_IN_ADMINS_WORKSPACE",
];

View File

@@ -90,6 +90,8 @@ pub struct QueuedJob {
pub timeout: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub flow_step_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_ttl: Option<i32>,
}
impl QueuedJob {
@@ -99,6 +101,17 @@ impl QueuedJob {
.map(String::as_str)
.unwrap_or("tmp/main")
}
pub fn is_flow(&self) -> bool {
matches!(self.job_kind, JobKind::Flow | JobKind::FlowPreview)
}
pub fn full_path(&self) -> String {
format!(
"{}/{}",
if self.is_flow() { "flow" } else { "script" },
self.script_path()
)
}
}
impl QueuedJob {
@@ -156,6 +169,7 @@ impl Default for QueuedJob {
concurrency_time_window_s: None,
timeout: None,
flow_step_id: None,
cache_ttl: None,
}
}
}
@@ -170,6 +184,7 @@ pub enum JobPayload {
path: String,
concurrent_limit: Option<i32>,
concurrency_time_window_s: Option<i32>,
cache_ttl: Option<i32>,
},
Code(RawCode),
Dependencies {
@@ -202,6 +217,7 @@ pub struct RawCode {
pub lock: Option<String>,
pub concurrent_limit: Option<i32>,
pub concurrency_time_window_s: Option<i32>,
pub cache_ttl: Option<i32>,
}
type Tag = String;
@@ -216,7 +232,7 @@ pub async fn script_path_to_payload(
let (job_payload, tag) = if script_path.starts_with("hub/") {
(JobPayload::ScriptHub { path: script_path.to_owned() }, None)
} else {
let (script_hash, tag, concurrent_limit, concurrency_time_window_s) =
let (script_hash, tag, concurrent_limit, concurrency_time_window_s, cache_ttl) =
get_latest_deployed_hash_for_path(db, w_id, script_path).await?;
(
JobPayload::ScriptHash {
@@ -224,6 +240,7 @@ pub async fn script_path_to_payload(
path: script_path.to_owned(),
concurrent_limit,
concurrency_time_window_s,
cache_ttl: cache_ttl,
},
tag,
)
@@ -235,9 +252,9 @@ pub async fn script_hash_to_tag_and_limits<'c>(
script_hash: &ScriptHash,
db: &mut Transaction<'c, Postgres>,
w_id: &String,
) -> error::Result<(Option<Tag>, Option<i32>, Option<i32>)> {
) -> error::Result<(Option<Tag>, Option<i32>, Option<i32>, Option<i32>)> {
let script = sqlx::query!(
"select tag, concurrent_limit, concurrency_time_window_s from script where hash = $1 AND workspace_id = $2",
"select tag, concurrent_limit, concurrency_time_window_s, cache_ttl from script where hash = $1 AND workspace_id = $2",
script_hash.0,
w_id
)
@@ -252,6 +269,7 @@ pub async fn script_hash_to_tag_and_limits<'c>(
script.tag,
script.concurrent_limit,
script.concurrency_time_window_s,
script.cache_ttl,
))
}

View File

@@ -17,6 +17,7 @@ pub mod error;
pub mod external_ip;
pub mod flow_status;
pub mod flows;
pub mod global_settings;
pub mod jobs;
pub mod more_serde;
pub mod oauth2;
@@ -163,24 +164,31 @@ pub async fn get_latest_deployed_hash_for_path(
db: &DB,
w_id: &str,
script_path: &str,
) -> error::Result<(scripts::ScriptHash, Option<Tag>, Option<i32>, Option<i32>)> {
) -> error::Result<(
scripts::ScriptHash,
Option<Tag>,
Option<i32>,
Option<i32>,
Option<i32>,
)> {
let r_o = sqlx::query!(
"select hash, tag, concurrent_limit, concurrency_time_window_s from script where path = $1 AND workspace_id = $2 AND
"select hash, tag, concurrent_limit, concurrency_time_window_s, cache_ttl from script where path = $1 AND workspace_id = $2 AND
created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2 AND
deleted = false AND archived = false AND lock IS not NULL AND lock_error_logs IS NULL)",
deleted = false AND lock IS not NULL AND lock_error_logs IS NULL)",
script_path,
w_id
)
.fetch_optional(db)
.await?;
let script = utils::not_found_if_none(r_o, "script", script_path)?;
let script = utils::not_found_if_none(r_o, "deployed script", script_path)?;
Ok((
scripts::ScriptHash(script.hash),
script.tag,
script.concurrent_limit,
script.concurrency_time_window_s,
script.cache_ttl,
))
}
@@ -188,9 +196,15 @@ pub async fn get_latest_hash_for_path<'c>(
db: &mut sqlx::Transaction<'c, sqlx::Postgres>,
w_id: &str,
script_path: &str,
) -> error::Result<(scripts::ScriptHash, Option<Tag>, Option<i32>, Option<i32>)> {
) -> error::Result<(
scripts::ScriptHash,
Option<Tag>,
Option<i32>,
Option<i32>,
Option<i32>,
)> {
let r_o = sqlx::query!(
"select hash, tag, concurrent_limit, concurrency_time_window_s from script where path = $1 AND workspace_id = $2 AND
"select hash, tag, concurrent_limit, concurrency_time_window_s, cache_ttl from script where path = $1 AND workspace_id = $2 AND
created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2 AND
deleted = false AND archived = false)",
script_path,
@@ -206,5 +220,6 @@ pub async fn get_latest_hash_for_path<'c>(
script.tag,
script.concurrent_limit,
script.concurrency_time_window_s,
script.cache_ttl,
))
}

View File

@@ -214,6 +214,7 @@ pub struct NewScript {
pub envs: Option<Vec<String>>,
pub concurrent_limit: Option<i32>,
pub concurrency_time_window_s: Option<i32>,
pub cache_ttl: Option<i32>,
}
#[derive(Deserialize)]

View File

@@ -48,3 +48,19 @@ pub fn initialize_tracing() {
.init(),
}
}
#[cfg(feature = "flamegraph")]
use tracing_flame::FlameLayer;
#[cfg(feature = "flamegraph")]
pub fn setup_flamegraph() -> impl Drop {
// let fmt_layer = Layer::default();
let (flame_layer, _guard) = FlameLayer::with_file("./tracing.folded").unwrap();
tracing_subscriber::registry()
// .with(fmt_layer)
.with(flame_layer)
.init();
_guard
}

View File

@@ -11,6 +11,7 @@ path = "src/lib.rs"
[features]
default = []
enterprise = []
benchmark = []
[dependencies]
windmill-audit.workspace = true

View File

@@ -8,10 +8,12 @@
use std::{collections::HashMap, sync::atomic::AtomicBool, vec};
#[cfg(feature = "benchmark")]
use std::time::Instant;
use anyhow::Context;
use async_recursion::async_recursion;
use chrono::{DateTime, Duration, Utc};
use itertools::Itertools;
use reqwest::Client;
use rsmq_async::RsmqConnection;
use serde_json::json;
@@ -91,26 +93,9 @@ lazy_static::lazy_static! {
pub static ref IS_WORKER_TAGS_DEFINED: bool = std::env::var("WORKER_TAGS").ok().is_some();
pub static ref PULL_QUERY: String = format!(
"UPDATE queue
SET running = true
, started_at = coalesce(started_at, now())
, last_ping = now()
, suspend_until = null
WHERE id = (
SELECT id
FROM queue
WHERE ((running = false AND scheduled_for <= now())
OR (suspend_until IS NOT NULL AND (suspend <= 0 OR suspend_until <= now())))
{}
ORDER BY scheduled_for, created_at
FOR UPDATE SKIP LOCKED
LIMIT 1
)
RETURNING *",
format!(" AND ({})",
ACCEPTED_TAGS.clone().into_iter().map(|x| format!("(tag = '{x}')")).join(" OR "))
);
// When compiled in 'benchmark' mode, this flags is exposed via the /workers/toggle endpoint
// and make it possible to disable to current active workers (such that they don't pull any)
@@ -161,11 +146,12 @@ pub async fn cancel_job<'c: 'async_recursion>(
let reason = reason
.clone()
.unwrap_or_else(|| "No reason provided".to_string());
let e = serde_json::json!({"message": format!("Job canceled: {reason} by {username}"), "name": "Canceled", "reason": reason, "canceler": username});
let add_job = add_completed_job_error(
&db,
&job_running,
format!("canceled by {username}: (force cancel: {force_cancel})"),
serde_json::json!({"message": format!("Job canceled: {reason} by {username}"), "name": "Canceled", "reason": reason, "canceler": username}),
&e,
None,
rsmq.clone(),
)
@@ -216,7 +202,7 @@ pub async fn add_completed_job_error<R: rsmq_async::RsmqConnection + Clone + Sen
db: &Pool<Postgres>,
queued_job: &QueuedJob,
logs: String,
e: serde_json::Value,
e: &serde_json::Value,
metrics: Option<Metrics>,
rsmq: Option<R>,
) -> Result<serde_json::Value, Error> {
@@ -224,7 +210,7 @@ pub async fn add_completed_job_error<R: rsmq_async::RsmqConnection + Clone + Sen
metrics.map(|m| m.worker_execution_failed.inc());
}
let result = serde_json::json!({ "error": e });
let _ = add_completed_job(db, &queued_job, false, false, result.clone(), logs, rsmq).await?;
let _ = add_completed_job(db, &queued_job, false, false, &result, logs, rsmq).await?;
Ok(result)
}
@@ -250,13 +236,13 @@ lazy_static::lazy_static! {
pub static ref GLOBAL_ERROR_HANDLER_PATH_IN_ADMINS_WORKSPACE: Option<String> = std::env::var("GLOBAL_ERROR_HANDLER_PATH_IN_ADMINS_WORKSPACE").ok();
}
#[instrument(level = "trace", skip_all)]
#[instrument(level = "trace", skip_all, name = "add_completed_job")]
pub async fn add_completed_job<R: rsmq_async::RsmqConnection + Clone + Send>(
db: &Pool<Postgres>,
queued_job: &QueuedJob,
success: bool,
skipped: bool,
result: serde_json::Value,
result: &serde_json::Value,
logs: String,
rsmq: Option<R>,
) -> Result<Uuid, Error> {
@@ -399,6 +385,19 @@ pub async fn add_completed_job<R: rsmq_async::RsmqConnection + Clone + Send>(
)
.await?;
}
if queued_job.concurrent_limit.is_some() {
if let Err(e) = sqlx::query_scalar!(
"UPDATE concurrency_counter SET counter = counter - 1 WHERE concurrency_id = $1",
queued_job.full_path()
)
.execute(&mut tx)
.await
{
tracing::error!("Could not decrement concurrency counter: {}", e);
}
tracing::debug!("decremented concurrency counter");
}
tx.commit().await?;
#[cfg(feature = "enterprise")]
@@ -940,14 +939,15 @@ async fn handle_on_recovery<'c, R: rsmq_async::RsmqConnection + Clone + Send + '
pub async fn pull<R: rsmq_async::RsmqConnection + Send + Clone>(
db: &Pool<Postgres>,
rsmq: Option<R>,
suspend_first: bool,
) -> windmill_common::error::Result<Option<QueuedJob>> {
// let rs = rd_string(2);
// let instant = Instant::now();
loop {
let tx: QueueTransaction<'_, _> = (rsmq.clone(), db.clone().begin().await?).into();
let (job, mut tx) =
pull_single_job_and_mark_as_running_no_concurrency_limit(tx, rsmq.clone()).await?;
let job = pull_single_job_and_mark_as_running_no_concurrency_limit(
db,
rsmq.clone(),
suspend_first,
)
.await?;
if job.is_none() {
return Ok(None);
@@ -962,10 +962,13 @@ pub async fn pull<R: rsmq_async::RsmqConnection + Send + Clone>(
if *METRICS_ENABLED {
QUEUE_PULL_COUNT.inc();
}
tx.commit().await?;
return Ok(Option::Some(pulled_job));
}
let itx = db.begin().await?;
let mut tx: QueueTransaction<'_, _> = (rsmq.clone(), itx).into();
// Else the job is subject to concurrency limits
let job_script_path = pulled_job.script_path.clone().unwrap();
@@ -979,15 +982,31 @@ pub async fn pull<R: rsmq_async::RsmqConnection + Send + Clone>(
job_custom_concurrency_time_window_s
);
let running_job = sqlx::query_scalar!(
"INSERT INTO concurrency_counter VALUES ($1, 1)
ON CONFLICT (concurrency_id)
DO UPDATE SET counter = concurrency_counter.counter + 1
RETURNING concurrency_counter.counter",
pulled_job.full_path(),
)
.fetch_one(&mut tx)
.await
.map_err(|e| {
Error::InternalErr(format!(
"Error getting concurrency count for script path {job_script_path}: {e}"
))
})?;
tracing::debug!("running_job: {}", running_job);
let script_path_live_stats = sqlx::query!(
"SELECT COALESCE(j.min_started_at, q.min_started_at) AS min_started_at, COALESCE(completed_count, 0) + COALESCE(running_count, 0) AS total_count
"SELECT COALESCE(j.min_started_at, q.min_started_at) AS min_started_at, COALESCE(completed_count, 0) AS completed_count
FROM
(SELECT script_path, MIN(started_at) as min_started_at, COUNT(*) as completed_count
FROM completed_job
WHERE script_path = $1 AND job_kind != 'dependencies' AND started_at + INTERVAL '1 MILLISECOND' * duration_ms > (now() - INTERVAL '1 second' * $2) AND workspace_id = $3 AND canceled = false
GROUP BY script_path) as j
FULL OUTER JOIN
(SELECT script_path, MIN(started_at) as min_started_at, COUNT(*) as running_count
(SELECT script_path, MIN(started_at) as min_started_at
FROM queue
WHERE script_path = $1 AND job_kind != 'dependencies' AND running = true AND workspace_id = $3 AND canceled = false
GROUP BY script_path) as q
@@ -1004,20 +1023,31 @@ pub async fn pull<R: rsmq_async::RsmqConnection + Send + Clone>(
))
})?;
let concurrent_jobs_for_this_script: Option<i64> = script_path_live_stats.total_count;
let concurrent_jobs_for_this_script =
script_path_live_stats.completed_count.unwrap_or_default() as i32 + running_job;
tracing::debug!(
"Current concurrent jobs for this script: {}",
concurrent_jobs_for_this_script.unwrap_or(-1)
concurrent_jobs_for_this_script
);
if concurrent_jobs_for_this_script.is_none()
|| concurrent_jobs_for_this_script.unwrap() <= i64::from(job_custom_concurrent_limit)
{
if concurrent_jobs_for_this_script <= job_custom_concurrent_limit {
if *METRICS_ENABLED {
QUEUE_PULL_COUNT.inc();
}
tx.commit().await?;
return Ok(Option::Some(pulled_job));
}
let x = sqlx::query_scalar!(
"UPDATE concurrency_counter SET counter = counter - 1 WHERE concurrency_id = $1 RETURNING counter",
pulled_job.full_path()
)
.fetch_one(&mut tx)
.await
.map_err(|e| {
Error::InternalErr(format!(
"Error decreasing concurrency count for script path {job_script_path}: {e}"
))
})?;
tracing::debug!("running_job after decrease: {}", x);
let job_uuid: Uuid = pulled_job.id;
let min_started_at: Option<DateTime<Utc>> = script_path_live_stats.min_started_at;
@@ -1091,24 +1121,35 @@ async fn pull_single_job_and_mark_as_running_no_concurrency_limit<
'c,
R: rsmq_async::RsmqConnection + Send + Clone,
>(
mut tx: QueueTransaction<'c, R>,
db: &Pool<Postgres>,
rsmq: Option<R>,
) -> windmill_common::error::Result<(Option<QueuedJob>, QueueTransaction<'c, R>)> {
suspend_first: bool,
) -> windmill_common::error::Result<Option<QueuedJob>> {
let job: Option<QueuedJob> = if let Some(mut rsmq) = rsmq {
#[cfg(feature = "benchmark")]
let instant = Instant::now();
// TODO: REDIS: Race conditions / replace last_ping
// TODO: shuffle this list to have fairness
let mut all_tags = ACCEPTED_TAGS.clone();
let mut msg: Option<_> = None;
let mut tag = None;
while msg.is_none() && !all_tags.is_empty() {
let ntag = all_tags.pop().unwrap();
tag = Some(ntag.clone());
msg = rsmq
.pop_message::<Vec<u8>>(&all_tags.pop().unwrap())
.receive_message::<Vec<u8>>(&ntag, Some(10))
.await
.map_err(|e| anyhow::anyhow!(e))?;
}
// println!("3.1: {:?} {rs}", instant.elapsed());
// #[cfg(feature = "benchmark")]
// println!("rsmq 1: {:?}", instant.elapsed());
// println!("3.1: {:?} {rs}", instant.elapsed());
if let Some(msg) = msg {
let uuid = Uuid::from_bytes_le(
msg.message
@@ -1116,7 +1157,7 @@ async fn pull_single_job_and_mark_as_running_no_concurrency_limit<
.map_err(|_| anyhow::anyhow!("Failed to parsed Redis message"))?,
);
sqlx::query_as::<_, QueuedJob>(
let m2 = sqlx::query_as::<_, QueuedJob>(
"UPDATE queue
SET running = true
, started_at = coalesce(started_at, now())
@@ -1126,8 +1167,17 @@ async fn pull_single_job_and_mark_as_running_no_concurrency_limit<
RETURNING *",
)
.bind(uuid)
.fetch_optional(&mut tx)
.await?
.fetch_optional(db)
.await?;
rsmq.delete_message(&tag.unwrap(), &msg.id)
.await
.map_err(|e| anyhow::anyhow!(e))?;
#[cfg(feature = "benchmark")]
println!("rsmq 2: {:?}", instant.elapsed());
m2
} else {
None
}
@@ -1139,11 +1189,62 @@ async fn pull_single_job_and_mark_as_running_no_concurrency_limit<
* suspend_until is non-null
* and suspend = 0 when the resume messages are received
* or suspend_until <= now() if it has timed out */
sqlx::query_as::<_, QueuedJob>(&PULL_QUERY)
.fetch_optional(&mut tx)
.await?
let r = if suspend_first {
sqlx::query_as::<_, QueuedJob>("UPDATE queue
SET running = true
, started_at = coalesce(started_at, now())
, last_ping = now()
, suspend_until = null
WHERE id = (
SELECT id
FROM queue
WHERE suspend_until IS NOT NULL AND (suspend <= 0 OR suspend_until <= now()) AND tag = ANY($1)
ORDER BY created_at
FOR UPDATE SKIP LOCKED
LIMIT 1
)
RETURNING *")
.bind(ACCEPTED_TAGS.as_slice())
.fetch_optional(db)
.await?
} else {
None
};
if r.is_none() {
// #[cfg(feature = "benchmark")]
// let instant = Instant::now();
let r = sqlx::query_as::<_, QueuedJob>(
"UPDATE queue
SET running = true
, started_at = coalesce(started_at, now())
, last_ping = now()
, suspend_until = null
WHERE id = (
SELECT id
FROM queue
WHERE running = false AND scheduled_for <= now() AND tag = ANY($1)
ORDER BY scheduled_for, created_at
FOR UPDATE SKIP LOCKED
LIMIT 1
)
RETURNING *",
)
.bind(ACCEPTED_TAGS.as_slice())
.fetch_optional(db)
.await?;
// #[cfg(feature = "benchmark")]
// println!("pull query: {:?}", instant.elapsed());
r
} else {
r
}
};
Ok((job, tx))
Ok(job)
}
#[async_recursion]
@@ -1409,8 +1510,15 @@ pub async fn push<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
language,
concurrent_limit,
concurrency_time_window_s,
cache_ttl,
) = match job_payload {
JobPayload::ScriptHash { hash, path, concurrent_limit, concurrency_time_window_s } => {
JobPayload::ScriptHash {
hash,
path,
concurrent_limit,
concurrency_time_window_s,
cache_ttl,
} => {
let language = fetch_scalar_isolated!(sqlx::query_scalar!(
"SELECT language as \"language: ScriptLang\" FROM script WHERE hash = $1 AND workspace_id = $2",
hash.0,
@@ -1431,6 +1539,7 @@ pub async fn push<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
Some(language),
concurrent_limit,
concurrency_time_window_s,
cache_ttl,
)
}
JobPayload::ScriptHub { path } => {
@@ -1444,6 +1553,7 @@ pub async fn push<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
None,
None,
None,
None,
)
}
JobPayload::Code(RawCode {
@@ -1453,6 +1563,7 @@ pub async fn push<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
lock,
concurrent_limit,
concurrency_time_window_s,
cache_ttl,
}) => (
None,
path,
@@ -1462,6 +1573,7 @@ pub async fn push<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
Some(language),
concurrent_limit,
concurrency_time_window_s,
cache_ttl,
),
JobPayload::Dependencies { hash, dependencies, language, path } => (
Some(hash.0),
@@ -1472,6 +1584,7 @@ pub async fn push<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
Some(language),
None,
None,
None,
),
JobPayload::FlowDependencies { path } => {
let value_json = fetch_scalar_isolated!(
@@ -1497,6 +1610,7 @@ pub async fn push<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
None,
None,
None,
None,
)
}
JobPayload::AppDependencies { path, version } => (
@@ -1508,6 +1622,7 @@ pub async fn push<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
None,
None,
None,
None,
),
JobPayload::RawFlow { value, path } => (
None,
@@ -1518,6 +1633,7 @@ pub async fn push<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
None,
value.concurrent_limit.clone(),
value.concurrency_time_window_s,
value.cache_ttl.map(|x| x as i32),
),
JobPayload::Flow(flow) => {
let value_json = fetch_scalar_isolated!(
@@ -1543,10 +1659,31 @@ pub async fn push<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
None,
value.concurrent_limit.clone(),
value.concurrency_time_window_s,
value.cache_ttl.map(|x| x as i32),
)
}
JobPayload::Identity => (None, None, None, JobKind::Identity, None, None, None, None),
JobPayload::Noop => (None, None, None, JobKind::Noop, None, None, None, None),
JobPayload::Identity => (
None,
None,
None,
JobKind::Identity,
None,
None,
None,
None,
None,
),
JobPayload::Noop => (
None,
None,
None,
JobKind::Noop,
None,
None,
None,
None,
None,
),
};
let is_running = same_worker;
@@ -1618,12 +1755,13 @@ pub async fn push<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
"deno".to_string()
}
};
tag.unwrap_or_else(|| {
language
.as_ref()
.map(|x| x.as_str().replace("$workspace", workspace_id))
.unwrap_or_else(default)
})
tag.map(|x| x.as_str().replace("$workspace", workspace_id).to_string())
.unwrap_or_else(|| {
language
.as_ref()
.map(|x| x.as_str().to_string())
.unwrap_or_else(default)
})
};
let mut tx = match tx {
@@ -1657,8 +1795,8 @@ pub async fn push<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
"INSERT INTO queue
(workspace_id, id, running, parent_job, created_by, permissioned_as, scheduled_for,
script_hash, script_path, raw_code, raw_lock, args, job_kind, schedule_path, raw_flow, \
flow_status, is_flow_step, language, started_at, same_worker, pre_run_error, email, visible_to_owner, root_job, tag, concurrent_limit, concurrency_time_window_s, timeout, flow_step_id)
VALUES ($1, $2, $3, $4, $5, $6, COALESCE($7, now()), $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, CASE WHEN $3 THEN now() END, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28) \
flow_status, is_flow_step, language, started_at, same_worker, pre_run_error, email, visible_to_owner, root_job, tag, concurrent_limit, concurrency_time_window_s, timeout, flow_step_id, cache_ttl)
VALUES ($1, $2, $3, $4, $5, $6, COALESCE($7, now()), $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, CASE WHEN $3 THEN now() END, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29) \
RETURNING id",
workspace_id,
job_id,
@@ -1687,7 +1825,8 @@ pub async fn push<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
concurrent_limit,
concurrency_time_window_s,
custom_timeout,
flow_step_id
flow_step_id,
cache_ttl
)
.fetch_one(&mut tx)
.await

View File

@@ -74,7 +74,7 @@ pub async fn push_scheduled_job<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
let (payload, tag) = if schedule.is_flow {
(JobPayload::Flow(schedule.script_path), None)
} else {
let (hash, tag, concurrent_limit, concurrency_time_window_s) =
let (hash, tag, concurrent_limit, concurrency_time_window_s, cache_ttl) =
windmill_common::get_latest_hash_for_path(
tx.transaction_mut(),
&schedule.workspace_id,
@@ -87,6 +87,7 @@ pub async fn push_scheduled_job<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
path: schedule.script_path,
concurrent_limit: concurrent_limit,
concurrency_time_window_s: concurrency_time_window_s,
cache_ttl: cache_ttl,
},
tag,
)

View File

@@ -11,6 +11,8 @@ path = "src/lib.rs"
[features]
default = []
enterprise = ["windmill-queue/enterprise", "dep:gcp_auth", "dep:jsonwebtoken", "dep:pem", "dep:sha2"]
benchmark = ["windmill-queue/benchmark"]
flamegraph = []
[dependencies]
windmill-queue.workspace = true

View File

@@ -6,7 +6,7 @@ use windmill_queue::HTTP_CLIENT;
use serde::Deserialize;
use crate::{common::transform_json_value, AuthedClient, JobCompleted};
use crate::{common::transform_json_value, AuthedClient};
use gcp_auth::{AuthenticationManager, CustomServiceAccount};
@@ -55,7 +55,7 @@ pub async fn do_bigquery(
job: QueuedJob,
client: &AuthedClient,
query: &str,
) -> windmill_common::error::Result<JobCompleted> {
) -> windmill_common::error::Result<serde_json::Value> {
let args = if let Some(args) = &job.args {
Some(transform_json_value("args", client, &job.workspace_id, args.clone()).await?)
} else {
@@ -176,12 +176,7 @@ pub async fn do_bigquery(
}
if result.rows.is_none() || result.rows.as_ref().unwrap().len() == 0 {
return Ok(JobCompleted {
job: job,
result: Value::Array(vec![]),
logs: "".to_string(),
success: true,
});
return Ok(Value::Array(vec![]));
}
if result.schema.is_none() {
@@ -223,12 +218,7 @@ pub async fn do_bigquery(
})
.collect();
return Ok(JobCompleted {
job: job,
result: rows,
logs: "".to_string(),
success: true,
});
return Ok(rows);
}
Err(e) => match response.json::<BigqueryErrorResponse>().await {
Ok(bq_err) => return Err(Error::ExecutionErr(bq_err.error.message)),

View File

@@ -71,8 +71,6 @@ pub async fn gen_lockfile(
.stderr(Stdio::piped())
.spawn()?;
// logs.push_str(format!("prepare: {:?}\n", start.elapsed().as_micros()).as_str());
// start = Instant::now();
handle_child(
job_id,
db,
@@ -139,8 +137,6 @@ pub async fn install_lockfile(
.stderr(Stdio::piped())
.spawn()?;
// logs.push_str(format!("prepare: {:?}\n", start.elapsed().as_micros()).as_str());
// start = Instant::now();
handle_child(
job_id,
db,
@@ -170,8 +166,6 @@ pub async fn handle_bun_job(
envs: HashMap<String, String>,
shared_mount: &str,
) -> error::Result<serde_json::Value> {
set_logs(&logs, &job.id, &db).await;
let _ = write_file(job_dir, "main.ts", inner_content).await?;
let common_bun_proc_envs: HashMap<String, String> =
get_common_bun_proc_envs(&base_internal_url);
@@ -209,6 +203,7 @@ pub async fn handle_bun_job(
.await?;
} else if !*DISABLE_NSJAIL {
logs.push_str("\n\n--- BUN INSTALL ---\n");
set_logs(&logs, &job.id, &db).await;
let _ = gen_lockfile(
logs,
&job.id,
@@ -224,9 +219,12 @@ pub async fn handle_bun_job(
.await?;
}
// let mut start = Instant::now();
logs.push_str("\n\n--- BUN CODE EXECUTION ---\n");
set_logs(&logs, &job.id, &db).await;
let logs_f = async {
set_logs(&logs, &job.id, &db).await;
Ok(()) as error::Result<()>
};
let write_wrapper_f = async {
// let mut start = Instant::now();
@@ -291,27 +289,35 @@ run().catch(async (e) => {{
Ok(reserved_variables) as error::Result<HashMap<String, String>>
};
let (reserved_variables, _) = tokio::try_join!(reserved_variables_args_out_f, write_wrapper_f)?;
let _ = write_file(
&job_dir,
"loader.bun.ts",
&format!(
r#"
let write_loader_f = async {
write_file(
&job_dir,
"loader.bun.ts",
&format!(
r#"
import {{ plugin }} from "bun";
{}
plugin(p)
"#,
RELATIVE_BUN_LOADER
.replace("W_ID", &job.workspace_id)
.replace("BASE_INTERNAL_URL", base_internal_url)
.replace("TOKEN", &client.get_token().await)
.replace("CURRENT_PATH", job.script_path())
),
)
.await?;
RELATIVE_BUN_LOADER
.replace("W_ID", &job.workspace_id)
.replace("BASE_INTERNAL_URL", base_internal_url)
.replace("TOKEN", &client.get_token().await)
.replace("CURRENT_PATH", job.script_path())
),
)
.await?;
Ok(()) as error::Result<()>
};
let (reserved_variables, _, _, _) = tokio::try_join!(
reserved_variables_args_out_f,
write_wrapper_f,
logs_f,
write_loader_f
)?;
//do not cache local dependencies
let child = if !*DISABLE_NSJAIL {
@@ -370,8 +376,6 @@ plugin(p)
.spawn()?
};
// logs.push_str(format!("prepare: {:?}\n", start.elapsed().as_micros()).as_str());
// start = Instant::now();
handle_child(
&job.id,
db,
@@ -384,7 +388,6 @@ plugin(p)
job.timeout,
)
.await?;
// logs.push_str(format!("execute: {:?}\n", start.elapsed().as_millis()).as_str());
read_result(job_dir).await
}

View File

@@ -2,6 +2,7 @@ use async_recursion::async_recursion;
use serde_json::{json, Value};
use sqlx::{Pool, Postgres};
use tokio::{fs::File, io::AsyncReadExt};
use windmill_api_client::{types::CreateResource, Client};
use windmill_common::{
error::{self, Error},
jobs::QueuedJob,
@@ -10,7 +11,12 @@ use windmill_queue::CLOUD_HOSTED;
use anyhow::Result;
use std::{
borrow::Borrow, collections::HashMap, io, os::unix::process::ExitStatusExt, panic,
borrow::Borrow,
collections::{hash_map::DefaultHasher, HashMap},
hash::{Hash, Hasher},
io,
os::unix::process::ExitStatusExt,
panic,
time::Duration,
};
@@ -289,7 +295,7 @@ pub async fn handle_child(
.expect("update worker ping");
}
let mem_peak = get_mem_peak(pid, nsjail).await;
tracing::info!("{job_id} still running. mem peak: {}kB", mem_peak);
tracing::info!("{job_id} in {} still running. mem peak: {}kB", _w_id, mem_peak);
let mem_peak = if mem_peak > 0 { Some(mem_peak) } else { None };
if sqlx::query_scalar!("UPDATE queue SET mem_peak = GREATEST($1, mem_peak), last_ping = now() WHERE id = $2 RETURNING canceled", mem_peak, job_id)
.fetch_optional(&db)
@@ -566,6 +572,32 @@ fn append_with_limit(dst: &mut String, src: &str, limit: &mut usize) {
}
}
pub fn hash_args(v: &serde_json::Value) -> i64 {
let mut dh = DefaultHasher::new();
serde_json::to_string(v).unwrap().hash(&mut dh);
dh.finish() as i64
}
pub async fn save_in_cache(client: &AuthedClient, job: &QueuedJob, cached_path: String, r: &Value) {
let client: &Client = client.get_client();
let expire = chrono::Utc::now().timestamp() + job.cache_ttl.unwrap() as i64;
let cr = &CreateResource {
path: cached_path,
description: None,
resource_type: "cache".to_string(),
value: serde_json::json!({
"value": r,
"expire": expire
}),
};
if let Err(e) = client
.create_resource(&job.workspace_id, Some(true), cr)
.await
{
tracing::error!("Error creating cache resource {e}")
}
}
/* TODO retry this? */
#[tracing::instrument(level = "trace", skip_all)]
async fn append_logs(job_id: uuid::Uuid, logs: impl AsRef<str>, db: impl Borrow<Pool<Postgres>>) {

View File

@@ -71,9 +71,21 @@ pub async fn generate_deno_lock(
db: &sqlx::Pool<sqlx::Postgres>,
w_id: &str,
worker_name: &str,
base_internal_url: &str,
) -> error::Result<String> {
let _ = write_file(job_dir, "main.ts", code).await?;
let import_map_path = format!("{job_dir}/import_map.json");
let import_map = format!(
r#"{{
"imports": {{
"/": "{base_internal_url}/api/scripts_u/empty_ts/"
}}
}}"#,
);
write_file(job_dir, "import_map.json", &import_map).await?;
write_file(job_dir, "empty.ts", "").await?;
let child = Command::new(DENO_PATH.as_str())
.current_dir(job_dir)
.args(vec![
@@ -81,6 +93,8 @@ pub async fn generate_deno_lock(
"--unstable",
"--lock=lock.json",
"--lock-write",
"--import-map",
&import_map_path,
"main.ts",
])
.stdout(Stdio::piped())

View File

@@ -6,6 +6,11 @@ use itertools::Itertools;
use rand::Rng;
#[cfg(feature = "enterprise")]
use std::process::Stdio;
#[cfg(feature = "enterprise")]
use windmill_common::DB;
#[cfg(feature = "enterprise")]
use windmill_common::global_settings::WORKER_S3_BUCKET_SYNC;
#[cfg(feature = "enterprise")]
use tokio::{process::Command, sync::mpsc::Sender, time::Instant};
@@ -219,6 +224,25 @@ pub async fn copy_cache_to_bucket(bucket: &str) -> error::Result<()> {
Ok(())
}
#[cfg(feature = "enterprise")]
pub async fn worker_s3_bucket_sync_enabled(db: &DB) -> bool {
let q = sqlx::query!(
"SELECT value FROM global_settings WHERE name = $1",
WORKER_S3_BUCKET_SYNC
)
.fetch_optional(db)
.await;
if let Ok(q) = q {
let r = q.map(|x| x.value.as_bool().unwrap_or(true)).unwrap_or(true);
tracing::info!("Got global setting {WORKER_S3_BUCKET_SYNC}: {}", r);
r
} else {
tracing::info!("Failed to get global setting {WORKER_S3_BUCKET_SYNC}");
false
}
}
#[cfg(feature = "enterprise")]
pub async fn copy_cache_to_bucket_as_tar(bucket: &str) {
tracing::info!("Copying cache to bucket {bucket} as tar");

View File

@@ -12,7 +12,7 @@ use windmill_common::{
jobs::QueuedJob,
utils::calculate_hash,
};
use windmill_parser_go::parse_go_imports;
use windmill_parser_go::{parse_go_imports, REQUIRE_PARSE};
use crate::{
common::{
@@ -296,6 +296,9 @@ async fn gen_go_mod(
}
}
use std::fs::OpenOptions;
use std::io::prelude::*;
pub async fn install_go_dependencies(
job_id: &Uuid,
code: &str,
@@ -329,6 +332,16 @@ pub async fn install_go_dependencies(
None,
)
.await?;
for x in REQUIRE_PARSE.captures_iter(code) {
let mut file = OpenOptions::new()
.write(true)
.append(true)
.open(format!("{job_dir}/go.mod"))
.unwrap();
writeln!(file, "require {}\n", &x[1])?;
}
}
let mut new_lockfile = false;

View File

@@ -7,7 +7,7 @@ use windmill_queue::HTTP_CLIENT;
use serde::Deserialize;
use crate::{common::transform_json_value, AuthedClient, JobCompleted};
use crate::{common::transform_json_value, AuthedClient};
#[derive(Deserialize)]
struct GraphqlApi {
@@ -31,7 +31,7 @@ pub async fn do_graphql(
job: QueuedJob,
client: &AuthedClient,
query: &str,
) -> windmill_common::error::Result<JobCompleted> {
) -> windmill_common::error::Result<serde_json::Value> {
let args = if let Some(args) = &job.args {
Some(transform_json_value("args", client, &job.workspace_id, args.clone()).await?)
} else {
@@ -88,10 +88,5 @@ pub async fn do_graphql(
}
// And then check that we got back the same string we sent over.
return Ok(JobCompleted {
job: job,
result: result.data.unwrap_or(json!({})),
logs: "".to_string(),
success: true,
});
return Ok(result.data.unwrap_or(json!({})));
}

View File

@@ -67,6 +67,7 @@ impl TimersPermission for PermissionsContainer {
()
}
}
pub struct OptAuthedClient(Option<AuthedClient>);
pub async fn eval_timeout(
expr: String,
@@ -122,8 +123,6 @@ pub async fn eval_timeout(
.enable_all()
.build()?;
let re = Regex::new(r"import (.*)\n").unwrap();
let expr = re.replace_all(&expr, "").to_string();
// pretty frail but this it to make the expr more user friendly and not require the user to write await
let expr = ["variable", "step", "resource", "result_by_id"]
.into_iter()

View File

@@ -10,7 +10,7 @@ use windmill_common::{
};
use windmill_parser_sql::parse_mysql_sig;
use crate::{common::transform_json_value, AuthedClient, JobCompleted};
use crate::{common::transform_json_value, AuthedClient};
#[derive(Deserialize)]
struct MysqlDatabase {
@@ -26,7 +26,7 @@ pub async fn do_mysql(
job: QueuedJob,
client: &AuthedClient,
query: &str,
) -> windmill_common::error::Result<JobCompleted> {
) -> windmill_common::error::Result<serde_json::Value> {
let args = if let Some(args) = &job.args {
Some(transform_json_value("args", client, &job.workspace_id, args.clone()).await?)
} else {
@@ -114,7 +114,7 @@ pub async fn do_mysql(
pool.disconnect().await.map_err(to_anyhow)?;
// And then check that we got back the same string we sent over.
return Ok(JobCompleted { job: job, result: json!(rows), logs: "".to_string(), success: true });
return Ok(json!(rows));
}
fn convert_row_to_value(row: Row) -> serde_json::Value {

View File

@@ -18,7 +18,7 @@ use windmill_common::{error::to_anyhow, jobs::QueuedJob};
use windmill_parser_sql::parse_pgsql_sig;
use crate::common::transform_json_value;
use crate::{AuthedClient, JobCompleted};
use crate::AuthedClient;
use urlencoding::encode;
#[derive(Deserialize)]
@@ -36,7 +36,7 @@ pub async fn do_postgresql(
job: QueuedJob,
client: &AuthedClient,
query: &str,
) -> error::Result<JobCompleted> {
) -> error::Result<serde_json::Value> {
let args = if let Some(args) = &job.args {
Some(transform_json_value("args", client, &job.workspace_id, args.clone()).await?)
} else {
@@ -181,7 +181,7 @@ pub async fn do_postgresql(
handle.abort();
// And then check that we got back the same string we sent over.
return Ok(JobCompleted { job: job, result, logs: "".to_string(), success: true });
return Ok(result);
}
pub fn pg_cell_to_json_value(

View File

@@ -280,8 +280,7 @@ pub async fn handle_python_job(
let name = &x.name;
format!(
"if \"{name}\" in kwargs and kwargs[\"{name}\"] is not None:\n \
kwargs[\"{name}\"] = datetime.strptime(kwargs[\"{name}\"], \
'%Y-%m-%dT%H:%M')\n",
kwargs[\"{name}\"] = datetime.fromisoformat(kwargs[\"{name}\"])\n",
)
}
_ => "".to_string(),

View File

@@ -12,7 +12,7 @@ use windmill_queue::HTTP_CLIENT;
use serde::{Deserialize, Serialize};
use crate::{common::transform_json_value, AuthedClient, JobCompleted};
use crate::{common::transform_json_value, AuthedClient};
#[derive(Serialize)]
struct Claims {
@@ -64,7 +64,7 @@ pub async fn do_snowflake(
job: QueuedJob,
client: &AuthedClient,
query: &str,
) -> windmill_common::error::Result<JobCompleted> {
) -> windmill_common::error::Result<serde_json::Value> {
let args = if let Some(args) = &job.args {
Some(transform_json_value("args", client, &job.workspace_id, args.clone()).await?)
} else {
@@ -201,7 +201,7 @@ pub async fn do_snowflake(
})
.collect();
Ok(JobCompleted { job: job, result: rows, logs: "".to_string(), success: true })
Ok(rows)
}
Err(e) => {
let resp = response.text().await.unwrap_or("".to_string());

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,7 @@
use std::collections::HashMap;
use std::time::Duration;
use crate::common::{hash_args, save_in_cache};
use crate::js_eval::{eval_timeout, IdContext};
use crate::{AuthedClient, KEEP_JOB_DIR};
use anyhow::Context;
@@ -53,7 +54,6 @@ pub async fn update_flow_status_after_job_completion<
same_worker_tx: Sender<Uuid>,
worker_dir: &str,
stop_early_override: Option<bool>,
base_internal_url: &str,
rsmq: Option<R>,
) -> error::Result<()> {
// this is manual tailrecursion because async_recursion blows up the stack
@@ -70,7 +70,6 @@ pub async fn update_flow_status_after_job_completion<
same_worker_tx.clone(),
worker_dir,
stop_early_override,
base_internal_url,
rsmq.clone(),
)
.await?;
@@ -88,7 +87,6 @@ pub async fn update_flow_status_after_job_completion<
same_worker_tx.clone(),
worker_dir,
nrec.stop_early_override,
base_internal_url,
rsmq.clone(),
)
.await?;
@@ -118,7 +116,6 @@ pub async fn update_flow_status_after_job_completion_internal<
same_worker_tx: Sender<Uuid>,
worker_dir: &str,
stop_early_override: Option<bool>,
base_internal_url: &str,
rsmq: Option<R>,
) -> error::Result<Option<RecUpdateFlowStatusAfterJobCompletion>> {
let (should_continue_flow, flow_job, stop_early, skip_if_stop_early, nresult) = {
@@ -574,18 +571,27 @@ pub async fn update_flow_status_after_job_completion_internal<
db,
&flow_job,
logs,
canceled_job_to_result(&flow_job),
&canceled_job_to_result(&flow_job),
metrics.clone(),
rsmq.clone(),
)
.await?;
} else {
if flow_job.cache_ttl.is_some() {
let cached_res_path = {
let args_hash = hash_args(&flow_job.args.clone().unwrap_or_else(|| json!({})));
let flow_path = flow_job.script_path();
format!("{flow_path}/flow/cache/{args_hash}")
};
save_in_cache(&client, &flow_job, cached_res_path, &nresult).await;
}
add_completed_job(
db,
&flow_job,
success,
stop_early && skip_if_stop_early,
nresult.clone(),
&nresult,
logs,
rsmq.clone(),
)
@@ -600,17 +606,17 @@ pub async fn update_flow_status_after_job_completion_internal<
nresult.clone(),
same_worker_tx.clone(),
worker_dir,
base_internal_url,
rsmq.clone(),
)
.await
{
Err(err) => {
let e = json!({"message": err.to_string(), "name": "InternalError"});
let _ = add_completed_job_error(
db,
&flow_job,
"Unexpected error during flow chaining:\n".to_string(),
json!({"message": err.to_string(), "name": "InternalError"}),
&e,
metrics.clone(),
rsmq.clone(),
)
@@ -745,28 +751,26 @@ async fn compute_bool_from_expr(
}
}
type CacheAndStep = (Option<i32>, Option<i32>);
pub async fn update_flow_status_in_progress(
db: &DB,
w_id: &str,
flow: Uuid,
job_in_progress: Uuid,
) -> error::Result<CacheAndStep> {
) -> error::Result<Option<i32>> {
let step = get_step_of_flow_status(db, flow).await?;
let cache_ttl = if let Step::Step(step) = step {
let ttl = sqlx::query_scalar(&format!(
if let Step::Step(step) = step {
sqlx::query(&format!(
"UPDATE queue
SET flow_status = jsonb_set(jsonb_set(flow_status, '{{modules, {step}, job}}', $1), '{{modules, {step}, type}}', $2)
WHERE id = $3 AND workspace_id = $4
RETURNING (raw_flow->'modules'->{step}->>'cache_ttl')::int as cache_ttl",
WHERE id = $3 AND workspace_id = $4",
))
.bind(json!(job_in_progress.to_string()))
.bind(json!("InProgress"))
.bind(flow)
.bind(w_id)
.fetch_one(db)
.execute(db)
.await?;
(ttl, Some(step))
Ok(Some(step))
} else {
sqlx::query(&format!(
"UPDATE queue
@@ -779,9 +783,8 @@ pub async fn update_flow_status_in_progress(
.bind(w_id)
.execute(db)
.await?;
(None, None)
};
Ok(cache_ttl)
Ok(None)
}
}
pub enum Step {
@@ -878,7 +881,6 @@ pub async fn handle_flow<R: rsmq_async::RsmqConnection + Send + Sync + Clone>(
last_result: serde_json::Value,
same_worker_tx: Sender<Uuid>,
worker_dir: &str,
base_internal_url: &str,
rsmq: Option<R>,
) -> anyhow::Result<()> {
let value = flow_job
@@ -902,7 +904,6 @@ pub async fn handle_flow<R: rsmq_async::RsmqConnection + Send + Sync + Clone>(
last_result,
same_worker_tx,
worker_dir,
base_internal_url,
rsmq,
)
.await?;
@@ -920,7 +921,6 @@ async fn push_next_flow_job<R: rsmq_async::RsmqConnection + Send + Sync + Clone>
mut last_result: serde_json::Value,
same_worker_tx: Sender<Uuid>,
worker_dir: &str,
base_internal_url: &str,
rsmq: Option<R>,
) -> error::Result<()> {
let job_root = flow_job
@@ -957,7 +957,6 @@ async fn push_next_flow_job<R: rsmq_async::RsmqConnection + Send + Sync + Clone>
same_worker_tx,
worker_dir,
None,
base_internal_url,
rsmq,
)
.await;
@@ -987,7 +986,6 @@ async fn push_next_flow_job<R: rsmq_async::RsmqConnection + Send + Sync + Clone>
same_worker_tx,
worker_dir,
Some(true),
base_internal_url,
rsmq,
)
.await;
@@ -1160,7 +1158,7 @@ async fn push_next_flow_job<R: rsmq_async::RsmqConnection + Send + Sync + Clone>
let logs = "Timed out waiting to be resumed".to_string();
let result = json!({ "error": {"message": logs, "name": "SuspendedTimeout"}});
let _uuid =
add_completed_job(db, &flow_job, success, skipped, result, logs, rsmq).await?;
add_completed_job(db, &flow_job, success, skipped, &result, logs, rsmq).await?;
return Ok(());
}
@@ -1410,6 +1408,7 @@ async fn push_next_flow_job<R: rsmq_async::RsmqConnection + Send + Sync + Clone>
concurrent_limit: None,
concurrency_time_window_s: None,
skip_expr: None,
cache_ttl: None,
},
path: Some(format!("{}/forloop", flow_job.script_path())),
},
@@ -1777,7 +1776,7 @@ async fn compute_next_flow_transform(
} else {
let hash = script_hash.clone().unwrap();
let mut tx: sqlx::Transaction<'_, sqlx::Postgres> = db.begin().await?;
let (tag, concurrent_limit, concurrency_time_window_s) =
let (tag, concurrent_limit, concurrency_time_window_s, cache_ttl) =
script_hash_to_tag_and_limits(&hash, &mut tx, &flow_job.workspace_id).await?;
(
JobPayload::ScriptHash {
@@ -1785,6 +1784,7 @@ async fn compute_next_flow_transform(
path: script_path.to_owned(),
concurrent_limit,
concurrency_time_window_s,
cache_ttl: module.cache_ttl.map(|x| x as i32).ok_or(cache_ttl).ok(),
},
tag,
)
@@ -1816,6 +1816,7 @@ async fn compute_next_flow_transform(
lock: lock.clone(),
concurrent_limit: *concurrent_limit,
concurrency_time_window_s: *concurrency_time_window_s,
cache_ttl: module.cache_ttl.map(|x| x as i32),
}),
tag: tag.clone(),
}),
@@ -1928,6 +1929,7 @@ async fn compute_next_flow_transform(
concurrent_limit: None,
concurrency_time_window_s: None,
skip_expr: None,
cache_ttl: None,
},
path: Some(format!("{}/loop-{}", flow_job.script_path(), ns.index)),
},
@@ -2002,6 +2004,7 @@ async fn compute_next_flow_transform(
concurrent_limit: None,
concurrency_time_window_s: None,
skip_expr: None,
cache_ttl: None,
},
path: Some(format!(
"{}/branchone-{}",
@@ -2045,6 +2048,7 @@ async fn compute_next_flow_transform(
concurrent_limit: None,
concurrency_time_window_s: None,
skip_expr: None,
cache_ttl: None,
},
path: Some(format!(
"{}/branchall-{}",
@@ -2119,6 +2123,7 @@ async fn compute_next_flow_transform(
concurrent_limit: None,
concurrency_time_window_s: None,
skip_expr: None,
cache_ttl: None,
},
path: Some(format!(
"{}/branchall-{}",

View File

@@ -48,26 +48,16 @@ await new Command()
"The workspace to spawn scripts from.",
{ default: "admins" }
)
.option(
"-j --jobs <jobs:number>",
"Number of NOOP jobs to create.",
{ default: 10000 }
)
.option("-j --jobs <jobs:number>", "Number of NOOP jobs to create.", {
default: 10000,
})
.option(
"-b --batches <batches:number>",
"Number of batches to create all the jobs.",
{ default: 1 }
)
.action(
async ({
host,
email,
password,
token,
workspace,
jobs,
batches,
}) => {
async ({ host, email, password, token, workspace, jobs, batches }) => {
windmill.setClient("", host);
console.log(
@@ -105,46 +95,54 @@ await new Command()
windmill.setClient(final_token, host);
const enc = (s: string) => new TextEncoder().encode(s);
console.log("Disabling workers before loading jobs")
console.log("Disabling workers before loading jobs");
const disable_workers = await fetch(
config.server + "/api/workers/toggle?disable=true",
{
method: "GET",
headers: { ["Authorization"]: "Bearer " + config.token },
}
)
);
if (!disable_workers.ok) {
console.error("Unable to disable workers. Is the Windmill server running in benchmark mode?")
console.error(
"Unable to disable workers. Is the Windmill server running in benchmark mode?"
);
}
const jobsSent = jobs;
const batch_num = batches;
console.log(`Bulk creating ${jobsSent} jobs in ${batch_num} batches`)
console.log(`Bulk creating ${jobsSent} jobs in ${batch_num} batches`);
const start_create = Date.now()
const all_create_operations = []
const start_create = Date.now();
const all_create_operations = [];
for (let i = 0; i < batch_num; i++) {
all_create_operations.push(fetch(
config.server +
"/api/w/" +
config.workspace_id +
`/jobs/add_noop_jobs/${jobsSent / batch_num}`,
{
method: "POST",
headers: { ["Authorization"]: "Bearer " + config.token },
}
));
all_create_operations.push(
fetch(
config.server +
"/api/w/" +
config.workspace_id +
`/jobs/add_noop_jobs/${jobsSent / batch_num}`,
{
method: "POST",
headers: { ["Authorization"]: "Bearer " + config.token },
}
)
);
}
await Promise.all(all_create_operations)
await Promise.all(all_create_operations);
const end_create = Date.now()
const create_duration = end_create - start_create
console.log(`Jobs successfully added to the queue in ${create_duration}s. Windmill will start pulling them\n`)
const start = Date.now()
const end_create = Date.now();
const create_duration = end_create - start_create;
console.log(
`Jobs successfully added to the queue in ${create_duration}s. Windmill will start pulling them\n`
);
const start = Date.now();
let queue_length = jobsSent
let queue_length = jobsSent;
let lastElapsed = 0;
let lastQueueLength = queue_length;
const updateState = setInterval(async () => {
const elapsed = start ? Math.ceil((Date.now() - start) / 1000) : 0;
const elapsed = start ? Date.now() - start : 0;
queue_length = (
await (
await fetch(
@@ -153,27 +151,41 @@ await new Command()
)
).json()
).database_length;
const avgThr = (((jobsSent - queue_length) / elapsed) * 1000).toFixed(
2
);
const instThr =
lastElapsed > 0
? (
((lastQueueLength - queue_length) / (elapsed - lastElapsed)) *
1000
).toFixed(2)
: 0;
lastElapsed = elapsed;
lastQueueLength = queue_length;
await Deno.stdout.write(
enc(
`elapsed: ${elapsed} | jobs executed: ${JSON.stringify(
`elapsed: ${(elapsed / 1000).toFixed(2)} | jobs executed: ${
jobsSent - queue_length
)}/${jobsSent} (thr: ${((jobsSent - queue_length) / elapsed).toFixed(
2
)}) | queue: ${queue_length} \r`
}/${jobsSent} (thr: inst ${instThr} - avg ${avgThr}) | queue: ${queue_length} \r`
)
);
}, 100);
console.log("Enabling workers to start processing jobs")
console.log("Enabling workers to start processing jobs");
const enable_workers = await fetch(
config.server + "/api/workers/toggle?disable=false",
{
method: "GET",
headers: { ["Authorization"]: "Bearer " + config.token },
}
)
);
if (!enable_workers.ok) {
console.error("Unable to disable workers. Is the Windmill server running in benchmark mode?")
console.error(
"Unable to disable workers. Is the Windmill server running in benchmark mode?"
);
}
while (queue_length > 0) {
@@ -182,10 +194,12 @@ await new Command()
clearInterval(updateState);
const total_duration_sec = (Date.now() - start) / 1000;
const total_duration_sec = (Date.now() - start) / 1000.0;
console.log(`jobs: ${jobsSent}`);
console.log(`duration: ${total_duration_sec}s`);
console.log(`avg. throughput (jobs/time): ${jobsSent / total_duration_sec}`);
console.log(
`avg. throughput (jobs/time): ${jobsSent / total_duration_sec}`
);
console.log(
"queue length:",

View File

@@ -3,7 +3,7 @@
import { Command } from "https://deno.land/x/cliffy@v0.25.7/command/mod.ts";
import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts";
import * as windmill from "https://deno.land/x/windmill@v1.38.5/mod.ts";
import * as windmill from "https://deno.land/x/windmill@v1.145.0/mod.ts";
import { Action } from "./action.ts";
import { UpgradeCommand } from "https://deno.land/x/cliffy@v0.25.7/command/upgrade/upgrade_command.ts";
import { DenoLandProvider } from "https://deno.land/x/cliffy@v0.25.7/command/upgrade/mod.ts";
@@ -21,7 +21,7 @@ async function login(email: string, password: string): Promise<string> {
});
}
export const VERSION = "v1.161.0";
export const VERSION = "v1.167.0";
await new Command()
.name("wmillbench")
@@ -157,6 +157,7 @@ await new Command()
custom,
}) => {
windmill.setClient("", host);
console.log("Backend version: " + (await fetch(`${host}/api/version`)));
const custom_content: Action | undefined = custom
? JSON.parse(await Deno.readTextFile(custom))
@@ -212,6 +213,7 @@ await new Command()
flowPattern,
scriptPattern,
zombieTimeout,
continous,
},
null,
4
@@ -227,7 +229,9 @@ await new Command()
let final_token: string;
if (!token) {
if (email && password) {
console.log("Logging in with email and password...");
final_token = await login(email, password);
console.log("Logged in!");
} else {
console.error("Token or email with password are required.");
return;
@@ -236,6 +240,8 @@ await new Command()
final_token = token;
}
console.log("Using token", final_token);
config.token = final_token;
windmill.setClient(final_token, host);

21
benchmarks/plot.py Normal file
View File

@@ -0,0 +1,21 @@
import matplotlib.pyplot as plt
import json
with open("/tmp/windmill/profiling.json") as json_file:
data = json.load(json_file)
i = 0
for impacts in data["timings"][:]:
i += 1
if i % 200 != 0:
continue
timefilteredForce = plt.plot(impacts)
plt.text(len(impacts) - 1, impacts[-1], f"{i}")
timefilteredForce = plt.xlabel("points")
timefilteredForce = plt.ylabel("Force fast")
# for impacts in data["timings"][0:100]:
# timefilteredForce = plt.plot(impacts)
# timefilteredForce = plt.xlabel("points")
# timefilteredForce = plt.ylabel("Force slow")
plt.show()

View File

@@ -1 +1,3 @@
node_modules
/bin/
/node_modules/

View File

@@ -0,0 +1,5 @@
config:
aws:region: us-east-2
tailscale:apiKey:
secure: AAABAMGx6JDiUSM4/2M7ZLmws26KJYAGVmCb+BdURSGdO32scaf96Xy3Ocu/Ncv7G1y1W6K1EwZieN6L0/cTAZDGCJpRcMrMmuDyqE8u8Uuv2uRLKRA0
tailscale:tailnet: windmill.dev

View File

@@ -0,0 +1,3 @@
name: aws-bench
runtime: nodejs
description: Windmill Benchmarks on AWS

BIN
benchmarks/pulumi/bun.lockb Executable file

Binary file not shown.

391
benchmarks/pulumi/index.ts Normal file
View File

@@ -0,0 +1,391 @@
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";
import * as tailscale from "@pulumi/tailscale";
const vpc = new aws.ec2.Vpc("bench", { cidrBlock: "10.0.0.0/16" });
// Create a security group allowing inbound access over port 80 and outbound
// access to anywhere.
const securityGroup = new aws.ec2.SecurityGroup("bench", {
vpcId: vpc.id,
ingress: [
{
cidrBlocks: ["0.0.0.0/0"],
protocol: "tcp",
fromPort: 80,
toPort: 50000,
},
{
cidrBlocks: ["0.0.0.0/0"],
protocol: "tcp",
fromPort: 22,
toPort: 22,
},
],
egress: [
{
cidrBlocks: ["0.0.0.0/0"],
fromPort: 0,
toPort: 0,
protocol: "-1",
},
],
});
// Create an an internet gateway.
const gateway = new aws.ec2.InternetGateway("gw", {
vpcId: vpc.id,
});
// Find the latest Amazon Linux 2 AMI.
const ami = pulumi.output(
aws.ec2.getAmi({
owners: ["amazon"],
mostRecent: true,
filters: [
{ name: "description", values: ["Amazon Linux 2 *"] },
{ name: "architecture", values: ["x86_64"] },
],
})
);
// Create a route table.
const routes = new aws.ec2.RouteTable("route", {
vpcId: vpc.id,
routes: [
{
cidrBlock: "0.0.0.0/0",
gatewayId: gateway.id,
},
],
});
const subnetA = new aws.ec2.Subnet("bench-a", {
availabilityZone: "us-east-2a",
vpcId: vpc.id,
cidrBlock: "10.0.5.0/24",
mapPublicIpOnLaunch: true,
tags: {
Name: "bench",
},
});
const subnetB = new aws.ec2.Subnet("bench-b", {
availabilityZone: "us-east-2b",
vpcId: vpc.id,
cidrBlock: "10.0.6.0/24",
tags: {
Name: "bench",
},
});
const subnetC = new aws.ec2.Subnet("bench-c", {
availabilityZone: "us-east-2c",
vpcId: vpc.id,
cidrBlock: "10.0.7.0/24",
tags: {
Name: "bench",
},
});
new aws.ec2.RouteTableAssociation("bench-a", {
subnetId: subnetA.id,
routeTableId: routes.id,
});
new aws.ec2.RouteTableAssociation("bench-b", {
subnetId: subnetB.id,
routeTableId: routes.id,
});
new aws.ec2.RouteTableAssociation("bench-c", {
subnetId: subnetC.id,
routeTableId: routes.id,
});
const rdSubnet = new aws.rds.SubnetGroup("bench", {
subnetIds: [subnetA.id, subnetB.id],
tags: {
Name: "bench",
},
});
const db = new aws.rds.Instance("bench", {
vpcSecurityGroupIds: [securityGroup.id],
dbSubnetGroupName: rdSubnet.name,
allocatedStorage: 20,
dbName: "windmill",
engine: "postgres",
engineVersion: "14.8",
instanceClass: "db.m5d.large",
password: "postgres",
skipFinalSnapshot: true,
username: "postgres",
availabilityZone: "us-east-2a",
performanceInsightsEnabled: true,
});
const deployer = new aws.ec2.KeyPair("bench", {
publicKey:
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDRi2xytvAIgfs4f5B8EVrWH3bsu8CI+98iBazq08TuPEkLFP/ZqIVm8ZoTnpPadj2o0DXWHNjiyDAnGwp6W1rcSou4KCxoBViRX0doXBMat5GWs6Za2c+bmKllWUH2jhy4aPrdoI3il2URi7dIMH6WpWZDtD98CFK22rTgiBxhDydFpn3X1ULOI18f/SZEKzcb+iffhP5K/Sjqo65xPiKSl2m40vtaQjn2jTE7vmTD9c/dLv/5A3Sgh7CW2PJe9HR7/8qkzZkRkXtyODOIFfWN+vk0rGvk97IpV+DlSUag+FFeSK5Jn41w9IQV7frPvJAQOZ6eLq2GNv8iO/7QnfhlIUlPeClNJBRgb76FmiuslH+AOBiy/Szy3KUtoV/LO+uzvJxRZVX9DhSvTCm4D2qUTjozS+OSeBVuug70euWe29I7djouAuIbDOFkIkxStjhwISXfgvyWYVh8IMxC6+umZMhMff5CfqzpHY0jy2Tw5r/v/2Iz8EQXNRNAxnqKMPE= rfiszel@rubx1",
});
const sampleKey = new tailscale.TailnetKey("bench-ipn", {
ephemeral: false,
expiry: 60 * 60 * 24 * 90,
preauthorized: true,
reusable: true,
});
const ipn = new aws.ec2.Instance("bench-ipn-2", {
ami: ami.id,
availabilityZone: "us-east-2a",
keyName: deployer.keyName,
instanceType: "t3.medium",
subnetId: subnetA.id,
associatePublicIpAddress: true,
rootBlockDevice: {
volumeSize: 40,
},
vpcSecurityGroupIds: pulumi
.all([db.vpcSecurityGroupIds, securityGroup.id])
.apply(([db, sg]) => db.concat(sg)),
userData: pulumi.interpolate`#!/bin/bash
sudo yum update -y
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://pkgs.tailscale.com/stable/amazon-linux/2/tailscale.repo
sudo yum install -y tailscale
sudo systemctl enable --now tailscaled
sleep 30
sudo tailscale up --authkey="${sampleKey.key}" --hostname=bench-ipn --ssh --advertise-tags=tag:ipns
sudo yum install -y wget unzip
`,
});
// Find the latest Amazon Linux 2 AMI.
const amiEcs = pulumi.output(
aws.ec2.getAmi({
owners: ["amazon"],
mostRecent: true,
filters: [{ name: "name", values: ["amzn2-ami-ecs-*-arm64-*"] }],
})
);
const clusterName = "windmill";
const cluster = new aws.ecs.Cluster("cluster", { name: clusterName });
const lt = new aws.ec2.LaunchTemplate("lt2", {
namePrefix: "windmill",
imageId: amiEcs.id,
keyName: deployer.keyName,
instanceType: "t4g.medium",
iamInstanceProfile: {
name: "ecsInstanceRole",
},
updateDefaultVersion: true,
vpcSecurityGroupIds: [securityGroup.id],
userData: btoa(`#!/bin/bash
echo ECS_CLUSTER=${clusterName} >> /etc/ecs/ecs.config
`),
});
const asg = new aws.autoscaling.Group("asg3", {
launchTemplate: {
id: lt.id,
version: "$Latest",
},
minSize: 0,
maxSize: 15,
vpcZoneIdentifiers: [subnetA.id],
tags: [
{
key: "AmazonECSManaged",
value: "true",
propagateAtLaunch: true,
},
],
});
const capProvider = new aws.ecs.CapacityProvider("cap-provider", {
autoScalingGroupProvider: {
autoScalingGroupArn: asg.arn,
managedTerminationProtection: "DISABLED",
managedScaling: {
maximumScalingStepSize: 1000,
minimumScalingStepSize: 1,
status: "ENABLED",
targetCapacity: 80,
},
},
});
const clusterCapProvider = new aws.ecs.ClusterCapacityProviders(
"cap-provider",
{
clusterName: cluster.name,
capacityProviders: [capProvider.name],
}
);
const lb = new awsx.lb.ApplicationLoadBalancer("lb2", {
defaultTargetGroup: {
targetType: "instance",
},
subnetIds: [subnetA.id, subnetB.id, subnetC.id],
});
// const service = new awsx.ecs.FargateService("service", {
// cluster: cluster.arn,
// assignPublicIp: true,
// desiredCount: 2,
// taskDefinitionArgs: {
// container: {
// image: "nginx:latest",
// cpu: 512,
// memory: 128,
// essential: true,
// portMappings: [
// {
// targetGroup: lb.defaultTargetGroup,
// },
// ],
// },
// },
// });
db.address.apply((address) => {
const worker_td = new aws.ecs.TaskDefinition("worker-2", {
family: "windmill-worker",
containerDefinitions: JSON.stringify([
{
name: "windmill-worker",
image: "ghcr.io/windmill-labs/windmill:1.165.0",
cpu: 1024,
memory: 1800,
essential: true,
mountPaths: [
{
containerPath: "/tmp/windmill/cache",
sourceVolume: "dependency_cache",
},
],
environment: [
{ name: "DISABLE_SERVER", value: "true" },
// { name: "METRICS_ADDR", value: "true" },
{ name: "RUST_LOG", value: "info" },
{
name: "DATABASE_URL",
value: `postgres://postgres:postgres@${address}/windmill?sslmode=disable`,
},
],
logConfiguration: {
logDriver: "awslogs",
options: {
"awslogs-group": "windmill-worker",
"awslogs-region": "us-east-2",
"awslogs-create-group": "true",
"awslogs-stream-prefix": "windmill-worker",
},
},
dockerLabels: {
PROMETHEUS_EXPORTER_PORT: "8001",
},
},
]),
volumes: [
{
name: "dependency_cache",
dockerVolumeConfiguration: {
scope: "shared",
autoprovision: true,
},
},
],
});
const server_td = new aws.ecs.TaskDefinition("server", {
family: "windmill-server",
containerDefinitions: JSON.stringify([
{
name: "windmill-server",
image: "ghcr.io/windmill-labs/windmill:1.165.0",
cpu: 1024,
memory: 1024,
essential: true,
environment: [
{ name: "NUM_WORKERS", value: "0" },
// { name: "METRICS_ADDR", value: "true" },
{ name: "RUST_LOG", value: "info" },
{
name: "DATABASE_URL",
value: `postgres://postgres:postgres@${address}/windmill?sslmode=disable`,
},
],
logConfiguration: {
logDriver: "awslogs",
options: {
"awslogs-group": "windmill-server",
"awslogs-region": "us-east-2",
"awslogs-create-group": "true",
"awslogs-stream-prefix": "windmill-server",
},
},
dockerLabels: {
PROMETHEUS_EXPORTER_PORT: "8001",
},
portMappings: [
{
containerPort: 8000,
},
{
containerPort: 8001,
},
],
},
]),
});
const service_server = new aws.ecs.Service("service-server", {
cluster: cluster.id,
taskDefinition: server_td.arn,
desiredCount: 2,
forceNewDeployment: true,
orderedPlacementStrategies: [
{
type: "binpack",
field: "cpu",
},
],
loadBalancers: [
{
targetGroupArn: lb.defaultTargetGroup.arn,
containerName: "windmill-server",
containerPort: 8000,
},
],
});
const service_worker = new aws.ecs.Service("service-worker", {
cluster: cluster.id,
taskDefinition: worker_td.arn,
desiredCount: 30,
forceNewDeployment: true,
orderedPlacementStrategies: [
{
type: "binpack",
field: "cpu",
},
],
});
});
module.exports = {
databaseAddress: db.address,
ipn: ipn.publicIp,
url: lb.loadBalancer.dnsName,
amiEcs: amiEcs.id,
// instanceURL: pulumi.interpolate`http://${instance.publicIp}`,
};

2412
benchmarks/pulumi/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
{
"name": "aws-bench",
"main": "index.ts",
"devDependencies": {
"@types/node": "^16",
"pulumi": "^0.0.1"
},
"dependencies": {
"@pulumi/aws": "^5.0.0",
"@pulumi/awsx": "^1.0.4",
"@pulumi/pulumi": "^3.0.0",
"@pulumi/tailscale": "^0.12.2",
"@pulumi/tls": "^4.10.0"
}
}

View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"strict": true,
"outDir": "bin",
"target": "es2016",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"experimentalDecorators": true,
"pretty": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true
},
"files": [
"index.ts"
]
}

View File

@@ -31,7 +31,7 @@ addEventListener("error", (event) => {
}
});
export const VERSION = "v1.161.0";
export const VERSION = "v1.167.0";
let command: any = new Command()
.name("wmill")

View File

@@ -83,7 +83,7 @@ export async function handleFile(
log.debug(`Processing local script ${path}`);
alreadySynced.push(path);
const remotePath = path.substring(0, path.length - 3);
const remotePath = path.substring(0, path.indexOf("."));
const metaPath = remotePath + ".script.json";
let typed = undefined;
try {
@@ -180,6 +180,8 @@ export async function handleFile(
export async function findContentFile(filePath: string) {
const candidates = filePath.endsWith("script.json")
? [
filePath.replace(".script.json", ".fetch.ts"),
filePath.replace(".script.json", ".bun.ts"),
filePath.replace(".script.json", ".ts"),
filePath.replace(".script.json", ".py"),
filePath.replace(".script.json", ".go"),
@@ -188,12 +190,12 @@ export async function findContentFile(filePath: string) {
filePath.replace(".script.json", "my.sql"),
filePath.replace(".script.json", "bq.sql"),
filePath.replace(".script.json", "sf.sql"),
filePath.replace(".script.json", ".fetch.ts"),
filePath.replace(".script.json", ".bun.ts"),
filePath.replace(".script.json", ".gql"),
filePath.replace(".script.json", ".ps1"),
]
: [
filePath.replace(".script.yaml", ".fetch.ts"),
filePath.replace(".script.yaml", ".bun.ts"),
filePath.replace(".script.yaml", ".ts"),
filePath.replace(".script.yaml", ".py"),
filePath.replace(".script.yaml", ".go"),
@@ -201,8 +203,6 @@ export async function findContentFile(filePath: string) {
filePath.replace(".script.yaml", "pg.sql"),
filePath.replace(".script.yaml", "bq.sql"),
filePath.replace(".script.yaml", "sf.sql"),
filePath.replace(".script.yaml", ".fetch.ts"),
filePath.replace(".script.yaml", ".bun.ts"),
filePath.replace(".script.yaml", ".gql"),
filePath.replace(".script.yaml", ".ps1"),
];

View File

@@ -1,12 +1,12 @@
{
"name": "windmill-components",
"version": "1.161.0",
"version": "1.167.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "windmill-components",
"version": "1.161.0",
"version": "1.167.0",
"license": "AGPL-3.0",
"dependencies": {
"@aws-crypto/sha256-js": "^4.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "windmill-components",
"version": "1.161.0",
"version": "1.167.0",
"scripts": {
"dev": "vite dev",
"build": "vite build",

View File

@@ -14,6 +14,7 @@
} from '@fortawesome/free-solid-svg-icons'
import ScheduleEditor from './ScheduleEditor.svelte'
import TimeAgo from './TimeAgo.svelte'
import { workspaceStore } from '$lib/stores'
export let job: Job
const SMALL_ICON_SCALE = 0.7
@@ -57,11 +58,15 @@
<Icon class="text-secondary" data={faBarsStaggered} scale={SMALL_ICON_SCALE} /><span
class="mx-2"
>
Step of flow <a href={`/run/${job.parent_job}`}>{job.parent_job}</a></span
Step of flow <a href={`/run/${job.parent_job}?workspace=${$workspaceStore}`}
>{job.parent_job}</a
></span
>
{:else}
<Icon class="text-secondary" data={faRobot} scale={SMALL_ICON_SCALE} /><span class="mx-2">
Triggered by parent <a href={`/run/${job.parent_job}`}>{job.parent_job}</a></span
Triggered by parent <a href={`/run/${job.parent_job}?workspace=${$workspaceStore}`}
>{job.parent_job}</a
></span
>
{/if}
{:else if job && job.schedule_path}

View File

@@ -489,10 +489,11 @@
<div class="px-2 flex gap-2 min-w-0">
<ModuleStatus type={node.type} scheduled_for={node.scheduled_for} />
{#if node.job_id}
<div class="truncate min-w-1/2"
><div class=" text-primary whitespace-nowrap truncate">
<div class="truncate w-full"
><div class=" text-primary whitespace-nowrap truncate w-full">
<span class="font-bold mr-2">Job Id</span>
<a
class="w-full text-right text-xs"
rel="noreferrer"
target="_blank"
href="/run/{node.job_id ?? ''}?workspace={job?.workspace_id}"

View File

@@ -293,7 +293,7 @@
<div class="col-span-2">
<a
target="_blank"
href="/runs/{i.id}"
href="/run/{i.id}?workspace={$workspaceStore}"
class="text-right float-right text-secondary"
title="See run detail in a new tab"
>

View File

@@ -10,7 +10,7 @@
import ScriptEditor from './ScriptEditor.svelte'
import ScriptSchema from './ScriptSchema.svelte'
import { dirtyStore } from './common/confirmationModal/dirtyStore'
import { Alert, Badge, Button, Drawer, Kbd } from './common'
import { Alert, Badge, Button, Drawer, Kbd, SecondsInput } from './common'
import { faPlus, faSave } from '@fortawesome/free-solid-svg-icons'
import LanguageIcon from './common/languageIcons/LanguageIcon.svelte'
import type { SupportedLanguage } from '$lib/common'
@@ -27,6 +27,7 @@
import { Icon } from 'svelte-awesome'
import { fade } from 'svelte/transition'
import Popover from './Popover.svelte'
import Toggle from './Toggle.svelte'
export let script: NewScript
export let initialPath: string = ''
@@ -168,7 +169,8 @@
tag: script.tag,
envs: script.envs,
concurrent_limit: script.concurrent_limit,
concurrency_time_window_s: script.concurrency_time_window_s
concurrency_time_window_s: script.concurrency_time_window_s,
cache_ttl: script.cache_ttl
}
})
history.replaceState(history.state, '', `/scripts/edit/${script.path}`)
@@ -210,7 +212,8 @@
draft_only: true,
envs: script.envs,
concurrent_limit: script.concurrent_limit,
concurrency_time_window_s: script.concurrency_time_window_s
concurrency_time_window_s: script.concurrency_time_window_s,
cache_ttl: script.cache_ttl
}
})
}
@@ -443,6 +446,34 @@
{:else}
<Loader2 class="animate-spin" />
{/if}
<h2 class="border-b pb-1 mt-10 mb-4 flex items-center gap-4"
>Cache <Toggle
size="xs"
checked={Boolean(script.cache_ttl)}
on:change={() => {
if (script.cache_ttl && script.cache_ttl != undefined) {
script.cache_ttl = undefined
} else {
script.cache_ttl = 300
}
}}
options={{
right: 'Cache the results for each possible inputs'
}}
/></h2
>
<div class="flex gap-x-4 shrink flex-col">
<div class="text-xs">How long to the keep cache valid</div>
{#if script.cache_ttl}
<SecondsInput bind:seconds={script.cache_ttl} />
{:else}
<SecondsInput disabled />
{/if}
</div>
{#if !isCloudHosted()}
<h2 class="border-b pb-1 mt-10 mb-4">
Custom env variables

View File

@@ -77,8 +77,11 @@
const debounce_ms = 50
export async function computeExpr() {
value = await evalExpr(lastInput as EvalAppInput)
return value
const nvalue = await evalExpr(lastInput as EvalAppInput)
if (!deepEqual(nvalue, value)) {
value = nvalue
}
return nvalue
}
function debounce(cb: () => Promise<void>) {

View File

@@ -33,10 +33,8 @@
<InputValue key="nonrunnable" {id} input={componentInput} bind:value={result} />
{/if}
{#if render}
<slot />
{:else if hasChildrens}
<div class="invisible h-0 overflow-hidden'}">
{#if render || hasChildrens}
<div class={render ? 'h-full w-full' : 'invisible h-0 overflow-hidden'}>
<slot />
</div>
{:else}

View File

@@ -16,6 +16,7 @@
import RefreshButton from './RefreshButton.svelte'
import { clearErrorByComponentId, selectId } from '../../editor/appUtils'
import ResultJobLoader from '$lib/components/ResultJobLoader.svelte'
import { userStore } from '$lib/stores'
// Component props
export let id: string
@@ -248,7 +249,8 @@
content: inlineScript.content,
language: inlineScript.language,
path: inlineScript.path,
lock: inlineScript.lock
lock: inlineScript.lock,
cache_ttl: inlineScript.cache_ttl
}
}
} else if (runnable?.type === 'runnableByPath') {
@@ -258,7 +260,7 @@
return AppService.executeComponent({
workspace,
path: defaultIfEmptyString(appPath, 'newapp'),
path: defaultIfEmptyString(appPath, `u/${$userStore?.username ?? 'unknown'}/newapp`),
requestBody
})
})
@@ -385,6 +387,7 @@
recordJob(jobId!, undefined, errors, transformerResult)
updateResult(res)
dispatch('handleError', errors)
donePromise?.()
return
}
@@ -394,6 +397,7 @@
recordJob(jobId!, res, undefined, transformerResult)
updateResult(transformerResult)
dispatch('handleError', transformerResult.error)
donePromise?.()
return
}
@@ -421,6 +425,7 @@
let p: Partial<CancelablePromise<void>> = new Promise<void>((resolve, reject) => {
rejectCb = reject
donePromise = resolve
executeComponent(true, inlineScript).catch(reject)
})
p.cancel = () => {

View File

@@ -18,7 +18,7 @@ export function computeGlobalContext(world: World | undefined, extraContext: any
}
}
function create_context_function_template(eval_string, context, noReturn: boolean) {
function create_context_function_template(eval_string: string, context, noReturn: boolean) {
return `
return async function (context, state, goto, setTab, recompute, getAgGrid, setValue, setSelectedIndex, openModal, closeModal) {
"use strict";
@@ -27,7 +27,11 @@ ${
? `let ${Object.keys(context).map((key) => ` ${key} = context['${key}']`)};`
: ``
}
${noReturn ? `return ${eval_string}` : eval_string}
${
noReturn
? `return ${eval_string.startsWith('return ') ? eval_string.substring(7) : eval_string}`
: eval_string
}
}
`
}

View File

@@ -115,7 +115,8 @@
`${
$mode == 'dnd' ? 'absolute' : 'fixed'
} top-0 bottom-0 left-0 right-0 transition-all duration-50`,
open ? 'z-[1100] bg-black bg-opacity-60' : 'h-0 overflow-hidden'
open ? ' bg-black bg-opacity-60' : 'h-0 overflow-hidden',
$mode === 'dnd' ? 'z-[1000]' : 'z-[1100]'
)}
>
<div

View File

@@ -30,22 +30,24 @@
mode
} = getContext<AppViewerContext>('AppViewerContext')
let selected: string = tabs[0]
let selected = tabs[0]
let tabHeight: number = 0
let footerHeight: number = 0
let runnableComponent: RunnableComponent
let selectedIndex = tabs?.indexOf(selected) ?? -1
let maxReachedIndex = -1
let statusByStep = [] as Array<'success' | 'error' | 'pending'>
let debugMode: boolean = false
let outputs = initOutput($worldStore, id, {
currentStepIndex: 0,
result: undefined,
loading: false
loading: false,
lastAction: undefined as 'previous' | 'next' | undefined
})
async function handleTabSelection() {
if (runnableComponent) {
if (runnableComponent && !debugMode) {
await runnableComponent?.runComponent()
}
@@ -68,7 +70,9 @@
async function runStep(targetIndex: number) {
statusByStep[selectedIndex] = 'pending'
if (runnableComponent) {
outputs?.lastAction.set(directionClicked === 'left' ? 'previous' : 'next')
if (runnableComponent && !debugMode) {
await runnableComponent?.runComponent()
}
@@ -100,7 +104,14 @@
return false
},
setTab: (tab: number) => {
selected = tabs[tab]
debugMode = tab >= 0
if (debugMode) {
selected = tabs[tab]
} else {
selected = tabs[0]
}
handleTabSelection()
}
}

View File

@@ -73,8 +73,7 @@
{/if}
{#if component.type === 'conditionalwrapper'}
<TabsDebug id={component.id} tabs={component.conditions ?? []} isConditionalDebugMode />
{/if}
{#if component.type === 'tabscomponent' && component.configuration.tabsKind.type === 'static' && component.configuration.tabsKind.value === 'invisibleOnView'}
{:else if component.type === 'steppercomponent' || (component.type === 'tabscomponent' && component.configuration.tabsKind.type === 'static' && component.configuration.tabsKind.value === 'invisibleOnView')}
<TabsDebug id={component.id} tabs={component.tabs ?? []} />
{/if}
<button

View File

@@ -58,7 +58,9 @@
return
}
return $runnableComponents?.[id]?.cb?.map((f) => f())
return $runnableComponents?.[id]?.cb?.map((f) =>
f().then(() => console.log('refreshed', id))
)
})
.filter(Boolean)

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