Compare commits
140 Commits
v1.548.2
...
alp/fix_ba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af4e9e6a22 | ||
|
|
50a6106436 | ||
|
|
6806f2193d | ||
|
|
7d5196170c | ||
|
|
7b9e2c2d68 | ||
|
|
258b275f9b | ||
|
|
f2070e9d9c | ||
|
|
820437c897 | ||
|
|
686cf8dd69 | ||
|
|
ac27cdc16e | ||
|
|
9f7250c9f0 | ||
|
|
7c757b68f6 | ||
|
|
a12d124871 | ||
|
|
abb813c019 | ||
|
|
75ceba81d9 | ||
|
|
4e171d0aa6 | ||
|
|
46c49072de | ||
|
|
9ddde9aa94 | ||
|
|
1df18a912c | ||
|
|
d9c01e0c03 | ||
|
|
605c5526f8 | ||
|
|
116c420326 | ||
|
|
0bc822dd10 | ||
|
|
3907c9f951 | ||
|
|
3c7a12da57 | ||
|
|
7c39aa6f6f | ||
|
|
c658f321d6 | ||
|
|
1913979012 | ||
|
|
06b152b295 | ||
|
|
047420e5ad | ||
|
|
6d436d7459 | ||
|
|
0b5a2425ae | ||
|
|
f35c9ffe63 | ||
|
|
6aaae00d54 | ||
|
|
cdb7524118 | ||
|
|
9f40ce2da9 | ||
|
|
7cd5f26f70 | ||
|
|
f545b1d572 | ||
|
|
49f5023fa5 | ||
|
|
a00991a293 | ||
|
|
a0bc0ee318 | ||
|
|
36f2ab4715 | ||
|
|
59cdb141c3 | ||
|
|
ae45a50eb2 | ||
|
|
12a2b58704 | ||
|
|
45be618c25 | ||
|
|
c7f6ff14af | ||
|
|
8eb6c13c19 | ||
|
|
e2e3ae9f02 | ||
|
|
9e6cebac55 | ||
|
|
ee0ffa47e2 | ||
|
|
74a75431c0 | ||
|
|
67325abcbb | ||
|
|
ee00964a06 | ||
|
|
5689411199 | ||
|
|
3306ee1718 | ||
|
|
be63a69649 | ||
|
|
526dfd7237 | ||
|
|
e92a99a101 | ||
|
|
8be898e5a2 | ||
|
|
d92d6c8f84 | ||
|
|
d382ea7c8b | ||
|
|
0ba5e3e9c7 | ||
|
|
898eb6231b | ||
|
|
5aeb3fa0b7 | ||
|
|
53fd4ccb82 | ||
|
|
ce653f8a05 | ||
|
|
2055e536a7 | ||
|
|
727314cd44 | ||
|
|
7da0548b30 | ||
|
|
e2608f9aac | ||
|
|
4bbbeb956f | ||
|
|
d56dea4969 | ||
|
|
74e9cffe58 | ||
|
|
481c877537 | ||
|
|
2b7ad0e2ef | ||
|
|
5f63ce6dd8 | ||
|
|
c751a5d6aa | ||
|
|
cc2afdb264 | ||
|
|
9dad8e7e10 | ||
|
|
cfec8e99fb | ||
|
|
8266fb84b0 | ||
|
|
5ae525a9f1 | ||
|
|
c066ab56fa | ||
|
|
629a378320 | ||
|
|
81616cbe1e | ||
|
|
4bc0283994 | ||
|
|
5d2a79e12b | ||
|
|
d59b9f5216 | ||
|
|
2047073c03 | ||
|
|
477ec7773f | ||
|
|
14780e49f2 | ||
|
|
abf823e85b | ||
|
|
a4138e037f | ||
|
|
bdd36c0b4d | ||
|
|
fe6199a867 | ||
|
|
b71e5c5adc | ||
|
|
924891b644 | ||
|
|
b76cfa3881 | ||
|
|
5b2ae8ea43 | ||
|
|
cd90d0e1ef | ||
|
|
452fb2b90a | ||
|
|
86254d89e8 | ||
|
|
6b907c8823 | ||
|
|
70d1b99a02 | ||
|
|
ef7af5a7b8 | ||
|
|
439b6da720 | ||
|
|
72b744c4e1 | ||
|
|
d6d5fad83b | ||
|
|
a9c114c4f8 | ||
|
|
ad32c940cb | ||
|
|
15731e7726 | ||
|
|
7df13b3e7b | ||
|
|
9b13dd99ba | ||
|
|
a1db857be4 | ||
|
|
2a62f8ff8f | ||
|
|
2421bdb839 | ||
|
|
f28ed9a5f5 | ||
|
|
311b410f2f | ||
|
|
7add57499c | ||
|
|
dff499484b | ||
|
|
0ad5618aae | ||
|
|
ec9e5a9acb | ||
|
|
ca4f9ee8c1 | ||
|
|
6ece0ac575 | ||
|
|
6cc85d78d7 | ||
|
|
87861301f2 | ||
|
|
341cdcf66e | ||
|
|
f990107c45 | ||
|
|
bce25bc283 | ||
|
|
db4bc7ee69 | ||
|
|
02692111a1 | ||
|
|
aae0919535 | ||
|
|
4828926be6 | ||
|
|
8f4fef9804 | ||
|
|
4205e83cfd | ||
|
|
78c0bea6b4 | ||
|
|
b47c21f299 | ||
|
|
f5d238edcf | ||
|
|
a308782bcf |
8
.github/DockerfileBackendTests
vendored
8
.github/DockerfileBackendTests
vendored
@@ -57,8 +57,12 @@ RUN apt-get update \
|
||||
RUN rustup component add rustfmt
|
||||
|
||||
# C#
|
||||
COPY --from=bitnami/dotnet-sdk:9.0.101-debian-12-r0 /opt/bitnami/dotnet-sdk /opt/dotnet-sdk
|
||||
RUN ln -s /opt/dotnet-sdk/bin/dotnet /usr/bin/dotnet
|
||||
RUN wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh \
|
||||
&& chmod +x dotnet-install.sh \
|
||||
&& ./dotnet-install.sh --channel 9.0 --install-dir /usr/share/dotnet \
|
||||
&& ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet \
|
||||
&& rm dotnet-install.sh
|
||||
|
||||
|
||||
# Nushell
|
||||
COPY --from=ghcr.io/nushell/nushell:0.101.0-bookworm /usr/bin/nu /usr/bin/nu
|
||||
|
||||
19
.github/workflows/backend-test.yml
vendored
19
.github/workflows/backend-test.yml
vendored
@@ -51,7 +51,22 @@ jobs:
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache-workspaces: backend
|
||||
toolchain: 1.85.0
|
||||
toolchain: 1.90.0
|
||||
- name: Read EE repo commit hash
|
||||
run: |
|
||||
echo "ee_repo_ref=$(cat ./ee-repo-ref.txt)" >> "$GITHUB_ENV"
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: windmill-labs/windmill-ee-private
|
||||
path: ./windmill-ee-private
|
||||
ref: ${{ env.ee_repo_ref }}
|
||||
token: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Substitute EE code (EE logic is behind feature flag)
|
||||
run: |
|
||||
./substitute_ee_code.sh --copy --dir ./windmill-ee-private
|
||||
- name: cargo test
|
||||
timeout-minutes: 16
|
||||
run:
|
||||
@@ -61,5 +76,5 @@ jobs:
|
||||
DISABLE_EMBEDDING=true RUST_LOG=info
|
||||
DENO_PATH=$(which deno) BUN_PATH=$(which bun) GO_PATH=$(which go)
|
||||
UV_PATH=$(which uv) cargo test --features
|
||||
enterprise,deno_core,license,python,rust,scoped_cache --all --
|
||||
enterprise,deno_core,license,python,rust,scoped_cache,private --all --
|
||||
--nocapture
|
||||
|
||||
172
CHANGELOG.md
172
CHANGELOG.md
@@ -1,5 +1,177 @@
|
||||
# Changelog
|
||||
|
||||
## [1.555.2](https://github.com/windmill-labs/windmill/compare/v1.555.1...v1.555.2) (2025-10-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backend:** use correct ai tool job dir ([#6757](https://github.com/windmill-labs/windmill/issues/6757)) ([7c757b6](https://github.com/windmill-labs/windmill/commit/7c757b68f64bf3cf770cd080c5025f202d40201c))
|
||||
|
||||
## [1.555.1](https://github.com/windmill-labs/windmill/compare/v1.555.0...v1.555.1) (2025-10-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* app button tooltip also when disabled + audit log filters ([#6751](https://github.com/windmill-labs/windmill/issues/6751)) ([605c552](https://github.com/windmill-labs/windmill/commit/605c5526f83d3b985f04570039bb4671cc5912f4))
|
||||
* init git repo preview save ([#6753](https://github.com/windmill-labs/windmill/issues/6753)) ([d9c01e0](https://github.com/windmill-labs/windmill/commit/d9c01e0c0392f13e52ad92f87c4216e64e89a3bc))
|
||||
|
||||
## [1.555.0](https://github.com/windmill-labs/windmill/compare/v1.554.1...v1.555.0) (2025-10-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* end user email env var ([#6750](https://github.com/windmill-labs/windmill/issues/6750)) ([3907c9f](https://github.com/windmill-labs/windmill/commit/3907c9f9512ebd73daf0a2f3ee2e8db6fb9f4df6))
|
||||
* **flow:** add option to turn flow into chat ([#6658](https://github.com/windmill-labs/windmill/issues/6658)) ([047420e](https://github.com/windmill-labs/windmill/commit/047420e5ad7b6178291bc7ed75d029794760d18b))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backend:** prevent s3 file upload infinite loop ([#6742](https://github.com/windmill-labs/windmill/issues/6742)) ([6d436d7](https://github.com/windmill-labs/windmill/commit/6d436d745994f954a37f33ca2cc2e9f0801b16b9))
|
||||
* show that user is disabled in workspacelist ([#6748](https://github.com/windmill-labs/windmill/issues/6748)) ([c658f32](https://github.com/windmill-labs/windmill/commit/c658f321d68e2d72622d9d167b20cac67364651c))
|
||||
* top level assigment doesn't propagate to setContext ([#6745](https://github.com/windmill-labs/windmill/issues/6745)) ([06b152b](https://github.com/windmill-labs/windmill/commit/06b152b295cd4892d7309651d382a05cdcf7d378))
|
||||
|
||||
## [1.554.1](https://github.com/windmill-labs/windmill/compare/v1.554.0...v1.554.1) (2025-10-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backend:** concurrency limits preprocessor ([#6727](https://github.com/windmill-labs/windmill/issues/6727)) ([cdb7524](https://github.com/windmill-labs/windmill/commit/cdb75241188ee0a6d7bc62ca6dd639606eef426a))
|
||||
|
||||
## [1.554.0](https://github.com/windmill-labs/windmill/compare/v1.553.0...v1.554.0) (2025-10-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **cli:** allow skipping branch validation ([#6721](https://github.com/windmill-labs/windmill/issues/6721)) ([9e6ceba](https://github.com/windmill-labs/windmill/commit/9e6cebac557fd9a8530df54c227e915435fd2de5))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow running scripts in json view ([74a7543](https://github.com/windmill-labs/windmill/commit/74a75431c072e9f084a43b8d8f195ca7757faf07))
|
||||
* fix job duration unwrap crash ([e2e3ae9](https://github.com/windmill-labs/windmill/commit/e2e3ae9f0280d4369f77064ed85e5fde7e9d5a0d))
|
||||
|
||||
## [1.553.0](https://github.com/windmill-labs/windmill/compare/v1.552.1...v1.553.0) (2025-09-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **backend:** allow multiple workspaces in jwt ([#6714](https://github.com/windmill-labs/windmill/issues/6714)) ([526dfd7](https://github.com/windmill-labs/windmill/commit/526dfd72377b90fb47b9c2c3924a1377e2715ae1))
|
||||
* **backend:** array and object params support in pwsh ([#6706](https://github.com/windmill-labs/windmill/issues/6706)) ([898eb62](https://github.com/windmill-labs/windmill/commit/898eb6231beb5ca45d59da069f458f7828f427f0))
|
||||
* support esm mode for codebase bundles ([#6709](https://github.com/windmill-labs/windmill/issues/6709)) ([d382ea7](https://github.com/windmill-labs/windmill/commit/d382ea7c8b372471dd3393720ff93749fde898f5))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* multiselect + jsoneditor nits ([5aeb3fa](https://github.com/windmill-labs/windmill/commit/5aeb3fa0b74fb0d72f0439ce540baf224654f1ae))
|
||||
|
||||
## [1.552.1](https://github.com/windmill-labs/windmill/compare/v1.552.0...v1.552.1) (2025-09-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix c# with nsjail ([2055e53](https://github.com/windmill-labs/windmill/commit/2055e536a7fcb9cfe155c0fa67de6ae49d925f97))
|
||||
* **frontend:** allow dates before 2000 in date picker ([#6707](https://github.com/windmill-labs/windmill/issues/6707)) ([ce653f8](https://github.com/windmill-labs/windmill/commit/ce653f8a0538fcc88ef78f6c50960a7340648b0f))
|
||||
|
||||
## [1.552.0](https://github.com/windmill-labs/windmill/compare/v1.551.4...v1.552.0) (2025-09-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* powershell private repo support ([#6684](https://github.com/windmill-labs/windmill/issues/6684)) ([4bbbeb9](https://github.com/windmill-labs/windmill/commit/4bbbeb956f8f09ea5a8af241912a1bead1e06520))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* external links in critical alert ([e2608f9](https://github.com/windmill-labs/windmill/commit/e2608f9aacd30e2a7aeb5b850802514f64a41380))
|
||||
* fix app schema form rendering ([481c877](https://github.com/windmill-labs/windmill/commit/481c8775377f7f01ad01b5db85a98ccffadada91))
|
||||
* **frontend:** prevent label interference with monaco editor in instance settings ([#6701](https://github.com/windmill-labs/windmill/issues/6701)) ([c751a5d](https://github.com/windmill-labs/windmill/commit/c751a5d6aa49e4bc0970f87b3f1e975e8ee58479))
|
||||
* **mcp:** filter out tools with long names ([#6692](https://github.com/windmill-labs/windmill/issues/6692)) ([cc2afdb](https://github.com/windmill-labs/windmill/commit/cc2afdb264b0eaa353e5f2736c98e475337b71f7))
|
||||
* show more autoscaling events ([#6704](https://github.com/windmill-labs/windmill/issues/6704)) ([d56dea4](https://github.com/windmill-labs/windmill/commit/d56dea4969ed5c6eec30c72cf9f1171889444007))
|
||||
* **uv:** log stdout on `uv pip install` error ([#6702](https://github.com/windmill-labs/windmill/issues/6702)) ([5f63ce6](https://github.com/windmill-labs/windmill/commit/5f63ce6dd8697533de1e0af786e463f3224912c2))
|
||||
|
||||
## [1.551.4](https://github.com/windmill-labs/windmill/compare/v1.551.3...v1.551.4) (2025-09-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* migrate dotnet from msft images to script install ([cfec8e9](https://github.com/windmill-labs/windmill/commit/cfec8e99fb55928dfed3b7e80fb63cc279553dec))
|
||||
|
||||
## [1.551.3](https://github.com/windmill-labs/windmill/compare/v1.551.2...v1.551.3) (2025-09-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* migrate dotnet from bitnami to microsoft images ([5ae525a](https://github.com/windmill-labs/windmill/commit/5ae525a9f14de20d45e6075baa979eb4aaac4850))
|
||||
|
||||
## [1.551.2](https://github.com/windmill-labs/windmill/compare/v1.551.1...v1.551.2) (2025-09-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix copy first step input ([81616cb](https://github.com/windmill-labs/windmill/commit/81616cbe1e27bc3f45cfa35cd359ce9a0f493f35))
|
||||
|
||||
## [1.551.1](https://github.com/windmill-labs/windmill/compare/v1.551.0...v1.551.1) (2025-09-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* buttons are back to semi-bold ([bdd36c0](https://github.com/windmill-labs/windmill/commit/bdd36c0b4d5c590e66bf471c8e4b5f681b9464aa))
|
||||
|
||||
## [1.551.0](https://github.com/windmill-labs/windmill/compare/v1.550.0...v1.551.0) (2025-09-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* UX improvements (all inputs) ([72b744c](https://github.com/windmill-labs/windmill/commit/72b744c4e1bc3c1f3098f4d6de6c9474d7a8fb84))
|
||||
|
||||
## [1.550.0](https://github.com/windmill-labs/windmill/compare/v1.549.1...v1.550.0) (2025-09-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* ai agent streaming ([#6644](https://github.com/windmill-labs/windmill/issues/6644)) ([f990107](https://github.com/windmill-labs/windmill/commit/f990107c45fbb2e955ef67439e92328976091eb0))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve behavior for already completed jobs when doing immediate cancels ([341cdcf](https://github.com/windmill-labs/windmill/commit/341cdcf66efdfd504492be32d9b4f5cb9db2df2a))
|
||||
* improve dyn select as flow input ([6ece0ac](https://github.com/windmill-labs/windmill/commit/6ece0ac5758d4f5e8c0d55ee78a524e454ad264b))
|
||||
* improve graph rendering performances ([7add574](https://github.com/windmill-labs/windmill/commit/7add57499c02ac53a7f7adbabbb279d7c41ab275))
|
||||
* improve performance of flow viewer ([311b410](https://github.com/windmill-labs/windmill/commit/311b410f2f65c3bdfc483c80cc5ef72b6864118a))
|
||||
* limit auto data tables to tables of col < 100 ([f28ed9a](https://github.com/windmill-labs/windmill/commit/f28ed9a5f5c6032c49734e6770c2f2c9e2e4a001))
|
||||
* make schedule more resilient in case of pg clock shifts ([8786130](https://github.com/windmill-labs/windmill/commit/87861301f28cab136fe7af094690539e3daa613f))
|
||||
* restore set_progress feature with sse ([7df13b3](https://github.com/windmill-labs/windmill/commit/7df13b3e7bb095475d0fe54b7f635e3861fb0f73))
|
||||
* scim group/users audit logs ([#6682](https://github.com/windmill-labs/windmill/issues/6682)) ([ca4f9ee](https://github.com/windmill-labs/windmill/commit/ca4f9ee8c12f01fc7c3bcedf5d41e59dc28eb1f2))
|
||||
* support label + value for dynamic enums of selects ([ec9e5a9](https://github.com/windmill-labs/windmill/commit/ec9e5a9acbd352b20399d403be55361c73084aff))
|
||||
|
||||
## [1.549.1](https://github.com/windmill-labs/windmill/compare/v1.549.0...v1.549.1) (2025-09-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* do not request unecessarily get_scheduled_for ([0269211](https://github.com/windmill-labs/windmill/commit/02692111a1a8eefb2675b14d53f109a66c1b9a78))
|
||||
* fix agent_workers completed job back-compatibility deserialization ([db4bc7e](https://github.com/windmill-labs/windmill/commit/db4bc7ee6963955abc7e290bd67ea913b0f5e2ad))
|
||||
|
||||
## [1.549.0](https://github.com/windmill-labs/windmill/compare/v1.548.3...v1.549.0) (2025-09-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **backend:** job result stream optimization ([#6673](https://github.com/windmill-labs/windmill/issues/6673)) ([8f4fef9](https://github.com/windmill-labs/windmill/commit/8f4fef98042c49346c89bdf5e0b9b1f2d52e371f))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* scim group handling when deleting instance user + conversion ([#6677](https://github.com/windmill-labs/windmill/issues/6677)) ([4205e83](https://github.com/windmill-labs/windmill/commit/4205e83cfde453827eab23c31e76a0f0490d31b7))
|
||||
|
||||
## [1.548.3](https://github.com/windmill-labs/windmill/compare/v1.548.2...v1.548.3) (2025-09-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix job loader token initialization ([f5d238e](https://github.com/windmill-labs/windmill/commit/f5d238edcfed6b0f066d459cdc718679a7b51187))
|
||||
* websocket runnable [#6675](https://github.com/windmill-labs/windmill/issues/6675) ([a308782](https://github.com/windmill-labs/windmill/commit/a308782bcf7ef9913887521d74796b490619d0c8))
|
||||
|
||||
## [1.548.2](https://github.com/windmill-labs/windmill/compare/v1.548.1...v1.548.2) (2025-09-24)
|
||||
|
||||
|
||||
|
||||
3
backend/.gitignore
vendored
3
backend/.gitignore
vendored
@@ -9,4 +9,5 @@ windmill-api/openapi-*.*
|
||||
.duckdb/*
|
||||
*ee.rs
|
||||
generate_mcp_endpoints_tools/venv
|
||||
bacon.toml
|
||||
bacon.toml
|
||||
libwindmill_duckdb_ffi_internal.so
|
||||
24
backend/.sqlx/query-01050e7057f3d1971ad9e47ac83bf6a3c3c9f41689c3607f0b264437ae6b3324.json
generated
Normal file
24
backend/.sqlx/query-01050e7057f3d1971ad9e47ac83bf6a3c3c9f41689c3607f0b264437ae6b3324.json
generated
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT data FROM app_bundles WHERE app_version_id = $1 AND file_type = $2 AND w_id = $3",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "data",
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "01050e7057f3d1971ad9e47ac83bf6a3c3c9f41689c3607f0b264437ae6b3324"
|
||||
}
|
||||
29
backend/.sqlx/query-1ff185d8b8b897a72180cd0002e0f7e9858eef249577ce23969522793c6b5608.json
generated
Normal file
29
backend/.sqlx/query-1ff185d8b8b897a72180cd0002e0f7e9858eef249577ce23969522793c6b5608.json
generated
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n eig.igroup as group_name,\n ws.auto_add_instance_groups_roles\n FROM email_to_igroup eig\n INNER JOIN workspace_settings ws ON ws.workspace_id = $1\n WHERE eig.email = $2\n AND eig.igroup = ANY(ws.auto_add_instance_groups)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "group_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "auto_add_instance_groups_roles",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "1ff185d8b8b897a72180cd0002e0f7e9858eef249577ce23969522793c6b5608"
|
||||
}
|
||||
15
backend/.sqlx/query-20888e946a5597989488e2a54e354e4ce2872f2bc3af7b26fcaafe8703ba70bf.json
generated
Normal file
15
backend/.sqlx/query-20888e946a5597989488e2a54e354e4ce2872f2bc3af7b26fcaafe8703ba70bf.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE flow_conversation_message\n SET content = $1\n WHERE job_id = $2\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "20888e946a5597989488e2a54e354e4ce2872f2bc3af7b26fcaafe8703ba70bf"
|
||||
}
|
||||
22
backend/.sqlx/query-25b7c964336321fa10ea988831526b391cc1f02185ee87dbbda3d8a388cc858a.json
generated
Normal file
22
backend/.sqlx/query-25b7c964336321fa10ea988831526b391cc1f02185ee87dbbda3d8a388cc858a.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM v2_job_completed WHERE id = $1)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "25b7c964336321fa10ea988831526b391cc1f02185ee87dbbda3d8a388cc858a"
|
||||
}
|
||||
23
backend/.sqlx/query-27b0c827467cc92979f094620957bc0edfa295d6c2292e509a5536765d120bd8.json
generated
Normal file
23
backend/.sqlx/query-27b0c827467cc92979f094620957bc0edfa295d6c2292e509a5536765d120bd8.json
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT draft_only FROM app WHERE path = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "draft_only",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "27b0c827467cc92979f094620957bc0edfa295d6c2292e509a5536765d120bd8"
|
||||
}
|
||||
23
backend/.sqlx/query-28f1ecca40c8b81cc59dffb75e2913c889b374999ece04173b2e67dc74005f60.json
generated
Normal file
23
backend/.sqlx/query-28f1ecca40c8b81cc59dffb75e2913c889b374999ece04173b2e67dc74005f60.json
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT draft_only FROM flow WHERE path = $1 AND workspace_id = $2 AND archived = false",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "draft_only",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "28f1ecca40c8b81cc59dffb75e2913c889b374999ece04173b2e67dc74005f60"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT \n v2_job_queue.workspace_id,\n v2_job_queue.id,\n v2_job.args as \"args: sqlx::types::Json<HashMap<String, Box<RawValue>>>\",\n v2_job.parent_job,\n v2_job.created_by,\n v2_job_queue.started_at,\n scheduled_for,\n runnable_path,\n kind as \"kind: JobKind\",\n runnable_id as \"runnable_id: ScriptHash\",\n canceled_reason,\n canceled_by,\n permissioned_as,\n permissioned_as_email,\n flow_status as \"flow_status: sqlx::types::Json<Box<RawValue>>\",\n v2_job.tag,\n script_lang as \"script_lang: ScriptLang\",\n same_worker,\n pre_run_error,\n concurrent_limit,\n concurrency_time_window_s,\n flow_innermost_root_job,\n root_job,\n timeout,\n flow_step_id,\n cache_ttl,\n v2_job_queue.priority,\n preprocessed,\n script_entrypoint_override,\n trigger,\n trigger_kind as \"trigger_kind: JobTriggerKind\",\n visible_to_owner\n FROM v2_job_queue INNER JOIN v2_job ON v2_job.id = v2_job_queue.id LEFT JOIN v2_job_status ON v2_job_status.id = v2_job_queue.id WHERE v2_job_queue.id = $1",
|
||||
"query": "SELECT \n v2_job_queue.workspace_id,\n v2_job_queue.id,\n v2_job.args as \"args: sqlx::types::Json<HashMap<String, Box<RawValue>>>\",\n v2_job.parent_job,\n v2_job.created_by,\n v2_job_queue.started_at,\n scheduled_for,\n runnable_path,\n kind as \"kind: JobKind\",\n runnable_id as \"runnable_id: ScriptHash\",\n canceled_reason,\n canceled_by,\n permissioned_as,\n permissioned_as_email,\n flow_status as \"flow_status: sqlx::types::Json<Box<RawValue>>\",\n v2_job.tag,\n script_lang as \"script_lang: ScriptLang\",\n same_worker,\n pre_run_error,\n concurrent_limit,\n concurrency_time_window_s,\n flow_innermost_root_job,\n root_job,\n timeout,\n flow_step_id,\n cache_ttl,\n v2_job_queue.priority,\n preprocessed,\n script_entrypoint_override,\n trigger,\n trigger_kind as \"trigger_kind: JobTriggerKind\",\n visible_to_owner,\n NULL as permissioned_as_end_user_email\n FROM v2_job_queue INNER JOIN v2_job ON v2_job.id = v2_job_queue.id LEFT JOIN v2_job_status ON v2_job_status.id = v2_job_queue.id WHERE v2_job_queue.id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -241,6 +241,11 @@
|
||||
"ordinal": 31,
|
||||
"name": "visible_to_owner",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 32,
|
||||
"name": "permissioned_as_end_user_email",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -280,8 +285,9 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
false,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "f3f96e066716e61042519a645d487b578bc63792cdb0f7ddaeb82e9771287c22"
|
||||
"hash": "3162ec92bb32af47a71cc41172cc740b5dea1304ce4dfdb4d3d0efa4266f38c5"
|
||||
}
|
||||
18
backend/.sqlx/query-326fd614ebd965b9bb6f3e578f75a54d80812ff144e711100e6ac659785c991d.json
generated
Normal file
18
backend/.sqlx/query-326fd614ebd965b9bb6f3e578f75a54d80812ff144e711100e6ac659785c991d.json
generated
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE usr SET added_via = $1, is_admin = $2, operator = $3 WHERE username = $4 AND workspace_id = $5",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Jsonb",
|
||||
"Bool",
|
||||
"Bool",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "326fd614ebd965b9bb6f3e578f75a54d80812ff144e711100e6ac659785c991d"
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT\n c.id IS NOT NULL AS completed,\n CASE\n WHEN q.id IS NOT NULL THEN (CASE WHEN NOT $5 AND q.running THEN true ELSE null END)\n ELSE false\n END AS running,\n CASE WHEN $7::BOOLEAN THEN NULL ELSE SUBSTR(logs, GREATEST($1 - log_offset, 0)) END AS logs,\n SUBSTR(rs.stream, $8) AS new_result_stream,\n COALESCE(r.memory_peak, c.memory_peak) AS mem_peak,\n COALESCE(c.flow_status, f.flow_status) AS \"flow_status: sqlx::types::Json<Box<RawValue>>\",\n (COALESCE(c.flow_status, f.flow_status)->>'stream_job')::uuid AS stream_job,\n COALESCE(c.workflow_as_code_status, f.workflow_as_code_status) AS \"workflow_as_code_status: sqlx::types::Json<Box<RawValue>>\",\n CASE WHEN $7::BOOLEAN THEN NULL ELSE job_logs.log_offset + CHAR_LENGTH(job_logs.logs) + 1 END AS log_offset,\n CHAR_LENGTH(rs.stream) + 1 AS stream_offset,\n created_by AS \"created_by!\",\n CASE WHEN $4::BOOLEAN THEN (\n SELECT scalar_int FROM job_stats WHERE job_id = $3 AND metric_id = 'progress_perc'\n ) END AS progress,\n rs.stream AS \"result_stream: Option<String>\"\n FROM v2_job j\n LEFT JOIN v2_job_queue q USING (id)\n LEFT JOIN v2_job_runtime r USING (id)\n LEFT JOIN v2_job_status f USING (id)\n LEFT JOIN v2_job_completed c USING (id)\n LEFT JOIN job_result_stream rs ON rs.job_id = $3\n LEFT JOIN job_logs ON job_logs.job_id = $3\n WHERE j.workspace_id = $2 AND j.id = $3\n AND ($6::text[] IS NULL OR j.tag = ANY($6))",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "completed",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "running",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "logs",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "new_result_stream",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "mem_peak",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "flow_status: sqlx::types::Json<Box<RawValue>>",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "stream_job",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "workflow_as_code_status: sqlx::types::Json<Box<RawValue>>",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "log_offset",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "stream_offset",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "created_by!",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "progress",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "result_stream: Option<String>",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Text",
|
||||
"Uuid",
|
||||
"Bool",
|
||||
"Bool",
|
||||
"TextArray",
|
||||
"Bool",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "40999264f09a781c4393b50c2c41ae5a5e64086198cb67aba72345bb3cdf7773"
|
||||
}
|
||||
49
backend/.sqlx/query-4b056d33215b3a1e9849bb66ce84e96c69d10e3970e38151c97d1fca0cb7388d.json
generated
Normal file
49
backend/.sqlx/query-4b056d33215b3a1e9849bb66ce84e96c69d10e3970e38151c97d1fca0cb7388d.json
generated
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n WITH result_stream AS (\n SELECT \n string_agg(stream, '' order by idx asc) as stream, \n job_id, \n max(idx) + 1 as offset \n FROM job_result_stream_v2\n WHERE job_id = $2 AND idx >= $3\n GROUP BY job_id\n )\n SELECT\n COALESCE(jc.result, NULL) as \"result: sqlx::types::Json<Box<RawValue>>\",\n rs.stream AS \"result_stream: Option<String>\",\n rs.offset AS stream_offset,\n COALESCE(js.flow_status, jc.flow_status) as \"flow_status: sqlx::types::Json<Box<RawValue>>\",\n CASE WHEN $4 THEN NULL ELSE (COALESCE(js.flow_status, jc.flow_status)->>'stream_job')::uuid END as stream_job\n FROM (\n SELECT $2::uuid as job_id, $1::text as workspace_id\n ) base\n LEFT JOIN v2_job_completed jc ON jc.id = base.job_id AND jc.workspace_id = base.workspace_id\n LEFT JOIN v2_job_status js ON js.id = base.job_id\n LEFT JOIN result_stream rs ON rs.job_id = base.job_id\n WHERE base.job_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "result: sqlx::types::Json<Box<RawValue>>",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "result_stream: Option<String>",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "stream_offset",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "flow_status: sqlx::types::Json<Box<RawValue>>",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "stream_job",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Uuid",
|
||||
"Int4",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "4b056d33215b3a1e9849bb66ce84e96c69d10e3970e38151c97d1fca0cb7388d"
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO job_result_stream (workspace_id, job_id, stream)\n VALUES ($1, $2, $3)\n ON CONFLICT (job_id) DO UPDATE SET stream = job_result_stream.stream || $3\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4bc533074c720820cebff8d97a203df52520b7606378ecca267e88383a45b49b"
|
||||
}
|
||||
14
backend/.sqlx/query-5adeb6989648ca42431c96069c4de88d2615e7e6f1267f2fb12ccc325d4e4148.json
generated
Normal file
14
backend/.sqlx/query-5adeb6989648ca42431c96069c4de88d2615e7e6f1267f2fb12ccc325d4e4148.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM job_result_stream_v2 WHERE job_id = ANY($1)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"UuidArray"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5adeb6989648ca42431c96069c4de88d2615e7e6f1267f2fb12ccc325d4e4148"
|
||||
}
|
||||
64
backend/.sqlx/query-5f57ec3330a6576f4017fd469848a1c214896dc5abbf2b898ac9a68810fd89dd.json
generated
Normal file
64
backend/.sqlx/query-5f57ec3330a6576f4017fd469848a1c214896dc5abbf2b898ac9a68810fd89dd.json
generated
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, conversation_id, message_type as \"message_type: MessageType\", content, job_id, created_at\n FROM (\n SELECT id, conversation_id, message_type, content, job_id, created_at\n FROM flow_conversation_message\n WHERE conversation_id = $1\n ORDER BY created_at DESC, CASE WHEN message_type = 'user' THEN 0 ELSE 1 END\n LIMIT $2 OFFSET $3\n ) AS messages\n ORDER BY created_at ASC, CASE WHEN message_type = 'user' THEN 0 ELSE 1 END\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "conversation_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "message_type: MessageType",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "message_type",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"user",
|
||||
"assistant"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "content",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "job_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "5f57ec3330a6576f4017fd469848a1c214896dc5abbf2b898ac9a68810fd89dd"
|
||||
}
|
||||
20
backend/.sqlx/query-5fdfc9427f455a4c1bc8f6ca41ddfd426bc0c2ac126792c926f3cf1182ded981.json
generated
Normal file
20
backend/.sqlx/query-5fdfc9427f455a4c1bc8f6ca41ddfd426bc0c2ac126792c926f3cf1182ded981.json
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT value->>'ducklake_user_pg_pwd' FROM global_settings WHERE name = 'ducklake_settings';",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "5fdfc9427f455a4c1bc8f6ca41ddfd426bc0c2ac126792c926f3cf1182ded981"
|
||||
}
|
||||
27
backend/.sqlx/query-69606859fe08d24f0306b866f9f50ad766d56a0d5aa8f0784f79aa5e211b00e4.json
generated
Normal file
27
backend/.sqlx/query-69606859fe08d24f0306b866f9f50ad766d56a0d5aa8f0784f79aa5e211b00e4.json
generated
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO flow_conversation_message (conversation_id, message_type, content, job_id)\n VALUES ($1, $2, $3, $4)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "message_type",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"user",
|
||||
"assistant"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Text",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "69606859fe08d24f0306b866f9f50ad766d56a0d5aa8f0784f79aa5e211b00e4"
|
||||
}
|
||||
62
backend/.sqlx/query-6bd23a98838e3eec309e6b696edc776bd56fc9dae1238b3272557d1562400dbe.json
generated
Normal file
62
backend/.sqlx/query-6bd23a98838e3eec309e6b696edc776bd56fc9dae1238b3272557d1562400dbe.json
generated
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO flow_conversation (id, workspace_id, flow_path, created_by, title)\n VALUES ($1, $2, $3, $4, $5)\n RETURNING id, workspace_id, flow_path, title, created_at, updated_at, created_by",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "flow_path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "title",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "updated_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "6bd23a98838e3eec309e6b696edc776bd56fc9dae1238b3272557d1562400dbe"
|
||||
}
|
||||
23
backend/.sqlx/query-71c945f93c0a1b561a85e8462b1687a54bd098cf6e84f57e5755eb84e1552345.json
generated
Normal file
23
backend/.sqlx/query-71c945f93c0a1b561a85e8462b1687a54bd098cf6e84f57e5755eb84e1552345.json
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT draft_only FROM script WHERE path = $1 AND workspace_id = $2 AND archived = false",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "draft_only",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "71c945f93c0a1b561a85e8462b1687a54bd098cf6e84f57e5755eb84e1552345"
|
||||
}
|
||||
20
backend/.sqlx/query-73c1c88bdf26ea0559b83314fed7a67d850e4e4dd60f4424ffb0b6f472acc8d5.json
generated
Normal file
20
backend/.sqlx/query-73c1c88bdf26ea0559b83314fed7a67d850e4e4dd60f4424ffb0b6f472acc8d5.json
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT value->'instance_catalog_db_status' FROM global_settings WHERE name = 'ducklake_settings'",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "73c1c88bdf26ea0559b83314fed7a67d850e4e4dd60f4424ffb0b6f472acc8d5"
|
||||
}
|
||||
22
backend/.sqlx/query-7628e7690aaf6da3dba3cf78dc9e78fe32c3a001c08920e02fb850695afdb7ec.json
generated
Normal file
22
backend/.sqlx/query-7628e7690aaf6da3dba3cf78dc9e78fe32c3a001c08920e02fb850695afdb7ec.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT (flow_status->>'memory_id')::uuid as memory_id \n FROM v2_job_status \n WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "memory_id",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "7628e7690aaf6da3dba3cf78dc9e78fe32c3a001c08920e02fb850695afdb7ec"
|
||||
}
|
||||
101
backend/.sqlx/query-7b524ee24bb78e494a93c5ea205259bd989a9d769a6a300d37ab116960438882.json
generated
Normal file
101
backend/.sqlx/query-7b524ee24bb78e494a93c5ea205259bd989a9d769a6a300d37ab116960438882.json
generated
Normal file
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n WITH result_stream AS (\n SELECT \n string_agg(stream, '' order by idx asc) as stream, \n job_id, \n max(idx) + 1 as offset \n FROM job_result_stream_v2\n WHERE job_id = $3 AND idx >= $8\n GROUP BY job_id\n )\n SELECT\n c.id IS NOT NULL AS completed,\n CASE\n WHEN q.id IS NOT NULL THEN (CASE WHEN NOT $5 AND q.running THEN true ELSE null END)\n ELSE false\n END AS running,\n CASE WHEN $7::BOOLEAN THEN NULL ELSE SUBSTR(logs, GREATEST($1 - log_offset, 0)) END AS logs,\n rs.stream AS new_result_stream,\n COALESCE(r.memory_peak, c.memory_peak) AS mem_peak,\n COALESCE(c.flow_status, f.flow_status) AS \"flow_status: sqlx::types::Json<Box<RawValue>>\",\n (COALESCE(c.flow_status, f.flow_status)->>'stream_job')::uuid AS stream_job,\n COALESCE(c.workflow_as_code_status, f.workflow_as_code_status) AS \"workflow_as_code_status: sqlx::types::Json<Box<RawValue>>\",\n CASE WHEN $7::BOOLEAN THEN NULL ELSE job_logs.log_offset + CHAR_LENGTH(job_logs.logs) + 1 END AS log_offset,\n rs.offset AS stream_offset,\n created_by AS \"created_by!\",\n CASE WHEN $4::BOOLEAN THEN (\n SELECT scalar_int FROM job_stats WHERE job_id = $3 AND metric_id = 'progress_perc'\n ) END AS progress,\n rs.stream AS \"result_stream: Option<String>\"\n FROM v2_job j\n LEFT JOIN v2_job_queue q USING (id)\n LEFT JOIN v2_job_runtime r USING (id)\n LEFT JOIN v2_job_status f USING (id)\n LEFT JOIN v2_job_completed c USING (id)\n LEFT JOIN result_stream rs ON rs.job_id = $3\n LEFT JOIN job_logs ON job_logs.job_id = $3\n WHERE j.workspace_id = $2 AND j.id = $3\n AND ($6::text[] IS NULL OR j.tag = ANY($6))",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "completed",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "running",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "logs",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "new_result_stream",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "mem_peak",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "flow_status: sqlx::types::Json<Box<RawValue>>",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "stream_job",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "workflow_as_code_status: sqlx::types::Json<Box<RawValue>>",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "log_offset",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "stream_offset",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "created_by!",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "progress",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "result_stream: Option<String>",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Text",
|
||||
"Uuid",
|
||||
"Bool",
|
||||
"Bool",
|
||||
"TextArray",
|
||||
"Bool",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "7b524ee24bb78e494a93c5ea205259bd989a9d769a6a300d37ab116960438882"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "VACUUM v2_job, v2_job_completed, job_result_stream, job_stats, job_logs, concurrency_key, log_file, metrics",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "807c920bff25f56b10e88900d879cf5e8484c147e457044d6b075323b163ebaa"
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT\n COALESCE(jc.result, NULL) as \"result: sqlx::types::Json<Box<RawValue>>\",\n SUBSTR(rs.stream, $3) AS \"result_stream: Option<String>\",\n CHAR_LENGTH(rs.stream) + 1 AS stream_offset,\n COALESCE(js.flow_status, jc.flow_status) as \"flow_status: sqlx::types::Json<Box<RawValue>>\",\n CASE WHEN $4 THEN NULL ELSE (COALESCE(js.flow_status, jc.flow_status)->>'stream_job')::uuid END as stream_job\n FROM (\n SELECT $2::uuid as job_id, $1::text as workspace_id\n ) base\n LEFT JOIN v2_job_completed jc ON jc.id = base.job_id AND jc.workspace_id = base.workspace_id\n LEFT JOIN v2_job_status js ON js.id = base.job_id\n LEFT JOIN job_result_stream rs ON rs.job_id = base.job_id\n WHERE base.job_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "result: sqlx::types::Json<Box<RawValue>>",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "result_stream: Option<String>",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "stream_offset",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "flow_status: sqlx::types::Json<Box<RawValue>>",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "stream_job",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Uuid",
|
||||
"Int4",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "80809d397cf84f7278ebb276078871b371663257a127eb35512695c487066fd7"
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT \n jc.result as \"result: sqlx::types::Json<Box<RawValue>>\",\n v2_job.tag,\n v2_job_queue.running as \"running: Option<bool>\",\n SUBSTR(rs.stream, $3) AS \"result_stream: Option<String>\",\n CHAR_LENGTH(rs.stream) AS stream_offset,\n CASE WHEN $4 THEN NULL ELSE (COALESCE(js.flow_status, jc.flow_status)->>'stream_job')::uuid END as stream_job\n FROM v2_job\n LEFT JOIN v2_job_queue USING (id)\n LEFT JOIN v2_job_completed jc USING (id)\n LEFT JOIN v2_job_status js USING (id)\n LEFT JOIN job_result_stream rs ON rs.job_id = $2\n WHERE v2_job.id = $2 AND v2_job.workspace_id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "result: sqlx::types::Json<Box<RawValue>>",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "tag",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "running: Option<bool>",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "result_stream: Option<String>",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "stream_offset",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "stream_job",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Uuid",
|
||||
"Int4",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "8126b118704341846e88bd289f1afe83c07b7a8b422f48022994370b3e433f34"
|
||||
}
|
||||
23
backend/.sqlx/query-81e997576319ae1d6d9a91f76465f6fa53892ff223f3b9d9ad3f4a1d2e720cc8.json
generated
Normal file
23
backend/.sqlx/query-81e997576319ae1d6d9a91f76465f6fa53892ff223f3b9d9ad3f4a1d2e720cc8.json
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM flow_conversation WHERE id = $1 AND workspace_id = $2)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "81e997576319ae1d6d9a91f76465f6fa53892ff223f3b9d9ad3f4a1d2e720cc8"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "WITH inserted_job AS (\n INSERT INTO v2_job (id, workspace_id, raw_code, raw_lock, raw_flow, tag, parent_job,\n created_by, permissioned_as, runnable_id, runnable_path, args, kind, trigger,\n script_lang, same_worker, pre_run_error, permissioned_as_email, visible_to_owner,\n flow_innermost_root_job, root_job, concurrent_limit, concurrency_time_window_s, timeout, flow_step_id,\n cache_ttl, priority, trigger_kind, script_entrypoint_override, preprocessed)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18,\n $19, $20, $38, $21, $22, $23, $24, $25, $26, $39::job_trigger_kind,\n ($12::JSONB)->>'_ENTRYPOINT_OVERRIDE', $27)\n ),\n inserted_runtime AS (\n INSERT INTO v2_job_runtime (id, ping) VALUES ($1, null)\n ),\n inserted_job_perms AS (\n INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups, workspace_id) \n values ($1, $32, $33, $34, $35, $36, $37, $2) \n ON CONFLICT (job_id) DO UPDATE SET email = $32, username = $33, is_admin = $34, is_operator = $35, folders = $36, groups = $37, workspace_id = $2\n )\n INSERT INTO v2_job_queue\n (workspace_id, id, running, scheduled_for, started_at, tag, priority)\n VALUES ($2, $1, $28, COALESCE($29, now()), CASE WHEN $27 OR $40 THEN now() END, $30, $31)",
|
||||
"query": "WITH inserted_job AS (\n INSERT INTO v2_job (id, workspace_id, raw_code, raw_lock, raw_flow, tag, parent_job,\n created_by, permissioned_as, runnable_id, runnable_path, args, kind, trigger,\n script_lang, same_worker, pre_run_error, permissioned_as_email, visible_to_owner,\n flow_innermost_root_job, root_job, concurrent_limit, concurrency_time_window_s, timeout, flow_step_id,\n cache_ttl, priority, trigger_kind, script_entrypoint_override, preprocessed)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18,\n $19, $20, $38, $21, $22, $23, $24, $25, $26, $39::job_trigger_kind,\n ($12::JSONB)->>'_ENTRYPOINT_OVERRIDE', $27)\n ),\n inserted_runtime AS (\n INSERT INTO v2_job_runtime (id, ping) VALUES ($1, null)\n ),\n inserted_job_perms AS (\n INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups, workspace_id, end_user_email) \n values ($1, $32, $33, $34, $35, $36, $37, $2, $41) \n ON CONFLICT (job_id) DO UPDATE SET email = $32, username = $33, is_admin = $34, is_operator = $35, folders = $36, groups = $37, workspace_id = $2\n )\n INSERT INTO v2_job_queue\n (workspace_id, id, running, scheduled_for, started_at, tag, priority)\n VALUES ($2, $1, $28, COALESCE($29, now()), CASE WHEN $27 OR $40 THEN now() END, $30, $31)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -123,10 +123,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Bool"
|
||||
"Bool",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "193d292c5ed44bf5266ad52c83704c3a36aa284fab3b7e638dbca12ac846b82b"
|
||||
"hash": "8d67ed8e1271a27b072a8e8ae9973e372949f5ec7d71b80d1ba3eaddf851adce"
|
||||
}
|
||||
22
backend/.sqlx/query-97e3a1439202e13e739ad2e3f22b3a21d0c9b0e57d7d35326753e8f6a804d4f8.json
generated
Normal file
22
backend/.sqlx/query-97e3a1439202e13e739ad2e3f22b3a21d0c9b0e57d7d35326753e8f6a804d4f8.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS (SELECT 1 FROM pg_catalog.pg_database WHERE datname = $1)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Name"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "97e3a1439202e13e739ad2e3f22b3a21d0c9b0e57d7d35326753e8f6a804d4f8"
|
||||
}
|
||||
17
backend/.sqlx/query-9bfc2a821b25641af48b0e3954163078922340294f1c6515400fb2c896666fde.json
generated
Normal file
17
backend/.sqlx/query-9bfc2a821b25641af48b0e3954163078922340294f1c6515400fb2c896666fde.json
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO job_result_stream_v2 (workspace_id, job_id, stream, idx)\n VALUES (\n $1, \n $2,\n $3, \n $4\n )\n ON CONFLICT (job_id, idx) DO UPDATE SET stream = job_result_stream_v2.stream || $3\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Uuid",
|
||||
"Text",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "9bfc2a821b25641af48b0e3954163078922340294f1c6515400fb2c896666fde"
|
||||
}
|
||||
55
backend/.sqlx/query-9c0bbd44902d8eee393236f7c2372b273d14a093bb29ec12dda8bbfaecd49a35.json
generated
Normal file
55
backend/.sqlx/query-9c0bbd44902d8eee393236f7c2372b273d14a093bb29ec12dda8bbfaecd49a35.json
generated
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n WITH result_stream AS (\n SELECT \n string_agg(stream, '' order by idx asc) as stream, \n job_id, \n max(idx) + 1 as offset \n FROM job_result_stream_v2\n WHERE job_id = $2 AND idx >= $3\n GROUP BY job_id\n )\n SELECT \n jc.result as \"result: sqlx::types::Json<Box<RawValue>>\",\n v2_job.tag,\n v2_job_queue.running as \"running: Option<bool>\",\n rs.stream AS \"result_stream: Option<String>\",\n rs.offset AS stream_offset,\n CASE WHEN $4 THEN NULL ELSE (COALESCE(js.flow_status, jc.flow_status)->>'stream_job')::uuid END as stream_job\n FROM v2_job\n LEFT JOIN v2_job_queue USING (id)\n LEFT JOIN v2_job_completed jc USING (id)\n LEFT JOIN v2_job_status js USING (id)\n LEFT JOIN result_stream rs ON rs.job_id = $2\n WHERE v2_job.id = $2 AND v2_job.workspace_id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "result: sqlx::types::Json<Box<RawValue>>",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "tag",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "running: Option<bool>",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "result_stream: Option<String>",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "stream_offset",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "stream_job",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Uuid",
|
||||
"Int4",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "9c0bbd44902d8eee393236f7c2372b273d14a093bb29ec12dda8bbfaecd49a35"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT workspace_id as workspace, path, summary, description, schema FROM script as o \n WHERE created_at = (select max(created_at) from script where o.path = path and workspace_id = $1 AND archived = false) \n AND workspace_id = $1 and archived = false",
|
||||
"query": "SELECT workspace_id as workspace, path, summary, description, schema FROM script as o\n WHERE created_at = (select max(created_at) from script where o.path = path and workspace_id = $1 AND archived = false)\n AND workspace_id = $1 and archived = false",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -42,5 +42,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "2d5f58dd2aff3bd49f3891ae76df23e2aa39891931516426f65b229314a0cee1"
|
||||
"hash": "9c3ddb90295db7d6afcbdb077f017950620e753dde97c8d2d88cd60ff8c3f339"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, worker_group, event_type::text, desired_workers, reason, applied_at FROM autoscaling_event WHERE worker_group = $1 ORDER BY applied_at DESC LIMIT 5",
|
||||
"query": "SELECT id, worker_group, event_type::text, desired_workers, reason, applied_at FROM autoscaling_event WHERE worker_group = $1 ORDER BY applied_at DESC LIMIT $2 OFFSET $3",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -36,7 +36,9 @@
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
"Text",
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
@@ -48,5 +50,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "6d134b137ae81534e145fc5b6474cf963ee26a3ad3a0a3d8dc064cb14c8fd9a6"
|
||||
"hash": "a38df5d7dc4577c715d9acdaf87c38535ad388b1948a95efafd71135cfe5e3a6"
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT\n COALESCE(jc.result, jc.result) as \"result: sqlx::types::Json<Box<RawValue>>\",\n jq.running as \"running: Option<bool>\",\n SUBSTR(rs.stream, $3) AS \"result_stream: Option<String>\",\n CHAR_LENGTH(rs.stream) + 1 AS stream_offset,\n CASE WHEN $4 THEN NULL ELSE (COALESCE(js.flow_status, jc.flow_status)->>'stream_job')::uuid END as stream_job\n FROM (\n SELECT $1::uuid as job_id, $2::text as workspace_id\n ) base\n LEFT JOIN v2_job_completed jc ON jc.id = base.job_id AND jc.workspace_id = base.workspace_id\n LEFT JOIN v2_job_queue jq ON jq.id = base.job_id AND jq.workspace_id = base.workspace_id\n LEFT JOIN v2_job_status js ON js.id = base.job_id\n LEFT JOIN job_result_stream rs ON rs.job_id = base.job_id\n WHERE base.job_id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "result: sqlx::types::Json<Box<RawValue>>",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "running: Option<bool>",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "result_stream: Option<String>",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "stream_offset",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "stream_job",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text",
|
||||
"Int4",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "a58a345f7082181f89e7f88929b7149791de48bc2e489edb55d63f67702cce05"
|
||||
}
|
||||
49
backend/.sqlx/query-a76eec5797ca8f97e63ed5542bf03873e7dfd1cf9fe984c769afb5a8bdb48d49.json
generated
Normal file
49
backend/.sqlx/query-a76eec5797ca8f97e63ed5542bf03873e7dfd1cf9fe984c769afb5a8bdb48d49.json
generated
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n WITH result_stream AS (\n SELECT \n string_agg(stream, '' order by idx asc) as stream, \n job_id, \n max(idx) + 1 as offset \n FROM job_result_stream_v2\n WHERE job_id = $1 AND idx >= $3\n GROUP BY job_id\n )\n SELECT\n COALESCE(jc.result, jc.result) as \"result: sqlx::types::Json<Box<RawValue>>\",\n jq.running as \"running: Option<bool>\",\n rs.stream AS \"result_stream: Option<String>\",\n rs.offset AS stream_offset,\n CASE WHEN $4 THEN NULL ELSE (COALESCE(js.flow_status, jc.flow_status)->>'stream_job')::uuid END as stream_job\n FROM (\n SELECT $1::uuid as job_id, $2::text as workspace_id\n ) base\n LEFT JOIN v2_job_completed jc ON jc.id = base.job_id AND jc.workspace_id = base.workspace_id\n LEFT JOIN v2_job_queue jq ON jq.id = base.job_id AND jq.workspace_id = base.workspace_id\n LEFT JOIN v2_job_status js ON js.id = base.job_id\n LEFT JOIN result_stream rs ON rs.job_id = base.job_id\n WHERE base.job_id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "result: sqlx::types::Json<Box<RawValue>>",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "running: Option<bool>",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "result_stream: Option<String>",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "stream_offset",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "stream_job",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text",
|
||||
"Int4",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "a76eec5797ca8f97e63ed5542bf03873e7dfd1cf9fe984c769afb5a8bdb48d49"
|
||||
}
|
||||
17
backend/.sqlx/query-abaae3dde751a41b2dbb7856ece1c840d0ea8d59346ed9e88f6f609edb543d7e.json
generated
Normal file
17
backend/.sqlx/query-abaae3dde751a41b2dbb7856ece1c840d0ea8d59346ed9e88f6f609edb543d7e.json
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO app_bundles (app_version_id, w_id, file_type, data) VALUES ($1, $2, $3, $4)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Bytea"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "abaae3dde751a41b2dbb7856ece1c840d0ea8d59346ed9e88f6f609edb543d7e"
|
||||
}
|
||||
22
backend/.sqlx/query-b344ba5a32ec873181390e205e16356f1b79bd994a4bd1a8655dbe17bd1e4a30.json
generated
Normal file
22
backend/.sqlx/query-b344ba5a32ec873181390e205e16356f1b79bd994a4bd1a8655dbe17bd1e4a30.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT ws.ducklake->'ducklakes' AS ducklake_name\n FROM workspace_settings ws\n WHERE ws.workspace_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "ducklake_name",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "b344ba5a32ec873181390e205e16356f1b79bd994a4bd1a8655dbe17bd1e4a30"
|
||||
}
|
||||
14
backend/.sqlx/query-bb46e5dcf5490ef3511faa131ad5693dedf34366e51044ddf30695995d194090.json
generated
Normal file
14
backend/.sqlx/query-bb46e5dcf5490ef3511faa131ad5693dedf34366e51044ddf30695995d194090.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM job_result_stream_v2 WHERE job_id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "bb46e5dcf5490ef3511faa131ad5693dedf34366e51044ddf30695995d194090"
|
||||
}
|
||||
14
backend/.sqlx/query-bd1b62a6435cfa7d8235bcb3cb104ef01831f400ba9bd1c3433412770d1d2a82.json
generated
Normal file
14
backend/.sqlx/query-bd1b62a6435cfa7d8235bcb3cb104ef01831f400ba9bd1c3433412770d1d2a82.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM autoscaling_event WHERE applied_at <= now() - ($1::bigint::text || ' s')::interval",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "bd1b62a6435cfa7d8235bcb3cb104ef01831f400ba9bd1c3433412770d1d2a82"
|
||||
}
|
||||
14
backend/.sqlx/query-bdb1ece5c233f242cf341c089a2f2b785dfa5cc14d9be224c0707e10247ed8b7.json
generated
Normal file
14
backend/.sqlx/query-bdb1ece5c233f242cf341c089a2f2b785dfa5cc14d9be224c0707e10247ed8b7.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE flow_conversation SET updated_at = NOW() WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "bdb1ece5c233f242cf341c089a2f2b785dfa5cc14d9be224c0707e10247ed8b7"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT workspace.id, workspace.name, usr.username, workspace_settings.color, workspace.parent_workspace_id,\n CASE WHEN usr.operator THEN workspace_settings.operator_settings ELSE NULL END as operator_settings\n FROM workspace\n JOIN usr ON usr.workspace_id = workspace.id\n JOIN workspace_settings ON workspace_settings.workspace_id = workspace.id\n WHERE usr.email = $1 AND workspace.deleted = false",
|
||||
"query": "SELECT workspace.id, workspace.name, usr.username, workspace_settings.color, workspace.parent_workspace_id,\n CASE WHEN usr.operator THEN workspace_settings.operator_settings ELSE NULL END as operator_settings,\n usr.disabled\n FROM workspace\n JOIN usr ON usr.workspace_id = workspace.id\n JOIN workspace_settings ON workspace_settings.workspace_id = workspace.id\n WHERE usr.email = $1 AND workspace.deleted = false",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -32,6 +32,11 @@
|
||||
"ordinal": 5,
|
||||
"name": "operator_settings",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "disabled",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -45,8 +50,9 @@
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
null
|
||||
null,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "d0037961e8e787c4277afc3eb79f3b72e3323f878387de8b6fa31493f1215a77"
|
||||
"hash": "c095a9658c542efc9f0255a1b536d2fd8a25fe4cd57c223db7d744493f8470c6"
|
||||
}
|
||||
59
backend/.sqlx/query-c383cc023714b361d10c10e8fef1fc148ab1da942951ee9ffdddaecee76a6be9.json
generated
Normal file
59
backend/.sqlx/query-c383cc023714b361d10c10e8fef1fc148ab1da942951ee9ffdddaecee76a6be9.json
generated
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, workspace_id, flow_path, title, created_at, updated_at, created_by\n FROM flow_conversation\n WHERE id = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "flow_path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "title",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "updated_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "c383cc023714b361d10c10e8fef1fc148ab1da942951ee9ffdddaecee76a6be9"
|
||||
}
|
||||
29
backend/.sqlx/query-dd92bce0ddbfbf06834807aab4d589fc104647cae7abf37ddce3ef7109726261.json
generated
Normal file
29
backend/.sqlx/query-dd92bce0ddbfbf06834807aab4d589fc104647cae7abf37ddce3ef7109726261.json
generated
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT \n string_agg(stream, '' order by idx asc) as stream, \n max(idx) + 1 as offset \n FROM job_result_stream_v2\n WHERE job_id = $2 AND idx >= $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "stream",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "offset",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "dd92bce0ddbfbf06834807aab4d589fc104647cae7abf37ddce3ef7109726261"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT tag, dedicated_worker, flow_version.value->>'early_return' as early_return, flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor, on_behalf_of_email, edited_by, flow_version.id AS version\n FROM flow\n INNER JOIN flow_version\n ON flow_version.id = $3\n WHERE flow.path = $1 and flow.workspace_id = $2",
|
||||
"query": "SELECT tag, dedicated_worker, flow_version.value->>'early_return' as early_return, flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor, (flow_version.value->>'chat_input_enabled')::boolean as chat_input_enabled, on_behalf_of_email, edited_by, flow_version.id AS version\n FROM flow\n INNER JOIN flow_version\n ON flow_version.id = $3\n WHERE flow.path = $1 and flow.workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -25,16 +25,21 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "chat_input_enabled",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "on_behalf_of_email",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"ordinal": 6,
|
||||
"name": "edited_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"ordinal": 7,
|
||||
"name": "version",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
@@ -51,10 +56,11 @@
|
||||
true,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "9b60fa8a1003015bc5a7cdbee9a4486b313d45347dfd9d4793d60e2760763ca3"
|
||||
"hash": "e1409c67b93881cf68f6ac9c8bae0856cf426c7e7860c8b5b799972baa8e6945"
|
||||
}
|
||||
22
backend/.sqlx/query-e2f4eeb896c22e3215370ed30ed5cc890503e4197b0fdb3a906504eba166aa97.json
generated
Normal file
22
backend/.sqlx/query-e2f4eeb896c22e3215370ed30ed5cc890503e4197b0fdb3a906504eba166aa97.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM flow_conversation_message WHERE job_id = $1 AND message_type = 'assistant')",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "e2f4eeb896c22e3215370ed30ed5cc890503e4197b0fdb3a906504eba166aa97"
|
||||
}
|
||||
15
backend/.sqlx/query-e6c4454e552dc82db1af2d6b887ebb4b78eb58600cc21e3ffb12dd9e5a0a6f08.json
generated
Normal file
15
backend/.sqlx/query-e6c4454e552dc82db1af2d6b887ebb4b78eb58600cc21e3ffb12dd9e5a0a6f08.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM flow_conversation WHERE id = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e6c4454e552dc82db1af2d6b887ebb4b78eb58600cc21e3ffb12dd9e5a0a6f08"
|
||||
}
|
||||
41
backend/.sqlx/query-eb110e722ba8ac32d9d69010dc7e5f5763c55a5ac255f939f1fda8d3f9200f8d.json
generated
Normal file
41
backend/.sqlx/query-eb110e722ba8ac32d9d69010dc7e5f5763c55a5ac255f939f1fda8d3f9200f8d.json
generated
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT email, is_admin, operator, added_via FROM usr WHERE username = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "email",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "is_admin",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "operator",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "added_via",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "eb110e722ba8ac32d9d69010dc7e5f5763c55a5ac255f939f1fda8d3f9200f8d"
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT SUBSTR(rs.stream, $1) AS new_result_stream, CHAR_LENGTH(rs.stream) + 1 AS stream_offset FROM job_result_stream rs WHERE rs.job_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "new_result_stream",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "stream_offset",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "f17f914d2522bf7cb5de9d7ba5557ee0dce940039ab42fd39bf079d87b6cad8a"
|
||||
}
|
||||
15
backend/.sqlx/query-f5568a691ec5931634cf986f806f5eae7bb8ed0f5c6e54ca3f49a991c53ed50d.json
generated
Normal file
15
backend/.sqlx/query-f5568a691ec5931634cf986f806f5eae7bb8ed0f5c6e54ca3f49a991c53ed50d.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE v2_job_status \n SET flow_status = jsonb_set(\n flow_status,\n '{memory_id}',\n to_jsonb($2::uuid)\n )\n WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f5568a691ec5931634cf986f806f5eae7bb8ed0f5c6e54ca3f49a991c53ed50d"
|
||||
}
|
||||
12
backend/.sqlx/query-f8ac5379ecfbff7b8ae75c821680737b249a64c8d9e8f7dbcc46fce98e874571.json
generated
Normal file
12
backend/.sqlx/query-f8ac5379ecfbff7b8ae75c821680737b249a64c8d9e8f7dbcc46fce98e874571.json
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "VACUUM v2_job, v2_job_completed, job_result_stream_v2, job_stats, job_logs, concurrency_key, log_file, metrics",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f8ac5379ecfbff7b8ae75c821680737b249a64c8d9e8f7dbcc46fce98e874571"
|
||||
}
|
||||
23
backend/.sqlx/query-fad966db585b91c9ce143c9aa26a826aec1ddb193a7f4988c5f12b1a2d8ce071.json
generated
Normal file
23
backend/.sqlx/query-fad966db585b91c9ce143c9aa26a826aec1ddb193a7f4988c5f12b1a2d8ce071.json
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT app.versions[array_upper(app.versions, 1)] FROM app\n WHERE app.path = $1 AND app.workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "versions",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "fad966db585b91c9ce143c9aa26a826aec1ddb193a7f4988c5f12b1a2d8ce071"
|
||||
}
|
||||
14
backend/.sqlx/query-fd55112d55995ab08d2c275aa6430cdec1cacebdf2f2b3dd6f678b434643eb50.json
generated
Normal file
14
backend/.sqlx/query-fd55112d55995ab08d2c275aa6430cdec1cacebdf2f2b3dd6f678b434643eb50.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE global_settings SET value = jsonb_set(value, '{instance_catalog_db_status}', (COALESCE(value->'instance_catalog_db_status', '{}'::jsonb) || to_jsonb($1::json))) WHERE name = 'ducklake_settings'",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Json"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "fd55112d55995ab08d2c275aa6430cdec1cacebdf2f2b3dd6f678b434643eb50"
|
||||
}
|
||||
551
backend/Cargo.lock
generated
551
backend/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.548.2"
|
||||
version = "1.555.2"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -34,7 +34,7 @@ members = [
|
||||
exclude = ["./windmill-duckdb-ffi-internal"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.548.2"
|
||||
version = "1.555.2"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script outputs all features except private. Usage :
|
||||
# > cargo build --features $(./all_features_oss.sh)
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
# Path to the Cargo.toml file
|
||||
CARGO_TOML_PATH="./Cargo.toml"
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
aa4c7936b99d0de2f7af31cde79dfc7b1b482c29
|
||||
33bdef405c678616b12084cf779a68a62d1f477e
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Add down migration script here
|
||||
DROP TABLE job_result_stream_v2;
|
||||
|
||||
ALTER TABLE job_result_stream ADD CONSTRAINT fk_job_result_stream_job_id FOREIGN KEY (job_id) REFERENCES v2_job_queue(id) ON DELETE CASCADE;
|
||||
@@ -0,0 +1,13 @@
|
||||
-- Add up migration script here
|
||||
CREATE TABLE job_result_stream_v2 (
|
||||
job_id UUID NOT NULL,
|
||||
workspace_id TEXT NOT NULL,
|
||||
stream TEXT NOT NULL,
|
||||
idx INT NOT NULL,
|
||||
PRIMARY KEY (job_id, idx)
|
||||
);
|
||||
|
||||
GRANT ALL ON TABLE job_result_stream_v2 TO windmill_admin;
|
||||
GRANT ALL ON TABLE job_result_stream_v2 TO windmill_user;
|
||||
|
||||
ALTER TABLE job_result_stream DROP CONSTRAINT fk_job_result_stream_job_id;
|
||||
@@ -0,0 +1,12 @@
|
||||
-- Add down migration script here
|
||||
|
||||
-- Drop indexes
|
||||
DROP INDEX IF EXISTS idx_conversation_message_conversation_time;
|
||||
DROP INDEX IF EXISTS idx_flow_conversation_workspace_path;
|
||||
|
||||
-- Drop tables (order matters due to foreign keys)
|
||||
DROP TABLE IF EXISTS flow_conversation_message;
|
||||
DROP TABLE IF EXISTS flow_conversation;
|
||||
|
||||
-- Drop enum
|
||||
DROP TYPE IF EXISTS MESSAGE_TYPE;
|
||||
57
backend/migrations/20250925105841_flow_conversations.up.sql
Normal file
57
backend/migrations/20250925105841_flow_conversations.up.sql
Normal file
@@ -0,0 +1,57 @@
|
||||
-- Add up migration script here
|
||||
|
||||
-- Create message_type enum
|
||||
CREATE TYPE MESSAGE_TYPE AS ENUM ('user', 'assistant');
|
||||
|
||||
-- Create flow_conversation table
|
||||
CREATE TABLE flow_conversation (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
workspace_id VARCHAR(50) NOT NULL REFERENCES workspace(id),
|
||||
flow_path VARCHAR(255) NOT NULL,
|
||||
title VARCHAR(255),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
created_by VARCHAR(50) NOT NULL
|
||||
);
|
||||
|
||||
-- Create flow_conversation_message table
|
||||
CREATE TABLE flow_conversation_message (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
conversation_id UUID NOT NULL REFERENCES flow_conversation(id) ON DELETE CASCADE,
|
||||
message_type MESSAGE_TYPE NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
job_id UUID REFERENCES v2_job(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Basic indexes for performance
|
||||
CREATE INDEX idx_flow_conversation_workspace_path ON flow_conversation(workspace_id, flow_path, updated_at DESC);
|
||||
CREATE INDEX idx_conversation_message_conversation_time ON flow_conversation_message(conversation_id, created_at DESC);
|
||||
|
||||
-- Grant permissions
|
||||
GRANT ALL ON flow_conversation TO windmill_admin;
|
||||
GRANT ALL ON flow_conversation TO windmill_user;
|
||||
GRANT ALL ON flow_conversation_message TO windmill_admin;
|
||||
GRANT ALL ON flow_conversation_message TO windmill_user;
|
||||
|
||||
-- RLS policies
|
||||
ALTER TABLE flow_conversation ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE flow_conversation_message ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Admin policies - admins can access all conversations
|
||||
CREATE POLICY admin_policy ON flow_conversation FOR ALL TO windmill_admin USING (true);
|
||||
CREATE POLICY admin_policy ON flow_conversation_message FOR ALL TO windmill_admin USING (true);
|
||||
|
||||
-- User policies - users can only access their own conversations
|
||||
CREATE POLICY see_own ON flow_conversation FOR ALL TO windmill_user
|
||||
USING (flow_conversation.created_by = current_setting('session.user'));
|
||||
|
||||
-- Users can see messages of conversations they own
|
||||
CREATE POLICY see_own ON flow_conversation_message FOR ALL TO windmill_user
|
||||
USING (
|
||||
EXISTS (
|
||||
SELECT 1 FROM flow_conversation
|
||||
WHERE flow_conversation.id = flow_conversation_message.conversation_id
|
||||
AND flow_conversation.created_by = current_setting('session.user')
|
||||
)
|
||||
);
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add down migration script here
|
||||
DROP TABLE app_bundles;
|
||||
8
backend/migrations/20251001140645_raw_app_bundles.up.sql
Normal file
8
backend/migrations/20251001140645_raw_app_bundles.up.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
-- Add up migration script here
|
||||
CREATE TABLE app_bundles (
|
||||
app_version_id BIGINT NOT NULL,
|
||||
w_id VARCHAR(255) NOT NULL,
|
||||
file_type VARCHAR(10) NOT NULL,
|
||||
data BYTEA NOT NULL,
|
||||
PRIMARY KEY (app_version_id, file_type)
|
||||
);
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add down migration script here
|
||||
ALTER TABLE job_perms DROP COLUMN end_user_email;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE job_perms ADD COLUMN end_user_email VARCHAR(255);
|
||||
@@ -0,0 +1,6 @@
|
||||
INSERT INTO global_settings (name, value) VALUES (
|
||||
'ducklake_user_pg_pwd',
|
||||
(SELECT g2.value->'ducklake_user_pg_pwd' FROM global_settings g2 WHERE g2.name = 'ducklake_settings')
|
||||
);
|
||||
|
||||
DELETE FROM global_settings WHERE name = 'ducklake_settings';
|
||||
@@ -0,0 +1,9 @@
|
||||
INSERT INTO global_settings (name, value) VALUES (
|
||||
'ducklake_settings',
|
||||
(SELECT json_build_object(
|
||||
'ducklake_user_pg_pwd', g2.value,
|
||||
'instance_catalog_db_status', '{}'::json
|
||||
) FROM global_settings g2 WHERE g2.name = 'ducklake_user_pg_pwd')
|
||||
);
|
||||
|
||||
DELETE FROM global_settings WHERE name = 'ducklake_user_pg_pwd';
|
||||
@@ -10,7 +10,7 @@ use regex_lite::Regex;
|
||||
use serde_json::json;
|
||||
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
use windmill_parser::{Arg, MainArgSignature, Typ};
|
||||
use windmill_parser::{Arg, MainArgSignature, ObjectType, Typ};
|
||||
|
||||
pub fn parse_bash_sig(code: &str) -> anyhow::Result<MainArgSignature> {
|
||||
let parsed = parse_bash_file(&code)?;
|
||||
@@ -48,7 +48,7 @@ lazy_static::lazy_static! {
|
||||
static ref RE_BASH: Regex = Regex::new(r#"(?m)^(\w+)="\$(?:(\d+)|\{(\d+)\}|\{(\d+):-(.*)\})"(?:[\t ]*)?(?:#.*)?$"#).unwrap();
|
||||
|
||||
pub static ref RE_POWERSHELL_PARAM: Regex = Regex::new(r#"(?m)param[\t ]*\(([^)]*)\)"#).unwrap();
|
||||
static ref RE_POWERSHELL_ARGS: Regex = Regex::new(r#"(?:\[(\w+)\])?\$(\w+)[\t ]*(?:=[\t ]*(?:(?:(?:"|')([^"\n\r\$]*)(?:"|'))|([\d.]+)))?"#).unwrap();
|
||||
static ref RE_POWERSHELL_ARGS: Regex = Regex::new(r#"(?:\[([\w\[\]]+)\])?\$(\w+)[\t ]*(?:=[\t ]*(?:(?:(?:"|')([^"\n\r\$]*)(?:"|'))|([\d.]+)))?"#).unwrap();
|
||||
}
|
||||
|
||||
fn parse_bash_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
@@ -124,6 +124,18 @@ fn split_pwsh_args(code: &str) -> Vec<&str> {
|
||||
splits
|
||||
}
|
||||
|
||||
fn parse_powershell_single_typ(typ: &str) -> Typ {
|
||||
match typ.to_lowercase().as_str() {
|
||||
"string" => Typ::Str(None),
|
||||
"int" | "long" => Typ::Int,
|
||||
"decimal" | "double" | "single" => Typ::Float,
|
||||
"datetime" => Typ::Datetime,
|
||||
"bool" => Typ::Bool,
|
||||
"pscustomobject" => Typ::Object(ObjectType::new(None, None)),
|
||||
_ => Typ::Str(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_powershell_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
let param_wrapper = RE_POWERSHELL_PARAM.captures(code);
|
||||
let mut args = vec![];
|
||||
@@ -136,12 +148,12 @@ fn parse_powershell_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
let name = cap.get(2).unwrap().as_str().to_string();
|
||||
|
||||
let mut parsed_typ = if let Some(typ) = typ {
|
||||
match typ.as_str() {
|
||||
"string" => Some(Typ::Str(None)),
|
||||
"int" | "long" => Some(Typ::Int),
|
||||
"decimal" | "double" | "single" => Some(Typ::Float),
|
||||
"datetime" | "DateTime" => Some(Typ::Datetime),
|
||||
_ => None,
|
||||
if typ.as_str().ends_with("[]") {
|
||||
Some(Typ::List(Box::new(parse_powershell_single_typ(
|
||||
typ.as_str().strip_suffix("[]").unwrap(),
|
||||
))))
|
||||
} else {
|
||||
Some(parse_powershell_single_typ(typ.as_str()))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
@@ -254,7 +266,7 @@ non_required="${5:-}"
|
||||
|
||||
#[test]
|
||||
fn test_parse_powershell_sig() -> anyhow::Result<()> {
|
||||
let code = r#"param($Msg, [string]$Msg2, $Dflt = "default value, with comma", [int]$Nb = 3 , $Nb2 = 5.0, $Nb3 = 5, $Wahoo = $env:WAHOO)"#;
|
||||
let code = r#"param($Msg, [string]$Msg2, $Dflt = "default value, with comma", [int]$Nb = 3 , $Nb2 = 5.0, $Nb3 = 5, $Wahoo = $env:WAHOO, [PSCustomObject]$Obj, [string[]]$Arr)"#;
|
||||
assert_eq!(
|
||||
parse_powershell_sig(code)?,
|
||||
MainArgSignature {
|
||||
@@ -316,6 +328,22 @@ non_required="${5:-}"
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "Obj".to_string(),
|
||||
typ: Typ::Object(ObjectType::new(None, None)),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
name: "Arr".to_string(),
|
||||
typ: Typ::List(Box::new(Typ::Str(None))),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None
|
||||
}
|
||||
],
|
||||
no_main_func: None,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use anyhow::anyhow;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use regex::Regex;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -491,13 +492,15 @@ fn parse_pg_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
let mut args = vec![];
|
||||
let mut hm: HashMap<i32, String> = HashMap::new();
|
||||
for cap in RE_CODE_PGSQL.captures_iter(code) {
|
||||
let typ = cap
|
||||
.get(2)
|
||||
.map(|cap| transform_types_with_spaces(&cap, &code))
|
||||
.unwrap_or("text");
|
||||
hm.insert(
|
||||
cap.get(1)
|
||||
.and_then(|x| x.as_str().parse::<i32>().ok())
|
||||
.ok_or_else(|| anyhow!("Impossible to parse arg digit"))?,
|
||||
cap.get(2)
|
||||
.map(|x| x.as_str().to_string())
|
||||
.unwrap_or_else(|| "text".to_string()),
|
||||
typ.to_string(),
|
||||
);
|
||||
}
|
||||
for (i, v) in hm.iter() {
|
||||
@@ -543,6 +546,37 @@ fn parse_pg_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
Ok(Some(args))
|
||||
}
|
||||
|
||||
// The regex doesn't parse types with space such as "character varying"
|
||||
// So we look for them manually and replace them with their shorter counterpart
|
||||
fn transform_types_with_spaces<'a>(cap: ®ex::Match<'a>, code: &str) -> &'a str {
|
||||
lazy_static! {
|
||||
static ref TYPES: [(&'static str, &'static str); 6] = [
|
||||
("character varying", "varchar"),
|
||||
("double precision", "double"),
|
||||
("time with time zone", "timetz"),
|
||||
("time without time zone", "time"),
|
||||
("timestamp with time zone", "timestamptz"),
|
||||
("timestamp without time zone", "timestamp"),
|
||||
];
|
||||
}
|
||||
let typ = &code[cap.start()..];
|
||||
for (long_type, alias) in TYPES.iter() {
|
||||
let mut typ = typ;
|
||||
let mut found_mismatch = false;
|
||||
for token in long_type.split(' ') {
|
||||
if typ.len() < token.len() || !typ[..token.len()].eq_ignore_ascii_case(token) {
|
||||
found_mismatch = true;
|
||||
break;
|
||||
}
|
||||
typ = typ[token.len()..].trim_start();
|
||||
}
|
||||
if !found_mismatch {
|
||||
return alias;
|
||||
}
|
||||
}
|
||||
cap.as_str()
|
||||
}
|
||||
|
||||
pub fn parse_sql_statement_named_params(code: &str, prefix: char) -> HashSet<String> {
|
||||
let mut arg_names = HashSet::new();
|
||||
run_on_sql_statement_matches(
|
||||
|
||||
@@ -10,9 +10,10 @@ use monitor::{
|
||||
load_base_url, load_otel, reload_critical_alerts_on_db_oversize,
|
||||
reload_delete_logs_periodically_setting, reload_indexer_config,
|
||||
reload_instance_python_version_setting, reload_maven_repos_setting,
|
||||
reload_no_default_maven_setting, reload_nuget_config_setting, reload_ruby_repos_setting,
|
||||
reload_timeout_wait_result_setting, send_current_log_file_to_object_store,
|
||||
send_logs_to_object_store, WORKERS_NAMES,
|
||||
reload_no_default_maven_setting, reload_nuget_config_setting,
|
||||
reload_powershell_repo_pat_setting, reload_powershell_repo_url_setting,
|
||||
reload_ruby_repos_setting, reload_timeout_wait_result_setting,
|
||||
send_current_log_file_to_object_store, send_logs_to_object_store, WORKERS_NAMES,
|
||||
};
|
||||
use rand::Rng;
|
||||
use sqlx::postgres::PgListener;
|
||||
@@ -45,7 +46,8 @@ use windmill_common::{
|
||||
JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING,
|
||||
LICENSE_KEY_SETTING, MAVEN_REPOS_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING,
|
||||
NO_DEFAULT_MAVEN_SETTING, NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING, OAUTH_SETTING,
|
||||
OTEL_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
|
||||
OTEL_SETTING, PIP_INDEX_URL_SETTING, POWERSHELL_REPO_PAT_SETTING,
|
||||
POWERSHELL_REPO_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
|
||||
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
|
||||
RUBY_REPOS_SETTING, SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, SMTP_SETTING, TEAMS_SETTING,
|
||||
TIMEOUT_WAIT_RESULT_SETTING,
|
||||
@@ -1054,6 +1056,12 @@ Windmill Community Edition {GIT_VERSION}
|
||||
NUGET_CONFIG_SETTING => {
|
||||
reload_nuget_config_setting(&conn).await
|
||||
},
|
||||
POWERSHELL_REPO_URL_SETTING => {
|
||||
reload_powershell_repo_url_setting(&conn).await
|
||||
},
|
||||
POWERSHELL_REPO_PAT_SETTING => {
|
||||
reload_powershell_repo_pat_setting(&conn).await
|
||||
},
|
||||
MAVEN_REPOS_SETTING => {
|
||||
reload_maven_repos_setting(&conn).await
|
||||
},
|
||||
|
||||
@@ -53,7 +53,8 @@ use windmill_common::{
|
||||
HUB_BASE_URL_SETTING, INSTANCE_PYTHON_VERSION_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING,
|
||||
JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING,
|
||||
MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING,
|
||||
OTEL_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
|
||||
OTEL_SETTING, PIP_INDEX_URL_SETTING, POWERSHELL_REPO_PAT_SETTING,
|
||||
POWERSHELL_REPO_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
|
||||
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
|
||||
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, TIMEOUT_WAIT_RESULT_SETTING,
|
||||
},
|
||||
@@ -83,7 +84,8 @@ use windmill_queue::{cancel_job, MiniPulledJob, SameWorkerPayload};
|
||||
use windmill_worker::{
|
||||
handle_job_error, JobCompletedSender, SameWorkerSender, BUNFIG_INSTALL_SCOPES,
|
||||
INSTANCE_PYTHON_VERSION, JOB_DEFAULT_TIMEOUT, KEEP_JOB_DIR, MAVEN_REPOS, NO_DEFAULT_MAVEN,
|
||||
NPM_CONFIG_REGISTRY, NUGET_CONFIG, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL,
|
||||
NPM_CONFIG_REGISTRY, NUGET_CONFIG, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL, POWERSHELL_REPO_PAT,
|
||||
POWERSHELL_REPO_URL,
|
||||
};
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
@@ -298,6 +300,8 @@ pub async fn initial_load(
|
||||
reload_bunfig_install_scopes_setting(&conn).await;
|
||||
reload_instance_python_version_setting(&conn).await;
|
||||
reload_nuget_config_setting(&conn).await;
|
||||
reload_powershell_repo_url_setting(&conn).await;
|
||||
reload_powershell_repo_pat_setting(&conn).await;
|
||||
reload_maven_repos_setting(&conn).await;
|
||||
reload_no_default_maven_setting(&conn).await;
|
||||
reload_ruby_repos_setting(&conn).await;
|
||||
@@ -867,6 +871,16 @@ pub async fn delete_expired_items(db: &DB) -> () {
|
||||
tracing::error!("Error deleting audit log on CE: {:?}", e);
|
||||
}
|
||||
|
||||
if let Err(e) = sqlx::query_scalar!(
|
||||
"DELETE FROM autoscaling_event WHERE applied_at <= now() - ($1::bigint::text || ' s')::interval",
|
||||
30 * 24 * 60 * 60, // 30 days
|
||||
)
|
||||
.fetch_all(db)
|
||||
.await
|
||||
{
|
||||
tracing::error!("Error deleting autoscaling event on CE: {:?}", e);
|
||||
}
|
||||
|
||||
match sqlx::query_scalar!(
|
||||
"DELETE FROM agent_token_blacklist WHERE expires_at <= now() RETURNING token",
|
||||
)
|
||||
@@ -950,6 +964,17 @@ pub async fn delete_expired_items(db: &DB) -> () {
|
||||
{
|
||||
tracing::error!("Error deleting job: {:?}", e);
|
||||
}
|
||||
|
||||
// should already be deleted but just in case
|
||||
if let Err(e) = sqlx::query!(
|
||||
"DELETE FROM job_result_stream_v2 WHERE job_id = ANY($1)",
|
||||
&deleted_jobs
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
{
|
||||
tracing::error!("Error deleting job result stream: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -1104,6 +1129,26 @@ pub async fn reload_nuget_config_setting(conn: &Connection) {
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn reload_powershell_repo_url_setting(conn: &Connection) {
|
||||
reload_option_setting_with_tracing(
|
||||
conn,
|
||||
POWERSHELL_REPO_URL_SETTING,
|
||||
"POWERSHELL_REPO_URL",
|
||||
POWERSHELL_REPO_URL.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn reload_powershell_repo_pat_setting(conn: &Connection) {
|
||||
reload_option_setting_with_tracing(
|
||||
conn,
|
||||
POWERSHELL_REPO_PAT_SETTING,
|
||||
"POWERSHELL_REPO_PAT",
|
||||
POWERSHELL_REPO_PAT.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn reload_maven_repos_setting(conn: &Connection) {
|
||||
reload_option_setting_with_tracing(
|
||||
conn,
|
||||
@@ -1654,7 +1699,7 @@ pub async fn monitor_db(
|
||||
}
|
||||
|
||||
async fn vacuuming_tables(db: &Pool<Postgres>) -> error::Result<()> {
|
||||
sqlx::query!("VACUUM v2_job, v2_job_completed, job_result_stream, job_stats, job_logs, concurrency_key, log_file, metrics")
|
||||
sqlx::query!("VACUUM v2_job, v2_job_completed, job_result_stream_v2, job_stats, job_logs, concurrency_key, log_file, metrics")
|
||||
.execute(db)
|
||||
.await?;
|
||||
Ok(())
|
||||
|
||||
@@ -156,6 +156,7 @@ impl RunJob {
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("push has to succeed");
|
||||
|
||||
@@ -2302,7 +2302,7 @@ async fn test_rust_client(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
#[sqlx::test(fixtures("base", "schedule"))]
|
||||
async fn test_script_schedule_handlers(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
@@ -2459,7 +2459,7 @@ async fn test_script_schedule_handlers(db: Pool<Postgres>) -> anyhow::Result<()>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
#[sqlx::test(fixtures("base", "schedule"))]
|
||||
async fn test_flow_schedule_handlers(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
# Default directory
|
||||
EE_DIR="../windmill-ee-private"
|
||||
|
||||
@@ -21,9 +23,17 @@ if [[ "$(uname)" == "Darwin" ]]; then
|
||||
sed -i '' 's/^samael = { version="0.0.14", features = \["xmlsec"\] }/#samael = { version="0.0.14", features = ["xmlsec"] }/' Cargo.toml
|
||||
# Uncomment the git-based samael dependency
|
||||
sed -i '' 's/^# \(samael = { git="https:\/\/github.com\/njaremko\/samael", rev="464d015e3ae393e4b5dd00b4d6baa1b617de0dd6", features = \["xmlsec"\] }\)/\1/' Cargo.toml
|
||||
|
||||
# Run cargo sqlx prepare with deno_core_mac
|
||||
echo "Running cargo sqlx prepare with deno_core_mac..."
|
||||
cargo sqlx prepare --workspace -- --all-targets --features all_sqlx_features,private,deno_core_mac
|
||||
else
|
||||
# Run cargo sqlx prepare
|
||||
echo "Running cargo sqlx prepare..."
|
||||
cargo sqlx prepare --workspace -- --all-targets --features all_sqlx_features,private
|
||||
fi
|
||||
|
||||
cargo sqlx prepare --workspace -- --all-targets --features all_sqlx_features,private
|
||||
|
||||
|
||||
# Undo the samael changes on macOS
|
||||
if [[ "$(uname)" == "Darwin" ]]; then
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.548.2
|
||||
version: 1.555.2
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -575,6 +575,27 @@ paths:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
/w/{workspace}/users/convert_to_group/{username}:
|
||||
post:
|
||||
summary: convert manual user to group user (require admin privilege)
|
||||
operationId: convertUserToGroup
|
||||
tags:
|
||||
- user
|
||||
- admin
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: username
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: convert user to group user
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/github_app/connected_repositories:
|
||||
get:
|
||||
@@ -749,34 +770,26 @@ paths:
|
||||
schema:
|
||||
type: boolean
|
||||
|
||||
/settings/databases_exist:
|
||||
/settings/get_ducklake_instance_catalog_db_status:
|
||||
post:
|
||||
summary: checks that all given databases exist or else return the ones that don't
|
||||
operationId: databasesExist
|
||||
summary: Returns the set-up statuses of ducklake instance catalog dbs
|
||||
operationId: getDucklakeInstanceCatalogDbStatus
|
||||
tags:
|
||||
- setting
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: databases that do not exist
|
||||
description: Statuses of all ducklake instance catalog dbs
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
type: object
|
||||
additionalProperties:
|
||||
$ref: "#/components/schemas/DucklakeInstanceCatalogDbStatus"
|
||||
|
||||
/settings/create_ducklake_database/{name}:
|
||||
/settings/setup_ducklake_catalog_db/{name}:
|
||||
post:
|
||||
summary: Runs CREATE DATABASE on the Windmill Postgres and grants access to the ducklake_user
|
||||
operationId: createDucklakeDatabase
|
||||
operationId: setupDucklakeCatalogDb
|
||||
tags:
|
||||
- setting
|
||||
parameters:
|
||||
@@ -791,7 +804,8 @@ paths:
|
||||
description: status
|
||||
content:
|
||||
application/json:
|
||||
schema: {}
|
||||
schema:
|
||||
$ref: "#/components/schemas/DucklakeInstanceCatalogDbStatus"
|
||||
|
||||
/settings/global/{key}:
|
||||
get:
|
||||
@@ -2285,7 +2299,6 @@ paths:
|
||||
channel_id:
|
||||
type: string
|
||||
|
||||
|
||||
/w/{workspace}/workspaces/connect_teams:
|
||||
post:
|
||||
summary: connect teams
|
||||
@@ -3948,7 +3961,6 @@ paths:
|
||||
items:
|
||||
type: string
|
||||
|
||||
|
||||
/teams/activities:
|
||||
post:
|
||||
summary: send update to Microsoft Teams activity
|
||||
@@ -5708,6 +5720,12 @@ paths:
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/SkipPreprocessor"
|
||||
- name: memory_id
|
||||
description: memory ID for chat-enabled flows
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
|
||||
requestBody:
|
||||
description: script args
|
||||
@@ -5724,6 +5742,230 @@ paths:
|
||||
application/json:
|
||||
schema: {}
|
||||
|
||||
/w/{workspace}/jobs/run_and_stream/f/{path}:
|
||||
post:
|
||||
summary: run flow by path and stream updates via SSE
|
||||
operationId: runAndStreamFlowByPath
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/SkipPreprocessor"
|
||||
- name: memory_id
|
||||
description: memory ID for chat-enabled flows
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- name: poll_delay_ms
|
||||
description: delay between polling for job updates in milliseconds
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
requestBody:
|
||||
description: flow args
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: server-sent events stream of job updates
|
||||
content:
|
||||
text/event-stream:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
get:
|
||||
summary: run flow by path with GET and stream updates via SSE
|
||||
operationId: runAndStreamFlowByPathGet
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
- $ref: "#/components/parameters/Payload"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/SkipPreprocessor"
|
||||
- name: memory_id
|
||||
description: memory ID for chat-enabled flows
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- name: poll_delay_ms
|
||||
description: delay between polling for job updates in milliseconds
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: server-sent events stream of job updates
|
||||
content:
|
||||
text/event-stream:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/jobs/run_and_stream/p/{path}:
|
||||
post:
|
||||
summary: run script by path and stream updates via SSE
|
||||
operationId: runAndStreamScriptByPath
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
- $ref: "#/components/parameters/ParentJob"
|
||||
- $ref: "#/components/parameters/WorkerTag"
|
||||
- $ref: "#/components/parameters/CacheTtl"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
- $ref: "#/components/parameters/SkipPreprocessor"
|
||||
- name: poll_delay_ms
|
||||
description: delay between polling for job updates in milliseconds
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
requestBody:
|
||||
description: script args
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: server-sent events stream of job updates
|
||||
content:
|
||||
text/event-stream:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
get:
|
||||
summary: run script by path with GET and stream updates via SSE
|
||||
operationId: runAndStreamScriptByPathGet
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
- $ref: "#/components/parameters/ParentJob"
|
||||
- $ref: "#/components/parameters/WorkerTag"
|
||||
- $ref: "#/components/parameters/CacheTtl"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
- $ref: "#/components/parameters/Payload"
|
||||
- $ref: "#/components/parameters/SkipPreprocessor"
|
||||
- name: poll_delay_ms
|
||||
description: delay between polling for job updates in milliseconds
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: server-sent events stream of job updates
|
||||
content:
|
||||
text/event-stream:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/jobs/run_and_stream/h/{hash}:
|
||||
post:
|
||||
summary: run script by hash and stream updates via SSE
|
||||
operationId: runAndStreamScriptByHash
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: hash
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- $ref: "#/components/parameters/ParentJob"
|
||||
- $ref: "#/components/parameters/WorkerTag"
|
||||
- $ref: "#/components/parameters/CacheTtl"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
- $ref: "#/components/parameters/SkipPreprocessor"
|
||||
- name: poll_delay_ms
|
||||
description: delay between polling for job updates in milliseconds
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
requestBody:
|
||||
description: script args
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: server-sent events stream of job updates
|
||||
content:
|
||||
text/event-stream:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
get:
|
||||
summary: run script by hash with GET and stream updates via SSE
|
||||
operationId: runAndStreamScriptByHashGet
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: hash
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- $ref: "#/components/parameters/ParentJob"
|
||||
- $ref: "#/components/parameters/WorkerTag"
|
||||
- $ref: "#/components/parameters/CacheTtl"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
- $ref: "#/components/parameters/Payload"
|
||||
- $ref: "#/components/parameters/SkipPreprocessor"
|
||||
- name: poll_delay_ms
|
||||
description: delay between polling for job updates in milliseconds
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: server-sent events stream of job updates
|
||||
content:
|
||||
text/event-stream:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/jobs/result_by_id/{flow_job_id}/{node_id}:
|
||||
get:
|
||||
summary: get job result by id
|
||||
@@ -6231,6 +6473,81 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/flow_conversations/list:
|
||||
get:
|
||||
summary: list flow conversations
|
||||
operationId: listFlowConversations
|
||||
tags:
|
||||
- flow_conversation
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Page"
|
||||
- $ref: "#/components/parameters/PerPage"
|
||||
- name: flow_path
|
||||
description: filter conversations by flow path
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: flow conversations list
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/FlowConversation"
|
||||
|
||||
/w/{workspace}/flow_conversations/delete/{conversation_id}:
|
||||
delete:
|
||||
summary: delete flow conversation
|
||||
operationId: deleteFlowConversation
|
||||
tags:
|
||||
- flow_conversation
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: conversation_id
|
||||
description: conversation id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
"200":
|
||||
description: flow conversation deleted
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/flow_conversations/{conversation_id}/messages:
|
||||
get:
|
||||
summary: list conversation messages
|
||||
operationId: listConversationMessages
|
||||
tags:
|
||||
- flow_conversation
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: conversation_id
|
||||
description: conversation id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- $ref: "#/components/parameters/Page"
|
||||
- $ref: "#/components/parameters/PerPage"
|
||||
responses:
|
||||
"200":
|
||||
description: conversation messages
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/FlowConversationMessage"
|
||||
|
||||
/w/{workspace}/raw_apps/list:
|
||||
get:
|
||||
summary: list all raw apps
|
||||
@@ -6679,6 +6996,23 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/apps/secret_of_latest_version/{path}:
|
||||
get:
|
||||
summary: get public secret of latest version of an app bundle
|
||||
operationId: getPublicSecretOfLatestVersionOfApp
|
||||
tags:
|
||||
- app
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
responses:
|
||||
"200":
|
||||
description: app secret
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/apps/get/v/{id}:
|
||||
get:
|
||||
summary: get app by version
|
||||
@@ -7103,6 +7437,12 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
- name: memory_id
|
||||
description: memory ID for chat-enabled flows
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
description: flow args
|
||||
required: true
|
||||
@@ -12058,6 +12398,8 @@ paths:
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- $ref: "#/components/parameters/Page"
|
||||
- $ref: "#/components/parameters/PerPage"
|
||||
responses:
|
||||
"200":
|
||||
description: List of autoscaling events
|
||||
@@ -14418,6 +14760,66 @@ components:
|
||||
# -- INLINE END --
|
||||
# Do not change line above
|
||||
|
||||
FlowConversation:
|
||||
type: object
|
||||
required:
|
||||
[id, workspace_id, flow_path, created_at, updated_at, created_by]
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Unique identifier for the conversation
|
||||
workspace_id:
|
||||
type: string
|
||||
description: The workspace ID where the conversation belongs
|
||||
flow_path:
|
||||
type: string
|
||||
description: Path of the flow this conversation is for
|
||||
title:
|
||||
type: string
|
||||
description: Optional title for the conversation
|
||||
nullable: true
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
description: When the conversation was created
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
description: When the conversation was last updated
|
||||
created_by:
|
||||
type: string
|
||||
description: Username who created the conversation
|
||||
|
||||
FlowConversationMessage:
|
||||
type: object
|
||||
required: [id, conversation_id, message_type, content, created_at]
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Unique identifier for the message
|
||||
conversation_id:
|
||||
type: string
|
||||
format: uuid
|
||||
description: The conversation this message belongs to
|
||||
message_type:
|
||||
type: string
|
||||
enum: [user, assistant, system]
|
||||
description: Type of the message
|
||||
content:
|
||||
type: string
|
||||
description: The message content
|
||||
job_id:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
description: Associated job ID if this message came from a flow run
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
description: When the message was created
|
||||
|
||||
EndpointTool:
|
||||
type: object
|
||||
required: [name, description, instructions, path, method]
|
||||
@@ -15353,6 +15755,8 @@ components:
|
||||
AuditLog:
|
||||
type: object
|
||||
properties:
|
||||
workspace_id:
|
||||
type: string
|
||||
id:
|
||||
type: integer
|
||||
timestamp:
|
||||
@@ -15458,6 +15862,7 @@ components:
|
||||
span:
|
||||
type: string
|
||||
required:
|
||||
- workspace_id
|
||||
- id
|
||||
- timestamp
|
||||
- username
|
||||
@@ -16921,6 +17326,49 @@ components:
|
||||
- enabled
|
||||
- aws_auth_resource_type
|
||||
|
||||
LoggedWizardStatus:
|
||||
type: string
|
||||
enum:
|
||||
- OK
|
||||
- SKIP
|
||||
- FAIL
|
||||
|
||||
DucklakeInstanceCatalogDbStatusLogs:
|
||||
type: object
|
||||
properties:
|
||||
super_admin:
|
||||
$ref: "#/components/schemas/LoggedWizardStatus"
|
||||
database_credentials:
|
||||
$ref: "#/components/schemas/LoggedWizardStatus"
|
||||
valid_dbname:
|
||||
$ref: "#/components/schemas/LoggedWizardStatus"
|
||||
created_database:
|
||||
$ref: "#/components/schemas/LoggedWizardStatus"
|
||||
description: Created database status log
|
||||
db_connect:
|
||||
$ref: "#/components/schemas/LoggedWizardStatus"
|
||||
grant_permissions:
|
||||
$ref: "#/components/schemas/LoggedWizardStatus"
|
||||
|
||||
DucklakeInstanceCatalogDbStatus:
|
||||
type: object
|
||||
required:
|
||||
- logs
|
||||
- success
|
||||
properties:
|
||||
logs:
|
||||
$ref: "#/components/schemas/DucklakeInstanceCatalogDbStatusLogs"
|
||||
success:
|
||||
type: boolean
|
||||
description: Whether the operation completed successfully
|
||||
example: true
|
||||
error:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Error message if the operation failed
|
||||
example: "Connection timeout"
|
||||
|
||||
|
||||
NewSqsTrigger:
|
||||
type: object
|
||||
properties:
|
||||
@@ -17615,11 +18063,14 @@ components:
|
||||
created_by:
|
||||
type: string
|
||||
nullable: true
|
||||
disabled:
|
||||
type: boolean
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- username
|
||||
- color
|
||||
- disabled
|
||||
required:
|
||||
- email
|
||||
- workspaces
|
||||
|
||||
@@ -17,7 +17,7 @@ use crate::db::DB;
|
||||
use axum::Router;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub fn global_service() -> Router {
|
||||
pub fn global_service(_job_completed_tx: windmill_worker::JobCompletedSender) -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,10 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/get/lite/*path", get(get_app_lite))
|
||||
.route("/get/draft/*path", get(get_app_w_draft))
|
||||
.route("/secret_of/*path", get(get_secret_id))
|
||||
.route(
|
||||
"/secret_of_latest_version/*path",
|
||||
get(get_latest_version_secret_id),
|
||||
)
|
||||
.route("/get/v/*id", get(get_app_by_id))
|
||||
.route("/get_data/v/*id", get(get_raw_app_data))
|
||||
.route("/exists/*path", get(exists_app))
|
||||
@@ -389,19 +393,83 @@ async fn list_apps(
|
||||
Ok(Json(rows))
|
||||
}
|
||||
|
||||
async fn get_raw_app_data(Path((w_id, version_id)): Path<(String, String)>) -> Result<Response> {
|
||||
let file_path = format!("/tmp/wmill/{}/{}", w_id, version_id);
|
||||
let file = tokio::fs::File::open(file_path).await?;
|
||||
let stream = tokio_util::io::ReaderStream::new(file);
|
||||
let res = Response::builder().header(
|
||||
http::header::CONTENT_TYPE,
|
||||
if version_id.ends_with(".css") {
|
||||
"text/css"
|
||||
} else {
|
||||
"text/javascript"
|
||||
},
|
||||
);
|
||||
Ok(res.body(Body::from_stream(stream)).unwrap())
|
||||
async fn get_raw_app_data(
|
||||
Path((w_id, secret_with_ext)): Path<(String, String)>,
|
||||
Extension(db): Extension<DB>,
|
||||
) -> Result<Response> {
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
let object_store = windmill_common::s3_helpers::get_object_store().await;
|
||||
|
||||
// tracing::info!("secret_with_ext: {}", secret_with_ext);
|
||||
let mut splitted = secret_with_ext.split('.');
|
||||
let secret_id = splitted.next().unwrap_or("");
|
||||
|
||||
if secret_id.is_empty() {
|
||||
return Err(Error::BadRequest("Invalid secret".to_string()));
|
||||
}
|
||||
|
||||
let id = get_id_from_secret(
|
||||
&db,
|
||||
&w_id,
|
||||
secret_id.to_string(),
|
||||
Some(BUNDLE_SECRET_PREFIX),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let file_type = splitted.next().unwrap_or("");
|
||||
let file_type = if file_type == "css" {
|
||||
"css"
|
||||
} else if file_type == "js" {
|
||||
"js"
|
||||
} else {
|
||||
return Err(Error::BadRequest(
|
||||
"Invalid file type, only .css and .js are supported".to_string(),
|
||||
));
|
||||
};
|
||||
// tracing::info!("file_type: {}", file_type);
|
||||
|
||||
#[allow(unused_assignments)]
|
||||
let mut body: Option<Body> = None;
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
if let Some(os) = object_store {
|
||||
let path = format!("/app_bundles/{}/{}.{}", w_id, id, file_type);
|
||||
let stream = os
|
||||
.get(&object_store::path::Path::from(path))
|
||||
.await?
|
||||
.bytes()
|
||||
.await?;
|
||||
tracing::info!("stream: {}", stream.len());
|
||||
body = Some(Body::from(stream));
|
||||
}
|
||||
|
||||
if body.is_none() {
|
||||
let get_raw_app_file = sqlx::query_scalar!(
|
||||
"SELECT data FROM app_bundles WHERE app_version_id = $1 AND file_type = $2 AND w_id = $3",
|
||||
id,
|
||||
file_type,
|
||||
&w_id,
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await?;
|
||||
if let Some(file) = get_raw_app_file {
|
||||
body = Some(Body::from(file));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(body) = body {
|
||||
// let stream = tokio_util::io::ReaderStream::new(file);
|
||||
let res = Response::builder().header(
|
||||
http::header::CONTENT_TYPE,
|
||||
if file_type == "css" {
|
||||
"text/css"
|
||||
} else {
|
||||
"text/javascript"
|
||||
},
|
||||
);
|
||||
Ok(res.body(body).unwrap())
|
||||
} else {
|
||||
return Err(Error::NotFound("File not found".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
// async fn get_app_version(
|
||||
@@ -692,14 +760,7 @@ async fn get_public_app_by_secret(
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, secret)): Path<(String, String)>,
|
||||
) -> JsonResult<AppWithLastVersion> {
|
||||
let mc = build_crypt(&db, &w_id).await?;
|
||||
|
||||
let decrypted = mc
|
||||
.decrypt_bytes_to_bytes(&(hex::decode(secret)?))
|
||||
.map_err(|e| Error::internal_err(e.to_string()))?;
|
||||
let bytes = str::from_utf8(&decrypted).map_err(to_anyhow)?;
|
||||
|
||||
let id: i64 = bytes.parse().map_err(to_anyhow)?;
|
||||
let id = get_id_from_secret(&db, &w_id, secret, None).await?;
|
||||
|
||||
let app_o = sqlx::query_as::<_, AppWithLastVersion>(
|
||||
"SELECT app.id, app.path, app.summary, app.versions, app.policy, app.custom_path,
|
||||
@@ -747,6 +808,27 @@ async fn get_public_app_by_secret(
|
||||
Ok(Json(app))
|
||||
}
|
||||
|
||||
async fn get_id_from_secret(
|
||||
db: &DB,
|
||||
w_id: &str,
|
||||
secret: String,
|
||||
prefix: Option<&str>,
|
||||
) -> Result<i64> {
|
||||
let mc = build_crypt(db, w_id).await?;
|
||||
let decrypted = mc
|
||||
.decrypt_bytes_to_bytes(&(hex::decode(secret)?))
|
||||
.map_err(|e| Error::internal_err(e.to_string()))?;
|
||||
let mut bytes = str::from_utf8(&decrypted).map_err(to_anyhow)?;
|
||||
if let Some(prefix) = prefix {
|
||||
if !bytes.starts_with(prefix) {
|
||||
return Err(Error::BadRequest("Invalid secret".to_string()));
|
||||
}
|
||||
bytes = bytes.strip_prefix(prefix).unwrap_or("");
|
||||
}
|
||||
let id: i64 = bytes.parse().map_err(to_anyhow)?;
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
async fn get_public_resource(
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
@@ -803,16 +885,85 @@ async fn get_secret_id(
|
||||
Ok(hx)
|
||||
}
|
||||
|
||||
const BUNDLE_SECRET_PREFIX: &str = "bundle_";
|
||||
|
||||
async fn get_latest_version_secret_id(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
) -> Result<String> {
|
||||
let path = path.to_path();
|
||||
check_scopes(&authed, || format!("apps:read:{}", path))?;
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
let id_o = sqlx::query_scalar!(
|
||||
"SELECT app.versions[array_upper(app.versions, 1)] FROM app
|
||||
WHERE app.path = $1 AND app.workspace_id = $2",
|
||||
path,
|
||||
&w_id
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?
|
||||
.flatten();
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
let id = not_found_if_none(id_o, "App", path.to_string())?;
|
||||
|
||||
let mc = build_crypt(&db, &w_id).await?;
|
||||
|
||||
let hx = hex::encode(mc.encrypt_str_to_bytes(format!("{}{}", BUNDLE_SECRET_PREFIX, id)));
|
||||
|
||||
Ok(hx)
|
||||
}
|
||||
|
||||
async fn store_raw_app_file<'a>(
|
||||
w_id: &str,
|
||||
id: &i64,
|
||||
file_type: &str,
|
||||
data: bytes::Bytes,
|
||||
tx: &mut sqlx::Transaction<'a, sqlx::Postgres>,
|
||||
) -> Result<()> {
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
{
|
||||
let object_store = windmill_common::s3_helpers::get_object_store().await;
|
||||
|
||||
let path: String = format!("/app_bundles/{}/{}.{}", w_id, id, file_type);
|
||||
|
||||
if let Some(os) = object_store {
|
||||
if let Err(e) = os
|
||||
.put(&object_store::path::Path::from(path.clone()), data.into())
|
||||
.await
|
||||
{
|
||||
tracing::error!("Failed to put snapshot to s3 at {path}: {:?}", e);
|
||||
return Err(windmill_common::error::Error::ExecutionErr(format!(
|
||||
"Failed to put {path} to s3"
|
||||
)));
|
||||
}
|
||||
tracing::info!("Successfully put snapshot to s3 at {path}");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO app_bundles (app_version_id, w_id, file_type, data) VALUES ($1, $2, $3, $4)",
|
||||
id,
|
||||
w_id,
|
||||
file_type,
|
||||
data.to_vec()
|
||||
)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
macro_rules! process_app_multipart {
|
||||
($authed:expr, $user_db:expr, $db:expr, $w_id:expr, $path:expr, $multipart:expr, $internal_fn:expr) => {
|
||||
async {
|
||||
let mut saved_app = None;
|
||||
let mut uploaded_js = false;
|
||||
|
||||
//todo: use s3 instead
|
||||
let file_path = format!("/tmp/wmill/{}", $w_id);
|
||||
std::fs::create_dir_all(&file_path).unwrap();
|
||||
|
||||
let mut multipart = $multipart;
|
||||
while let Some(field) = multipart.next_field().await.unwrap() {
|
||||
let name = field.name().unwrap().to_string();
|
||||
@@ -831,9 +982,8 @@ macro_rules! process_app_multipart {
|
||||
.await?;
|
||||
saved_app = Some((npath, nid, ntx));
|
||||
} else if name == "js" {
|
||||
if let Some((_npath, id, _tx)) = saved_app.as_ref() {
|
||||
let file_path = format!("{}/{}.js", file_path, id);
|
||||
std::fs::write(file_path, data).unwrap();
|
||||
if let Some((_npath, id, tx)) = saved_app.as_mut() {
|
||||
store_raw_app_file($w_id, &id, "js", data, tx).await?;
|
||||
uploaded_js = true;
|
||||
} else {
|
||||
return Err(Error::BadRequest(
|
||||
@@ -841,9 +991,8 @@ macro_rules! process_app_multipart {
|
||||
));
|
||||
}
|
||||
} else if name == "css" {
|
||||
if let Some((_npath, id, _tx)) = saved_app.as_ref() {
|
||||
let file_path = format!("{}/{}.css", file_path, id);
|
||||
std::fs::write(file_path, data).unwrap();
|
||||
if let Some((_npath, id, tx)) = saved_app.as_mut() {
|
||||
store_raw_app_file($w_id, &id, "css", data, tx).await?;
|
||||
} else {
|
||||
return Err(Error::BadRequest(
|
||||
"App payload need to be created first".to_string(),
|
||||
@@ -1090,6 +1239,7 @@ async fn create_app_internal<'a>(
|
||||
None,
|
||||
Some(&authed.clone().into()),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tracing::info!("Pushed app dependency job {}", dependency_job_uuid);
|
||||
@@ -1469,6 +1619,7 @@ async fn update_app_internal<'a>(
|
||||
None,
|
||||
Some(&authed.clone().into()),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tracing::info!("Pushed app dependency job {}", dependency_job_uuid);
|
||||
@@ -1755,6 +1906,8 @@ async fn execute_component(
|
||||
(email.as_str(), permissioned_as)
|
||||
};
|
||||
|
||||
let end_user_email = opt_authed.as_ref().map(|a| a.email.clone());
|
||||
|
||||
let (uuid, tx) = push(
|
||||
&db,
|
||||
tx,
|
||||
@@ -1784,6 +1937,7 @@ async fn execute_component(
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
end_user_email,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
@@ -121,11 +121,10 @@ impl AuthCache {
|
||||
|
||||
match jwt_result {
|
||||
Ok(claims) => {
|
||||
if w_id.is_some_and(|w_id| w_id != claims.workspace_id) {
|
||||
if w_id.is_some_and(|w_id| !claims.allowed_in_workspace(&w_id)) {
|
||||
tracing::error!("JWT auth error: workspace_id mismatch");
|
||||
return None;
|
||||
}
|
||||
|
||||
let username_override = username_override_from_label(claims.label);
|
||||
let authed = crate::db::ApiAuthed {
|
||||
email: claims.email,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
use axum::{
|
||||
extract::{Extension, Path},
|
||||
extract::{Extension, Path, Query},
|
||||
routing::{get, post},
|
||||
Json, Router,
|
||||
};
|
||||
@@ -18,6 +18,7 @@ use windmill_audit::audit_oss::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::{
|
||||
error::{self},
|
||||
utils::Pagination,
|
||||
worker::MIN_PERIODIC_SCRIPT_INTERVAL_SECONDS,
|
||||
DB,
|
||||
};
|
||||
@@ -239,11 +240,19 @@ struct AutoscalingEvent {
|
||||
async fn list_autoscaling_events(
|
||||
Extension(db): Extension<DB>,
|
||||
Path(worker_group): Path<String>,
|
||||
Query(mut pagination): Query<Pagination>,
|
||||
) -> error::JsonResult<Vec<AutoscalingEvent>> {
|
||||
if pagination.per_page.is_none() {
|
||||
pagination.per_page = Some(5);
|
||||
}
|
||||
let (per_page, offset) = windmill_common::utils::paginate(pagination);
|
||||
|
||||
let events = sqlx::query_as!(
|
||||
AutoscalingEvent,
|
||||
"SELECT id, worker_group, event_type::text, desired_workers, reason, applied_at FROM autoscaling_event WHERE worker_group = $1 ORDER BY applied_at DESC LIMIT 5",
|
||||
worker_group
|
||||
"SELECT id, worker_group, event_type::text, desired_workers, reason, applied_at FROM autoscaling_event WHERE worker_group = $1 ORDER BY applied_at DESC LIMIT $2 OFFSET $3",
|
||||
worker_group,
|
||||
per_page as i64,
|
||||
offset as i64
|
||||
)
|
||||
.fetch_all(&db)
|
||||
.await?;
|
||||
@@ -263,8 +272,7 @@ async fn native_kubernetes_autoscaling_healthcheck(
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "enterprise", feature = "private")))]
|
||||
async fn native_kubernetes_autoscaling_healthcheck(
|
||||
) -> Result<(), error::Error> {
|
||||
async fn native_kubernetes_autoscaling_healthcheck() -> Result<(), error::Error> {
|
||||
Err(error::Error::BadRequest(
|
||||
"Native Kubernetes autoscaling available only in the enterprise version".to_string(),
|
||||
))
|
||||
|
||||
297
backend/windmill-api/src/flow_conversations.rs
Normal file
297
backend/windmill-api/src/flow_conversations.rs
Normal file
@@ -0,0 +1,297 @@
|
||||
use axum::{
|
||||
extract::{Path, Query},
|
||||
routing::{delete, get},
|
||||
Extension, Json, Router,
|
||||
};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sql_builder::prelude::*;
|
||||
use sqlx::{FromRow, Postgres};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::db::ApiAuthed;
|
||||
use windmill_common::{
|
||||
db::UserDB,
|
||||
error::{JsonResult, Result},
|
||||
utils::{not_found_if_none, paginate, Pagination},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, sqlx::Type)]
|
||||
#[sqlx(type_name = "MESSAGE_TYPE", rename_all = "lowercase")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum MessageType {
|
||||
User,
|
||||
Assistant,
|
||||
}
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
.route("/list", get(list_conversations))
|
||||
.route("/delete/:conversation_id", delete(delete_conversation))
|
||||
.route("/:conversation_id/messages", get(list_messages))
|
||||
}
|
||||
|
||||
#[derive(Serialize, FromRow, Debug)]
|
||||
pub struct FlowConversation {
|
||||
pub id: Uuid,
|
||||
pub workspace_id: String,
|
||||
pub flow_path: String,
|
||||
pub title: Option<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub created_by: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, FromRow, Debug)]
|
||||
pub struct FlowConversationMessage {
|
||||
pub id: Uuid,
|
||||
pub conversation_id: Uuid,
|
||||
pub message_type: MessageType,
|
||||
pub content: String,
|
||||
pub job_id: Option<Uuid>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ListConversationsQuery {
|
||||
pub flow_path: Option<String>,
|
||||
}
|
||||
|
||||
async fn list_conversations(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path(w_id): Path<String>,
|
||||
Query(pagination): Query<Pagination>,
|
||||
Query(query): Query<ListConversationsQuery>,
|
||||
) -> JsonResult<Vec<FlowConversation>> {
|
||||
let (per_page, offset) = paginate(pagination);
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
|
||||
let mut sqlb = SqlBuilder::select_from("flow_conversation");
|
||||
sqlb.fields(&[
|
||||
"id",
|
||||
"workspace_id",
|
||||
"flow_path",
|
||||
"title",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"created_by",
|
||||
])
|
||||
.and_where_eq("workspace_id", "?".bind(&w_id));
|
||||
|
||||
if let Some(flow_path) = &query.flow_path {
|
||||
sqlb.and_where_eq("flow_path", "?".bind(flow_path));
|
||||
}
|
||||
|
||||
sqlb.order_by("updated_at", true)
|
||||
.limit(per_page as i64)
|
||||
.offset(offset as i64);
|
||||
|
||||
let sql = sqlb.sql().map_err(|e| {
|
||||
windmill_common::error::Error::internal_err(format!("Failed to build SQL: {}", e))
|
||||
})?;
|
||||
|
||||
let conversations = sqlx::query_as::<Postgres, FlowConversation>(&sql)
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
Ok(Json(conversations))
|
||||
}
|
||||
|
||||
pub async fn get_or_create_conversation_with_id(
|
||||
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
w_id: &str,
|
||||
flow_path: &str,
|
||||
username: &str,
|
||||
title: &str,
|
||||
conversation_id: Uuid,
|
||||
) -> Result<FlowConversation> {
|
||||
// Check if conversation already exists
|
||||
let existing_conversation = sqlx::query_as!(
|
||||
FlowConversation,
|
||||
"SELECT id, workspace_id, flow_path, title, created_at, updated_at, created_by
|
||||
FROM flow_conversation
|
||||
WHERE id = $1 AND workspace_id = $2",
|
||||
conversation_id,
|
||||
w_id
|
||||
)
|
||||
.fetch_optional(&mut **tx)
|
||||
.await?;
|
||||
|
||||
if let Some(existing) = existing_conversation {
|
||||
return Ok(existing);
|
||||
}
|
||||
|
||||
// Truncate title to 25 char characters max
|
||||
let title = if title.len() > 25 {
|
||||
format!("{}...", &title[..25])
|
||||
} else {
|
||||
title.to_string()
|
||||
};
|
||||
// Create new conversation with provided ID
|
||||
let conversation = sqlx::query_as!(
|
||||
FlowConversation,
|
||||
"INSERT INTO flow_conversation (id, workspace_id, flow_path, created_by, title)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id, workspace_id, flow_path, title, created_at, updated_at, created_by",
|
||||
conversation_id,
|
||||
w_id,
|
||||
flow_path,
|
||||
username,
|
||||
title
|
||||
)
|
||||
.fetch_one(&mut **tx)
|
||||
.await?;
|
||||
Ok(conversation)
|
||||
}
|
||||
|
||||
async fn delete_conversation(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, conversation_id)): Path<(String, Uuid)>,
|
||||
) -> Result<String> {
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
|
||||
// Verify the conversation exists and belongs to the user
|
||||
let conversation = sqlx::query_as!(
|
||||
FlowConversation,
|
||||
"SELECT id, workspace_id, flow_path, title, created_at, updated_at, created_by
|
||||
FROM flow_conversation
|
||||
WHERE id = $1 AND workspace_id = $2",
|
||||
conversation_id,
|
||||
&w_id
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?;
|
||||
|
||||
not_found_if_none(conversation, "Conversation", conversation_id.to_string())?;
|
||||
|
||||
// Delete the conversation (messages will be cascade deleted)
|
||||
sqlx::query!(
|
||||
"DELETE FROM flow_conversation WHERE id = $1 AND workspace_id = $2",
|
||||
conversation_id,
|
||||
&w_id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
// Delete associated memory in background (non-blocking cleanup)
|
||||
let w_id_clone = w_id.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) =
|
||||
windmill_worker::memory_oss::delete_conversation_memory(&w_id_clone, conversation_id)
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"Failed to delete memory for conversation {} in workspace {}: {:?}",
|
||||
conversation_id,
|
||||
w_id_clone,
|
||||
e
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Ok(format!("Conversation {} deleted", conversation_id))
|
||||
}
|
||||
|
||||
async fn list_messages(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, conversation_id)): Path<(String, Uuid)>,
|
||||
Query(pagination): Query<Pagination>,
|
||||
) -> JsonResult<Vec<FlowConversationMessage>> {
|
||||
let (per_page, offset) = paginate(pagination);
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
|
||||
// Verify the conversation exists and belongs to the user
|
||||
let conversation_exists = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM flow_conversation WHERE id = $1 AND workspace_id = $2)",
|
||||
conversation_id,
|
||||
&w_id
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
if !conversation_exists {
|
||||
return Err(windmill_common::error::Error::NotFound(format!(
|
||||
"Conversation not found or access denied: {}",
|
||||
conversation_id
|
||||
)));
|
||||
}
|
||||
|
||||
// Fetch messages for this conversation, oldest first, but reverse the order of the messages for easy rendering on the frontend
|
||||
let messages = sqlx::query_as!(
|
||||
FlowConversationMessage,
|
||||
r#"SELECT id, conversation_id, message_type as "message_type: MessageType", content, job_id, created_at
|
||||
FROM (
|
||||
SELECT id, conversation_id, message_type, content, job_id, created_at
|
||||
FROM flow_conversation_message
|
||||
WHERE conversation_id = $1
|
||||
ORDER BY created_at DESC, CASE WHEN message_type = 'user' THEN 0 ELSE 1 END
|
||||
LIMIT $2 OFFSET $3
|
||||
) AS messages
|
||||
ORDER BY created_at ASC, CASE WHEN message_type = 'user' THEN 0 ELSE 1 END
|
||||
"#,
|
||||
conversation_id,
|
||||
per_page as i64,
|
||||
offset as i64
|
||||
)
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
Ok(Json(messages))
|
||||
}
|
||||
|
||||
// Helper function to create a message using an existing transaction
|
||||
pub async fn create_message(
|
||||
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
conversation_id: Uuid,
|
||||
message_type: MessageType,
|
||||
content: &str,
|
||||
job_id: Option<Uuid>,
|
||||
workspace_id: &str,
|
||||
) -> windmill_common::error::Result<()> {
|
||||
// Verify the conversation exists and belongs to the user
|
||||
let conversation_exists = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM flow_conversation WHERE id = $1 AND workspace_id = $2)",
|
||||
conversation_id,
|
||||
workspace_id
|
||||
)
|
||||
.fetch_one(&mut **tx)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
if !conversation_exists {
|
||||
return Err(windmill_common::error::Error::NotFound(format!(
|
||||
"Conversation not found or access denied: {}",
|
||||
conversation_id
|
||||
)));
|
||||
}
|
||||
|
||||
// Insert the message
|
||||
sqlx::query!(
|
||||
"INSERT INTO flow_conversation_message (conversation_id, message_type, content, job_id)
|
||||
VALUES ($1, $2, $3, $4)",
|
||||
conversation_id,
|
||||
message_type as MessageType,
|
||||
content,
|
||||
job_id
|
||||
)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
|
||||
// Update conversation updated_at timestamp
|
||||
sqlx::query!(
|
||||
"UPDATE flow_conversation SET updated_at = NOW() WHERE id = $1",
|
||||
conversation_id
|
||||
)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -545,6 +545,7 @@ async fn create_flow(
|
||||
None,
|
||||
Some(&authed.clone().into()),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -937,7 +938,7 @@ async fn update_flow(
|
||||
clear_schedule(&mut tx, &schedule.path, &w_id).await?;
|
||||
|
||||
if schedule.enabled {
|
||||
tx = push_scheduled_job(&db, tx, &schedule, None).await?;
|
||||
tx = push_scheduled_job(&db, tx, &schedule, None, None).await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1012,6 +1013,7 @@ async fn update_flow(
|
||||
None,
|
||||
Some(&authed.clone().into()),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
sqlx::query!(
|
||||
@@ -1593,6 +1595,7 @@ mod tests {
|
||||
priority: None,
|
||||
early_return: None,
|
||||
concurrency_key: None,
|
||||
chat_input_enabled: None,
|
||||
};
|
||||
let expect = serde_json::json!({
|
||||
"modules": [
|
||||
|
||||
@@ -35,13 +35,13 @@ use windmill_common::jobs::{
|
||||
check_tag_available_for_workspace_internal, format_completed_job_result, format_result,
|
||||
DynamicInput, ENTRYPOINT_OVERRIDE,
|
||||
};
|
||||
use windmill_common::s3_helpers::{upload_artifact_to_store, BundleFormat};
|
||||
use windmill_common::utils::{RunnableKind, WarnAfterExt};
|
||||
use windmill_common::worker::{Connection, CLOUD_HOSTED, TMP_DIR};
|
||||
use windmill_common::DYNAMIC_INPUT_CACHE;
|
||||
#[cfg(all(feature = "enterprise", feature = "smtp"))]
|
||||
use windmill_common::{email_oss::send_email_html, server::load_smtp_config};
|
||||
|
||||
use windmill_common::scripts::PREVIEW_IS_CODEBASE_HASH;
|
||||
use windmill_common::variables::get_workspace_key;
|
||||
|
||||
use crate::triggers::trigger_helpers::ScriptId;
|
||||
@@ -102,6 +102,8 @@ use windmill_queue::{
|
||||
PushArgsOwned, PushIsolationLevel,
|
||||
};
|
||||
|
||||
use crate::flow_conversations::{self, MessageType};
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
let cors = CorsLayer::new()
|
||||
.allow_methods([http::Method::GET, http::Method::POST])
|
||||
@@ -175,6 +177,7 @@ pub fn workspaced_service() -> Router {
|
||||
.route(
|
||||
"/run_and_stream/f/*script_path",
|
||||
get(stream_flow_by_path)
|
||||
.post(stream_flow_by_path)
|
||||
.head(|| async { "" })
|
||||
.layer(cors.clone())
|
||||
.layer(ce_headers.clone()),
|
||||
@@ -182,6 +185,7 @@ pub fn workspaced_service() -> Router {
|
||||
.route(
|
||||
"/run_and_stream/p/*script_path",
|
||||
get(stream_script_by_path)
|
||||
.post(stream_script_by_path)
|
||||
.head(|| async { "" })
|
||||
.layer(cors.clone())
|
||||
.layer(ce_headers.clone()),
|
||||
@@ -189,6 +193,7 @@ pub fn workspaced_service() -> Router {
|
||||
.route(
|
||||
"/run_and_stream/h/:hash",
|
||||
get(stream_script_by_hash)
|
||||
.post(stream_script_by_hash)
|
||||
.head(|| async { "" })
|
||||
.layer(cors.clone())
|
||||
.layer(ce_headers.clone()),
|
||||
@@ -1752,6 +1757,7 @@ pub struct RunJobQuery {
|
||||
pub cache_ttl: Option<i32>,
|
||||
pub skip_preprocessor: Option<bool>,
|
||||
pub poll_delay_ms: Option<u64>,
|
||||
pub memory_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
impl RunJobQuery {
|
||||
@@ -3523,6 +3529,7 @@ struct Preview {
|
||||
tag: Option<String>,
|
||||
dedicated_worker: Option<bool>,
|
||||
lock: Option<String>,
|
||||
format: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -3552,7 +3559,7 @@ pub enum DynamicSelectRunnableRef {
|
||||
#[serde(rename = "deployed")]
|
||||
Deployed { path: String, runnable_kind: RunnableKind },
|
||||
#[serde(rename = "inline")]
|
||||
Inline { code: String, language: Option<ScriptLang> },
|
||||
Inline { code: String, lang: Option<ScriptLang> },
|
||||
}
|
||||
|
||||
pub struct QueryOrBody<D>(pub Option<D>);
|
||||
@@ -3907,6 +3914,91 @@ async fn batch_rerun_handle_job(
|
||||
))
|
||||
}
|
||||
|
||||
/// Set the memory_id in flow_status for agent memory persistence
|
||||
async fn set_flow_memory_id(
|
||||
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
job_id: Uuid,
|
||||
memory_id: Uuid,
|
||||
) -> error::Result<()> {
|
||||
sqlx::query!(
|
||||
"UPDATE v2_job_status
|
||||
SET flow_status = jsonb_set(
|
||||
flow_status,
|
||||
'{memory_id}',
|
||||
to_jsonb($2::uuid)
|
||||
)
|
||||
WHERE id = $1",
|
||||
job_id,
|
||||
memory_id
|
||||
)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_chat_conversation_messages(
|
||||
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
authed: &ApiAuthed,
|
||||
w_id: &str,
|
||||
flow_path: &str,
|
||||
run_query: &RunJobQuery,
|
||||
user_message_raw: Option<&Box<serde_json::value::RawValue>>,
|
||||
uuid: Uuid,
|
||||
) -> error::Result<()> {
|
||||
let memory_id = run_query.memory_id.ok_or_else(|| {
|
||||
windmill_common::error::Error::BadRequest(
|
||||
"memory_id is required for chat-enabled flows".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let user_message_raw = user_message_raw.ok_or_else(|| {
|
||||
windmill_common::error::Error::BadRequest(
|
||||
"user_message argument is required for chat-enabled flows".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
// Deserialize the RawValue to get the actual string without quotes
|
||||
let user_message: String = serde_json::from_str(user_message_raw.get())
|
||||
.map_err(|e| windmill_common::error::Error::BadRequest(
|
||||
format!("Failed to deserialize user_message: {}", e)
|
||||
))?;
|
||||
|
||||
// Create conversation with provided ID (or get existing one)
|
||||
flow_conversations::get_or_create_conversation_with_id(
|
||||
tx,
|
||||
w_id,
|
||||
flow_path,
|
||||
&authed.username,
|
||||
&user_message,
|
||||
memory_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Create user message
|
||||
flow_conversations::create_message(
|
||||
tx,
|
||||
memory_id,
|
||||
MessageType::User,
|
||||
&user_message,
|
||||
None, // No job_id for user message
|
||||
w_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Create placeholder assistant message in the same transaction as the job
|
||||
flow_conversations::create_message(
|
||||
tx,
|
||||
memory_id,
|
||||
MessageType::Assistant,
|
||||
"", // Empty content, will be updated when job completes
|
||||
Some(uuid), // Associate with the job
|
||||
w_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run_flow_by_path(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
@@ -3953,6 +4045,7 @@ pub async fn run_flow_by_path_inner(
|
||||
tag,
|
||||
dedicated_worker,
|
||||
has_preprocessor,
|
||||
chat_input_enabled,
|
||||
on_behalf_of_email,
|
||||
edited_by,
|
||||
early_return,
|
||||
@@ -3978,11 +4071,11 @@ pub async fn run_flow_by_path_inner(
|
||||
&authed.email,
|
||||
username_to_permissioned_as(&authed.username),
|
||||
Some(authed.clone().into()),
|
||||
PushIsolationLevel::Isolated(user_db, authed.clone().into()),
|
||||
PushIsolationLevel::Isolated(user_db.clone(), authed.clone().into()),
|
||||
)
|
||||
};
|
||||
|
||||
let (uuid, tx) = push(
|
||||
let (uuid, mut tx) = push(
|
||||
&db,
|
||||
tx,
|
||||
&w_id,
|
||||
@@ -4014,8 +4107,29 @@ pub async fn run_flow_by_path_inner(
|
||||
None,
|
||||
push_authed.as_ref(),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Set memory_id if provided (for agent memory)
|
||||
if let Some(memory_id) = run_query.memory_id {
|
||||
set_flow_memory_id(&mut tx, uuid, memory_id).await?;
|
||||
}
|
||||
|
||||
// Handle conversation messages for chat-enabled flows
|
||||
if chat_input_enabled.unwrap_or(false) {
|
||||
handle_chat_conversation_messages(
|
||||
&mut tx,
|
||||
&authed,
|
||||
&w_id,
|
||||
&flow_path.to_string(),
|
||||
&run_query,
|
||||
args.args.get("user_message"),
|
||||
uuid,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
tx.commit().await?;
|
||||
Ok((uuid, early_return))
|
||||
}
|
||||
@@ -4110,6 +4224,7 @@ pub async fn restart_flow(
|
||||
completed_job.priority,
|
||||
Some(&authed.clone().into()),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -4212,6 +4327,7 @@ pub async fn run_script_by_path_inner(
|
||||
None,
|
||||
push_authed.as_ref(),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -4364,6 +4480,7 @@ pub async fn run_workflow_as_code(
|
||||
None,
|
||||
push_authed.as_ref(),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -4907,6 +5024,7 @@ pub async fn run_wait_result_job_by_path_get(
|
||||
None,
|
||||
push_authed.as_ref(),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -5059,6 +5177,7 @@ pub async fn run_wait_result_script_by_path_internal(
|
||||
None,
|
||||
push_authed.as_ref(),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -5175,6 +5294,7 @@ pub async fn run_wait_result_script_by_hash(
|
||||
None,
|
||||
push_authed.as_ref(),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -5220,6 +5340,7 @@ pub async fn stream_flow_by_path(
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, flow_path)): Path<(String, StripPath)>,
|
||||
Query(run_query): Query<RunJobQuery>,
|
||||
method: hyper::http::Method,
|
||||
args: RawWebhookArgs,
|
||||
) -> error::Result<Response> {
|
||||
stream_job(
|
||||
@@ -5230,6 +5351,7 @@ pub async fn stream_flow_by_path(
|
||||
RunnableId::from_flow_path(flow_path.to_path()),
|
||||
args,
|
||||
run_query,
|
||||
method == http::Method::GET,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -5240,6 +5362,7 @@ pub async fn stream_script_by_path(
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, script_path)): Path<(String, StripPath)>,
|
||||
Query(run_query): Query<RunJobQuery>,
|
||||
method: hyper::http::Method,
|
||||
args: RawWebhookArgs,
|
||||
) -> error::Result<Response> {
|
||||
stream_job(
|
||||
@@ -5250,6 +5373,7 @@ pub async fn stream_script_by_path(
|
||||
RunnableId::from_script_path(script_path.to_path()),
|
||||
args,
|
||||
run_query,
|
||||
method == http::Method::GET,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -5260,6 +5384,7 @@ pub async fn stream_script_by_hash(
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, script_hash)): Path<(String, ScriptHash)>,
|
||||
Query(run_query): Query<RunJobQuery>,
|
||||
method: hyper::http::Method,
|
||||
args: RawWebhookArgs,
|
||||
) -> error::Result<Response> {
|
||||
stream_job(
|
||||
@@ -5270,6 +5395,7 @@ pub async fn stream_script_by_hash(
|
||||
RunnableId::from_script_hash(script_hash),
|
||||
args,
|
||||
run_query,
|
||||
method == http::Method::GET,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -5282,24 +5408,39 @@ pub async fn stream_job(
|
||||
runnable_id: RunnableId,
|
||||
args: RawWebhookArgs,
|
||||
run_query: RunJobQuery,
|
||||
is_get: bool,
|
||||
) -> error::Result<Response> {
|
||||
let payload_r = run_query.payload.clone().map(decode_payload).map(|x| {
|
||||
x.map_err(|e| Error::internal_err(format!("Impossible to decode query payload: {e:#?}")))
|
||||
});
|
||||
let args = if is_get {
|
||||
let payload_r = run_query.payload.clone().map(decode_payload).map(|x| {
|
||||
x.map_err(|e| {
|
||||
Error::internal_err(format!("Impossible to decode query payload: {e:#?}"))
|
||||
})
|
||||
});
|
||||
|
||||
let payload_args = if let Some(payload) = payload_r {
|
||||
payload?
|
||||
let payload_args = if let Some(payload) = payload_r {
|
||||
payload?
|
||||
} else {
|
||||
HashMap::new()
|
||||
};
|
||||
|
||||
let mut args = args.process_args(&authed, &db, &w_id, None).await?;
|
||||
args.body = args::Body::HashMap(payload_args);
|
||||
|
||||
let args = args
|
||||
.to_args_from_runnable(&db, &w_id, runnable_id.clone(), run_query.skip_preprocessor)
|
||||
.await?;
|
||||
args
|
||||
} else {
|
||||
HashMap::new()
|
||||
args.to_args_from_runnable(
|
||||
&authed,
|
||||
&db,
|
||||
&w_id,
|
||||
runnable_id.clone(),
|
||||
run_query.skip_preprocessor,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
let mut args = args.process_args(&authed, &db, &w_id, None).await?;
|
||||
args.body = args::Body::HashMap(payload_args);
|
||||
|
||||
let args = args
|
||||
.to_args_from_runnable(&db, &w_id, runnable_id.clone(), run_query.skip_preprocessor)
|
||||
.await?;
|
||||
|
||||
let poll_delay_ms = run_query.poll_delay_ms;
|
||||
let uuid = match runnable_id {
|
||||
RunnableId::ScriptId(ScriptId::ScriptPath(script_path))
|
||||
@@ -5406,6 +5547,7 @@ pub async fn run_wait_result_flow_by_path_internal(
|
||||
dedicated_worker,
|
||||
early_return,
|
||||
has_preprocessor,
|
||||
chat_input_enabled,
|
||||
on_behalf_of_email,
|
||||
edited_by,
|
||||
version,
|
||||
@@ -5428,11 +5570,11 @@ pub async fn run_wait_result_flow_by_path_internal(
|
||||
&authed.email,
|
||||
username_to_permissioned_as(&authed.username),
|
||||
Some(authed.clone().into()),
|
||||
PushIsolationLevel::Isolated(user_db, authed.clone().into()),
|
||||
PushIsolationLevel::Isolated(user_db.clone(), authed.clone().into()),
|
||||
)
|
||||
};
|
||||
|
||||
let (uuid, tx) = push(
|
||||
let (uuid, mut tx) = push(
|
||||
&db,
|
||||
tx,
|
||||
&w_id,
|
||||
@@ -5464,8 +5606,29 @@ pub async fn run_wait_result_flow_by_path_internal(
|
||||
None,
|
||||
push_authed.as_ref(),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Set conversation_id if provided (for agent memory)
|
||||
if let Some(memory_id) = run_query.memory_id {
|
||||
set_flow_memory_id(&mut tx, uuid, memory_id).await?;
|
||||
}
|
||||
|
||||
// Handle conversation messages for chat-enabled flows
|
||||
if chat_input_enabled.unwrap_or(false) {
|
||||
handle_chat_conversation_messages(
|
||||
&mut tx,
|
||||
&authed,
|
||||
&w_id,
|
||||
&flow_path.to_string(),
|
||||
&run_query,
|
||||
args.args.get("user_message"),
|
||||
uuid,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
run_wait_result(&db, uuid, w_id, early_return, &authed.username).await
|
||||
@@ -5535,6 +5698,7 @@ async fn run_preview_script(
|
||||
None,
|
||||
Some(&authed.clone().into()),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -5574,8 +5738,6 @@ async fn run_bundle_preview_script(
|
||||
Query(run_query): Query<RunJobQuery>,
|
||||
mut multipart: axum::extract::Multipart,
|
||||
) -> error::Result<(StatusCode, String)> {
|
||||
use windmill_common::scripts::PREVIEW_IS_TAR_CODEBASE_HASH;
|
||||
|
||||
if authed.is_operator {
|
||||
return Err(error::Error::NotAuthorized(
|
||||
"Operators cannot run preview jobs for security reasons".to_string(),
|
||||
@@ -5586,6 +5748,7 @@ async fn run_bundle_preview_script(
|
||||
let mut tx = None;
|
||||
let mut uploaded = false;
|
||||
let mut is_tar = false;
|
||||
let mut format = BundleFormat::Cjs;
|
||||
|
||||
while let Some(field) = multipart.next_field().await.unwrap() {
|
||||
let name = field.name().unwrap().to_string();
|
||||
@@ -5593,6 +5756,10 @@ async fn run_bundle_preview_script(
|
||||
let data = data.map_err(to_anyhow)?;
|
||||
if name == "preview" {
|
||||
let preview: Preview = serde_json::from_slice(&data).map_err(to_anyhow)?;
|
||||
format = preview
|
||||
.format
|
||||
.and_then(|s| BundleFormat::from_string(&s))
|
||||
.unwrap_or(BundleFormat::Cjs);
|
||||
|
||||
let scheduled_for = run_query.get_scheduled_for(&db).await?;
|
||||
let tag = run_query.tag.clone().or(preview.tag.clone());
|
||||
@@ -5613,11 +5780,10 @@ async fn run_bundle_preview_script(
|
||||
ltx,
|
||||
&w_id,
|
||||
JobPayload::Code(RawCode {
|
||||
hash: if is_tar {
|
||||
Some(PREVIEW_IS_TAR_CODEBASE_HASH)
|
||||
} else {
|
||||
Some(PREVIEW_IS_CODEBASE_HASH)
|
||||
},
|
||||
hash: Some(windmill_common::scripts::codebase_to_hash(
|
||||
is_tar,
|
||||
format == BundleFormat::Esm,
|
||||
)),
|
||||
content: preview.content.unwrap_or_default(),
|
||||
path: preview.path,
|
||||
language: preview.language.unwrap_or(ScriptLang::Deno),
|
||||
@@ -5649,6 +5815,7 @@ async fn run_bundle_preview_script(
|
||||
None,
|
||||
Some(&authed.clone().into()),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
job_id = Some(uuid);
|
||||
@@ -5666,52 +5833,22 @@ async fn run_bundle_preview_script(
|
||||
|
||||
// tracing::info!("is_tar 2: {is_tar}");
|
||||
|
||||
if format == BundleFormat::Esm {
|
||||
id = format!("{}.esm", id);
|
||||
}
|
||||
if is_tar {
|
||||
id = format!("{}.tar", id);
|
||||
}
|
||||
|
||||
uploaded = true;
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
let object_store = windmill_common::s3_helpers::get_object_store().await;
|
||||
|
||||
#[cfg(not(all(feature = "enterprise", feature = "parquet")))]
|
||||
let object_store: Option<()> = None;
|
||||
|
||||
if &windmill_common::utils::MODE_AND_ADDONS.mode
|
||||
== &windmill_common::utils::Mode::Standalone
|
||||
&& object_store.is_none()
|
||||
{
|
||||
std::fs::create_dir_all(
|
||||
windmill_common::worker::ROOT_STANDALONE_BUNDLE_DIR.clone(),
|
||||
)?;
|
||||
windmill_common::worker::write_file_bytes(
|
||||
&windmill_common::worker::ROOT_STANDALONE_BUNDLE_DIR,
|
||||
&id,
|
||||
&data,
|
||||
)?;
|
||||
} else {
|
||||
#[cfg(not(all(feature = "enterprise", feature = "parquet")))]
|
||||
{
|
||||
return Err(Error::ExecutionErr("codebase is an EE feature".to_string()));
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
if let Some(os) = object_store {
|
||||
check_license_key_valid().await?;
|
||||
|
||||
let path = windmill_common::s3_helpers::bundle(&w_id, &id);
|
||||
if let Err(e) = os
|
||||
.put(&object_store::path::Path::from(path.clone()), data.into())
|
||||
.await
|
||||
{
|
||||
tracing::info!("Failed to put snapshot to s3 at {path}: {:?}", e);
|
||||
return Err(Error::ExecutionErr(format!("Failed to put {path} to s3")));
|
||||
}
|
||||
} else {
|
||||
return Err(Error::BadConfig("Object store is required for snapshot script and is not configured for servers".to_string()));
|
||||
}
|
||||
}
|
||||
let path = windmill_common::s3_helpers::bundle(&w_id, &id);
|
||||
upload_artifact_to_store(
|
||||
&path,
|
||||
data,
|
||||
&windmill_common::worker::ROOT_STANDALONE_BUNDLE_DIR,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
// println!("Length of `{}` is {} bytes", name, data.len());
|
||||
}
|
||||
@@ -5816,6 +5953,7 @@ async fn run_dependencies_job(
|
||||
None,
|
||||
Some(&authed.clone().into()),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -5883,6 +6021,7 @@ async fn run_flow_dependencies_job(
|
||||
None,
|
||||
Some(&authed.clone().into()),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -6226,6 +6365,7 @@ async fn run_preview_flow_job(
|
||||
None,
|
||||
Some(&authed.clone().into()),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -6351,7 +6491,7 @@ async fn run_dynamic_select(
|
||||
dynamic_input = dynamic_input_res;
|
||||
}
|
||||
},
|
||||
DynamicSelectRunnableRef::Inline { code, language } => {
|
||||
DynamicSelectRunnableRef::Inline { code, lang: language } => {
|
||||
dynamic_input = DynamicInput {
|
||||
x_windmill_dyn_select_code: code,
|
||||
x_windmill_dyn_select_lang: language.unwrap_or_default(),
|
||||
@@ -6399,6 +6539,7 @@ async fn run_dynamic_select(
|
||||
None,
|
||||
Some(&authed.clone().into()),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -6526,6 +6667,7 @@ pub async fn run_job_by_hash_inner(
|
||||
None,
|
||||
push_authed.as_ref(),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -6678,7 +6820,7 @@ async fn get_job_update(
|
||||
&job_id,
|
||||
log_offset,
|
||||
stream_offset,
|
||||
get_progress,
|
||||
get_progress.unwrap_or(false),
|
||||
running,
|
||||
true,
|
||||
false,
|
||||
@@ -6782,6 +6924,7 @@ fn start_job_update_sse_stream(
|
||||
// Send initial update immediately
|
||||
let mut running = running;
|
||||
let mut mem_peak = 0;
|
||||
|
||||
match get_job_update_data(
|
||||
&opt_authed,
|
||||
&opt_tokened,
|
||||
@@ -6790,7 +6933,7 @@ fn start_job_update_sse_stream(
|
||||
&job_id,
|
||||
log_offset,
|
||||
stream_offset,
|
||||
get_progress,
|
||||
false,
|
||||
running,
|
||||
true,
|
||||
true,
|
||||
@@ -6847,11 +6990,12 @@ fn start_job_update_sse_stream(
|
||||
}
|
||||
}
|
||||
|
||||
let mut get_progress_m: bool = false;
|
||||
// Poll for updates every 1 second
|
||||
let mut i = 0;
|
||||
let start = Instant::now();
|
||||
let mut last_ping = Instant::now();
|
||||
|
||||
let mut last_progress_check = Instant::now();
|
||||
loop {
|
||||
i += 1;
|
||||
|
||||
@@ -6894,6 +7038,10 @@ fn start_job_update_sse_stream(
|
||||
}
|
||||
tokio::time::sleep(std::time::Duration::from_millis(ms_duration)).await;
|
||||
|
||||
// Check progress if the user requested it, and check periodically if the job has progress
|
||||
// Once it has progress, we always check progress
|
||||
let check_progress = get_progress.unwrap_or(false)
|
||||
&& (get_progress_m || last_progress_check.elapsed().as_secs() > 5);
|
||||
match get_job_update_data(
|
||||
&opt_authed,
|
||||
&opt_tokened,
|
||||
@@ -6902,7 +7050,7 @@ fn start_job_update_sse_stream(
|
||||
&job_id,
|
||||
log_offset,
|
||||
stream_offset,
|
||||
get_progress,
|
||||
check_progress,
|
||||
running,
|
||||
false,
|
||||
true,
|
||||
@@ -6923,6 +7071,13 @@ fn start_job_update_sse_stream(
|
||||
if update.new_logs.as_ref().is_some_and(|x| x.is_empty()) {
|
||||
update.new_logs = None;
|
||||
}
|
||||
if check_progress {
|
||||
if update.progress.is_some() {
|
||||
get_progress_m = true;
|
||||
} else {
|
||||
last_progress_check = Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
// if !only_result.unwrap_or(false) {
|
||||
// tracing::error!("update {:?}", update);
|
||||
@@ -6990,14 +7145,20 @@ async fn get_flow_stream_delta(
|
||||
) -> error::Result<Option<(Option<String>, Option<i32>)>> {
|
||||
if let Some(job_id) = flow_stream_job_id {
|
||||
let record = sqlx::query!(
|
||||
"SELECT SUBSTR(rs.stream, $1) AS new_result_stream, CHAR_LENGTH(rs.stream) + 1 AS stream_offset FROM job_result_stream rs WHERE rs.job_id = $2",
|
||||
"
|
||||
SELECT
|
||||
string_agg(stream, '' order by idx asc) as stream,
|
||||
max(idx) + 1 as offset
|
||||
FROM job_result_stream_v2
|
||||
WHERE job_id = $2 AND idx >= $1
|
||||
",
|
||||
stream_offset.unwrap_or(0),
|
||||
job_id,
|
||||
)
|
||||
.fetch_optional(db)
|
||||
.await?;
|
||||
if let Some(record) = record {
|
||||
Ok(Some((record.new_result_stream, record.stream_offset)))
|
||||
Ok(Some((record.stream, record.offset)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
@@ -7014,7 +7175,7 @@ async fn get_job_update_data(
|
||||
job_id: &Uuid,
|
||||
log_offset: Option<i32>,
|
||||
stream_offset: Option<i32>,
|
||||
get_progress: Option<bool>,
|
||||
get_progress: bool,
|
||||
running: Option<bool>,
|
||||
log_view: bool,
|
||||
get_full_job_on_completion: bool,
|
||||
@@ -7046,18 +7207,28 @@ async fn get_job_update_data(
|
||||
let (result, running, mut result_stream, mut new_stream_offset, new_flow_stream_job_id) =
|
||||
if let Some(tags) = tags {
|
||||
let r = sqlx::query!(
|
||||
"SELECT
|
||||
"
|
||||
WITH result_stream AS (
|
||||
SELECT
|
||||
string_agg(stream, '' order by idx asc) as stream,
|
||||
job_id,
|
||||
max(idx) + 1 as offset
|
||||
FROM job_result_stream_v2
|
||||
WHERE job_id = $2 AND idx >= $3
|
||||
GROUP BY job_id
|
||||
)
|
||||
SELECT
|
||||
jc.result as \"result: sqlx::types::Json<Box<RawValue>>\",
|
||||
v2_job.tag,
|
||||
v2_job_queue.running as \"running: Option<bool>\",
|
||||
SUBSTR(rs.stream, $3) AS \"result_stream: Option<String>\",
|
||||
CHAR_LENGTH(rs.stream) AS stream_offset,
|
||||
rs.stream AS \"result_stream: Option<String>\",
|
||||
rs.offset AS stream_offset,
|
||||
CASE WHEN $4 THEN NULL ELSE (COALESCE(js.flow_status, jc.flow_status)->>'stream_job')::uuid END as stream_job
|
||||
FROM v2_job
|
||||
LEFT JOIN v2_job_queue USING (id)
|
||||
LEFT JOIN v2_job_completed jc USING (id)
|
||||
LEFT JOIN v2_job_status js USING (id)
|
||||
LEFT JOIN job_result_stream rs ON rs.job_id = $2
|
||||
LEFT JOIN result_stream rs ON rs.job_id = $2
|
||||
WHERE v2_job.id = $2 AND v2_job.workspace_id = $1",
|
||||
w_id,
|
||||
job_id,
|
||||
@@ -7086,11 +7257,21 @@ async fn get_job_update_data(
|
||||
} else {
|
||||
if running.is_some_and(|x| !x) {
|
||||
let r = sqlx::query!(
|
||||
"SELECT
|
||||
"
|
||||
WITH result_stream AS (
|
||||
SELECT
|
||||
string_agg(stream, '' order by idx asc) as stream,
|
||||
job_id,
|
||||
max(idx) + 1 as offset
|
||||
FROM job_result_stream_v2
|
||||
WHERE job_id = $1 AND idx >= $3
|
||||
GROUP BY job_id
|
||||
)
|
||||
SELECT
|
||||
COALESCE(jc.result, jc.result) as \"result: sqlx::types::Json<Box<RawValue>>\",
|
||||
jq.running as \"running: Option<bool>\",
|
||||
SUBSTR(rs.stream, $3) AS \"result_stream: Option<String>\",
|
||||
CHAR_LENGTH(rs.stream) + 1 AS stream_offset,
|
||||
rs.stream AS \"result_stream: Option<String>\",
|
||||
rs.offset AS stream_offset,
|
||||
CASE WHEN $4 THEN NULL ELSE (COALESCE(js.flow_status, jc.flow_status)->>'stream_job')::uuid END as stream_job
|
||||
FROM (
|
||||
SELECT $1::uuid as job_id, $2::text as workspace_id
|
||||
@@ -7098,7 +7279,7 @@ async fn get_job_update_data(
|
||||
LEFT JOIN v2_job_completed jc ON jc.id = base.job_id AND jc.workspace_id = base.workspace_id
|
||||
LEFT JOIN v2_job_queue jq ON jq.id = base.job_id AND jq.workspace_id = base.workspace_id
|
||||
LEFT JOIN v2_job_status js ON js.id = base.job_id
|
||||
LEFT JOIN job_result_stream rs ON rs.job_id = base.job_id
|
||||
LEFT JOIN result_stream rs ON rs.job_id = base.job_id
|
||||
WHERE base.job_id = $1",
|
||||
job_id,
|
||||
w_id,
|
||||
@@ -7119,10 +7300,20 @@ async fn get_job_update_data(
|
||||
}
|
||||
} else {
|
||||
let q = sqlx::query!(
|
||||
"SELECT
|
||||
"
|
||||
WITH result_stream AS (
|
||||
SELECT
|
||||
string_agg(stream, '' order by idx asc) as stream,
|
||||
job_id,
|
||||
max(idx) + 1 as offset
|
||||
FROM job_result_stream_v2
|
||||
WHERE job_id = $2 AND idx >= $3
|
||||
GROUP BY job_id
|
||||
)
|
||||
SELECT
|
||||
COALESCE(jc.result, NULL) as \"result: sqlx::types::Json<Box<RawValue>>\",
|
||||
SUBSTR(rs.stream, $3) AS \"result_stream: Option<String>\",
|
||||
CHAR_LENGTH(rs.stream) + 1 AS stream_offset,
|
||||
rs.stream AS \"result_stream: Option<String>\",
|
||||
rs.offset AS stream_offset,
|
||||
COALESCE(js.flow_status, jc.flow_status) as \"flow_status: sqlx::types::Json<Box<RawValue>>\",
|
||||
CASE WHEN $4 THEN NULL ELSE (COALESCE(js.flow_status, jc.flow_status)->>'stream_job')::uuid END as stream_job
|
||||
FROM (
|
||||
@@ -7130,7 +7321,7 @@ async fn get_job_update_data(
|
||||
) base
|
||||
LEFT JOIN v2_job_completed jc ON jc.id = base.job_id AND jc.workspace_id = base.workspace_id
|
||||
LEFT JOIN v2_job_status js ON js.id = base.job_id
|
||||
LEFT JOIN job_result_stream rs ON rs.job_id = base.job_id
|
||||
LEFT JOIN result_stream rs ON rs.job_id = base.job_id
|
||||
WHERE base.job_id = $2",
|
||||
w_id,
|
||||
job_id,
|
||||
@@ -7180,20 +7371,30 @@ async fn get_job_update_data(
|
||||
})
|
||||
} else {
|
||||
let mut record = sqlx::query!(
|
||||
"SELECT
|
||||
"
|
||||
WITH result_stream AS (
|
||||
SELECT
|
||||
string_agg(stream, '' order by idx asc) as stream,
|
||||
job_id,
|
||||
max(idx) + 1 as offset
|
||||
FROM job_result_stream_v2
|
||||
WHERE job_id = $3 AND idx >= $8
|
||||
GROUP BY job_id
|
||||
)
|
||||
SELECT
|
||||
c.id IS NOT NULL AS completed,
|
||||
CASE
|
||||
WHEN q.id IS NOT NULL THEN (CASE WHEN NOT $5 AND q.running THEN true ELSE null END)
|
||||
ELSE false
|
||||
END AS running,
|
||||
CASE WHEN $7::BOOLEAN THEN NULL ELSE SUBSTR(logs, GREATEST($1 - log_offset, 0)) END AS logs,
|
||||
SUBSTR(rs.stream, $8) AS new_result_stream,
|
||||
rs.stream AS new_result_stream,
|
||||
COALESCE(r.memory_peak, c.memory_peak) AS mem_peak,
|
||||
COALESCE(c.flow_status, f.flow_status) AS \"flow_status: sqlx::types::Json<Box<RawValue>>\",
|
||||
(COALESCE(c.flow_status, f.flow_status)->>'stream_job')::uuid AS stream_job,
|
||||
COALESCE(c.workflow_as_code_status, f.workflow_as_code_status) AS \"workflow_as_code_status: sqlx::types::Json<Box<RawValue>>\",
|
||||
CASE WHEN $7::BOOLEAN THEN NULL ELSE job_logs.log_offset + CHAR_LENGTH(job_logs.logs) + 1 END AS log_offset,
|
||||
CHAR_LENGTH(rs.stream) + 1 AS stream_offset,
|
||||
rs.offset AS stream_offset,
|
||||
created_by AS \"created_by!\",
|
||||
CASE WHEN $4::BOOLEAN THEN (
|
||||
SELECT scalar_int FROM job_stats WHERE job_id = $3 AND metric_id = 'progress_perc'
|
||||
@@ -7204,14 +7405,14 @@ async fn get_job_update_data(
|
||||
LEFT JOIN v2_job_runtime r USING (id)
|
||||
LEFT JOIN v2_job_status f USING (id)
|
||||
LEFT JOIN v2_job_completed c USING (id)
|
||||
LEFT JOIN job_result_stream rs ON rs.job_id = $3
|
||||
LEFT JOIN result_stream rs ON rs.job_id = $3
|
||||
LEFT JOIN job_logs ON job_logs.job_id = $3
|
||||
WHERE j.workspace_id = $2 AND j.id = $3
|
||||
AND ($6::text[] IS NULL OR j.tag = ANY($6))",
|
||||
log_offset,
|
||||
w_id,
|
||||
job_id,
|
||||
get_progress.unwrap_or(false),
|
||||
get_progress,
|
||||
running,
|
||||
tags.as_ref().map(|v| v.as_slice()) as Option<&[&str]>,
|
||||
no_logs.unwrap_or(false),
|
||||
|
||||
@@ -87,6 +87,7 @@ pub mod ee;
|
||||
pub mod ee_oss;
|
||||
pub mod embeddings;
|
||||
mod favorite;
|
||||
mod flow_conversations;
|
||||
pub mod flows;
|
||||
mod folders;
|
||||
mod granular_acls;
|
||||
@@ -411,7 +412,7 @@ pub async fn run_server(
|
||||
};
|
||||
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
let (agent_workers_router, agent_workers_bg_processor, agent_workers_killpill_tx) =
|
||||
let (agent_workers_router, agent_workers_bg_processor, agent_workers_job_completed_tx) =
|
||||
if server_mode {
|
||||
agent_workers_oss::workspaced_service(db.clone(), _base_internal_url.clone())
|
||||
} else {
|
||||
@@ -443,6 +444,10 @@ pub async fn run_server(
|
||||
.nest("/drafts", drafts::workspaced_service())
|
||||
.nest("/favorites", favorite::workspaced_service())
|
||||
.nest("/flows", flows::workspaced_service())
|
||||
.nest(
|
||||
"/flow_conversations",
|
||||
flow_conversations::workspaced_service(),
|
||||
)
|
||||
.nest("/folders", folders::workspaced_service())
|
||||
.nest("/groups", groups::workspaced_service())
|
||||
.nest("/inputs", inputs::workspaced_service())
|
||||
@@ -535,7 +540,14 @@ pub async fn run_server(
|
||||
.nest("/agent_workers", {
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
{
|
||||
agent_workers_oss::global_service().layer(Extension(agent_cache.clone()))
|
||||
if let Some(agent_workers_job_completed_tx) =
|
||||
agent_workers_job_completed_tx.clone()
|
||||
{
|
||||
agent_workers_oss::global_service(agent_workers_job_completed_tx)
|
||||
.layer(Extension(agent_cache.clone()))
|
||||
} else {
|
||||
Router::new()
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "agent_worker_server"))]
|
||||
{
|
||||
@@ -705,8 +717,8 @@ pub async fn run_server(
|
||||
let server = server.with_graceful_shutdown(async move {
|
||||
killpill_rx.recv().await.ok();
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
if let Some(agent_workers_killpill_tx) = agent_workers_killpill_tx {
|
||||
if let Err(e) = agent_workers_killpill_tx.kill().await {
|
||||
if let Some(agent_workers_job_completed_tx) = agent_workers_job_completed_tx {
|
||||
if let Err(e) = agent_workers_job_completed_tx.kill().await {
|
||||
tracing::error!("Error killing agent workers: {e:#}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +151,22 @@ impl ServerHandler for Runner {
|
||||
|
||||
check_scopes(authed)?;
|
||||
|
||||
if request.name.ends_with("_TRUNC") {
|
||||
return Ok(CallToolResult::error(
|
||||
vec![
|
||||
Annotated::new(
|
||||
RawContent::Text(RawTextContent {
|
||||
text:
|
||||
"Tool path is too long. Consider shortening it to make it compatible with MCP."
|
||||
.to_string(),
|
||||
meta: None,
|
||||
}),
|
||||
None
|
||||
),
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
let db = http_parts.extensions.get::<DB>().ok_or_else(|| {
|
||||
tracing::error!("DB Axum extension not found");
|
||||
ErrorData::internal_error("DB Axum extension not found", None)
|
||||
@@ -435,7 +451,6 @@ impl ServerHandler for Runner {
|
||||
protocol_version: ProtocolVersion::default(),
|
||||
capabilities: ServerCapabilities::builder()
|
||||
.enable_tools()
|
||||
.enable_tool_list_changed()
|
||||
.build(),
|
||||
server_info: Implementation::from_build_env(),
|
||||
instructions: Some("This server provides a list of scripts and flows the user can run on Windmill. Each flow and script is a tool callable with their respective arguments.".to_string()),
|
||||
|
||||
@@ -6,9 +6,8 @@
|
||||
use crate::db::ApiAuthed;
|
||||
use rmcp::{model::Tool, ErrorData};
|
||||
use std::sync::Arc;
|
||||
use windmill_common::auth::create_jwt_token;
|
||||
use windmill_common::db::Authed;
|
||||
use windmill_common::BASE_URL;
|
||||
use windmill_common::{auth::create_jwt_token, BASE_INTERNAL_URL};
|
||||
|
||||
// Import the auto-generated tools
|
||||
use super::auto_generated_endpoints;
|
||||
@@ -130,7 +129,7 @@ pub async fn call_endpoint_tool(
|
||||
let query_string = build_query_string(args_map, &tool.query_params_schema);
|
||||
let full_url = format!(
|
||||
"{}/api{}{}",
|
||||
BASE_URL.read().await,
|
||||
BASE_INTERNAL_URL.as_str(),
|
||||
path_template,
|
||||
query_string
|
||||
);
|
||||
|
||||
@@ -5,22 +5,34 @@
|
||||
|
||||
use super::models::SchemaType;
|
||||
|
||||
// MCP clients do not allow names longer than 60 characters
|
||||
const MAX_PATH_LENGTH: usize = 60;
|
||||
|
||||
/// Transform the path for workspace scripts/flows
|
||||
///
|
||||
/// This function takes a path and a type string and formats the transformed
|
||||
/// path with the type prefix. This is used when listing, because we can't
|
||||
/// have names with slashes. Because we replace slashes with underscores,
|
||||
///
|
||||
/// This function takes a path and a type string and formats the transformed
|
||||
/// path with the type prefix. This is used when listing, because we can't
|
||||
/// have names with slashes. Because we replace slashes with underscores,
|
||||
/// we also need to escape underscores.
|
||||
pub fn transform_path(path: &str, type_str: &str) -> String {
|
||||
let escaped_path = path.replace('_', "__").replace('/', "_");
|
||||
// first letter of type_str is used as prefix, only one letter to avoid reaching 60 char name limit
|
||||
format!("{}-{}", &type_str[..1], escaped_path)
|
||||
let transformed_path = format!("{}-{}", &type_str[..1], escaped_path);
|
||||
if transformed_path.len() > MAX_PATH_LENGTH {
|
||||
let suffix = "_TRUNC";
|
||||
return format!(
|
||||
"{}{}",
|
||||
&transformed_path[..MAX_PATH_LENGTH - suffix.len()],
|
||||
suffix
|
||||
);
|
||||
}
|
||||
transformed_path
|
||||
}
|
||||
|
||||
/// Reverse the transformation of a path
|
||||
///
|
||||
/// This function takes a transformed path and reverses the transformation
|
||||
/// applied by `transform_path`. It checks if the path starts with "h"
|
||||
/// This function takes a transformed path and reverses the transformation
|
||||
/// applied by `transform_path`. It checks if the path starts with "h"
|
||||
/// (indicating a Hub script) and removes the prefix if present.
|
||||
/// It then determines the type of the item (script or flow) based on the prefix.
|
||||
/// This is used in call_tool to get the original path, and the type of the item.
|
||||
@@ -54,7 +66,10 @@ pub fn reverse_transform(transformed_path: &str) -> Result<(&str, String, bool),
|
||||
parts[0].to_string()
|
||||
} else {
|
||||
const TEMP_PLACEHOLDER: &str = "@@UNDERSCORE@@";
|
||||
mangled_path.replace("__", TEMP_PLACEHOLDER).replace('_', "/").replace(TEMP_PLACEHOLDER, "_")
|
||||
mangled_path
|
||||
.replace("__", TEMP_PLACEHOLDER)
|
||||
.replace('_', "/")
|
||||
.replace(TEMP_PLACEHOLDER, "_")
|
||||
};
|
||||
|
||||
Ok((type_str, original_path, is_hub))
|
||||
@@ -64,7 +79,7 @@ pub fn reverse_transform(transformed_path: &str) -> Result<(&str, String, bool),
|
||||
///
|
||||
/// This function takes a key and replaces spaces with underscores.
|
||||
/// It also removes any characters that are not alphanumeric or underscores.
|
||||
/// This is used when listing, because we can't have names with spaces
|
||||
/// This is used when listing, because we can't have names with spaces
|
||||
/// or special characters in the schema properties.
|
||||
pub fn apply_key_transformation(key: &str) -> String {
|
||||
key.replace(' ', "_")
|
||||
@@ -75,8 +90,8 @@ pub fn apply_key_transformation(key: &str) -> String {
|
||||
|
||||
/// Reverse the transformation of a key
|
||||
///
|
||||
/// This function takes a transformed key and a schema object and reverses
|
||||
/// the transformation applied by `apply_key_transformation`. This can be
|
||||
/// This function takes a transformed key and a schema object and reverses
|
||||
/// the transformation applied by `apply_key_transformation`. This can be
|
||||
/// subject to collisions, but it's unlikely and is ok for our use case.
|
||||
pub fn reverse_transform_key(transformed_key: &str, schema_obj: &Option<SchemaType>) -> String {
|
||||
let schema_obj = match schema_obj {
|
||||
@@ -98,4 +113,4 @@ pub fn reverse_transform_key(transformed_key: &str, schema_obj: &Option<SchemaTy
|
||||
}
|
||||
|
||||
transformed_key.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -515,7 +515,8 @@ pub async fn transform_json_value<'c>(
|
||||
match v {
|
||||
Value::String(y) if y.starts_with("$var:") => {
|
||||
let path = y.strip_prefix("$var:").unwrap();
|
||||
let userdb_authed = UserDbWithOptAuthed { authed: authed, user_db: user_db.clone(), db: db.clone() };
|
||||
let userdb_authed =
|
||||
UserDbWithOptAuthed { authed: authed, user_db: user_db.clone(), db: db.clone() };
|
||||
|
||||
let v = crate::variables::get_value_internal(
|
||||
&userdb_authed,
|
||||
@@ -616,6 +617,7 @@ pub async fn transform_json_value<'c>(
|
||||
job.root_job.map(|x| x.to_string()),
|
||||
Some(job.scheduled_for.clone()),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
@@ -277,7 +277,7 @@ async fn create_schedule(
|
||||
.await?;
|
||||
|
||||
if ns.enabled.unwrap_or(true) {
|
||||
tx = push_scheduled_job(&db, tx, &schedule, Some(&authed.clone().into())).await?
|
||||
tx = push_scheduled_job(&db, tx, &schedule, Some(&authed.clone().into()), None).await?
|
||||
}
|
||||
tx.commit().await?;
|
||||
|
||||
@@ -419,7 +419,7 @@ async fn edit_schedule(
|
||||
.await?;
|
||||
|
||||
if schedule.enabled {
|
||||
tx = push_scheduled_job(&db, tx, &schedule, None).await?;
|
||||
tx = push_scheduled_job(&db, tx, &schedule, None, None).await?;
|
||||
}
|
||||
tx.commit().await?;
|
||||
|
||||
@@ -683,7 +683,7 @@ pub async fn set_enabled(
|
||||
.await?;
|
||||
|
||||
if payload.enabled {
|
||||
tx = push_scheduled_job(&db, tx, &schedule, None).await?;
|
||||
tx = push_scheduled_job(&db, tx, &schedule, None, None).await?;
|
||||
}
|
||||
tx.commit().await?;
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ use windmill_worker::process_relative_imports;
|
||||
use windmill_common::{
|
||||
assets::{clear_asset_usage, insert_asset_usage, AssetUsageKind, AssetWithAltAccessType},
|
||||
error::to_anyhow,
|
||||
s3_helpers::upload_artifact_to_store,
|
||||
scripts::hash_script,
|
||||
utils::WarnAfterExt,
|
||||
worker::CLOUD_HOSTED,
|
||||
@@ -421,45 +422,13 @@ async fn create_snapshot_script(
|
||||
|
||||
uploaded = true;
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
let object_store = windmill_common::s3_helpers::get_object_store().await;
|
||||
|
||||
#[cfg(not(all(feature = "enterprise", feature = "parquet")))]
|
||||
let object_store: Option<()> = None;
|
||||
|
||||
if &windmill_common::utils::MODE_AND_ADDONS.mode
|
||||
== &windmill_common::utils::Mode::Standalone
|
||||
&& object_store.is_none()
|
||||
{
|
||||
std::fs::create_dir_all(
|
||||
windmill_common::worker::ROOT_STANDALONE_BUNDLE_DIR.clone(),
|
||||
)?;
|
||||
windmill_common::worker::write_file_bytes(
|
||||
&windmill_common::worker::ROOT_STANDALONE_BUNDLE_DIR,
|
||||
&hash,
|
||||
&data,
|
||||
)?;
|
||||
} else {
|
||||
#[cfg(not(all(feature = "enterprise", feature = "parquet")))]
|
||||
{
|
||||
return Err(Error::ExecutionErr("codebase is an EE feature".to_string()));
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
if let Some(os) = object_store {
|
||||
let path = windmill_common::s3_helpers::bundle(&w_id, &hash);
|
||||
|
||||
if let Err(e) = os
|
||||
.put(&object_store::path::Path::from(path.clone()), data.into())
|
||||
.await
|
||||
{
|
||||
tracing::info!("Failed to put snapshot to s3 at {path}: {:?}", e);
|
||||
return Err(Error::ExecutionErr(format!("Failed to put {path} to s3")));
|
||||
}
|
||||
} else {
|
||||
return Err(Error::BadConfig("Object store is required for snapshot script and is not configured for servers".to_string()));
|
||||
}
|
||||
}
|
||||
let path = windmill_common::s3_helpers::bundle(&w_id, &hash);
|
||||
upload_artifact_to_store(
|
||||
&path,
|
||||
data,
|
||||
&windmill_common::worker::ROOT_STANDALONE_BUNDLE_DIR,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
// println!("Length of `{}` is {} bytes", name, data.len());
|
||||
}
|
||||
@@ -900,7 +869,7 @@ async fn create_script_internal<'c>(
|
||||
clear_schedule(&mut tx, &schedule.path, &w_id).await?;
|
||||
|
||||
if schedule.enabled {
|
||||
tx = push_scheduled_job(&db, tx, &schedule, None).await?;
|
||||
tx = push_scheduled_job(&db, tx, &schedule, None, None).await?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1020,6 +989,7 @@ async fn create_script_internal<'c>(
|
||||
None,
|
||||
Some(&authed.clone().into()),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
Ok((hash, new_tx, None))
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use std::time::Duration;
|
||||
use std::{collections::HashMap, time::Duration};
|
||||
|
||||
use crate::{
|
||||
db::{ApiAuthed, DB},
|
||||
@@ -23,6 +23,7 @@ use axum::{
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
use axum::extract::Query;
|
||||
use serde_json::json;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
use crate::utils::require_devops_role;
|
||||
@@ -42,7 +43,6 @@ use windmill_common::{
|
||||
},
|
||||
parse_postgres_url,
|
||||
server::Smtp,
|
||||
utils::build_arg_str,
|
||||
};
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
@@ -69,10 +69,13 @@ pub fn global_service() -> Router {
|
||||
"/critical_alerts/:id/acknowledge",
|
||||
post(acknowledge_critical_alert),
|
||||
)
|
||||
.route("/databases_exist", post(databases_exist))
|
||||
.route(
|
||||
"/create_ducklake_database/:name",
|
||||
post(create_ducklake_database),
|
||||
"/get_ducklake_instance_catalog_db_status",
|
||||
post(get_ducklake_instance_catalog_db_status),
|
||||
)
|
||||
.route(
|
||||
"/setup_ducklake_catalog_db/:name",
|
||||
post(setup_ducklake_catalog_db),
|
||||
)
|
||||
.route(
|
||||
"/critical_alerts/acknowledge_all",
|
||||
@@ -569,91 +572,164 @@ pub async fn acknowledge_all_critical_alerts() -> error::Error {
|
||||
error::Error::NotFound("Critical Alerts require EE".to_string())
|
||||
}
|
||||
|
||||
async fn databases_exist(
|
||||
_authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Json(database_names): Json<Vec<String>>,
|
||||
) -> JsonResult<Vec<String>> {
|
||||
let result = sqlx::query_scalar!(
|
||||
r#"SELECT elem FROM (SELECT unnest($1::TEXT[]) AS elem) AS e
|
||||
WHERE elem NOT IN (SELECT datname FROM pg_catalog.pg_database);"#,
|
||||
database_names.as_slice()
|
||||
)
|
||||
.fetch_all(&db)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter_map(|x| x)
|
||||
.collect();
|
||||
|
||||
Ok(Json(result))
|
||||
#[derive(Deserialize, Debug, Serialize)]
|
||||
struct DucklakeInstanceCatalogDbStatus {
|
||||
logs: DucklakeInstanceCatalogDbStatusLogs, // (Step, Message)[]
|
||||
success: bool,
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
async fn create_ducklake_database(
|
||||
#[derive(Deserialize, Debug, Serialize, Default)]
|
||||
#[serde(default)]
|
||||
struct DucklakeInstanceCatalogDbStatusLogs {
|
||||
super_admin: String,
|
||||
#[serde(skip_serializing_if = "String::is_empty")]
|
||||
database_credentials: String,
|
||||
#[serde(skip_serializing_if = "String::is_empty")]
|
||||
valid_dbname: String,
|
||||
#[serde(skip_serializing_if = "String::is_empty")]
|
||||
created_database: String,
|
||||
#[serde(skip_serializing_if = "String::is_empty")]
|
||||
db_connect: String,
|
||||
#[serde(skip_serializing_if = "String::is_empty")]
|
||||
grant_permissions: String,
|
||||
}
|
||||
|
||||
async fn get_ducklake_instance_catalog_db_status(
|
||||
_authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
) -> JsonResult<HashMap<String, DucklakeInstanceCatalogDbStatus>> {
|
||||
let result = sqlx::query_scalar!(
|
||||
r#"SELECT value->'instance_catalog_db_status' FROM global_settings WHERE name = 'ducklake_settings'"#,
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
.ok_or_else(|| error::Error::ExecutionErr("Couldn't find ducklake_settings".to_string()))?;
|
||||
let result = serde_json::from_value(result).map_err(|e| {
|
||||
error::Error::ExecutionErr(format!(
|
||||
"couldn't parse instance_catalog_db_status : {}",
|
||||
e.to_string()
|
||||
))
|
||||
})?;
|
||||
return Ok(Json(result));
|
||||
}
|
||||
|
||||
async fn setup_ducklake_catalog_db(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Path(dbname): Path<String>,
|
||||
) -> JsonResult<DucklakeInstanceCatalogDbStatus> {
|
||||
let mut logs = DucklakeInstanceCatalogDbStatusLogs::default();
|
||||
let result = setup_ducklake_catalog_db_inner(authed, &db, &dbname, &mut logs).await;
|
||||
let success = result.is_ok();
|
||||
let error = result.err().map(|e| e.to_string());
|
||||
let status = DucklakeInstanceCatalogDbStatus { logs, success, error };
|
||||
let status_json = serde_json::to_value(&status).map_err(to_anyhow)?;
|
||||
// Save that the database was setup successfully
|
||||
sqlx::query!(
|
||||
r#"UPDATE global_settings SET value = jsonb_set(value, '{instance_catalog_db_status}', (COALESCE(value->'instance_catalog_db_status', '{}'::jsonb) || to_jsonb($1::json))) WHERE name = 'ducklake_settings'"#,
|
||||
json!({ dbname: status_json })
|
||||
).execute(&db).await?;
|
||||
|
||||
Ok(Json(status))
|
||||
}
|
||||
|
||||
async fn setup_ducklake_catalog_db_inner(
|
||||
authed: ApiAuthed,
|
||||
db: &DB,
|
||||
dbname: &str,
|
||||
logs: &mut DucklakeInstanceCatalogDbStatusLogs,
|
||||
) -> Result<()> {
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
require_super_admin(db, &authed.email).await?;
|
||||
logs.super_admin = "OK".to_string();
|
||||
let pg_creds = &get_database_url().await?;
|
||||
let pg_creds = parse_postgres_url(pg_creds)?;
|
||||
logs.database_credentials = "OK".to_string();
|
||||
|
||||
// Validate name to ensure it only contains alphanumeric characters
|
||||
// Prevents SQL injection on the instance database
|
||||
let valid_name = regex::Regex::new(r"^[a-zA-Z0-9_]+$")
|
||||
.map_err(|_| error::Error::internal_err("Failed to compile regex".to_string()))?;
|
||||
if !valid_name.is_match(&dbname) {
|
||||
lazy_static::lazy_static! {
|
||||
static ref VALID_NAME: regex::Regex = regex::Regex::new(r"^[a-zA-Z0-9_]+$").unwrap();
|
||||
}
|
||||
if !VALID_NAME.is_match(dbname) {
|
||||
return Err(error::Error::BadRequest(
|
||||
"Invalid database name".to_string(),
|
||||
"Catalog name must be alphanumeric, underscores allowed".to_string(),
|
||||
));
|
||||
}
|
||||
if pg_creds.database.trim().eq_ignore_ascii_case(dbname.trim()) {
|
||||
return Err(error::Error::BadRequest(
|
||||
"Database name cannot be the same as the main database".to_string(),
|
||||
));
|
||||
}
|
||||
logs.valid_dbname = "OK".to_string();
|
||||
|
||||
sqlx::query(&format!("CREATE DATABASE \"{dbname}\""))
|
||||
.execute(&db)
|
||||
.await?;
|
||||
let db_exists = sqlx::query_scalar!(
|
||||
"SELECT EXISTS (SELECT 1 FROM pg_catalog.pg_database WHERE datname = $1)",
|
||||
dbname
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
sqlx::query(&format!(
|
||||
"GRANT CONNECT ON DATABASE \"{dbname}\" TO ducklake_user"
|
||||
))
|
||||
.execute(&db)
|
||||
.await?;
|
||||
logs.created_database = "SKIP".to_string();
|
||||
if !db_exists {
|
||||
sqlx::query(&format!("CREATE DATABASE \"{dbname}\""))
|
||||
.execute(db)
|
||||
.await?;
|
||||
logs.created_database = "OK".to_string();
|
||||
}
|
||||
|
||||
// We have to connect to the newly created database as admin to grant permissions
|
||||
let pg_creds = parse_postgres_url(&get_database_url().await?)?;
|
||||
let Some(wm_pg_pwd) = pg_creds.password else {
|
||||
return Err(error::Error::BadRequest("Password not found".to_string()));
|
||||
let ssl_mode = match pg_creds.ssl_mode.as_deref() {
|
||||
Some("allow") => "prefer".to_string(),
|
||||
Some("verify-ca") | Some("verify-full") => "require".to_string(),
|
||||
Some(s) => s.to_string(),
|
||||
None => "prefer".to_string(),
|
||||
};
|
||||
let conn_str: String = build_arg_str(
|
||||
&[
|
||||
("host", Some(&pg_creds.host)),
|
||||
("port", pg_creds.port.map(|p| p.to_string()).as_deref()),
|
||||
("password", Some(&wm_pg_pwd)),
|
||||
("user", pg_creds.username.as_deref()),
|
||||
("dbname", Some(&dbname)),
|
||||
],
|
||||
" ",
|
||||
"=",
|
||||
// We have to connect to the newly created database as admin to grant permissions
|
||||
let conn_str = format!(
|
||||
"postgres://{user}:{password}@{host}:{port}/{dbname}?sslmode={sslmode}",
|
||||
user = urlencoding::encode(&pg_creds.username.unwrap_or_else(|| "postgres".to_string())),
|
||||
password = urlencoding::encode(&pg_creds.password.as_deref().unwrap_or("")),
|
||||
host = urlencoding::encode(&pg_creds.host),
|
||||
port = pg_creds.port.unwrap_or(5432),
|
||||
dbname = dbname,
|
||||
sslmode = ssl_mode
|
||||
);
|
||||
|
||||
let (client, connection) = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(20),
|
||||
tokio_postgres::connect(&conn_str, tokio_postgres::NoTls),
|
||||
)
|
||||
.await
|
||||
.map_err(to_anyhow)?
|
||||
.map_err(to_anyhow)?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = connection.await {
|
||||
eprintln!("connection error: {}", e);
|
||||
}
|
||||
});
|
||||
.map_err(|e| error::Error::ExecutionErr(format!("timeout: {}", e.to_string())))?
|
||||
.map_err(|e| error::Error::ExecutionErr(format!("error: {}", e.to_string())))?;
|
||||
let join_handle = tokio::spawn(async move { connection.await });
|
||||
logs.db_connect = "OK".to_string();
|
||||
|
||||
client
|
||||
.batch_execute(&format!(
|
||||
"GRANT USAGE ON SCHEMA public TO ducklake_user;
|
||||
"GRANT CONNECT ON DATABASE \"{dbname}\" TO ducklake_user;
|
||||
GRANT USAGE ON SCHEMA public TO ducklake_user;
|
||||
GRANT CREATE ON SCHEMA public TO ducklake_user;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO ducklake_user;"
|
||||
))
|
||||
.await
|
||||
.map_err(to_anyhow)?;
|
||||
.map_err(|e| {
|
||||
error::Error::ExecutionErr(format!(
|
||||
"Failed to grant permissions to ducklake_user: {}",
|
||||
e.to_string(),
|
||||
))
|
||||
})?;
|
||||
logs.grant_permissions = "OK".to_string();
|
||||
|
||||
drop(client); // /!\ Drop before joining to avoid deadlock
|
||||
join_handle
|
||||
.await
|
||||
.map_err(|e| error::Error::ExecutionErr(format!("join error: {}", e.to_string())))?
|
||||
.map_err(|e| {
|
||||
error::Error::ExecutionErr(format!("tokio_postgres error: {}", e.to_string()))
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -684,7 +684,8 @@ pub async fn trigger_runnable_and_wait_for_raw_result_with_error_ctx(
|
||||
error_handler_path,
|
||||
error_handler_args,
|
||||
trigger_path,
|
||||
).await?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
if !success {
|
||||
Err(windmill_common::error::Error::internal_err(format!(
|
||||
@@ -857,6 +858,7 @@ async fn trigger_script_with_retry_and_error_handler(
|
||||
None,
|
||||
push_authed.as_ref(),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
@@ -12,6 +12,7 @@ use tokio_tungstenite::connect_async;
|
||||
use windmill_common::{
|
||||
db::UserDB,
|
||||
error::{Error, Result},
|
||||
worker::to_raw_value,
|
||||
};
|
||||
use windmill_git_sync::DeployedObject;
|
||||
|
||||
@@ -236,7 +237,7 @@ impl TriggerCrud for WebsocketTrigger {
|
||||
url.starts_with("$flow:"),
|
||||
&db,
|
||||
authed.clone(),
|
||||
config.url_runnable_args.as_ref(),
|
||||
config.url_runnable_args.as_ref().map(to_raw_value).as_ref(),
|
||||
&workspace_id,
|
||||
)
|
||||
.await?,
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::triggers::{
|
||||
trigger_runnable, trigger_runnable_and_wait_for_raw_result,
|
||||
trigger_runnable_and_wait_for_raw_result_with_error_ctx, TriggerJobArgs,
|
||||
},
|
||||
websocket::WebsocketConfig,
|
||||
websocket::{get_url_from_runnable_value, WebsocketConfig},
|
||||
Listener,
|
||||
};
|
||||
use anyhow::Context;
|
||||
@@ -31,43 +31,6 @@ use windmill_common::{
|
||||
use windmill_queue::PushArgsOwned;
|
||||
|
||||
impl ListeningTrigger<WebsocketConfig> {
|
||||
async fn get_url_from_runnable(&self, db: &DB) -> Result<String> {
|
||||
let runnable_kind = if self.is_flow { "Flow" } else { "Script" };
|
||||
tracing::info!(
|
||||
"Running {} {} to get WebSocket URL",
|
||||
runnable_kind.to_lowercase(),
|
||||
self.path
|
||||
);
|
||||
|
||||
let authed = self.authed(db, "ws").await?;
|
||||
|
||||
let args = raw_value_to_args_hashmap(
|
||||
self.trigger_config.url_runnable_args.as_ref().map(|r| &r.0),
|
||||
)?;
|
||||
|
||||
let result = trigger_runnable_and_wait_for_raw_result_with_error_ctx(
|
||||
db,
|
||||
None,
|
||||
authed,
|
||||
&self.workspace_id,
|
||||
&self.script_path,
|
||||
self.is_flow,
|
||||
PushArgsOwned { args, extra: None },
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
"".to_string(), // doesn't matter as no retry/error handler
|
||||
)
|
||||
.await?;
|
||||
|
||||
serde_json::from_str::<String>(result.get()).map_err(|_| {
|
||||
Error::BadConfig(format!(
|
||||
"{} {} did not return a string",
|
||||
runnable_kind, self.path,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
async fn send_initial_messages(
|
||||
&self,
|
||||
writer: &mut SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>,
|
||||
@@ -189,8 +152,12 @@ impl Listener for WebsocketTrigger {
|
||||
)) => {
|
||||
return Ok(None);
|
||||
},
|
||||
|
||||
url_result = listening_trigger.get_url_from_runnable(&db) => match url_result {
|
||||
url_result = {
|
||||
let authed = listening_trigger.authed(db, "ws").await?;
|
||||
let args = listening_trigger.trigger_config.url_runnable_args.as_ref().map(|r| &r.0);
|
||||
let path = url.splitn(2, ':').nth(1).unwrap();
|
||||
get_url_from_runnable_value(path, url.starts_with("$flow:"), db, authed, args, &listening_trigger.workspace_id)
|
||||
} => match url_result {
|
||||
Ok(url) => Cow::Owned(url),
|
||||
Err(err) => {
|
||||
return Err(anyhow::anyhow!("Error getting WebSocket URL from runnable after 5 tries: {:?}", err).into());
|
||||
|
||||
@@ -61,11 +61,11 @@ pub struct TestWebsocketConfig {
|
||||
}
|
||||
|
||||
pub fn value_to_args_hashmap(
|
||||
args: Option<&serde_json::Value>,
|
||||
args: Option<&Box<RawValue>>,
|
||||
) -> Result<HashMap<String, Box<RawValue>>> {
|
||||
let args = if let Some(args) = args {
|
||||
let args_map: Option<HashMap<String, serde_json::Value>> =
|
||||
serde_json::from_value(args.clone())
|
||||
serde_json::from_str(args.get())
|
||||
.map_err(|e| Error::BadRequest(format!("invalid json: {}", e)))?;
|
||||
|
||||
args_map
|
||||
@@ -89,7 +89,7 @@ pub async fn get_url_from_runnable_value(
|
||||
is_flow: bool,
|
||||
db: &DB,
|
||||
authed: ApiAuthed,
|
||||
args: Option<&serde_json::Value>,
|
||||
args: Option<&Box<RawValue>>,
|
||||
workspace_id: &str,
|
||||
) -> Result<String> {
|
||||
tracing::info!(
|
||||
|
||||
@@ -71,6 +71,7 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/get/:user", get(get_workspace_user))
|
||||
.route("/update/:user", post(update_workspace_user))
|
||||
.route("/delete/:user", delete(delete_workspace_user))
|
||||
.route("/convert_to_group/:user", post(convert_user_to_group))
|
||||
.route("/is_owner/*path", get(is_owner_of_path))
|
||||
.route("/whois/:username", get(whois))
|
||||
.route("/whoami", get(whoami))
|
||||
@@ -1362,6 +1363,138 @@ async fn update_workspace_user(
|
||||
Ok(format!("user {} updated", user_email))
|
||||
}
|
||||
|
||||
async fn convert_user_to_group(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, username_to_convert)): Path<(String, String)>,
|
||||
) -> Result<String> {
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
// Get user email and current status
|
||||
let user_info = sqlx::query!(
|
||||
"SELECT email, is_admin, operator, added_via FROM usr WHERE username = $1 AND workspace_id = $2",
|
||||
username_to_convert,
|
||||
&w_id
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?;
|
||||
|
||||
let user_info = not_found_if_none(user_info, "User", &username_to_convert)?;
|
||||
|
||||
// Check if user is already a group user
|
||||
if let Some(added_via) = &user_info.added_via {
|
||||
if added_via.get("source").and_then(|v| v.as_str()) == Some("instance_group") {
|
||||
return Err(Error::BadRequest("User is already a group user".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
// Find which instance groups this user belongs to that are configured for auto-add in this workspace
|
||||
let eligible_groups = sqlx::query!(
|
||||
r#"
|
||||
SELECT
|
||||
eig.igroup as group_name,
|
||||
ws.auto_add_instance_groups_roles
|
||||
FROM email_to_igroup eig
|
||||
INNER JOIN workspace_settings ws ON ws.workspace_id = $1
|
||||
WHERE eig.email = $2
|
||||
AND eig.igroup = ANY(ws.auto_add_instance_groups)
|
||||
"#,
|
||||
&w_id,
|
||||
&user_info.email
|
||||
)
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
|
||||
if eligible_groups.is_empty() {
|
||||
return Err(Error::BadRequest(
|
||||
"User is not a member of any instance groups configured for auto-add in this workspace".to_string()
|
||||
));
|
||||
}
|
||||
|
||||
// Determine the group with highest precedence (same logic as process_instance_group_auto_adds)
|
||||
let roles: std::collections::HashMap<String, String> = if let Some(roles_json) = &eligible_groups[0].auto_add_instance_groups_roles {
|
||||
serde_json::from_value(roles_json.clone()).unwrap_or_default()
|
||||
} else {
|
||||
std::collections::HashMap::new()
|
||||
};
|
||||
|
||||
let mut best_group = &eligible_groups[0].group_name;
|
||||
let mut best_precedence = 0u8;
|
||||
|
||||
for group in &eligible_groups {
|
||||
let default_role = "developer".to_string();
|
||||
let role = roles.get(&group.group_name).unwrap_or(&default_role);
|
||||
|
||||
let precedence = match role.as_str() {
|
||||
"admin" => 3,
|
||||
"developer" => 2,
|
||||
"operator" => 1,
|
||||
_ => 2,
|
||||
};
|
||||
|
||||
if precedence > best_precedence {
|
||||
best_precedence = precedence;
|
||||
best_group = &group.group_name;
|
||||
}
|
||||
}
|
||||
|
||||
let primary_group_name = best_group;
|
||||
|
||||
// Determine role from group configuration using the selected primary group
|
||||
let default_role = "developer".to_string();
|
||||
let role = roles.get(primary_group_name).unwrap_or(&default_role).as_str();
|
||||
|
||||
let (is_admin, is_operator) = match role {
|
||||
"admin" => (true, false),
|
||||
"operator" => (false, true),
|
||||
_ => (false, false),
|
||||
};
|
||||
|
||||
// Update user with instance group information
|
||||
let instance_group_source = serde_json::json!({
|
||||
"source": "instance_group",
|
||||
"group": primary_group_name
|
||||
});
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE usr SET added_via = $1, is_admin = $2, operator = $3 WHERE username = $4 AND workspace_id = $5",
|
||||
instance_group_source,
|
||||
is_admin,
|
||||
is_operator,
|
||||
username_to_convert,
|
||||
&w_id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed,
|
||||
"users.convert_to_group",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
Some(&username_to_convert),
|
||||
Some([("group", primary_group_name.as_str()), ("role", role)].into()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
handle_deployment_metadata(
|
||||
&authed.email,
|
||||
&authed.username,
|
||||
&db,
|
||||
&w_id,
|
||||
windmill_git_sync::DeployedObject::User { email: user_info.email.clone() },
|
||||
Some(format!("Converted user '{}' to group user (group: {}, role: {})", &user_info.email, primary_group_name, role)),
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(format!("User {} converted to group user (group: {}, role: {})", username_to_convert, primary_group_name, role))
|
||||
}
|
||||
|
||||
async fn update_user(
|
||||
authed: ApiAuthed,
|
||||
Path(email_to_update): Path<String>,
|
||||
@@ -1459,6 +1592,12 @@ async fn delete_user(
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Remove user from all instance groups email_to_igroup
|
||||
sqlx::query!("DELETE FROM email_to_igroup WHERE email = $1", &email_to_delete)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed,
|
||||
@@ -2191,8 +2330,8 @@ async fn get_all_runnables(
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
let scripts = sqlx::query!(
|
||||
"SELECT workspace_id as workspace, path, summary, description, schema FROM script as o
|
||||
WHERE created_at = (select max(created_at) from script where o.path = path and workspace_id = $1 AND archived = false)
|
||||
"SELECT workspace_id as workspace, path, summary, description, schema FROM script as o
|
||||
WHERE created_at = (select max(created_at) from script where o.path = path and workspace_id = $1 AND archived = false)
|
||||
AND workspace_id = $1 and archived = false", workspace
|
||||
)
|
||||
.fetch_all(&mut *tx)
|
||||
|
||||
@@ -83,6 +83,7 @@ async fn list_contextual_variables(
|
||||
Some("017e0ad5-f499-73b6-5488-92a61c5196dd".to_string()),
|
||||
Some(chrono::offset::Utc::now()),
|
||||
Some(ScriptHash(1234567890)),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.to_vec(),
|
||||
@@ -843,11 +844,11 @@ pub async fn get_value_internal<'a, 'e, A: sqlx::Acquire<'e, Database = Postgres
|
||||
} else if !value.is_empty() {
|
||||
let mc = build_crypt(&db, &w_id).await?;
|
||||
decrypt(&mc, value).map_err(|e| {
|
||||
Error::internal_err(format!(
|
||||
"Error decrypting variable {}: {}",
|
||||
variable.path, e
|
||||
))
|
||||
})?
|
||||
Error::internal_err(format!(
|
||||
"Error decrypting variable {}: {}",
|
||||
variable.path, e
|
||||
))
|
||||
})?
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
@@ -884,11 +885,8 @@ pub async fn get_variable_or_self(path: String, db: &DB, w_id: &str) -> Result<S
|
||||
if record.is_secret {
|
||||
let mc = build_crypt(db, w_id).await?;
|
||||
value = decrypt(&mc, value).map_err(|e| {
|
||||
Error::internal_err(format!(
|
||||
"Error decrypting variable {}: {}",
|
||||
path, e
|
||||
))
|
||||
})?;
|
||||
Error::internal_err(format!("Error decrypting variable {}: {}", path, e))
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(value)
|
||||
|
||||
@@ -366,6 +366,7 @@ struct UserWorkspace {
|
||||
pub color: Option<String>,
|
||||
pub operator_settings: Option<Option<serde_json::Value>>,
|
||||
pub parent_workspace_id: Option<String>,
|
||||
pub disabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -1022,10 +1023,11 @@ async fn edit_ducklake_config(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Path(w_id): Path<String>,
|
||||
ApiAuthed { is_admin, username, .. }: ApiAuthed,
|
||||
ApiAuthed { is_admin, username, email, .. }: ApiAuthed,
|
||||
Json(new_config): Json<EditDucklakeConfig>,
|
||||
) -> Result<String> {
|
||||
require_admin(is_admin, &username)?;
|
||||
let is_superadmin = require_super_admin(&db, &email).await.is_ok();
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
@@ -1064,6 +1066,38 @@ async fn edit_ducklake_config(
|
||||
}
|
||||
}
|
||||
|
||||
// Check that non-superadmins are not abusing Instance catalogs
|
||||
if !is_superadmin {
|
||||
let old_ducklakes = sqlx::query_scalar!(
|
||||
r#"
|
||||
SELECT ws.ducklake->'ducklakes' AS ducklake_name
|
||||
FROM workspace_settings ws
|
||||
WHERE ws.workspace_id = $1
|
||||
"#,
|
||||
&w_id
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
let old_ducklakes: HashMap<String, Ducklake> =
|
||||
serde_json::from_value(old_ducklakes).unwrap_or_default();
|
||||
for (name, dl) in new_config.settings.ducklakes.iter() {
|
||||
if dl.catalog.resource_type == DucklakeCatalogResourceType::Instance {
|
||||
let old_dl = old_ducklakes.get(name);
|
||||
if old_dl.is_none()
|
||||
|| old_dl.unwrap().catalog.resource_type
|
||||
!= DucklakeCatalogResourceType::Instance
|
||||
|| old_dl.unwrap().catalog.resource_path != dl.catalog.resource_path
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
"Only superadmins can create or modify ducklakes with Instance catalogs"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let config: serde_json::Value = serde_json::to_value(new_config.settings)
|
||||
.map_err(|err| Error::internal_err(err.to_string()))?;
|
||||
|
||||
@@ -2109,7 +2143,8 @@ async fn user_workspaces(
|
||||
let workspaces = sqlx::query_as!(
|
||||
UserWorkspace,
|
||||
"SELECT workspace.id, workspace.name, usr.username, workspace_settings.color, workspace.parent_workspace_id,
|
||||
CASE WHEN usr.operator THEN workspace_settings.operator_settings ELSE NULL END as operator_settings
|
||||
CASE WHEN usr.operator THEN workspace_settings.operator_settings ELSE NULL END as operator_settings,
|
||||
usr.disabled
|
||||
FROM workspace
|
||||
JOIN usr ON usr.workspace_id = workspace.id
|
||||
JOIN workspace_settings ON workspace_settings.workspace_id = workspace.id
|
||||
|
||||
@@ -204,7 +204,6 @@ where
|
||||
"edited_by",
|
||||
"archived",
|
||||
"has_draft",
|
||||
"draft_only",
|
||||
"error",
|
||||
"last_server_ping",
|
||||
"server_id",
|
||||
@@ -354,6 +353,7 @@ pub(crate) async fn tarball_workspace(
|
||||
{
|
||||
let scripts = sqlx::query_as::<_, Script>(
|
||||
"SELECT * FROM script as o WHERE workspace_id = $1 AND archived = false
|
||||
AND (draft_only IS NULL OR draft_only = false)
|
||||
AND created_at = (select max(created_at) from script where path = o.path AND \
|
||||
workspace_id = $1)",
|
||||
)
|
||||
@@ -476,7 +476,7 @@ pub(crate) async fn tarball_workspace(
|
||||
"SELECT flow.workspace_id, flow.path, flow.summary, flow.description, flow.archived, flow.extra_perms, flow.draft_only, flow.dedicated_worker, flow.tag, flow.ws_error_handler_muted, flow.timeout, flow.visible_to_runner_only, flow.on_behalf_of_email, flow_version.schema, flow_version.value, flow_version.created_at as edited_at, flow_version.created_by as edited_by
|
||||
FROM flow
|
||||
LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)]
|
||||
WHERE flow.workspace_id = $1 AND flow.archived = false",
|
||||
WHERE flow.workspace_id = $1 AND flow.archived = false AND (flow.draft_only IS NULL OR flow.draft_only = false)",
|
||||
)
|
||||
.bind(&w_id)
|
||||
.fetch_all(&mut *tx)
|
||||
@@ -527,7 +527,8 @@ pub(crate) async fn tarball_workspace(
|
||||
"SELECT app.id, app.path, app.summary, app.versions, app.policy, app.custom_path,
|
||||
app.extra_perms, app_version.value,
|
||||
app_version.created_at, app_version.created_by from app, app_version
|
||||
WHERE app.workspace_id = $1 AND app_version.id = app.versions[array_upper(app.versions, 1)] AND app_version.raw_app IS false",
|
||||
WHERE app.workspace_id = $1 AND app_version.id = app.versions[array_upper(app.versions, 1)] AND app_version.raw_app IS false
|
||||
AND (app.draft_only IS NULL OR app.draft_only = false)",
|
||||
)
|
||||
.bind(&w_id)
|
||||
.fetch_all(&mut *tx)
|
||||
|
||||
@@ -140,13 +140,26 @@ pub struct JWTAuthClaims {
|
||||
pub groups: Vec<String>,
|
||||
pub folders: Vec<(String, bool, bool)>,
|
||||
pub label: Option<String>,
|
||||
pub workspace_id: String,
|
||||
pub workspace_id: Option<String>,
|
||||
pub workspace_ids: Option<Vec<String>>,
|
||||
pub exp: usize,
|
||||
pub job_id: Option<String>,
|
||||
pub scopes: Option<Vec<String>>,
|
||||
pub audit_span: Option<String>,
|
||||
}
|
||||
|
||||
impl JWTAuthClaims {
|
||||
pub fn allowed_in_workspace(&self, w_id: &str) -> bool {
|
||||
self.workspace_id
|
||||
.as_ref()
|
||||
.is_some_and(|token_w_id| w_id == token_w_id)
|
||||
|| self
|
||||
.workspace_ids
|
||||
.as_ref()
|
||||
.is_some_and(|token_w_ids| token_w_ids.iter().any(|token_w_id| w_id == token_w_id))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct JobPerms {
|
||||
pub email: String,
|
||||
@@ -411,7 +424,8 @@ pub async fn create_jwt_token(
|
||||
groups: authed.groups.clone(),
|
||||
folders: authed.folders.clone(),
|
||||
label,
|
||||
workspace_id: workspace_id.to_string(),
|
||||
workspace_id: Some(workspace_id.to_string()),
|
||||
workspace_ids: None,
|
||||
exp: (chrono::Utc::now() + chrono::Duration::seconds(expires_in_seconds as i64)).timestamp()
|
||||
as usize,
|
||||
job_id: job_id.map(|id| id.to_string()),
|
||||
|
||||
@@ -45,6 +45,10 @@ pub struct FlowStatus {
|
||||
pub restarted_from: Option<RestartedFrom>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stream_job: Option<Uuid>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub chat_input_enabled: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub memory_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
@@ -508,6 +512,8 @@ impl FlowStatus {
|
||||
restarted_from: None,
|
||||
user_states: HashMap::new(),
|
||||
stream_job: None,
|
||||
chat_input_enabled: f.chat_input_enabled,
|
||||
memory_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user